| Message ID | 20261019224902.177382-4-robert.mader@collabora.com |
|---|---|
| State | Accepted |
| Headers | show |
| Series |
|
| Related | show |
On Tue, Oct 20, 2026 at 12:49:02AM +0200, Robert Mader wrote: > While NV12 is the most common native format for hardware video encoders, > YUV420 is the equivalent for most software video encoders. Thus > supporting both is great for testing. > > Similarly having some RGB formats with and without alpha can be very > useful, especially as the swISP is currently limited to such formats. > > Signed-off-by: Robert Mader <robert.mader@collabora.com> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > Tested-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > --- > .../pipeline/virtual/frame_generator.h | 3 +- > .../virtual/image_frame_generator.cpp | 61 ++++++++++++++++--- > .../pipeline/virtual/image_frame_generator.h | 2 +- > .../virtual/test_pattern_generator.cpp | 52 +++++++++++++--- > .../pipeline/virtual/test_pattern_generator.h | 2 +- > src/libcamera/pipeline/virtual/virtual.cpp | 45 +++++++++++--- > 6 files changed, 135 insertions(+), 30 deletions(-) > > diff --git a/src/libcamera/pipeline/virtual/frame_generator.h b/src/libcamera/pipeline/virtual/frame_generator.h > index a0658c45cd82..fd34a91a5e50 100644 > --- a/src/libcamera/pipeline/virtual/frame_generator.h > +++ b/src/libcamera/pipeline/virtual/frame_generator.h > @@ -9,6 +9,7 @@ > > #include <libcamera/framebuffer.h> > #include <libcamera/geometry.h> > +#include <libcamera/stream.h> > > namespace libcamera { > > @@ -19,7 +20,7 @@ public: > > virtual void configure(const Size &size) = 0; > > - virtual int generateFrame(const Size &size, > + virtual int generateFrame(const StreamConfiguration &cfg, > const FrameBuffer *buffer) = 0; > > protected: > diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.cpp b/src/libcamera/pipeline/virtual/image_frame_generator.cpp > index 5caad8b72763..7a9bc9ac1ebd 100644 > --- a/src/libcamera/pipeline/virtual/image_frame_generator.cpp > +++ b/src/libcamera/pipeline/virtual/image_frame_generator.cpp > @@ -126,7 +126,7 @@ void ImageFrameGenerator::configure(const Size &size) > } > } > > -int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buffer) > +int ImageFrameGenerator::generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) > { > ASSERT(!scaledFrameDatas_.empty()); > > @@ -143,17 +143,62 @@ int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buff > MappedFrameBuffer::MapFlag::Write); > > const auto &planes = mappedFrameBuffer.planes(); > - const PixelFormatInfo &info = PixelFormatInfo::info(formats::NV12); > + const Size size = cfg.size; Sorry, I forgot the & in my review of v4: const Size &size = cfg.size; > + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); > > /* Loop only around the number of images available */ > frameIndex_ %= imageFrameDatas_.size(); > > + int ret; > + auto Y = scaledFrameDatas_[frameIndex_].Y.get(); > + auto UV = scaledFrameDatas_[frameIndex_].UV.get(); Lowercase for variables please. > + > /* Write the scaledY and scaledUV to the mapped frame buffer */ > - libyuv::NV12Copy(scaledFrameDatas_[frameIndex_].Y.get(), size.width, > - scaledFrameDatas_[frameIndex_].UV.get(), size.width, > - planes[0].data(), info.stride(size.width, 0), > - planes[1].data(), info.stride(size.width, 1), > - size.width, size.height); > + switch (cfg.pixelFormat) { > + case formats::NV12: > + ret = libyuv::NV12Copy(Y, > + size.width, > + UV, > + size.width, > + planes[0].data(), > + info.stride(size.width, 0), > + planes[1].data(), > + info.stride(size.width, 1), > + size.width, size.height); I think the following would be more readable as it groups related arguments without making lines too long: ret = libyuv::NV12Copy(Y, size.width, UV, size.width, planes[0].data(), info.stride(size.width, 0), planes[1].data(), info.stride(size.width, 1), size.width, size.height); Same below. > + break; > + > + case formats::YUV420: > + ret = libyuv::NV12ToI420(Y, > + size.width, > + UV, > + size.width, > + planes[0].data(), > + info.stride(size.width, 0), > + planes[1].data(), > + info.stride(size.width, 1), > + planes[2].data(), > + info.stride(size.width, 2), > + size.width, size.height); > + break; > + > + case formats::ABGR8888: > + case formats::XBGR8888: > + ret = libyuv::NV12ToABGR(Y, > + size.width, > + UV, > + size.width, > + planes[0].data(), > + info.stride(size.width, 0), > + size.width, size.height); > + break; > + > + default: > + LOG(Virtual, Error) << "Unsupported format"; > + return -1; > + } > + > + if (ret != 0) > + LOG(Virtual, Error) << "Conversation failed with " << ret; > > /* Proceed to the next image every 4 frames */ > /* \todo Consider setting the frameRepeat in the config file */ > @@ -161,7 +206,7 @@ int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buff > if (parameter_ % frameRepeat == 0) > frameIndex_++; > > - return 0; > + return ret; > } > > /* > diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.h b/src/libcamera/pipeline/virtual/image_frame_generator.h > index 42a077ba803c..19b6fe38654e 100644 > --- a/src/libcamera/pipeline/virtual/image_frame_generator.h > +++ b/src/libcamera/pipeline/virtual/image_frame_generator.h > @@ -37,7 +37,7 @@ private: > }; > > void configure(const Size &size) override; > - int generateFrame(const Size &size, const FrameBuffer *buffer) override; > + int generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) override; > > std::vector<ImageFrameData> imageFrameDatas_; > std::vector<ImageFrameData> scaledFrameDatas_; > diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp > index 571be4fbf65d..e214048d288a 100644 > --- a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp > +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp > @@ -44,7 +44,7 @@ LOG_DECLARE_CATEGORY(Virtual) > > static const unsigned int kARGBSize = 4; > > -int TestPatternGenerator::generateFrame(const Size &size, > +int TestPatternGenerator::generateFrame(const StreamConfiguration &cfg, > const FrameBuffer *buffer) > { > /* > @@ -60,19 +60,53 @@ int TestPatternGenerator::generateFrame(const Size &size, > MappedFrameBuffer::MapFlag::Write); > > const auto &planes = mappedFrameBuffer.planes(); > - const PixelFormatInfo &info = PixelFormatInfo::info(formats::NV12); > + const Size size = cfg.size; const Size &size = cfg.size; > + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); > > rotateLeft1Column<kARGBSize>(size, template_.get()); > > + int ret; > + > /* Convert the template_ to the frame buffer */ > - int ret = libyuv::ARGBToNV12(template_.get(), size.width * kARGBSize, > - planes[0].data(), > - info.stride(size.width, 0), > - planes[1].data(), > - info.stride(size.width, 1), > - size.width, size.height); > + switch (cfg.pixelFormat) { > + case formats::NV12: > + ret = libyuv::ARGBToNV12(template_.get(), > + size.width * kARGBSize, > + planes[0].data(), > + info.stride(size.width, 0), > + planes[1].data(), > + info.stride(size.width, 1), > + size.width, size.height); Similarly as above, ret = libyuv::ARGBToNV12(template_.get(), size.width * kARGBSize, planes[0].data(), info.stride(size.width, 0), planes[1].data(), info.stride(size.width, 1), size.width, size.height); Same below. Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> I think we can handle that when applying if you let us know if you're fine with the small changes. > + break; > + > + case formats::YUV420: > + ret = libyuv::ARGBToI420(template_.get(), > + size.width * kARGBSize, > + planes[0].data(), > + info.stride(size.width, 0), > + planes[1].data(), > + info.stride(size.width, 1), > + planes[2].data(), > + info.stride(size.width, 2), > + size.width, size.height); > + break; > + > + case formats::ABGR8888: > + case formats::XBGR8888: > + ret = libyuv::ARGBCopy(template_.get(), > + size.width * kARGBSize, > + planes[0].data(), > + info.stride(size.width, 0), > + size.width, size.height); > + break; > + > + default: > + LOG(Virtual, Error) << "Unsupported format"; > + return -1; > + } > + > if (ret != 0) > - LOG(Virtual, Error) << "ARGBToNV12() failed with " << ret; > + LOG(Virtual, Error) << "Conversation failed with " << ret; > > return ret; > } > diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.h b/src/libcamera/pipeline/virtual/test_pattern_generator.h > index 2a51bd31abdf..9f72d652ab2c 100644 > --- a/src/libcamera/pipeline/virtual/test_pattern_generator.h > +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.h > @@ -24,7 +24,7 @@ enum class TestPattern : char { > class TestPatternGenerator : public FrameGenerator > { > public: > - int generateFrame(const Size &size, const FrameBuffer *buffer) override; > + int generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) override; > > protected: > /* Buffer of test pattern template */ > diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp > index e25dd85a7391..f35af97b103d 100644 > --- a/src/libcamera/pipeline/virtual/virtual.cpp > +++ b/src/libcamera/pipeline/virtual/virtual.cpp > @@ -47,6 +47,13 @@ LOG_DEFINE_CATEGORY(Virtual) > > namespace { > > +constexpr PixelFormat outputFormats[] = { > + formats::NV12, > + formats::YUV420, > + formats::ABGR8888, > + formats::XBGR8888, > +}; > + > uint64_t currentTimestamp() > { > const auto now = std::chrono::steady_clock::now(); > @@ -148,7 +155,7 @@ void VirtualCameraData::processRequest(Request *request) > found = true; > > if (streamConfig.frameGenerator->generateFrame( > - stream->configuration().size, buffer)) > + stream->configuration(), buffer)) > fmd.status = FrameMetadata::Status::FrameError; > > bufferCompleted.emit(buffer); > @@ -202,18 +209,37 @@ CameraConfiguration::Status VirtualCameraConfiguration::validate() > adjusted = true; > } > > - if (cfg.pixelFormat != formats::NV12) { > - cfg.pixelFormat = formats::NV12; > + if (std::find(std::begin(outputFormats), > + std::end(outputFormats), > + cfg.pixelFormat) == std::end(outputFormats)) { > + cfg.pixelFormat = outputFormats[0]; > status = Adjusted; > adjusted = true; > } > > - if (cfg.colorSpace != ColorSpace::Smpte170m) { > - cfg.colorSpace = ColorSpace::Smpte170m; > + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); > + std::optional<ColorSpace> colorSpace; > + > + switch (info.colourEncoding) { > + case PixelFormatInfo::ColourEncodingRGB: > + colorSpace = ColorSpace::Srgb; > + break; > + > + case PixelFormatInfo::ColourEncodingYUV: > + colorSpace = ColorSpace::Smpte170m; > + break; > + > + default: > + ASSERT(false); > + } > + > + if (cfg.colorSpace && cfg.colorSpace != colorSpace) { > status = Adjusted; > adjusted = true; > } > > + cfg.colorSpace = colorSpace; > + > if (validateColorSpaces() == Adjusted) { > status = Adjusted; > adjusted = true; > @@ -223,7 +249,6 @@ CameraConfiguration::Status VirtualCameraConfiguration::validate() > LOG(Virtual, Info) > << "Stream configuration adjusted to " << cfg.toString(); > > - const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); > cfg.stride = info.stride(cfg.size.width, 0, 1); > cfg.frameSize = info.frameSize(cfg.size, 1); > > @@ -275,12 +300,12 @@ PipelineHandlerVirtual::generateConfiguration(Camera *camera, > } > > std::map<PixelFormat, std::vector<SizeRange>> streamFormats; > - PixelFormat pixelFormat = formats::NV12; > - streamFormats[pixelFormat] = { { data->config_.minResolutionSize, > - data->config_.maxResolutionSize } }; > + for (auto pixelFormat : outputFormats) > + streamFormats[pixelFormat] = { { data->config_.minResolutionSize, > + data->config_.maxResolutionSize } }; > StreamFormats formats(streamFormats); > StreamConfiguration cfg(formats); > - cfg.pixelFormat = pixelFormat; > + cfg.pixelFormat = outputFormats[0]; > cfg.size = data->config_.maxResolutionSize; > cfg.bufferCount = VirtualCameraConfiguration::kBufferCount; > cfg.colorSpace = ColorSpace::Smpte170m;
Hi, On 20.10.26 01:20, Laurent Pinchart wrote: > On Tue, Oct 20, 2026 at 12:49:02AM +0200, Robert Mader wrote: >> While NV12 is the most common native format for hardware video encoders, >> YUV420 is the equivalent for most software video encoders. Thus >> supporting both is great for testing. >> >> Similarly having some RGB formats with and without alpha can be very >> useful, especially as the swISP is currently limited to such formats. >> >> Signed-off-by: Robert Mader <robert.mader@collabora.com> >> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> >> Tested-by: Kieran Bingham <kieran.bingham@ideasonboard.com> >> Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> >> --- >> .../pipeline/virtual/frame_generator.h | 3 +- >> .../virtual/image_frame_generator.cpp | 61 ++++++++++++++++--- >> .../pipeline/virtual/image_frame_generator.h | 2 +- >> .../virtual/test_pattern_generator.cpp | 52 +++++++++++++--- >> .../pipeline/virtual/test_pattern_generator.h | 2 +- >> src/libcamera/pipeline/virtual/virtual.cpp | 45 +++++++++++--- >> 6 files changed, 135 insertions(+), 30 deletions(-) >> >> diff --git a/src/libcamera/pipeline/virtual/frame_generator.h b/src/libcamera/pipeline/virtual/frame_generator.h >> index a0658c45cd82..fd34a91a5e50 100644 >> --- a/src/libcamera/pipeline/virtual/frame_generator.h >> +++ b/src/libcamera/pipeline/virtual/frame_generator.h >> @@ -9,6 +9,7 @@ >> >> #include <libcamera/framebuffer.h> >> #include <libcamera/geometry.h> >> +#include <libcamera/stream.h> >> >> namespace libcamera { >> >> @@ -19,7 +20,7 @@ public: >> >> virtual void configure(const Size &size) = 0; >> >> - virtual int generateFrame(const Size &size, >> + virtual int generateFrame(const StreamConfiguration &cfg, >> const FrameBuffer *buffer) = 0; >> >> protected: >> diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.cpp b/src/libcamera/pipeline/virtual/image_frame_generator.cpp >> index 5caad8b72763..7a9bc9ac1ebd 100644 >> --- a/src/libcamera/pipeline/virtual/image_frame_generator.cpp >> +++ b/src/libcamera/pipeline/virtual/image_frame_generator.cpp >> @@ -126,7 +126,7 @@ void ImageFrameGenerator::configure(const Size &size) >> } >> } >> >> -int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buffer) >> +int ImageFrameGenerator::generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) >> { >> ASSERT(!scaledFrameDatas_.empty()); >> >> @@ -143,17 +143,62 @@ int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buff >> MappedFrameBuffer::MapFlag::Write); >> >> const auto &planes = mappedFrameBuffer.planes(); >> - const PixelFormatInfo &info = PixelFormatInfo::info(formats::NV12); >> + const Size size = cfg.size; > Sorry, I forgot the & in my review of v4: > > const Size &size = cfg.size; > >> + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); >> >> /* Loop only around the number of images available */ >> frameIndex_ %= imageFrameDatas_.size(); >> >> + int ret; >> + auto Y = scaledFrameDatas_[frameIndex_].Y.get(); >> + auto UV = scaledFrameDatas_[frameIndex_].UV.get(); > Lowercase for variables please. > >> + >> /* Write the scaledY and scaledUV to the mapped frame buffer */ >> - libyuv::NV12Copy(scaledFrameDatas_[frameIndex_].Y.get(), size.width, >> - scaledFrameDatas_[frameIndex_].UV.get(), size.width, >> - planes[0].data(), info.stride(size.width, 0), >> - planes[1].data(), info.stride(size.width, 1), >> - size.width, size.height); >> + switch (cfg.pixelFormat) { >> + case formats::NV12: >> + ret = libyuv::NV12Copy(Y, >> + size.width, >> + UV, >> + size.width, >> + planes[0].data(), >> + info.stride(size.width, 0), >> + planes[1].data(), >> + info.stride(size.width, 1), >> + size.width, size.height); > I think the following would be more readable as it groups related > arguments without making lines too long: > > ret = libyuv::NV12Copy(Y, size.width, UV, size.width, > planes[0].data(), info.stride(size.width, 0), > planes[1].data(), info.stride(size.width, 1), > size.width, size.height); > > Same below. > >> + break; >> + >> + case formats::YUV420: >> + ret = libyuv::NV12ToI420(Y, >> + size.width, >> + UV, >> + size.width, >> + planes[0].data(), >> + info.stride(size.width, 0), >> + planes[1].data(), >> + info.stride(size.width, 1), >> + planes[2].data(), >> + info.stride(size.width, 2), >> + size.width, size.height); >> + break; >> + >> + case formats::ABGR8888: >> + case formats::XBGR8888: >> + ret = libyuv::NV12ToABGR(Y, >> + size.width, >> + UV, >> + size.width, >> + planes[0].data(), >> + info.stride(size.width, 0), >> + size.width, size.height); >> + break; >> + >> + default: >> + LOG(Virtual, Error) << "Unsupported format"; >> + return -1; >> + } >> + >> + if (ret != 0) >> + LOG(Virtual, Error) << "Conversation failed with " << ret; >> >> /* Proceed to the next image every 4 frames */ >> /* \todo Consider setting the frameRepeat in the config file */ >> @@ -161,7 +206,7 @@ int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buff >> if (parameter_ % frameRepeat == 0) >> frameIndex_++; >> >> - return 0; >> + return ret; >> } >> >> /* >> diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.h b/src/libcamera/pipeline/virtual/image_frame_generator.h >> index 42a077ba803c..19b6fe38654e 100644 >> --- a/src/libcamera/pipeline/virtual/image_frame_generator.h >> +++ b/src/libcamera/pipeline/virtual/image_frame_generator.h >> @@ -37,7 +37,7 @@ private: >> }; >> >> void configure(const Size &size) override; >> - int generateFrame(const Size &size, const FrameBuffer *buffer) override; >> + int generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) override; >> >> std::vector<ImageFrameData> imageFrameDatas_; >> std::vector<ImageFrameData> scaledFrameDatas_; >> diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp >> index 571be4fbf65d..e214048d288a 100644 >> --- a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp >> +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp >> @@ -44,7 +44,7 @@ LOG_DECLARE_CATEGORY(Virtual) >> >> static const unsigned int kARGBSize = 4; >> >> -int TestPatternGenerator::generateFrame(const Size &size, >> +int TestPatternGenerator::generateFrame(const StreamConfiguration &cfg, >> const FrameBuffer *buffer) >> { >> /* >> @@ -60,19 +60,53 @@ int TestPatternGenerator::generateFrame(const Size &size, >> MappedFrameBuffer::MapFlag::Write); >> >> const auto &planes = mappedFrameBuffer.planes(); >> - const PixelFormatInfo &info = PixelFormatInfo::info(formats::NV12); >> + const Size size = cfg.size; > const Size &size = cfg.size; > >> + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); >> >> rotateLeft1Column<kARGBSize>(size, template_.get()); >> >> + int ret; >> + >> /* Convert the template_ to the frame buffer */ >> - int ret = libyuv::ARGBToNV12(template_.get(), size.width * kARGBSize, >> - planes[0].data(), >> - info.stride(size.width, 0), >> - planes[1].data(), >> - info.stride(size.width, 1), >> - size.width, size.height); >> + switch (cfg.pixelFormat) { >> + case formats::NV12: >> + ret = libyuv::ARGBToNV12(template_.get(), >> + size.width * kARGBSize, >> + planes[0].data(), >> + info.stride(size.width, 0), >> + planes[1].data(), >> + info.stride(size.width, 1), >> + size.width, size.height); > Similarly as above, > > ret = libyuv::ARGBToNV12(template_.get(), size.width * kARGBSize, > planes[0].data(), info.stride(size.width, 0), > planes[1].data(), info.stride(size.width, 1), > size.width, size.height); > > Same below. > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > > I think we can handle that when applying if you let us know if you're > fine with the small changes. Sure, and thanks! > >> + break; >> + >> + case formats::YUV420: >> + ret = libyuv::ARGBToI420(template_.get(), >> + size.width * kARGBSize, >> + planes[0].data(), >> + info.stride(size.width, 0), >> + planes[1].data(), >> + info.stride(size.width, 1), >> + planes[2].data(), >> + info.stride(size.width, 2), >> + size.width, size.height); >> + break; >> + >> + case formats::ABGR8888: >> + case formats::XBGR8888: >> + ret = libyuv::ARGBCopy(template_.get(), >> + size.width * kARGBSize, >> + planes[0].data(), >> + info.stride(size.width, 0), >> + size.width, size.height); >> + break; >> + >> + default: >> + LOG(Virtual, Error) << "Unsupported format"; >> + return -1; >> + } >> + >> if (ret != 0) >> - LOG(Virtual, Error) << "ARGBToNV12() failed with " << ret; >> + LOG(Virtual, Error) << "Conversation failed with " << ret; >> >> return ret; >> } >> diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.h b/src/libcamera/pipeline/virtual/test_pattern_generator.h >> index 2a51bd31abdf..9f72d652ab2c 100644 >> --- a/src/libcamera/pipeline/virtual/test_pattern_generator.h >> +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.h >> @@ -24,7 +24,7 @@ enum class TestPattern : char { >> class TestPatternGenerator : public FrameGenerator >> { >> public: >> - int generateFrame(const Size &size, const FrameBuffer *buffer) override; >> + int generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) override; >> >> protected: >> /* Buffer of test pattern template */ >> diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp >> index e25dd85a7391..f35af97b103d 100644 >> --- a/src/libcamera/pipeline/virtual/virtual.cpp >> +++ b/src/libcamera/pipeline/virtual/virtual.cpp >> @@ -47,6 +47,13 @@ LOG_DEFINE_CATEGORY(Virtual) >> >> namespace { >> >> +constexpr PixelFormat outputFormats[] = { >> + formats::NV12, >> + formats::YUV420, >> + formats::ABGR8888, >> + formats::XBGR8888, >> +}; >> + >> uint64_t currentTimestamp() >> { >> const auto now = std::chrono::steady_clock::now(); >> @@ -148,7 +155,7 @@ void VirtualCameraData::processRequest(Request *request) >> found = true; >> >> if (streamConfig.frameGenerator->generateFrame( >> - stream->configuration().size, buffer)) >> + stream->configuration(), buffer)) >> fmd.status = FrameMetadata::Status::FrameError; >> >> bufferCompleted.emit(buffer); >> @@ -202,18 +209,37 @@ CameraConfiguration::Status VirtualCameraConfiguration::validate() >> adjusted = true; >> } >> >> - if (cfg.pixelFormat != formats::NV12) { >> - cfg.pixelFormat = formats::NV12; >> + if (std::find(std::begin(outputFormats), >> + std::end(outputFormats), >> + cfg.pixelFormat) == std::end(outputFormats)) { >> + cfg.pixelFormat = outputFormats[0]; >> status = Adjusted; >> adjusted = true; >> } >> >> - if (cfg.colorSpace != ColorSpace::Smpte170m) { >> - cfg.colorSpace = ColorSpace::Smpte170m; >> + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); >> + std::optional<ColorSpace> colorSpace; >> + >> + switch (info.colourEncoding) { >> + case PixelFormatInfo::ColourEncodingRGB: >> + colorSpace = ColorSpace::Srgb; >> + break; >> + >> + case PixelFormatInfo::ColourEncodingYUV: >> + colorSpace = ColorSpace::Smpte170m; >> + break; >> + >> + default: >> + ASSERT(false); >> + } >> + >> + if (cfg.colorSpace && cfg.colorSpace != colorSpace) { >> status = Adjusted; >> adjusted = true; >> } >> >> + cfg.colorSpace = colorSpace; >> + >> if (validateColorSpaces() == Adjusted) { >> status = Adjusted; >> adjusted = true; >> @@ -223,7 +249,6 @@ CameraConfiguration::Status VirtualCameraConfiguration::validate() >> LOG(Virtual, Info) >> << "Stream configuration adjusted to " << cfg.toString(); >> >> - const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); >> cfg.stride = info.stride(cfg.size.width, 0, 1); >> cfg.frameSize = info.frameSize(cfg.size, 1); >> >> @@ -275,12 +300,12 @@ PipelineHandlerVirtual::generateConfiguration(Camera *camera, >> } >> >> std::map<PixelFormat, std::vector<SizeRange>> streamFormats; >> - PixelFormat pixelFormat = formats::NV12; >> - streamFormats[pixelFormat] = { { data->config_.minResolutionSize, >> - data->config_.maxResolutionSize } }; >> + for (auto pixelFormat : outputFormats) >> + streamFormats[pixelFormat] = { { data->config_.minResolutionSize, >> + data->config_.maxResolutionSize } }; >> StreamFormats formats(streamFormats); >> StreamConfiguration cfg(formats); >> - cfg.pixelFormat = pixelFormat; >> + cfg.pixelFormat = outputFormats[0]; >> cfg.size = data->config_.maxResolutionSize; >> cfg.bufferCount = VirtualCameraConfiguration::kBufferCount; >> cfg.colorSpace = ColorSpace::Smpte170m;
Quoting Laurent Pinchart (2026-10-20 00:20:12) > On Tue, Oct 20, 2026 at 12:49:02AM +0200, Robert Mader wrote: > > While NV12 is the most common native format for hardware video encoders, > > YUV420 is the equivalent for most software video encoders. Thus > > supporting both is great for testing. > > > > Similarly having some RGB formats with and without alpha can be very > > useful, especially as the swISP is currently limited to such formats. > > > > Signed-off-by: Robert Mader <robert.mader@collabora.com> > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > Tested-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > > --- > > .../pipeline/virtual/frame_generator.h | 3 +- > > .../virtual/image_frame_generator.cpp | 61 ++++++++++++++++--- > > .../pipeline/virtual/image_frame_generator.h | 2 +- > > .../virtual/test_pattern_generator.cpp | 52 +++++++++++++--- > > .../pipeline/virtual/test_pattern_generator.h | 2 +- > > src/libcamera/pipeline/virtual/virtual.cpp | 45 +++++++++++--- > > 6 files changed, 135 insertions(+), 30 deletions(-) > > > > diff --git a/src/libcamera/pipeline/virtual/frame_generator.h b/src/libcamera/pipeline/virtual/frame_generator.h > > index a0658c45cd82..fd34a91a5e50 100644 > > --- a/src/libcamera/pipeline/virtual/frame_generator.h > > +++ b/src/libcamera/pipeline/virtual/frame_generator.h > > @@ -9,6 +9,7 @@ > > > > #include <libcamera/framebuffer.h> > > #include <libcamera/geometry.h> > > +#include <libcamera/stream.h> > > > > namespace libcamera { > > > > @@ -19,7 +20,7 @@ public: > > > > virtual void configure(const Size &size) = 0; > > > > - virtual int generateFrame(const Size &size, > > + virtual int generateFrame(const StreamConfiguration &cfg, > > const FrameBuffer *buffer) = 0; > > > > protected: > > diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.cpp b/src/libcamera/pipeline/virtual/image_frame_generator.cpp > > index 5caad8b72763..7a9bc9ac1ebd 100644 > > --- a/src/libcamera/pipeline/virtual/image_frame_generator.cpp > > +++ b/src/libcamera/pipeline/virtual/image_frame_generator.cpp > > @@ -126,7 +126,7 @@ void ImageFrameGenerator::configure(const Size &size) > > } > > } > > > > -int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buffer) > > +int ImageFrameGenerator::generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) > > { > > ASSERT(!scaledFrameDatas_.empty()); > > > > @@ -143,17 +143,62 @@ int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buff > > MappedFrameBuffer::MapFlag::Write); > > > > const auto &planes = mappedFrameBuffer.planes(); > > - const PixelFormatInfo &info = PixelFormatInfo::info(formats::NV12); > > + const Size size = cfg.size; > > Sorry, I forgot the & in my review of v4: > > const Size &size = cfg.size; > > > + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); > > > > /* Loop only around the number of images available */ > > frameIndex_ %= imageFrameDatas_.size(); > > > > + int ret; > > + auto Y = scaledFrameDatas_[frameIndex_].Y.get(); > > + auto UV = scaledFrameDatas_[frameIndex_].UV.get(); > > Lowercase for variables please. > > > + > > /* Write the scaledY and scaledUV to the mapped frame buffer */ > > - libyuv::NV12Copy(scaledFrameDatas_[frameIndex_].Y.get(), size.width, > > - scaledFrameDatas_[frameIndex_].UV.get(), size.width, > > - planes[0].data(), info.stride(size.width, 0), > > - planes[1].data(), info.stride(size.width, 1), > > - size.width, size.height); > > + switch (cfg.pixelFormat) { > > + case formats::NV12: > > + ret = libyuv::NV12Copy(Y, > > + size.width, > > + UV, > > + size.width, > > + planes[0].data(), > > + info.stride(size.width, 0), > > + planes[1].data(), > > + info.stride(size.width, 1), > > + size.width, size.height); > > I think the following would be more readable as it groups related > arguments without making lines too long: > > ret = libyuv::NV12Copy(Y, size.width, UV, size.width, > planes[0].data(), info.stride(size.width, 0), > planes[1].data(), info.stride(size.width, 1), > size.width, size.height); > > Same below. > > > + break; > > + > > + case formats::YUV420: > > + ret = libyuv::NV12ToI420(Y, > > + size.width, > > + UV, > > + size.width, > > + planes[0].data(), > > + info.stride(size.width, 0), > > + planes[1].data(), > > + info.stride(size.width, 1), > > + planes[2].data(), > > + info.stride(size.width, 2), > > + size.width, size.height); > > + break; > > + > > + case formats::ABGR8888: > > + case formats::XBGR8888: > > + ret = libyuv::NV12ToABGR(Y, > > + size.width, > > + UV, > > + size.width, > > + planes[0].data(), > > + info.stride(size.width, 0), > > + size.width, size.height); > > + break; > > + > > + default: > > + LOG(Virtual, Error) << "Unsupported format"; > > + return -1; > > + } > > + > > + if (ret != 0) > > + LOG(Virtual, Error) << "Conversation failed with " << ret; As much as I love a good chat, I'll change this to 'Conversion' ;-) -- Kieran > > > > /* Proceed to the next image every 4 frames */ > > /* \todo Consider setting the frameRepeat in the config file */ > > @@ -161,7 +206,7 @@ int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buff > > if (parameter_ % frameRepeat == 0) > > frameIndex_++; > > > > - return 0; > > + return ret; > > } > > > > /* > > diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.h b/src/libcamera/pipeline/virtual/image_frame_generator.h > > index 42a077ba803c..19b6fe38654e 100644 > > --- a/src/libcamera/pipeline/virtual/image_frame_generator.h > > +++ b/src/libcamera/pipeline/virtual/image_frame_generator.h > > @@ -37,7 +37,7 @@ private: > > }; > > > > void configure(const Size &size) override; > > - int generateFrame(const Size &size, const FrameBuffer *buffer) override; > > + int generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) override; > > > > std::vector<ImageFrameData> imageFrameDatas_; > > std::vector<ImageFrameData> scaledFrameDatas_; > > diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp > > index 571be4fbf65d..e214048d288a 100644 > > --- a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp > > +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp > > @@ -44,7 +44,7 @@ LOG_DECLARE_CATEGORY(Virtual) > > > > static const unsigned int kARGBSize = 4; > > > > -int TestPatternGenerator::generateFrame(const Size &size, > > +int TestPatternGenerator::generateFrame(const StreamConfiguration &cfg, > > const FrameBuffer *buffer) > > { > > /* > > @@ -60,19 +60,53 @@ int TestPatternGenerator::generateFrame(const Size &size, > > MappedFrameBuffer::MapFlag::Write); > > > > const auto &planes = mappedFrameBuffer.planes(); > > - const PixelFormatInfo &info = PixelFormatInfo::info(formats::NV12); > > + const Size size = cfg.size; > > const Size &size = cfg.size; > > > + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); > > > > rotateLeft1Column<kARGBSize>(size, template_.get()); > > > > + int ret; > > + > > /* Convert the template_ to the frame buffer */ > > - int ret = libyuv::ARGBToNV12(template_.get(), size.width * kARGBSize, > > - planes[0].data(), > > - info.stride(size.width, 0), > > - planes[1].data(), > > - info.stride(size.width, 1), > > - size.width, size.height); > > + switch (cfg.pixelFormat) { > > + case formats::NV12: > > + ret = libyuv::ARGBToNV12(template_.get(), > > + size.width * kARGBSize, > > + planes[0].data(), > > + info.stride(size.width, 0), > > + planes[1].data(), > > + info.stride(size.width, 1), > > + size.width, size.height); > > Similarly as above, > > ret = libyuv::ARGBToNV12(template_.get(), size.width * kARGBSize, > planes[0].data(), info.stride(size.width, 0), > planes[1].data(), info.stride(size.width, 1), > size.width, size.height); > > Same below. > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > > I think we can handle that when applying if you let us know if you're > fine with the small changes. I'm going to apply these fixups, as I also prefer keeping the data/stride for each plane on a single line. Far more readable IMO. And also I found a typo below, so I'll do that too > > > + break; > > + > > + case formats::YUV420: > > + ret = libyuv::ARGBToI420(template_.get(), > > + size.width * kARGBSize, > > + planes[0].data(), > > + info.stride(size.width, 0), > > + planes[1].data(), > > + info.stride(size.width, 1), > > + planes[2].data(), > > + info.stride(size.width, 2), > > + size.width, size.height); > > + break; > > + > > + case formats::ABGR8888: > > + case formats::XBGR8888: > > + ret = libyuv::ARGBCopy(template_.get(), > > + size.width * kARGBSize, > > + planes[0].data(), > > + info.stride(size.width, 0), > > + size.width, size.height); > > + break; > > + > > + default: > > + LOG(Virtual, Error) << "Unsupported format"; > > + return -1; > > + } > > + > > if (ret != 0) > > - LOG(Virtual, Error) << "ARGBToNV12() failed with " << ret; > > + LOG(Virtual, Error) << "Conversation failed with " << ret; > > > > return ret; > > } > > diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.h b/src/libcamera/pipeline/virtual/test_pattern_generator.h > > index 2a51bd31abdf..9f72d652ab2c 100644 > > --- a/src/libcamera/pipeline/virtual/test_pattern_generator.h > > +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.h > > @@ -24,7 +24,7 @@ enum class TestPattern : char { > > class TestPatternGenerator : public FrameGenerator > > { > > public: > > - int generateFrame(const Size &size, const FrameBuffer *buffer) override; > > + int generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) override; > > > > protected: > > /* Buffer of test pattern template */ > > diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp > > index e25dd85a7391..f35af97b103d 100644 > > --- a/src/libcamera/pipeline/virtual/virtual.cpp > > +++ b/src/libcamera/pipeline/virtual/virtual.cpp > > @@ -47,6 +47,13 @@ LOG_DEFINE_CATEGORY(Virtual) > > > > namespace { > > > > +constexpr PixelFormat outputFormats[] = { > > + formats::NV12, > > + formats::YUV420, > > + formats::ABGR8888, > > + formats::XBGR8888, > > +}; > > + > > uint64_t currentTimestamp() > > { > > const auto now = std::chrono::steady_clock::now(); > > @@ -148,7 +155,7 @@ void VirtualCameraData::processRequest(Request *request) > > found = true; > > > > if (streamConfig.frameGenerator->generateFrame( > > - stream->configuration().size, buffer)) > > + stream->configuration(), buffer)) > > fmd.status = FrameMetadata::Status::FrameError; > > > > bufferCompleted.emit(buffer); > > @@ -202,18 +209,37 @@ CameraConfiguration::Status VirtualCameraConfiguration::validate() > > adjusted = true; > > } > > > > - if (cfg.pixelFormat != formats::NV12) { > > - cfg.pixelFormat = formats::NV12; > > + if (std::find(std::begin(outputFormats), > > + std::end(outputFormats), > > + cfg.pixelFormat) == std::end(outputFormats)) { > > + cfg.pixelFormat = outputFormats[0]; > > status = Adjusted; > > adjusted = true; > > } > > > > - if (cfg.colorSpace != ColorSpace::Smpte170m) { > > - cfg.colorSpace = ColorSpace::Smpte170m; > > + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); > > + std::optional<ColorSpace> colorSpace; > > + > > + switch (info.colourEncoding) { > > + case PixelFormatInfo::ColourEncodingRGB: > > + colorSpace = ColorSpace::Srgb; > > + break; > > + > > + case PixelFormatInfo::ColourEncodingYUV: > > + colorSpace = ColorSpace::Smpte170m; > > + break; > > + > > + default: > > + ASSERT(false); > > + } > > + > > + if (cfg.colorSpace && cfg.colorSpace != colorSpace) { > > status = Adjusted; > > adjusted = true; > > } > > > > + cfg.colorSpace = colorSpace; > > + > > if (validateColorSpaces() == Adjusted) { > > status = Adjusted; > > adjusted = true; > > @@ -223,7 +249,6 @@ CameraConfiguration::Status VirtualCameraConfiguration::validate() > > LOG(Virtual, Info) > > << "Stream configuration adjusted to " << cfg.toString(); > > > > - const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); > > cfg.stride = info.stride(cfg.size.width, 0, 1); > > cfg.frameSize = info.frameSize(cfg.size, 1); > > > > @@ -275,12 +300,12 @@ PipelineHandlerVirtual::generateConfiguration(Camera *camera, > > } > > > > std::map<PixelFormat, std::vector<SizeRange>> streamFormats; > > - PixelFormat pixelFormat = formats::NV12; > > - streamFormats[pixelFormat] = { { data->config_.minResolutionSize, > > - data->config_.maxResolutionSize } }; > > + for (auto pixelFormat : outputFormats) > > + streamFormats[pixelFormat] = { { data->config_.minResolutionSize, > > + data->config_.maxResolutionSize } }; > > StreamFormats formats(streamFormats); > > StreamConfiguration cfg(formats); > > - cfg.pixelFormat = pixelFormat; > > + cfg.pixelFormat = outputFormats[0]; > > cfg.size = data->config_.maxResolutionSize; > > cfg.bufferCount = VirtualCameraConfiguration::kBufferCount; > > cfg.colorSpace = ColorSpace::Smpte170m; > > -- > Regards, > > Laurent Pinchart
I've made the local fixups and pushed to CI to retest, but they were more substantial in volume than I expected, though I don't expect/believe there to be any specific risk to the code. Changes below Quoting Laurent Pinchart (2026-10-20 00:20:12) > On Tue, Oct 20, 2026 at 12:49:02AM +0200, Robert Mader wrote: > > While NV12 is the most common native format for hardware video encoders, > > YUV420 is the equivalent for most software video encoders. Thus > > supporting both is great for testing. > > > > Similarly having some RGB formats with and without alpha can be very > > useful, especially as the swISP is currently limited to such formats. I'll add: The ImageFrameData members Y and UV are converted to lowercase as part of the implementation. > > > > Signed-off-by: Robert Mader <robert.mader@collabora.com> > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > Tested-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > > --- > > .../pipeline/virtual/frame_generator.h | 3 +- > > .../virtual/image_frame_generator.cpp | 61 ++++++++++++++++--- > > .../pipeline/virtual/image_frame_generator.h | 2 +- > > .../virtual/test_pattern_generator.cpp | 52 +++++++++++++--- > > .../pipeline/virtual/test_pattern_generator.h | 2 +- > > src/libcamera/pipeline/virtual/virtual.cpp | 45 +++++++++++--- > > 6 files changed, 135 insertions(+), 30 deletions(-) > > Applying the following fixups to this patch: So I worked through this and it became a big diff, which I think should be squashed into this commit: diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.cpp b/src/libcamera/pipeline/virtual/image_frame_generator.cpp index 7a9bc9ac1ebd..a368e8ed0b90 100644 --- a/src/libcamera/pipeline/virtual/image_frame_generator.cpp +++ b/src/libcamera/pipeline/virtual/image_frame_generator.cpp @@ -115,8 +115,8 @@ void ImageFrameGenerator::configure(const Size &size) * The width needs to be a multiple of the stride to work * properly for now. */ - libyuv::NV12Scale(src.Y.get(), src.size.width, - src.UV.get(), src.size.width, + libyuv::NV12Scale(src.y.get(), src.size.width, + src.uv.get(), src.size.width, src.size.width, src.size.height, scaledY.get(), size.width, scaledUV.get(), size.width, size.width, size.height, libyuv::FilterMode::kFilterBilinear); @@ -143,52 +143,37 @@ int ImageFrameGenerator::generateFrame(const StreamConfiguration &cfg, const Fra MappedFrameBuffer::MapFlag::Write); const auto &planes = mappedFrameBuffer.planes(); - const Size size = cfg.size; + const Size &size = cfg.size; const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); /* Loop only around the number of images available */ frameIndex_ %= imageFrameDatas_.size(); int ret; - auto Y = scaledFrameDatas_[frameIndex_].Y.get(); - auto UV = scaledFrameDatas_[frameIndex_].UV.get(); + auto y = scaledFrameDatas_[frameIndex_].y.get(); + auto uv = scaledFrameDatas_[frameIndex_].uv.get(); /* Write the scaledY and scaledUV to the mapped frame buffer */ switch (cfg.pixelFormat) { case formats::NV12: - ret = libyuv::NV12Copy(Y, - size.width, - UV, - size.width, - planes[0].data(), - info.stride(size.width, 0), - planes[1].data(), - info.stride(size.width, 1), + ret = libyuv::NV12Copy(y, size.width, uv, size.width, + planes[0].data(), info.stride(size.width, 0), + planes[1].data(), info.stride(size.width, 1), size.width, size.height); break; case formats::YUV420: - ret = libyuv::NV12ToI420(Y, - size.width, - UV, - size.width, - planes[0].data(), - info.stride(size.width, 0), - planes[1].data(), - info.stride(size.width, 1), - planes[2].data(), - info.stride(size.width, 2), + ret = libyuv::NV12ToI420(y, size.width, uv, size.width, + planes[0].data(), info.stride(size.width, 0), + planes[1].data(), info.stride(size.width, 1), + planes[2].data(), info.stride(size.width, 2), size.width, size.height); break; case formats::ABGR8888: case formats::XBGR8888: - ret = libyuv::NV12ToABGR(Y, - size.width, - UV, - size.width, - planes[0].data(), - info.stride(size.width, 0), + ret = libyuv::NV12ToABGR(y, size.width, uv, size.width, + planes[0].data(), info.stride(size.width, 0), size.width, size.height); break; @@ -198,7 +183,7 @@ int ImageFrameGenerator::generateFrame(const StreamConfiguration &cfg, const Fra } if (ret != 0) - LOG(Virtual, Error) << "Conversation failed with " << ret; + LOG(Virtual, Error) << "Conversion failed with " << ret; /* Proceed to the next image every 4 frames */ /* \todo Consider setting the frameRepeat in the config file */ diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.h b/src/libcamera/pipeline/virtual/image_frame_generator.h index 19b6fe38654e..3c70c34b9042 100644 --- a/src/libcamera/pipeline/virtual/image_frame_generator.h +++ b/src/libcamera/pipeline/virtual/image_frame_generator.h @@ -31,8 +31,8 @@ private: static constexpr unsigned int frameRepeat = 4; struct ImageFrameData { - std::unique_ptr<uint8_t[]> Y; - std::unique_ptr<uint8_t[]> UV; + std::unique_ptr<uint8_t[]> y; + std::unique_ptr<uint8_t[]> uv; Size size; }; diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp index e214048d288a..9e9bc2c5bfef 100644 --- a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp @@ -60,7 +60,7 @@ int TestPatternGenerator::generateFrame(const StreamConfiguration &cfg, MappedFrameBuffer::MapFlag::Write); const auto &planes = mappedFrameBuffer.planes(); - const Size size = cfg.size; + const Size &size = cfg.size; const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); rotateLeft1Column<kARGBSize>(size, template_.get()); @@ -72,22 +72,17 @@ int TestPatternGenerator::generateFrame(const StreamConfiguration &cfg, case formats::NV12: ret = libyuv::ARGBToNV12(template_.get(), size.width * kARGBSize, - planes[0].data(), - info.stride(size.width, 0), - planes[1].data(), - info.stride(size.width, 1), + planes[0].data(), info.stride(size.width, 0), + planes[1].data(), info.stride(size.width, 1), size.width, size.height); break; case formats::YUV420: ret = libyuv::ARGBToI420(template_.get(), size.width * kARGBSize, - planes[0].data(), - info.stride(size.width, 0), - planes[1].data(), - info.stride(size.width, 1), - planes[2].data(), - info.stride(size.width, 2), + planes[0].data(), info.stride(size.width, 0), + planes[1].data(), info.stride(size.width, 1), + planes[2].data(), info.stride(size.width, 2), size.width, size.height); break; @@ -95,8 +90,7 @@ int TestPatternGenerator::generateFrame(const StreamConfiguration &cfg, case formats::XBGR8888: ret = libyuv::ARGBCopy(template_.get(), size.width * kARGBSize, - planes[0].data(), - info.stride(size.width, 0), + planes[0].data(), info.stride(size.width, 0), size.width, size.height); break; @@ -106,7 +100,7 @@ int TestPatternGenerator::generateFrame(const StreamConfiguration &cfg, } if (ret != 0) - LOG(Virtual, Error) << "Conversation failed with " << ret; + LOG(Virtual, Error) << "Conversion failed with " << ret; return ret; }
diff --git a/src/libcamera/pipeline/virtual/frame_generator.h b/src/libcamera/pipeline/virtual/frame_generator.h index a0658c45cd82..fd34a91a5e50 100644 --- a/src/libcamera/pipeline/virtual/frame_generator.h +++ b/src/libcamera/pipeline/virtual/frame_generator.h @@ -9,6 +9,7 @@ #include <libcamera/framebuffer.h> #include <libcamera/geometry.h> +#include <libcamera/stream.h> namespace libcamera { @@ -19,7 +20,7 @@ public: virtual void configure(const Size &size) = 0; - virtual int generateFrame(const Size &size, + virtual int generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) = 0; protected: diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.cpp b/src/libcamera/pipeline/virtual/image_frame_generator.cpp index 5caad8b72763..7a9bc9ac1ebd 100644 --- a/src/libcamera/pipeline/virtual/image_frame_generator.cpp +++ b/src/libcamera/pipeline/virtual/image_frame_generator.cpp @@ -126,7 +126,7 @@ void ImageFrameGenerator::configure(const Size &size) } } -int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buffer) +int ImageFrameGenerator::generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) { ASSERT(!scaledFrameDatas_.empty()); @@ -143,17 +143,62 @@ int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buff MappedFrameBuffer::MapFlag::Write); const auto &planes = mappedFrameBuffer.planes(); - const PixelFormatInfo &info = PixelFormatInfo::info(formats::NV12); + const Size size = cfg.size; + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); /* Loop only around the number of images available */ frameIndex_ %= imageFrameDatas_.size(); + int ret; + auto Y = scaledFrameDatas_[frameIndex_].Y.get(); + auto UV = scaledFrameDatas_[frameIndex_].UV.get(); + /* Write the scaledY and scaledUV to the mapped frame buffer */ - libyuv::NV12Copy(scaledFrameDatas_[frameIndex_].Y.get(), size.width, - scaledFrameDatas_[frameIndex_].UV.get(), size.width, - planes[0].data(), info.stride(size.width, 0), - planes[1].data(), info.stride(size.width, 1), - size.width, size.height); + switch (cfg.pixelFormat) { + case formats::NV12: + ret = libyuv::NV12Copy(Y, + size.width, + UV, + size.width, + planes[0].data(), + info.stride(size.width, 0), + planes[1].data(), + info.stride(size.width, 1), + size.width, size.height); + break; + + case formats::YUV420: + ret = libyuv::NV12ToI420(Y, + size.width, + UV, + size.width, + planes[0].data(), + info.stride(size.width, 0), + planes[1].data(), + info.stride(size.width, 1), + planes[2].data(), + info.stride(size.width, 2), + size.width, size.height); + break; + + case formats::ABGR8888: + case formats::XBGR8888: + ret = libyuv::NV12ToABGR(Y, + size.width, + UV, + size.width, + planes[0].data(), + info.stride(size.width, 0), + size.width, size.height); + break; + + default: + LOG(Virtual, Error) << "Unsupported format"; + return -1; + } + + if (ret != 0) + LOG(Virtual, Error) << "Conversation failed with " << ret; /* Proceed to the next image every 4 frames */ /* \todo Consider setting the frameRepeat in the config file */ @@ -161,7 +206,7 @@ int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buff if (parameter_ % frameRepeat == 0) frameIndex_++; - return 0; + return ret; } /* diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.h b/src/libcamera/pipeline/virtual/image_frame_generator.h index 42a077ba803c..19b6fe38654e 100644 --- a/src/libcamera/pipeline/virtual/image_frame_generator.h +++ b/src/libcamera/pipeline/virtual/image_frame_generator.h @@ -37,7 +37,7 @@ private: }; void configure(const Size &size) override; - int generateFrame(const Size &size, const FrameBuffer *buffer) override; + int generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) override; std::vector<ImageFrameData> imageFrameDatas_; std::vector<ImageFrameData> scaledFrameDatas_; diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp index 571be4fbf65d..e214048d288a 100644 --- a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp @@ -44,7 +44,7 @@ LOG_DECLARE_CATEGORY(Virtual) static const unsigned int kARGBSize = 4; -int TestPatternGenerator::generateFrame(const Size &size, +int TestPatternGenerator::generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) { /* @@ -60,19 +60,53 @@ int TestPatternGenerator::generateFrame(const Size &size, MappedFrameBuffer::MapFlag::Write); const auto &planes = mappedFrameBuffer.planes(); - const PixelFormatInfo &info = PixelFormatInfo::info(formats::NV12); + const Size size = cfg.size; + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); rotateLeft1Column<kARGBSize>(size, template_.get()); + int ret; + /* Convert the template_ to the frame buffer */ - int ret = libyuv::ARGBToNV12(template_.get(), size.width * kARGBSize, - planes[0].data(), - info.stride(size.width, 0), - planes[1].data(), - info.stride(size.width, 1), - size.width, size.height); + switch (cfg.pixelFormat) { + case formats::NV12: + ret = libyuv::ARGBToNV12(template_.get(), + size.width * kARGBSize, + planes[0].data(), + info.stride(size.width, 0), + planes[1].data(), + info.stride(size.width, 1), + size.width, size.height); + break; + + case formats::YUV420: + ret = libyuv::ARGBToI420(template_.get(), + size.width * kARGBSize, + planes[0].data(), + info.stride(size.width, 0), + planes[1].data(), + info.stride(size.width, 1), + planes[2].data(), + info.stride(size.width, 2), + size.width, size.height); + break; + + case formats::ABGR8888: + case formats::XBGR8888: + ret = libyuv::ARGBCopy(template_.get(), + size.width * kARGBSize, + planes[0].data(), + info.stride(size.width, 0), + size.width, size.height); + break; + + default: + LOG(Virtual, Error) << "Unsupported format"; + return -1; + } + if (ret != 0) - LOG(Virtual, Error) << "ARGBToNV12() failed with " << ret; + LOG(Virtual, Error) << "Conversation failed with " << ret; return ret; } diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.h b/src/libcamera/pipeline/virtual/test_pattern_generator.h index 2a51bd31abdf..9f72d652ab2c 100644 --- a/src/libcamera/pipeline/virtual/test_pattern_generator.h +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.h @@ -24,7 +24,7 @@ enum class TestPattern : char { class TestPatternGenerator : public FrameGenerator { public: - int generateFrame(const Size &size, const FrameBuffer *buffer) override; + int generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) override; protected: /* Buffer of test pattern template */ diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp index e25dd85a7391..f35af97b103d 100644 --- a/src/libcamera/pipeline/virtual/virtual.cpp +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -47,6 +47,13 @@ LOG_DEFINE_CATEGORY(Virtual) namespace { +constexpr PixelFormat outputFormats[] = { + formats::NV12, + formats::YUV420, + formats::ABGR8888, + formats::XBGR8888, +}; + uint64_t currentTimestamp() { const auto now = std::chrono::steady_clock::now(); @@ -148,7 +155,7 @@ void VirtualCameraData::processRequest(Request *request) found = true; if (streamConfig.frameGenerator->generateFrame( - stream->configuration().size, buffer)) + stream->configuration(), buffer)) fmd.status = FrameMetadata::Status::FrameError; bufferCompleted.emit(buffer); @@ -202,18 +209,37 @@ CameraConfiguration::Status VirtualCameraConfiguration::validate() adjusted = true; } - if (cfg.pixelFormat != formats::NV12) { - cfg.pixelFormat = formats::NV12; + if (std::find(std::begin(outputFormats), + std::end(outputFormats), + cfg.pixelFormat) == std::end(outputFormats)) { + cfg.pixelFormat = outputFormats[0]; status = Adjusted; adjusted = true; } - if (cfg.colorSpace != ColorSpace::Smpte170m) { - cfg.colorSpace = ColorSpace::Smpte170m; + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); + std::optional<ColorSpace> colorSpace; + + switch (info.colourEncoding) { + case PixelFormatInfo::ColourEncodingRGB: + colorSpace = ColorSpace::Srgb; + break; + + case PixelFormatInfo::ColourEncodingYUV: + colorSpace = ColorSpace::Smpte170m; + break; + + default: + ASSERT(false); + } + + if (cfg.colorSpace && cfg.colorSpace != colorSpace) { status = Adjusted; adjusted = true; } + cfg.colorSpace = colorSpace; + if (validateColorSpaces() == Adjusted) { status = Adjusted; adjusted = true; @@ -223,7 +249,6 @@ CameraConfiguration::Status VirtualCameraConfiguration::validate() LOG(Virtual, Info) << "Stream configuration adjusted to " << cfg.toString(); - const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); cfg.stride = info.stride(cfg.size.width, 0, 1); cfg.frameSize = info.frameSize(cfg.size, 1); @@ -275,12 +300,12 @@ PipelineHandlerVirtual::generateConfiguration(Camera *camera, } std::map<PixelFormat, std::vector<SizeRange>> streamFormats; - PixelFormat pixelFormat = formats::NV12; - streamFormats[pixelFormat] = { { data->config_.minResolutionSize, - data->config_.maxResolutionSize } }; + for (auto pixelFormat : outputFormats) + streamFormats[pixelFormat] = { { data->config_.minResolutionSize, + data->config_.maxResolutionSize } }; StreamFormats formats(streamFormats); StreamConfiguration cfg(formats); - cfg.pixelFormat = pixelFormat; + cfg.pixelFormat = outputFormats[0]; cfg.size = data->config_.maxResolutionSize; cfg.bufferCount = VirtualCameraConfiguration::kBufferCount; cfg.colorSpace = ColorSpace::Smpte170m;