[PATCH v14 4/7] libcamera: pipeline: Add test pattern for VirtualPipelineHandler
Cheng-Hao Yang
chenghaoyang at chromium.org
Fri Oct 4 12:00:30 CEST 2024
Hi Kieran,
On Thu, Oct 3, 2024 at 6:05 PM Kieran Bingham
<kieran.bingham at ideasonboard.com> wrote:
>
> Quoting Harvey Yang (2024-09-30 07:29:45)
> > From: Konami Shu <konamiz at google.com>
> >
> > Add a test pattern generator class hierarchy for the Virtual
> > pipeline handler.
> >
> > Implement two types of test patterns: color bars and diagonal lines
> > generator and use them in the Virtual pipeline handler.
> >
> > A shifting mechanism is enabled. For each frame, the image is shifted to
> > the left by 1 pixel. It drops FPS though.
>
> I guess we could solve the throughput here by generating a double width
> image, handling the stride accordingly and just changing the offset when
> calling ARGBtoNV12 ...
>
> But no need for that now. I guess other implementations here could be
> done on top.
>
> > Add a dependency for libyuv to the build system to generate images
> > in NV12 format from the test pattern.
> >
> > Signed-off-by: Konami Shu <konamiz at google.com>
> > Co-developed-by: Harvey Yang <chenghaoyang at chromium.org>
> > Co-developed-by: Yunke Cao <yunkec at chromium.org>
> > Co-developed-by: Tomasz Figa <tfiga at chromium.org>
> > Reviewed-by: Jacopo Mondi <jacopo.mondi at ideasonboard.com>
> > ---
> > src/android/meson.build | 19 ---
> > .../pipeline/virtual/frame_generator.h | 29 ++++
> > src/libcamera/pipeline/virtual/meson.build | 3 +
> > .../virtual/test_pattern_generator.cpp | 137 ++++++++++++++++++
> > .../pipeline/virtual/test_pattern_generator.h | 53 +++++++
> > src/libcamera/pipeline/virtual/virtual.cpp | 41 +++++-
> > src/libcamera/pipeline/virtual/virtual.h | 5 +
> > src/meson.build | 19 +++
> > 8 files changed, 284 insertions(+), 22 deletions(-)
> > create mode 100644 src/libcamera/pipeline/virtual/frame_generator.h
> > create mode 100644 src/libcamera/pipeline/virtual/test_pattern_generator.cpp
> > create mode 100644 src/libcamera/pipeline/virtual/test_pattern_generator.h
> >
> > diff --git a/src/android/meson.build b/src/android/meson.build
> > index 68646120..6341ee8b 100644
> > --- a/src/android/meson.build
> > +++ b/src/android/meson.build
> > @@ -15,25 +15,6 @@ foreach dep : android_deps
> > endif
> > endforeach
> >
> > -libyuv_dep = dependency('libyuv', required : false)
> > -
> > -# Fallback to a subproject if libyuv isn't found, as it's typically not
> > -# provided by distributions.
> > -if not libyuv_dep.found()
> > - cmake = import('cmake')
> > -
> > - libyuv_vars = cmake.subproject_options()
> > - libyuv_vars.add_cmake_defines({'CMAKE_POSITION_INDEPENDENT_CODE': 'ON'})
> > - libyuv_vars.set_override_option('cpp_std', 'c++17')
> > - libyuv_vars.append_compile_args('cpp',
> > - '-Wno-sign-compare',
> > - '-Wno-unused-variable',
> > - '-Wno-unused-parameter')
> > - libyuv_vars.append_link_args('-ljpeg')
> > - libyuv = cmake.subproject('libyuv', options : libyuv_vars)
> > - libyuv_dep = libyuv.dependency('yuv')
> > -endif
> > -
> > android_deps += [libyuv_dep]
> >
> > android_hal_sources = files([
> > diff --git a/src/libcamera/pipeline/virtual/frame_generator.h b/src/libcamera/pipeline/virtual/frame_generator.h
> > new file mode 100644
> > index 00000000..4ff41dd8
> > --- /dev/null
> > +++ b/src/libcamera/pipeline/virtual/frame_generator.h
> > @@ -0,0 +1,29 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2024, Google Inc.
> > + *
> > + * frame_generator.h - Virtual cameras helper to generate frames
> > + */
> > +
> > +#pragma once
> > +
> > +#include <libcamera/framebuffer.h>
> > +#include <libcamera/geometry.h>
> > +
> > +namespace libcamera {
> > +
> > +class FrameGenerator
> > +{
> > +public:
> > + virtual ~FrameGenerator() = default;
> > +
> > + virtual void configure(const Size &size) = 0;
> > +
> > + virtual int generateFrame(const Size &size,
> > + const FrameBuffer *buffer) = 0;
> > +
> > +protected:
> > + FrameGenerator() {}
> > +};
> > +
> > +} /* namespace libcamera */
> > diff --git a/src/libcamera/pipeline/virtual/meson.build b/src/libcamera/pipeline/virtual/meson.build
> > index ada1b335..0c537777 100644
> > --- a/src/libcamera/pipeline/virtual/meson.build
> > +++ b/src/libcamera/pipeline/virtual/meson.build
> > @@ -1,5 +1,8 @@
> > # SPDX-License-Identifier: CC0-1.0
> >
> > libcamera_internal_sources += files([
> > + 'test_pattern_generator.cpp',
> > 'virtual.cpp',
> > ])
> > +
> > +libcamera_deps += [libyuv_dep]
> > diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp
> > new file mode 100644
> > index 00000000..c5a93d37
> > --- /dev/null
> > +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp
> > @@ -0,0 +1,137 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2024, Google Inc.
> > + *
> > + * test_pattern_generator.cpp - Derived class of FrameGenerator for
> > + * generating test patterns
> > + */
> > +
> > +#include "test_pattern_generator.h"
> > +
> > +#include <libcamera/base/log.h>
> > +
> > +#include "libcamera/internal/mapped_framebuffer.h"
> > +
> > +#include <libyuv/convert_from_argb.h>
> > +
> > +namespace libcamera {
> > +
> > +LOG_DECLARE_CATEGORY(Virtual)
> > +
> > +static const unsigned int kARGBSize = 4;
> > +
> > +int TestPatternGenerator::generateFrame(const Size &size,
> > + const FrameBuffer *buffer)
> > +{
> > + MappedFrameBuffer mappedFrameBuffer(buffer,
> > + MappedFrameBuffer::MapFlag::Write);
> > +
> > + auto planes = mappedFrameBuffer.planes();
> > +
> > + shiftLeft(size);
> > +
> > + /* Convert the template_ to the frame buffer */
> > + int ret = libyuv::ARGBToNV12(template_.get(), size.width * kARGBSize,
>
> Do we need to do anything here to make sure we match the stride of the
> output buffer if it comes from an external source?
>
> Maybe that's not something we need to deal with currently.
>
> > + planes[0].begin(), size.width,
> > + planes[1].begin(), size.width,
> > + size.width, size.height);
> > + if (ret != 0)
> > + LOG(Virtual, Error) << "ARGBToNV12() failed with " << ret;
> > +
> > + return ret;
> > +}
> > +
> > +void TestPatternGenerator::shiftLeft(const Size &size)
> > +{
> > + /* Store the first column temporarily */
> > + auto firstColumn = std::make_unique<uint8_t[]>(size.height * kARGBSize);
> > + for (size_t h = 0; h < size.height; h++) {
> > + unsigned int index = h * size.width * kARGBSize;
> > + unsigned int index1 = h * kARGBSize;
> > + firstColumn[index1] = template_[index];
> > + firstColumn[index1 + 1] = template_[index + 1];
> > + firstColumn[index1 + 2] = template_[index + 2];
> > + firstColumn[index1 + 3] = 0x00;
> > + }
> > +
> > + /* Overwrite template_ */
> > + uint8_t *buf = template_.get();
> > + for (size_t h = 0; h < size.height; h++) {
> > + for (size_t w = 0; w < size.width - 1; w++) {
> > + /* Overwrite with the pixel on the right */
> > + unsigned int index = (h * size.width + w + 1) * kARGBSize;
> > + *buf++ = template_[index]; /* B */
> > + *buf++ = template_[index + 1]; /* G */
> > + *buf++ = template_[index + 2]; /* R */
> > + *buf++ = 0x00; /* A */
> > + }
> > + /* Overwrite the new last column with the original first column */
> > + unsigned int index1 = h * kARGBSize;
> > + *buf++ = firstColumn[index1]; /* B */
> > + *buf++ = firstColumn[index1 + 1]; /* G */
> > + *buf++ = firstColumn[index1 + 2]; /* R */
> > + *buf++ = 0x00; /* A */
> > + }
>
> I could imagine ways we could try to optimise this or do different
> things - but I don't think performance is key here right away - so I'm
> fine with the above.
>
> > +}
> > +
> > +void ColorBarsGenerator::configure(const Size &size)
> > +{
> > + constexpr uint8_t kColorBar[8][3] = {
> > + /* R, G, B */
> > + { 0xff, 0xff, 0xff }, /* White */
> > + { 0xff, 0xff, 0x00 }, /* Yellow */
> > + { 0x00, 0xff, 0xff }, /* Cyan */
> > + { 0x00, 0xff, 0x00 }, /* Green */
> > + { 0xff, 0x00, 0xff }, /* Magenta */
> > + { 0xff, 0x00, 0x00 }, /* Red */
> > + { 0x00, 0x00, 0xff }, /* Blue */
> > + { 0x00, 0x00, 0x00 }, /* Black */
> > + };
> > +
> > + template_ = std::make_unique<uint8_t[]>(
> > + size.width * size.height * kARGBSize);
> > +
> > + unsigned int colorBarWidth = size.width / std::size(kColorBar);
> > +
> > + uint8_t *buf = template_.get();
> > + for (size_t h = 0; h < size.height; h++) {
> > + for (size_t w = 0; w < size.width; w++) {
> > + /* Repeat when the width is exceed */
> > + unsigned int index = (w / colorBarWidth) % std::size(kColorBar);
> > +
> > + *buf++ = kColorBar[index][2]; /* B */
> > + *buf++ = kColorBar[index][1]; /* G */
> > + *buf++ = kColorBar[index][0]; /* R */
> > + *buf++ = 0x00; /* A */
> > + }
> > + }
> > +}
> > +
> > +void DiagonalLinesGenerator::configure(const Size &size)
> > +{
> > + constexpr uint8_t kColorBar[8][3] = {
>
> [8][3] ?
Ah, updated to [2][3].
>
> > + /* R, G, B */
> > + { 0xff, 0xff, 0xff }, /* White */
> > + { 0x00, 0x00, 0x00 }, /* Black */
> > + };
> > +
> > + template_ = std::make_unique<uint8_t[]>(
> > + size.width * size.height * kARGBSize);
> > +
> > + unsigned int lineWidth = size.width / 10;
> > +
> > + uint8_t *buf = template_.get();
> > + for (size_t h = 0; h < size.height; h++) {
> > + for (size_t w = 0; w < size.width; w++) {
> > + /* Repeat when the width is exceed */
> > + int index = ((w + h) / lineWidth) % 2;
> > +
> > + *buf++ = kColorBar[index][2]; /* B */
> > + *buf++ = kColorBar[index][1]; /* G */
> > + *buf++ = kColorBar[index][0]; /* R */
> > + *buf++ = 0x00; /* A */
> > + }
> > + }
> > +}
> > +
> > +} /* namespace libcamera */
> > diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.h b/src/libcamera/pipeline/virtual/test_pattern_generator.h
> > new file mode 100644
> > index 00000000..2ff1e40e
> > --- /dev/null
> > +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.h
> > @@ -0,0 +1,53 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2024, Google Inc.
> > + *
> > + * test_pattern_generator.h - Derived class of FrameGenerator for
> > + * generating test patterns
> > + */
> > +
> > +#pragma once
> > +
> > +#include <memory>
> > +
> > +#include <libcamera/framebuffer.h>
> > +#include <libcamera/geometry.h>
> > +
> > +#include "frame_generator.h"
> > +
> > +namespace libcamera {
> > +
> > +enum class TestPattern : char {
> > + ColorBars = 0,
> > + DiagonalLines = 1,
> > +};
> > +
> > +class TestPatternGenerator : public FrameGenerator
> > +{
> > +public:
> > + int generateFrame(const Size &size, const FrameBuffer *buffer) override;
> > +
> > +protected:
> > + /* Buffer of test pattern template */
> > + std::unique_ptr<uint8_t[]> template_;
> > +
> > +private:
> > + /* Shift the buffer by 1 pixel left each frame */
> > + void shiftLeft(const Size &size);
> > +};
> > +
> > +class ColorBarsGenerator : public TestPatternGenerator
> > +{
> > +public:
> > + /* Generate a template buffer of the color bar test pattern. */
> > + void configure(const Size &size) override;
> > +};
> > +
> > +class DiagonalLinesGenerator : public TestPatternGenerator
> > +{
> > +public:
> > + /* Generate a template buffer of the diagonal lines test pattern. */
> > + void configure(const Size &size) override;
> > +};
> > +
> > +} /* namespace libcamera */
> > diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp
> > index d1584f59..2b258492 100644
> > --- a/src/libcamera/pipeline/virtual/virtual.cpp
> > +++ b/src/libcamera/pipeline/virtual/virtual.cpp
> > @@ -34,6 +34,7 @@
> > #include "libcamera/internal/camera.h"
> > #include "libcamera/internal/dma_buf_allocator.h"
> > #include "libcamera/internal/formats.h"
> > +#include "libcamera/internal/framebuffer.h"
> > #include "libcamera/internal/pipeline_handler.h"
> >
> > namespace libcamera {
> > @@ -93,6 +94,8 @@ private:
> > return static_cast<VirtualCameraData *>(camera->_d());
> > }
> >
> > + void initFrameGenerator(Camera *camera);
> > +
> > DmaBufAllocator dmaBufAllocator_;
> > };
> >
> > @@ -227,8 +230,10 @@ int PipelineHandlerVirtual::configure(Camera *camera,
> > CameraConfiguration *config)
> > {
> > VirtualCameraData *data = cameraData(camera);
> > - for (auto [i, c] : utils::enumerate(*config))
> > + for (auto [i, c] : utils::enumerate(*config)) {
> > c.setStream(&data->streamConfigs_[i].stream);
> > + data->streamConfigs_[i].frameGenerator->configure(c.size);
> > + }
> >
> > return 0;
> > }
> > @@ -264,8 +269,24 @@ void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera)
> > int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera,
> > Request *request)
> > {
> > - for (auto it : request->buffers())
> > - completeBuffer(request, it.second);
> > + VirtualCameraData *data = cameraData(camera);
> > +
> > + 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) {
> > + found = true;
> > + if (streamConfig.frameGenerator->generateFrame(
> > + stream->configuration().size, buffer))
> > + buffer->_d()->cancel();
> > +
> > + completeBuffer(request, buffer);
> > + break;
> > + }
> > + }
> > + ASSERT(found);
> > + }
> >
> > request->metadata().set(controls::SensorTimestamp, currentTimestamp());
> > completeRequest(request);
> > @@ -307,11 +328,25 @@ bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator *enumerator
> >
> > const std::string id = "Virtual0";
> > std::shared_ptr<Camera> camera = Camera::create(std::move(data), id, streams);
> > +
> > + initFrameGenerator(camera.get());
> > +
> > registerCamera(std::move(camera));
> >
> > return true;
> > }
> >
> > +void PipelineHandlerVirtual::initFrameGenerator(Camera *camera)
> > +{
> > + auto data = cameraData(camera);
> > + for (auto &streamConfig : data->streamConfigs_) {
> > + if (data->testPattern_ == TestPattern::DiagonalLines)
> > + streamConfig.frameGenerator = std::make_unique<DiagonalLinesGenerator>();
> > + else
> > + streamConfig.frameGenerator = std::make_unique<ColorBarsGenerator>();
> > + }
> > +}
> > +
> > REGISTER_PIPELINE_HANDLER(PipelineHandlerVirtual, "virtual")
> >
> > } /* namespace libcamera */
> > diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h
> > index 4df70a13..9b79ac09 100644
> > --- a/src/libcamera/pipeline/virtual/virtual.h
> > +++ b/src/libcamera/pipeline/virtual/virtual.h
> > @@ -15,6 +15,8 @@
> > #include "libcamera/internal/camera.h"
> > #include "libcamera/internal/pipeline_handler.h"
> >
> > +#include "test_pattern_generator.h"
> > +
> > namespace libcamera {
> >
> > class VirtualCameraData : public Camera::Private
> > @@ -28,6 +30,7 @@ public:
> > };
> > struct StreamConfig {
> > Stream stream;
> > + std::unique_ptr<FrameGenerator> frameGenerator;
> > };
> >
> > VirtualCameraData(PipelineHandler *pipe,
> > @@ -35,6 +38,8 @@ public:
> >
> > ~VirtualCameraData() = default;
> >
> > + TestPattern testPattern_ = TestPattern::ColorBars;
> > +
> > const std::vector<Resolution> supportedResolutions_;
> > Size maxResolutionSize_;
> > Size minResolutionSize_;
> > diff --git a/src/meson.build b/src/meson.build
> > index 165a77bb..91bea775 100644
> > --- a/src/meson.build
> > +++ b/src/meson.build
> > @@ -27,6 +27,25 @@ else
> > ipa_sign_module = false
> > endif
> >
> > +libyuv_dep = dependency('libyuv', required : false)
> > +
> > +# Fallback to a subproject if libyuv isn't found, as it's typically not
> > +# provided by distributions.
> > +if not libyuv_dep.found()
> > + cmake = import('cmake')
> > +
> > + libyuv_vars = cmake.subproject_options()
> > + libyuv_vars.add_cmake_defines({'CMAKE_POSITION_INDEPENDENT_CODE': 'ON'})
> > + libyuv_vars.set_override_option('cpp_std', 'c++17')
> > + libyuv_vars.append_compile_args('cpp',
> > + '-Wno-sign-compare',
> > + '-Wno-unused-variable',
> > + '-Wno-unused-parameter')
> > + libyuv_vars.append_link_args('-ljpeg')
> > + libyuv = cmake.subproject('libyuv', options : libyuv_vars)
> > + libyuv_dep = libyuv.dependency('yuv')
> > +endif
> > +
>
> I'm not yet sure in my mind if this should be in the top level where
> other dependencies are handled, or if here is fine. As this is the
> highest common layer for both android and libcamera - I think this is
> fine.
>
> It would have been nicer to split this meson change out to a distinct
> patch but it's fine now.
>
> I'm going to say it's fine to move here, and if we decide differently
> later - it's easy to move.
>
>
> With at least that [8][3] fixed, which I think is the only 'incorrect'
> thing, then you can add:
>
> Reviewed-by: Kieran Bingham <kieran.bingham at ideasonboard.com>
Thanks Kieran, true that there are some optimization that we
can work on, while maybe save the work for now, until we
really need them.
>
>
> > # libcamera must be built first as a dependency to the other components.
> > subdir('libcamera')
> >
> > --
> > 2.46.1.824.gd892dcdcdd-goog
> >
More information about the libcamera-devel
mailing list