The cam and qcam test application share code, currently through a crude hack that references the cam source files directly from the qcam meson.build file. To prepare for the introduction of hosting that code in a static library, move all applications to src/apps/. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
215 lines
4.4 KiB
C++
215 lines
4.4 KiB
C++
/* 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"
|
|
#ifdef HAVE_LIBJPEG
|
|
#include "sdl_texture_mjpg.h"
|
|
#endif
|
|
#include "sdl_texture_yuv.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) {
|
|
#ifdef HAVE_LIBJPEG
|
|
case libcamera::formats::MJPEG:
|
|
texture_ = std::make_unique<SDLTextureMJPG>(rect_);
|
|
break;
|
|
#endif
|
|
#if SDL_VERSION_ATLEAST(2, 0, 16)
|
|
case libcamera::formats::NV12:
|
|
texture_ = std::make_unique<SDLTextureNV12>(rect_, cfg.stride);
|
|
break;
|
|
#endif
|
|
case libcamera::formats::YUYV:
|
|
texture_ = std::make_unique<SDLTextureYUYV>(rect_, cfg.stride);
|
|
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();
|
|
|
|
std::vector<Span<const uint8_t>> planes;
|
|
unsigned int i = 0;
|
|
|
|
planes.reserve(buffer->metadata().planes().size());
|
|
|
|
for (const FrameMetadata::Plane &meta : buffer->metadata().planes()) {
|
|
Span<uint8_t> data = image->data(i);
|
|
if (meta.bytesused > data.size())
|
|
std::cerr << "payload size " << meta.bytesused
|
|
<< " larger than plane size " << data.size()
|
|
<< std::endl;
|
|
|
|
planes.push_back(data);
|
|
i++;
|
|
}
|
|
|
|
texture_->update(planes);
|
|
|
|
SDL_RenderClear(renderer_);
|
|
SDL_RenderCopy(renderer_, texture_->get(), nullptr, nullptr);
|
|
SDL_RenderPresent(renderer_);
|
|
}
|