summaryrefslogtreecommitdiff
path: root/src/cam
diff options
context:
space:
mode:
Diffstat (limited to 'src/cam')
-rw-r--r--src/cam/camera_session.cpp8
-rw-r--r--src/cam/main.cpp4
-rw-r--r--src/cam/main.h1
-rw-r--r--src/cam/meson.build11
-rw-r--r--src/cam/sdl_sink.cpp194
-rw-r--r--src/cam/sdl_sink.h48
-rw-r--r--src/cam/sdl_texture.cpp36
-rw-r--r--src/cam/sdl_texture.h29
-rw-r--r--src/cam/sdl_texture_yuyv.cpp20
-rw-r--r--src/cam/sdl_texture_yuyv.h17
10 files changed, 368 insertions, 0 deletions
diff --git a/src/cam/camera_session.cpp b/src/cam/camera_session.cpp
index 76d552d9..238186a3 100644
--- a/src/cam/camera_session.cpp
+++ b/src/cam/camera_session.cpp
@@ -21,6 +21,9 @@
#include "kms_sink.h"
#endif
#include "main.h"
+#ifdef HAVE_SDL
+#include "sdl_sink.h"
+#endif
#include "stream_options.h"
using namespace libcamera;
@@ -197,6 +200,11 @@ int CameraSession::start()
sink_ = std::make_unique<KMSSink>(options_[OptDisplay].toString());
#endif
+#ifdef HAVE_SDL
+ if (options_.isSet(OptSDL))
+ sink_ = std::make_unique<SDLSink>();
+#endif
+
if (options_.isSet(OptFile)) {
if (!options_[OptFile].toString().empty())
sink_ = std::make_unique<FileSink>(streamNames_,
diff --git a/src/cam/main.cpp b/src/cam/main.cpp
index 9c3370d7..79875ed7 100644
--- a/src/cam/main.cpp
+++ b/src/cam/main.cpp
@@ -147,6 +147,10 @@ int CamApp::parseOptions(int argc, char *argv[])
"The default file name is 'frame-#.bin'.",
"file", ArgumentOptional, "filename", false,
OptCamera);
+#ifdef HAVE_SDL
+ parser.addOption(OptSDL, OptionNone, "Display viewfinder through SDL",
+ "sdl", ArgumentNone, "", false, OptCamera);
+#endif
parser.addOption(OptStream, &streamKeyValue,
"Set configuration of a camera stream", "stream", true,
OptCamera);
diff --git a/src/cam/main.h b/src/cam/main.h
index 51b87927..526aecec 100644
--- a/src/cam/main.h
+++ b/src/cam/main.h
@@ -17,6 +17,7 @@ enum {
OptList = 'l',
OptListProperties = 'p',
OptMonitor = 'm',
+ OptSDL = 'S',
OptStream = 's',
OptListControls = 256,
OptStrictFormats = 257,
diff --git a/src/cam/meson.build b/src/cam/meson.build
index c47a8559..c17a971f 100644
--- a/src/cam/meson.build
+++ b/src/cam/meson.build
@@ -24,6 +24,7 @@ cam_sources = files([
cam_cpp_args = []
libdrm = dependency('libdrm', required : false)
+libsdl2 = dependency('SDL2', required : false)
if libdrm.found()
cam_cpp_args += [ '-DHAVE_KMS' ]
@@ -33,12 +34,22 @@ if libdrm.found()
])
endif
+if libsdl2.found()
+ cam_cpp_args += ['-DHAVE_SDL']
+ cam_sources += files([
+ 'sdl_sink.cpp',
+ 'sdl_texture.cpp',
+ 'sdl_texture_yuyv.cpp'
+ ])
+endif
+
cam = executable('cam', cam_sources,
dependencies : [
libatomic,
libcamera_public,
libdrm,
libevent,
+ libsdl2,
libyaml,
],
cpp_args : cam_cpp_args,
diff --git a/src/cam/sdl_sink.cpp b/src/cam/sdl_sink.cpp
new file mode 100644
index 00000000..4c74bce7
--- /dev/null
+++ b/src/cam/sdl_sink.cpp
@@ -0,0 +1,194 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Ideas on Board Oy
+ *
+ * sdl_sink.h - SDL Sink
+ */
+
+#include "sdl_sink.h"
+
+#include <assert.h>
+#include <fcntl.h>
+#include <iomanip>
+#include <iostream>
+#include <signal.h>
+#include <sstream>
+#include <string.h>
+#include <unistd.h>
+
+#include <libcamera/camera.h>
+#include <libcamera/formats.h>
+
+#include "event_loop.h"
+#include "image.h"
+#include "sdl_texture_yuyv.h"
+
+using namespace libcamera;
+
+using namespace std::chrono_literals;
+
+SDLSink::SDLSink()
+ : window_(nullptr), renderer_(nullptr), rect_({}),
+ init_(false)
+{
+}
+
+SDLSink::~SDLSink()
+{
+ stop();
+}
+
+int SDLSink::configure(const libcamera::CameraConfiguration &config)
+{
+ int ret = FrameSink::configure(config);
+ if (ret < 0)
+ return ret;
+
+ if (config.size() > 1) {
+ std::cerr
+ << "SDL sink only supports one camera stream at present, streaming first camera stream"
+ << std::endl;
+ } else if (config.empty()) {
+ std::cerr << "Require at least one camera stream to process"
+ << std::endl;
+ return -EINVAL;
+ }
+
+ const libcamera::StreamConfiguration &cfg = config.at(0);
+ rect_.w = cfg.size.width;
+ rect_.h = cfg.size.height;
+
+ switch (cfg.pixelFormat) {
+ case libcamera::formats::YUYV:
+ texture_ = std::make_unique<SDLTextureYUYV>(rect_);
+ break;
+ default:
+ std::cerr << "Unsupported pixel format "
+ << cfg.pixelFormat.toString() << std::endl;
+ return -EINVAL;
+ };
+
+ return 0;
+}
+
+int SDLSink::start()
+{
+ int ret = SDL_Init(SDL_INIT_VIDEO);
+ if (ret) {
+ std::cerr << "Failed to initialize SDL: " << SDL_GetError()
+ << std::endl;
+ return ret;
+ }
+
+ init_ = true;
+ window_ = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED, rect_.w,
+ rect_.h,
+ SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
+ if (!window_) {
+ std::cerr << "Failed to create SDL window: " << SDL_GetError()
+ << std::endl;
+ return -EINVAL;
+ }
+
+ renderer_ = SDL_CreateRenderer(window_, -1, 0);
+ if (!renderer_) {
+ std::cerr << "Failed to create SDL renderer: " << SDL_GetError()
+ << std::endl;
+ return -EINVAL;
+ }
+
+ /*
+ * Set for scaling purposes, not critical, don't return in case of
+ * error.
+ */
+ ret = SDL_RenderSetLogicalSize(renderer_, rect_.w, rect_.h);
+ if (ret)
+ std::cerr << "Failed to set SDL render logical size: "
+ << SDL_GetError() << std::endl;
+
+ ret = texture_->create(renderer_);
+ if (ret) {
+ return ret;
+ }
+
+ /* \todo Make the event cancellable to support stop/start cycles. */
+ EventLoop::instance()->addTimerEvent(
+ 10ms, std::bind(&SDLSink::processSDLEvents, this));
+
+ return 0;
+}
+
+int SDLSink::stop()
+{
+ texture_.reset();
+
+ if (renderer_) {
+ SDL_DestroyRenderer(renderer_);
+ renderer_ = nullptr;
+ }
+
+ if (window_) {
+ SDL_DestroyWindow(window_);
+ window_ = nullptr;
+ }
+
+ if (init_) {
+ SDL_Quit();
+ init_ = false;
+ }
+
+ return FrameSink::stop();
+}
+
+void SDLSink::mapBuffer(FrameBuffer *buffer)
+{
+ std::unique_ptr<Image> image =
+ Image::fromFrameBuffer(buffer, Image::MapMode::ReadOnly);
+ assert(image != nullptr);
+
+ mappedBuffers_[buffer] = std::move(image);
+}
+
+bool SDLSink::processRequest(Request *request)
+{
+ for (auto [stream, buffer] : request->buffers()) {
+ renderBuffer(buffer);
+ break; /* to be expanded to launch SDL window per buffer */
+ }
+
+ return true;
+}
+
+/*
+ * Process SDL events, required for things like window resize and quit button
+ */
+void SDLSink::processSDLEvents()
+{
+ for (SDL_Event e; SDL_PollEvent(&e);) {
+ if (e.type == SDL_QUIT) {
+ /* Click close icon then quit */
+ EventLoop::instance()->exit(0);
+ }
+ }
+}
+
+void SDLSink::renderBuffer(FrameBuffer *buffer)
+{
+ Image *image = mappedBuffers_[buffer].get();
+
+ /* \todo Implement support for multi-planar formats. */
+ const FrameMetadata::Plane &meta = buffer->metadata().planes()[0];
+
+ Span<uint8_t> data = image->data(0);
+ if (meta.bytesused > data.size())
+ std::cerr << "payload size " << meta.bytesused
+ << " larger than plane size " << data.size()
+ << std::endl;
+
+ texture_->update(data);
+
+ SDL_RenderClear(renderer_);
+ SDL_RenderCopy(renderer_, texture_->get(), nullptr, nullptr);
+ SDL_RenderPresent(renderer_);
+}
diff --git a/src/cam/sdl_sink.h b/src/cam/sdl_sink.h
new file mode 100644
index 00000000..6c19c663
--- /dev/null
+++ b/src/cam/sdl_sink.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Ideas on Board Oy
+ *
+ * sdl_sink.h - SDL Sink
+ */
+
+#pragma once
+
+#include <map>
+#include <memory>
+
+#include <libcamera/stream.h>
+
+#include <SDL2/SDL.h>
+
+#include "frame_sink.h"
+
+class Image;
+class SDLTexture;
+
+class SDLSink : public FrameSink
+{
+public:
+ SDLSink();
+ ~SDLSink();
+
+ int configure(const libcamera::CameraConfiguration &config) override;
+ int start() override;
+ int stop() override;
+ void mapBuffer(libcamera::FrameBuffer *buffer) override;
+
+ bool processRequest(libcamera::Request *request) override;
+
+private:
+ void renderBuffer(libcamera::FrameBuffer *buffer);
+ void processSDLEvents();
+
+ std::map<libcamera::FrameBuffer *, std::unique_ptr<Image>>
+ mappedBuffers_;
+
+ std::unique_ptr<SDLTexture> texture_;
+
+ SDL_Window *window_;
+ SDL_Renderer *renderer_;
+ SDL_Rect rect_;
+ bool init_;
+};
diff --git a/src/cam/sdl_texture.cpp b/src/cam/sdl_texture.cpp
new file mode 100644
index 00000000..2ca2add2
--- /dev/null
+++ b/src/cam/sdl_texture.cpp
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Ideas on Board Oy
+ *
+ * sdl_texture.cpp - SDL Texture
+ */
+
+#include "sdl_texture.h"
+
+#include <iostream>
+
+SDLTexture::SDLTexture(const SDL_Rect &rect, SDL_PixelFormatEnum pixelFormat,
+ const int pitch)
+ : ptr_(nullptr), rect_(rect), pixelFormat_(pixelFormat), pitch_(pitch)
+{
+}
+
+SDLTexture::~SDLTexture()
+{
+ if (ptr_)
+ SDL_DestroyTexture(ptr_);
+}
+
+int SDLTexture::create(SDL_Renderer *renderer)
+{
+ ptr_ = SDL_CreateTexture(renderer, pixelFormat_,
+ SDL_TEXTUREACCESS_STREAMING, rect_.w,
+ rect_.h);
+ if (!ptr_) {
+ std::cerr << "Failed to create SDL texture: " << SDL_GetError()
+ << std::endl;
+ return -ENOMEM;
+ }
+
+ return 0;
+}
diff --git a/src/cam/sdl_texture.h b/src/cam/sdl_texture.h
new file mode 100644
index 00000000..90974798
--- /dev/null
+++ b/src/cam/sdl_texture.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Ideas on Board Oy
+ *
+ * sdl_texture.h - SDL Texture
+ */
+
+#pragma once
+
+#include <SDL2/SDL.h>
+
+#include "image.h"
+
+class SDLTexture
+{
+public:
+ SDLTexture(const SDL_Rect &rect, SDL_PixelFormatEnum pixelFormat,
+ const int pitch);
+ virtual ~SDLTexture();
+ int create(SDL_Renderer *renderer);
+ virtual void update(const libcamera::Span<uint8_t> &data) = 0;
+ SDL_Texture *get() const { return ptr_; }
+
+protected:
+ SDL_Texture *ptr_;
+ const SDL_Rect rect_;
+ const SDL_PixelFormatEnum pixelFormat_;
+ const int pitch_;
+};
diff --git a/src/cam/sdl_texture_yuyv.cpp b/src/cam/sdl_texture_yuyv.cpp
new file mode 100644
index 00000000..cc161b2c
--- /dev/null
+++ b/src/cam/sdl_texture_yuyv.cpp
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Ideas on Board Oy
+ *
+ * sdl_texture_yuyv.cpp - SDL Texture YUYV
+ */
+
+#include "sdl_texture_yuyv.h"
+
+using namespace libcamera;
+
+SDLTextureYUYV::SDLTextureYUYV(const SDL_Rect &rect)
+ : SDLTexture(rect, SDL_PIXELFORMAT_YUY2, 4 * ((rect.w + 1) / 2))
+{
+}
+
+void SDLTextureYUYV::update(const Span<uint8_t> &data)
+{
+ SDL_UpdateTexture(ptr_, &rect_, data.data(), pitch_);
+}
diff --git a/src/cam/sdl_texture_yuyv.h b/src/cam/sdl_texture_yuyv.h
new file mode 100644
index 00000000..9f7c72f0
--- /dev/null
+++ b/src/cam/sdl_texture_yuyv.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2022, Ideas on Board Oy
+ *
+ * sdl_texture_yuyv.h - SDL Texture YUYV
+ */
+
+#pragma once
+
+#include "sdl_texture.h"
+
+class SDLTextureYUYV : public SDLTexture
+{
+public:
+ SDLTextureYUYV(const SDL_Rect &rect);
+ void update(const libcamera::Span<uint8_t> &data) override;
+};