From 6c251ae3ef0e148bccb1a8ed53b11d947df5c792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 26 Sep 2025 13:07:08 +0200 Subject: [PATCH] libcamera: pipeline: virtual: Move image generation to separate thread MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently the virtual pipeline generates the images synchronously. This is not ideal because it blocks the camera manager's internal thread, and because its behaviour is different from other existing pipeline handlers, all of which complete requests asynchronously. So move the image generation to a separate thread by deriving `VirtualCameraData` from `Thread`, as well as `Object` and using the existing asynchronous signal and method call mechanism. Signed-off-by: Barnabás Pőcze Reviewed-by: Laurent Pinchart Signed-off-by: Kieran Bingham --- src/libcamera/pipeline/virtual/virtual.cpp | 91 +++++++++++++++------- src/libcamera/pipeline/virtual/virtual.h | 11 ++- 2 files changed, 71 insertions(+), 31 deletions(-) diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp index f9538129..23eae852 100644 --- a/src/libcamera/pipeline/virtual/virtual.cpp +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -106,6 +106,7 @@ private: } bool initFrameGenerator(Camera *camera); + void bufferCompleted(FrameBuffer *buffer); DmaBufAllocator dmaBufAllocator_; @@ -129,6 +130,39 @@ VirtualCameraData::VirtualCameraData(PipelineHandler *pipe, /* \todo Support multiple streams and pass multi_stream_test */ streamConfigs_.resize(kMaxStream); + + moveToThread(this); +} + +void VirtualCameraData::processRequest(Request *request) +{ + for (auto const &[stream, buffer] : request->buffers()) { + bool found = false; + /* map buffer and fill test patterns */ + for (auto &streamConfig : streamConfigs_) { + if (stream == &streamConfig.stream) { + FrameMetadata &fmd = buffer->_d()->metadata(); + + fmd.status = FrameMetadata::Status::FrameSuccess; + fmd.sequence = streamConfig.seq++; + fmd.timestamp = currentTimestamp(); + + Span planes = buffer->planes(); + for (const auto [i, p] : utils::enumerate(planes)) + fmd.planes()[i].bytesused = p.length; + + found = true; + + if (streamConfig.frameGenerator->generateFrame( + stream->configuration().size, buffer)) + fmd.status = FrameMetadata::Status::FrameError; + + bufferCompleted.emit(buffer); + break; + } + } + ASSERT(found); + } } VirtualCameraConfiguration::VirtualCameraConfiguration(VirtualCameraData *data) @@ -291,11 +325,27 @@ int PipelineHandlerVirtual::start([[maybe_unused]] Camera *camera, for (auto &s : data->streamConfigs_) s.seq = 0; + data->bufferCompleted.connect(this, &PipelineHandlerVirtual::bufferCompleted); + data->start(); + return 0; } -void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera) +void PipelineHandlerVirtual::stopDevice(Camera *camera) { + VirtualCameraData *data = cameraData(camera); + + /* Cancel pending work. */ + data->exit(); + data->wait(); + data->removeMessages(data); + + /* Process pending `bufferCompleted` signals. */ + thread()->dispatchMessages(Message::Type::InvokeMessage, this); + data->bufferCompleted.disconnect(this); + + while (!data->queuedRequests_.empty()) + cancelRequest(data->queuedRequests_.front()); } int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, @@ -304,36 +354,9 @@ int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, VirtualCameraData *data = cameraData(camera); const auto timestamp = currentTimestamp(); - for (auto const &[stream, buffer] : request->buffers()) { - bool found = false; - /* map buffer and fill test patterns */ - for (auto &streamConfig : data->streamConfigs_) { - if (stream == &streamConfig.stream) { - FrameMetadata &fmd = buffer->_d()->metadata(); - - fmd.status = FrameMetadata::Status::FrameSuccess; - fmd.sequence = streamConfig.seq++; - fmd.timestamp = timestamp; - - Span planes = buffer->planes(); - for (const auto [i, p] : utils::enumerate(planes)) - fmd.planes()[i].bytesused = p.length; - - found = true; - - if (streamConfig.frameGenerator->generateFrame( - stream->configuration().size, buffer)) - fmd.status = FrameMetadata::Status::FrameError; - - completeBuffer(request, buffer); - break; - } - } - ASSERT(found); - } - request->metadata().set(controls::SensorTimestamp, timestamp); - completeRequest(request); + data->invokeMethod(&VirtualCameraData::processRequest, + ConnectionTypeQueued, request); return 0; } @@ -415,6 +438,14 @@ bool PipelineHandlerVirtual::initFrameGenerator(Camera *camera) return true; } +void PipelineHandlerVirtual::bufferCompleted(FrameBuffer *buffer) +{ + Request *request = buffer->request(); + + if (completeBuffer(request, buffer)) + completeRequest(request); +} + REGISTER_PIPELINE_HANDLER(PipelineHandlerVirtual, "virtual") } /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h index 683cb82b..215e56fa 100644 --- a/src/libcamera/pipeline/virtual/virtual.h +++ b/src/libcamera/pipeline/virtual/virtual.h @@ -11,6 +11,10 @@ #include #include +#include +#include +#include + #include #include @@ -25,7 +29,9 @@ namespace libcamera { using VirtualFrame = std::variant; -class VirtualCameraData : public Camera::Private +class VirtualCameraData : public Camera::Private, + public Thread, + public Object { public: const static unsigned int kMaxStream = 3; @@ -54,9 +60,12 @@ public: ~VirtualCameraData() = default; + void processRequest(Request *request); + Configuration config_; std::vector streamConfigs_; + Signal bufferCompleted; }; } /* namespace libcamera */