<div dir="ltr"><div dir="ltr">Hi Jacopo,</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Sat, Aug 31, 2024 at 8:44 PM Jacopo Mondi <<a href="mailto:jacopo.mondi@ideasonboard.com" target="_blank">jacopo.mondi@ideasonboard.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Hi<br>
<br>
On Thu, Aug 29, 2024 at 09:57:58PM GMT, Cheng-Hao Yang wrote:<br>
> Thanks for the review, Jacopo!<br>
><br>
> On Tue, Aug 27, 2024 at 5:43 PM Jacopo Mondi <<a href="mailto:jacopo.mondi@ideasonboard.com" target="_blank">jacopo.mondi@ideasonboard.com</a>><br>
> wrote:<br>
><br>
> > Hi Harvey<br>
> ><br>
> > On Tue, Aug 20, 2024 at 04:23:34PM GMT, Harvey Yang wrote:<br>
> > > From: Harvey Yang <<a href="mailto:chenghaoyang@chromium.org" target="_blank">chenghaoyang@chromium.org</a>><br>
> > ><br>
> > > Add VirtualPipelineHandler for more unit tests and verfiy libcamera<br>
> > > infrastructure works on devices without using hardware cameras.<br>
> > ><br>
> > > Signed-off-by: Harvey Yang <<a href="mailto:chenghaoyang@chromium.org" target="_blank">chenghaoyang@chromium.org</a>><br>
> > > ---<br>
> > > meson.build | 1 +<br>
> > > meson_options.txt | 3 +-<br>
> > > src/libcamera/pipeline/virtual/meson.build | 5 +<br>
> > > src/libcamera/pipeline/virtual/virtual.cpp | 251 +++++++++++++++++++++<br>
> > > src/libcamera/pipeline/virtual/virtual.h | 78 +++++++<br>
> ><br>
> > Do you expect other components to include this header in future ? If<br>
> > not, you can move its content to the .cpp file I guess<br>
> ><br>
> ><br>
> Actually `virtual/parser.h` needs to include it to return VirtualCameraData<br>
> when parsing the yaml config file [1]. Does that count :p?<br>
<br>
I guess it does :)<br>
<br>
><br>
> [1]: <a href="https://patchwork.libcamera.org/patch/20971/" rel="noreferrer" target="_blank">https://patchwork.libcamera.org/patch/20971/</a><br>
><br>
> > 5 files changed, 337 insertions(+), 1 deletion(-)<br>
> > > create mode 100644 src/libcamera/pipeline/virtual/meson.build<br>
> > > create mode 100644 src/libcamera/pipeline/virtual/virtual.cpp<br>
> > > create mode 100644 src/libcamera/pipeline/virtual/virtual.h<br>
> > ><br>
> > > diff --git a/meson.build b/meson.build<br>
> > > index f946eba94..3cad3249a 100644<br>
> > > --- a/meson.build<br>
> > > +++ b/meson.build<br>
> > > @@ -222,6 +222,7 @@ pipelines_support = {<br>
> > > 'simple': arch_arm,<br>
> > > 'uvcvideo': ['any'],<br>
> > > 'vimc': ['test'],<br>
> > > + 'virtual': ['test'],<br>
> > > }<br>
> > ><br>
> > > if pipelines.contains('all')<br>
> > > diff --git a/meson_options.txt b/meson_options.txt<br>
> > > index 7aa412491..c91cd241a 100644<br>
> > > --- a/meson_options.txt<br>
> > > +++ b/meson_options.txt<br>
> > > @@ -53,7 +53,8 @@ option('pipelines',<br>
> > > 'rpi/vc4',<br>
> > > 'simple',<br>
> > > 'uvcvideo',<br>
> > > - 'vimc'<br>
> > > + 'vimc',<br>
> > > + 'virtual'<br>
> > > ],<br>
> > > description : 'Select which pipeline handlers to build. If this<br>
> > is set to "auto", all the pipelines applicable to the target architecture<br>
> > will be built. If this is set to "all", all the pipelines will be built. If<br>
> > both are selected then "all" will take precedence.')<br>
> > ><br>
> > > diff --git a/src/libcamera/pipeline/virtual/meson.build<br>
> > b/src/libcamera/pipeline/virtual/meson.build<br>
> > > new file mode 100644<br>
> > > index 000000000..ba7ff754e<br>
> > > --- /dev/null<br>
> > > +++ b/src/libcamera/pipeline/virtual/meson.build<br>
> > > @@ -0,0 +1,5 @@<br>
> > > +# SPDX-License-Identifier: CC0-1.0<br>
> > > +<br>
> > > +libcamera_sources += files([<br>
> > > + 'virtual.cpp',<br>
> > > +])<br>
> > > diff --git a/src/libcamera/pipeline/virtual/virtual.cpp<br>
> > b/src/libcamera/pipeline/virtual/virtual.cpp<br>
> > > new file mode 100644<br>
> > > index 000000000..74eb8c7ad<br>
> > > --- /dev/null<br>
> > > +++ b/src/libcamera/pipeline/virtual/virtual.cpp<br>
> > > @@ -0,0 +1,251 @@<br>
> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */<br>
> > > +/*<br>
> > > + * Copyright (C) 2023, Google Inc.<br>
> > > + *<br>
> > > + * virtual.cpp - Pipeline handler for virtual cameras<br>
> > > + */<br>
> > > +<br>
> ><br>
> > You should include the header for the standard library construct you<br>
> > use. I see vectors, maps, unique_ptrs etc<br>
> ><br>
> Done, please check again.<br>
><br>
><br>
> ><br>
> > > +#include "virtual.h"<br>
> > > +<br>
> > > +#include <libcamera/base/log.h><br>
> > > +<br>
> > > +#include <libcamera/camera.h><br>
> > > +#include <libcamera/control_ids.h><br>
> > > +#include <libcamera/controls.h><br>
> > > +#include <libcamera/formats.h><br>
> > > +#include <libcamera/property_ids.h><br>
> > > +<br>
> > > +#include "libcamera/internal/camera.h"<br>
> ><br>
> > The internal header includes the public one by definition<br>
> ><br>
> Ack. Removed the public one.<br>
><br>
><br>
> ><br>
> > > +#include "libcamera/internal/formats.h"<br>
> ><br>
> > This doesn't as <libcamera/formats.h> is generated. I wonder if it<br>
> > should.<br>
> ><br>
> Keeping `#include <libcamera/formats.h>`.<br>
><br>
><br>
> ><br>
> > > +#include "libcamera/internal/pipeline_handler.h"<br>
> > > +<br>
> > > +namespace libcamera {<br>
> > > +<br>
> > > +LOG_DEFINE_CATEGORY(Virtual)<br>
> > > +<br>
> > > +namespace {<br>
> > > +<br>
> > > +uint64_t currentTimestamp()<br>
> > > +{<br>
> > > + struct timespec ts;<br>
> > > + if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {<br>
> > > + LOG(Virtual, Error) << "Get clock time fails";<br>
> > > + return 0;<br>
> > > + }<br>
> > > +<br>
> > > + return ts.tv_sec * 1'000'000'000LL + ts.tv_nsec;<br>
> > > +}<br>
> ><br>
> > Could <a href="https://en.cppreference.com/w/cpp/chrono/steady_clock/now" rel="noreferrer" target="_blank">https://en.cppreference.com/w/cpp/chrono/steady_clock/now</a> save<br>
> > you a custom function ?<br>
> ><br>
> > In example:<br>
> ><br>
> > const auto now = std::chrono::steady_clock::now();<br>
> > auto nsecs =<br>
> > std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch());<br>
> > std::cout << nsecs.count();<br>
> ><br>
> > should give you the time in nanoseconds since system boot (if I got it<br>
> > right)<br>
> ><br>
> ><br>
> > > +<br>
> > > +} // namespace<br>
> ><br>
> > nit: /* namespace */<br>
> > here and in other places<br>
> ><br>
> > Done<br>
><br>
><br>
> > > +<br>
> > ><br>
> > +VirtualCameraConfiguration::VirtualCameraConfiguration(VirtualCameraData<br>
> > *data)<br>
> > > + : CameraConfiguration(), data_(data)<br>
> > > +{<br>
> > > +}<br>
> > > +<br>
> > > +CameraConfiguration::Status VirtualCameraConfiguration::validate()<br>
> > > +{<br>
> > > + Status status = Valid;<br>
> > > +<br>
> > > + if (config_.empty()) {<br>
> > > + LOG(Virtual, Error) << "Empty config";<br>
> > > + return Invalid;<br>
> > > + }<br>
> > > +<br>
> > > + /* Currently only one stream is supported */<br>
> > > + if (config_.size() > 1) {<br>
> > > + config_.resize(1);<br>
> > > + status = Adjusted;<br>
> > > + }<br>
> > > +<br>
> > > + Size maxSize;<br>
> > > + for (const auto &resolution : data_->supportedResolutions_)<br>
> > > + maxSize = std::max(maxSize, resolution.size);<br>
> > > +<br>
> > > + for (StreamConfiguration &cfg : config_) {<br>
> ><br>
> > you only have config, or in the next patches will this be augmented ?<br>
> ><br>
> > Do you mean that I should check `sensorConfig` or `orientation` as well?<br>
><br>
<br>
No I meant I was assuming the virtual pipline works with a single<br>
stream by design. But seeing your replies to the next patches makes me<br>
think my assumption was wrong.<br>
<br>
> > + bool found = false;<br>
> > > + for (const auto &resolution :<br>
> > data_->supportedResolutions_) {<br>
> > > + if (resolution.size.width == cfg.size.width &&<br>
> > > + resolution.size.height == cfg.size.height) {<br>
> > > + found = true;<br>
> > > + break;<br>
> > > + }<br>
> > > + }<br>
> > > +<br>
> > > + if (!found) {<br>
> > > + cfg.size = maxSize;<br>
> > > + status = Adjusted;<br>
> > > + }<br>
> ><br>
> > so it's either the exact resolution or the biggest available one ?<br>
> ><br>
> > As you have a single config it's easy to get the closest one to the<br>
> > requested size, if it doesn't match exactly one of the supported<br>
> > resolutions.<br>
> ><br>
><br>
> Hmm, I think it's a bit hard to define the "closest". Do we compare<br>
> the area size directly? Do we prefer a size that has both larger or<br>
> the same height and width?<br>
><br>
<br>
Good question, it's generally a pipeline decision (which is not<br>
optimal, as we should aim to a unified behaviour among pipelines).<br>
<br>
As this is for testing, I think it's fine to keep what you have, but<br>
could you add a comment to highlight this implementation decision ?<br>
<br></blockquote><div><br></div><div>Sure, will be updated in v11. Please take another look.</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
><br>
> ><br>
> > > +<br>
> > > + const PixelFormatInfo &info =<br>
> > PixelFormatInfo::info(cfg.pixelFormat);<br>
> > > + cfg.stride = info.stride(cfg.size.width, 0, 1);<br>
> > > + cfg.frameSize = info.frameSize(cfg.size, 1);<br>
> > > +<br>
> > > + cfg.setStream(const_cast<Stream *>(&data_->stream_));<br>
> > > +<br>
> > > + cfg.bufferCount = VirtualCameraConfiguration::kBufferCount;<br>
> > > + }<br>
> > > +<br>
> > > + return status;<br>
> > > +}<br>
> > > +<br>
> > > +PipelineHandlerVirtual::PipelineHandlerVirtual(CameraManager *manager)<br>
> > > + : PipelineHandler(manager)<br>
> > > +{<br>
> > > +}<br>
> > > +<br>
> > > +std::unique_ptr<CameraConfiguration><br>
> > > +PipelineHandlerVirtual::generateConfiguration(Camera *camera,<br>
> > > + Span<const StreamRole> roles)<br>
> > > +{<br>
> > > + VirtualCameraData *data = cameraData(camera);<br>
> > > + auto config =<br>
> > > + std::make_unique<VirtualCameraConfiguration>(data);<br>
> > > +<br>
> > > + if (roles.empty())<br>
> > > + return config;<br>
> > > +<br>
> > > + Size minSize, sensorResolution;<br>
> > > + for (const auto &resolution : data->supportedResolutions_) {<br>
> > > + if (minSize.isNull() || minSize > resolution.size)<br>
> > > + minSize = resolution.size;<br>
> > > +<br>
> > > + sensorResolution = std::max(sensorResolution,<br>
> > resolution.size);<br>
> ><br>
> > As you do this min/max search in a few places, why not doing it when<br>
> > you construct data->supportedResolutions_ once ?<br>
> ><br>
> Added in VirtualCameraData.<br>
><br>
><br>
> ><br>
> > > + }<br>
> > > +<br>
> > > + for (const StreamRole role : roles) {<br>
> ><br>
> > If the pipeline handler works with a single stream, you should only<br>
> > consider the first role maybe<br>
> ><br>
> Actually I think there's no reason to only support one Stream in<br>
> Virtual Pipeline Handler. The raw stream doesn't seem to be<br>
> properly supported in the following patches though. I think we<br>
> should drop the support of raw.<br>
><br>
<br>
I assumed (maybe from a previous discussion) you were going to have a<br>
single stream. I'm sorry, I was wrong.<br></blockquote><div><br></div><div>Nah, I prepared for multiple streams, but haven't enabled it yet :p.</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
Please drop RAW, yes.<br></blockquote><div><br></div><div>Done in v10.</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
><br>
> ><br>
> > > + std::map<PixelFormat, std::vector<SizeRange>><br>
> > streamFormats;<br>
> > > + unsigned int bufferCount;<br>
> > > + PixelFormat pixelFormat;<br>
> > > +<br>
> > > + switch (role) {<br>
> > > + case StreamRole::StillCapture:<br>
> > > + pixelFormat = formats::NV12;<br>
> > > + bufferCount =<br>
> > VirtualCameraConfiguration::kBufferCount;<br>
> > > + streamFormats[pixelFormat] = { { minSize,<br>
> > sensorResolution } };<br>
> ><br>
> > bufferCount and streamFormats can be assigned outsize of the cases,<br>
> > they're the same for all roles<br>
> ><br>
> Done<br>
><br>
><br>
> ><br>
> > > +<br>
> > > + break;<br>
> > > +<br>
> > > + case StreamRole::Raw: {<br>
> > > + /* \todo check */<br>
> > > + pixelFormat = formats::SBGGR10;<br>
> > > + bufferCount =<br>
> > VirtualCameraConfiguration::kBufferCount;<br>
> > > + streamFormats[pixelFormat] = { { minSize,<br>
> > sensorResolution } };<br>
> > > +<br>
> > > + break;<br>
> > > + }<br>
> > > +<br>
> > > + case StreamRole::Viewfinder:<br>
> > > + case StreamRole::VideoRecording: {<br>
> > > + pixelFormat = formats::NV12;<br>
> > > + bufferCount =<br>
> > VirtualCameraConfiguration::kBufferCount;<br>
> > > + streamFormats[pixelFormat] = { { minSize,<br>
> > sensorResolution } };<br>
> > > +<br>
> > > + break;<br>
> > > + }<br>
> > > +<br>
> > > + default:<br>
> > > + LOG(Virtual, Error)<br>
> > > + << "Requested stream role not supported: "<br>
> > << role;<br>
> > > + config.reset();<br>
> > > + return config;<br>
> > > + }<br>
> > > +<br>
> > > + StreamFormats formats(streamFormats);<br>
> > > + StreamConfiguration cfg(formats);<br>
> > > + cfg.size = sensorResolution;<br>
> > > + cfg.pixelFormat = pixelFormat;<br>
> > > + cfg.bufferCount = bufferCount;<br>
> > > + config->addConfiguration(cfg);<br>
> > > + }<br>
> > > +<br>
> > > + if (config->validate() == CameraConfiguration::Invalid)<br>
> > > + config.reset();<br>
> > > +<br>
> > > + return config;<br>
> > > +}<br>
> > > +<br>
> > > +int PipelineHandlerVirtual::configure(<br>
> > > + [[maybe_unused]] Camera *camera,<br>
> > > + [[maybe_unused]] CameraConfiguration *config)<br>
> ><br>
> > int PipelineHandlerVirtual::configure([[maybe_unused]] Camera *camera,<br>
> > [[maybe_unused]] CameraConfiguration<br>
> > *config)<br>
> ><br>
> > Done<br>
><br>
><br>
> > > +{<br>
> > > + // Nothing to be done.<br>
> > > + return 0;<br>
> > > +}<br>
> > > +<br>
> > > +int PipelineHandlerVirtual::exportFrameBuffers(<br>
> > > + [[maybe_unused]] Camera *camera,<br>
> > > + Stream *stream,<br>
> > > + std::vector<std::unique_ptr<FrameBuffer>> *buffers)<br>
> ><br>
> > int PipelineHandlerVirtual::exportFrameBuffers([[maybe_unused]] Camera<br>
> > *camera,<br>
> > Stream *stream,<br>
> ><br>
> > std::vector<std::unique_ptr<FrameBuffer>> *buffers)<br>
> ><br>
> > if you prefer<br>
> ><br>
> > Done<br>
><br>
><br>
> > > +{<br>
> > > + if (!dmaBufAllocator_.isValid())<br>
> > > + return -ENOBUFS;<br>
> > > +<br>
> > > + const StreamConfiguration &config = stream->configuration();<br>
> > > +<br>
> > > + auto info = PixelFormatInfo::info(config.pixelFormat);<br>
> > > +<br>
> > > + std::vector<std::size_t> planeSizes;<br>
> > > + for (size_t i = 0; i < info.planes.size(); ++i)<br>
> > > + planeSizes.push_back(info.planeSize(config.size, i));<br>
> > > +<br>
> > > + return dmaBufAllocator_.exportBuffers(config.bufferCount,<br>
> > planeSizes, buffers);<br>
> ><br>
> > ah that's probably why you return count from<br>
> > DmaBufferAllocator::exportBuffers()<br>
> ><br>
> Exactly :)<br>
><br>
><br>
> ><br>
> > > +}<br>
> > > +<br>
> > > +int PipelineHandlerVirtual::start([[maybe_unused]] Camera *camera,<br>
> > > + [[maybe_unused]] const ControlList<br>
> > *controls)<br>
> > > +{<br>
> > > + /* \todo Start reading the virtual video if any. */<br>
> > > + return 0;<br>
> > > +}<br>
> > > +<br>
> > > +void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera)<br>
> > > +{<br>
> > > + /* \todo Reset the virtual video if any. */<br>
> > > +}<br>
> > > +<br>
> > > +int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera<br>
> > *camera,<br>
> > > + Request *request)<br>
> > > +{<br>
> > > + /* \todo Read from the virtual video if any. */<br>
> > > + for (auto it : request->buffers())<br>
> > > + completeBuffer(request, it.second);<br>
> > > +<br>
> > > + request->metadata().set(controls::SensorTimestamp,<br>
> > currentTimestamp());<br>
> > > + completeRequest(request);<br>
> > > +<br>
> > > + return 0;<br>
> > > +}<br>
> > > +<br>
> > > +bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator<br>
> > *enumerator)<br>
> > > +{<br>
> > > + /* \todo Add virtual cameras according to a config file. */<br>
> > > +<br>
> > > + std::unique_ptr<VirtualCameraData> data =<br>
> > std::make_unique<VirtualCameraData>(this);<br>
> > > +<br>
> > > + data->supportedResolutions_.resize(2);<br>
> > > + data->supportedResolutions_[0] = { .size = Size(1920, 1080),<br>
> > .frame_rates = { 30 } };<br>
> > > + data->supportedResolutions_[1] = { .size = Size(1280, 720),<br>
> > .frame_rates = { 30, 60 } };<br>
> > > +<br>
> > > + data->properties_.set(properties::Location,<br>
> > properties::CameraLocationFront);<br>
> > > + data->properties_.set(properties::Model, "Virtual Video Device");<br>
> > > + data->properties_.set(properties::PixelArrayActiveAreas, {<br>
> > Rectangle(Size(1920, 1080)) });<br>
> > > +<br>
> > > + /* \todo Set FrameDurationLimits based on config. */<br>
> > > + ControlInfoMap::Map controls;<br>
> > > + int64_t min_frame_duration = 30, max_frame_duration = 60;<br>
> ><br>
> > doesn't match the above frame rates and it should be expressed in<br>
> > microseconds. I would suggest for this patch to set both framerates to<br>
> > 30 and initialize FrameDurationLimits with {33333, 333333}<br>
> ><br>
> > It's not a big deal however, it will be replaced later in the series<br>
> ><br>
> ><br>
> Done, thanks!<br>
><br>
><br>
> ><br>
> > > + controls[&controls::FrameDurationLimits] =<br>
> > ControlInfo(min_frame_duration, max_frame_duration);<br>
> > > + data->controlInfo_ = ControlInfoMap(std::move(controls),<br>
> > controls::controls);<br>
> > > +<br>
> > > + /* Create and register the camera. */<br>
> > > + std::set<Stream *> streams{ &data->stream_ };<br>
> > > + const std::string id = "Virtual0";<br>
> > > + std::shared_ptr<Camera> camera = Camera::create(std::move(data),<br>
> > id, streams);<br>
> > > + registerCamera(std::move(camera));<br>
> > > +<br>
> > > + return false; // Prevent infinite loops for now<br>
> ><br>
> > I presume this will also be changed in next patches...<br>
> ><br>
> Updated in this patch, with a static boolean though.<br>
><br>
><br>
> ><br>
> > > +}<br>
> > > +<br>
> > > +REGISTER_PIPELINE_HANDLER(PipelineHandlerVirtual, "virtual")<br>
> > > +<br>
> > > +} /* namespace libcamera */<br>
> > > diff --git a/src/libcamera/pipeline/virtual/virtual.h<br>
> > b/src/libcamera/pipeline/virtual/virtual.h<br>
> > > new file mode 100644<br>
> > > index 000000000..6fc6b34d8<br>
> > > --- /dev/null<br>
> > > +++ b/src/libcamera/pipeline/virtual/virtual.h<br>
> > > @@ -0,0 +1,78 @@<br>
> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */<br>
> > > +/*<br>
> > > + * Copyright (C) 2023, Google Inc.<br>
> > > + *<br>
> > > + * virtual.h - Pipeline handler for virtual cameras<br>
> > > + */<br>
> > > +<br>
> > > +#pragma once<br>
> > > +<br>
> > > +#include <libcamera/base/file.h><br>
> > > +<br>
> > > +#include "libcamera/internal/camera.h"<br>
> > > +#include "libcamera/internal/dma_buf_allocator.h"<br>
> > > +#include "libcamera/internal/pipeline_handler.h"<br>
> > > +<br>
> > > +namespace libcamera {<br>
> > > +<br>
> > > +class VirtualCameraData : public Camera::Private<br>
> > > +{<br>
> > > +public:<br>
> > > + struct Resolution {<br>
> > > + Size size;<br>
> > > + std::vector<int> frame_rates;<br>
> > > + };<br>
> > > + VirtualCameraData(PipelineHandler *pipe)<br>
> > > + : Camera::Private(pipe)<br>
> > > + {<br>
> > > + }<br>
> > > +<br>
> > > + ~VirtualCameraData() = default;<br>
> > > +<br>
> > > + std::vector<Resolution> supportedResolutions_;<br>
> > > +<br>
> > > + Stream stream_;<br>
> > > +};<br>
> > > +<br>
> > > +class VirtualCameraConfiguration : public CameraConfiguration<br>
> > > +{<br>
> > > +public:<br>
> > > + static constexpr unsigned int kBufferCount = 4;<br>
> > > +<br>
> > > + VirtualCameraConfiguration(VirtualCameraData *data);<br>
> > > +<br>
> > > + Status validate() override;<br>
> > > +<br>
> > > +private:<br>
> > > + const VirtualCameraData *data_;<br>
> > > +};<br>
> > > +<br>
> > > +class PipelineHandlerVirtual : public PipelineHandler<br>
> > > +{<br>
> > > +public:<br>
> > > + PipelineHandlerVirtual(CameraManager *manager);<br>
> > > +<br>
> > > + std::unique_ptr<CameraConfiguration> generateConfiguration(Camera<br>
> > *camera,<br>
> > > +<br>
> > Span<const StreamRole> roles) override;<br>
> > > + int configure(Camera *camera, CameraConfiguration *config)<br>
> > override;<br>
> > > +<br>
> > > + int exportFrameBuffers(Camera *camera, Stream *stream,<br>
> > > + std::vector<std::unique_ptr<FrameBuffer>><br>
> > *buffers) override;<br>
> > > +<br>
> > > + int start(Camera *camera, const ControlList *controls) override;<br>
> > > + void stopDevice(Camera *camera) override;<br>
> > > +<br>
> > > + int queueRequestDevice(Camera *camera, Request *request) override;<br>
> > > +<br>
> > > + bool match(DeviceEnumerator *enumerator) override;<br>
> > > +<br>
> > > +private:<br>
> > > + VirtualCameraData *cameraData(Camera *camera)<br>
> > > + {<br>
> > > + return static_cast<VirtualCameraData *>(camera->_d());<br>
> > > + }<br>
> > > +<br>
> > > + DmaBufAllocator dmaBufAllocator_;<br>
> > > +};<br>
> > > +<br>
> > > +} // namespace libcamera<br>
> > > --<br>
> > > 2.46.0.184.g6999bdac58-goog<br>
> > ><br>
> ><br>
</blockquote></div></div>