Message ID | 20240910044834.2477701-5-chenghaoyang@google.com |
---|---|
State | Superseded |
Headers | show |
Series |
|
Related | show |
Hi Harvey On Tue, Sep 10, 2024 at 04:40:17AM GMT, Harvey Yang wrote: > From: Konami Shu <konamiz@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. > > 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@google.com> > Co-developed-by: Harvey Yang <chenghaoyang@chromium.org> > Co-developed-by: Yunke Cao <yunkec@chromium.org> > Co-developed-by: Tomasz Figa <tfiga@chromium.org> > Reviewed-by: Jacopo Mondi <jacopo.mondi@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 | 135 ++++++++++++++++++ > .../pipeline/virtual/test_pattern_generator.h | 53 +++++++ > src/libcamera/pipeline/virtual/virtual.cpp | 38 ++++- > src/libcamera/pipeline/virtual/virtual.h | 5 + > src/meson.build | 19 +++ > 8 files changed, 279 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..d8727b8f > --- /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 void 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..f84ab2f3 > --- /dev/null > +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp > @@ -0,0 +1,135 @@ > +/* 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; > + > +void TestPatternGenerator::generateFrame(const Size &size, I wonder if it's worth returning an error to the caller and complete the buffer in error state in the pipeline handler in that case. > + 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, > + planes[0].begin(), size.width, > + planes[1].begin(), size.width, > + size.width, size.height); > + if (ret != 0) > + LOG(Virtual, Error) << "ARGBToNV12() failed with " << 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 */ > + } > +} > + > +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] = { > + /* 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..b8bbe0b8 > --- /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: > + void 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 56e05528..4e9a6973 100644 > --- a/src/libcamera/pipeline/virtual/virtual.cpp > +++ b/src/libcamera/pipeline/virtual/virtual.cpp > @@ -84,6 +84,8 @@ private: > return static_cast<VirtualCameraData *>(camera->_d()); > } > > + void initFrameGenerator(Camera *camera); > + > DmaBufAllocator dmaBufAllocator_; > }; > > @@ -218,8 +220,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; > } > @@ -255,9 +259,23 @@ void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera) > int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, > Request *request) > { > + VirtualCameraData *data = cameraData(camera); > + > /* \todo Read from the virtual video if any. */ > - for (auto it : request->buffers()) > - completeBuffer(request, it.second); > + 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; > + streamConfig.frameGenerator->generateFrame( > + stream->configuration().size, buffer); > + completeBuffer(request, buffer); > + break; > + } > + } > + ASSERT(found); > + } Not a requirement, but you can make this a little more C++ with for (auto const &[stream, buffer] : request->buffers()) { auto it = std::find_if(data->streamConfigs_.begin(), data->streamConfigs_.end(), [stream](const auto &c) { return &c.stream == stream; }); ASSERT(it == data->streamConfigs_.end()); it->frameGenerator->generateFrame(stream->configuration().size, buffer); completeBuffer(request, buffer); } warning: compile-tested only The rest looks good > > request->metadata().set(controls::SensorTimestamp, currentTimestamp()); > completeRequest(request); > @@ -299,11 +317,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 77823c41..acdd437e 100644 > --- a/src/libcamera/pipeline/virtual/virtual.h > +++ b/src/libcamera/pipeline/virtual/virtual.h > @@ -14,6 +14,8 @@ > #include "libcamera/internal/camera.h" > #include "libcamera/internal/pipeline_handler.h" > > +#include "test_pattern_generator.h" > + > namespace libcamera { > > class VirtualCameraData : public Camera::Private > @@ -27,6 +29,7 @@ public: > }; > struct StreamConfig { > Stream stream; > + std::unique_ptr<FrameGenerator> frameGenerator; > }; > > VirtualCameraData(PipelineHandler *pipe, > @@ -34,6 +37,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 > + > # libcamera must be built first as a dependency to the other components. > subdir('libcamera') > > -- > 2.46.0.598.g6f2099f65c-goog >
Hi Jacopo, On Thu, Sep 26, 2024 at 7:18 PM Jacopo Mondi <jacopo.mondi@ideasonboard.com> wrote: > > Hi Harvey > > On Tue, Sep 10, 2024 at 04:40:17AM GMT, Harvey Yang wrote: > > From: Konami Shu <konamiz@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. > > > > 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@google.com> > > Co-developed-by: Harvey Yang <chenghaoyang@chromium.org> > > Co-developed-by: Yunke Cao <yunkec@chromium.org> > > Co-developed-by: Tomasz Figa <tfiga@chromium.org> > > Reviewed-by: Jacopo Mondi <jacopo.mondi@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 | 135 ++++++++++++++++++ > > .../pipeline/virtual/test_pattern_generator.h | 53 +++++++ > > src/libcamera/pipeline/virtual/virtual.cpp | 38 ++++- > > src/libcamera/pipeline/virtual/virtual.h | 5 + > > src/meson.build | 19 +++ > > 8 files changed, 279 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..d8727b8f > > --- /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 void 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..f84ab2f3 > > --- /dev/null > > +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp > > @@ -0,0 +1,135 @@ > > +/* 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; > > + > > +void TestPatternGenerator::generateFrame(const Size &size, > > I wonder if it's worth returning an error to the caller and complete > the buffer in error state in the pipeline handler in that case. Sure, updated. > > > + 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, > > + planes[0].begin(), size.width, > > + planes[1].begin(), size.width, > > + size.width, size.height); > > + if (ret != 0) > > + LOG(Virtual, Error) << "ARGBToNV12() failed with " << 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 */ > > + } > > +} > > + > > +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] = { > > + /* 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..b8bbe0b8 > > --- /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: > > + void 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 56e05528..4e9a6973 100644 > > --- a/src/libcamera/pipeline/virtual/virtual.cpp > > +++ b/src/libcamera/pipeline/virtual/virtual.cpp > > @@ -84,6 +84,8 @@ private: > > return static_cast<VirtualCameraData *>(camera->_d()); > > } > > > > + void initFrameGenerator(Camera *camera); > > + > > DmaBufAllocator dmaBufAllocator_; > > }; > > > > @@ -218,8 +220,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; > > } > > @@ -255,9 +259,23 @@ void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera) > > int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, > > Request *request) > > { > > + VirtualCameraData *data = cameraData(camera); > > + > > /* \todo Read from the virtual video if any. */ > > - for (auto it : request->buffers()) > > - completeBuffer(request, it.second); > > + 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; > > + streamConfig.frameGenerator->generateFrame( > > + stream->configuration().size, buffer); > > + completeBuffer(request, buffer); > > + break; > > + } > > + } > > + ASSERT(found); > > + } > > Not a requirement, but you can make this a little more C++ with > > for (auto const &[stream, buffer] : request->buffers()) { > auto it = std::find_if(data->streamConfigs_.begin(), > data->streamConfigs_.end(), > [stream](const auto &c) { > return &c.stream == stream; > }); > ASSERT(it == data->streamConfigs_.end()); > > it->frameGenerator->generateFrame(stream->configuration().size, > buffer); > completeBuffer(request, buffer); > } > > warning: compile-tested only > > The rest looks good Let's skip this for now, as my linter shows an error `Captured structured bindings are a C++20 extension`, and it also fails the gitlab pipeline: https://gitlab.freedesktop.org/chenghaoyang/libcamera/-/jobs/64229041 > > > > > request->metadata().set(controls::SensorTimestamp, currentTimestamp()); > > completeRequest(request); > > @@ -299,11 +317,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 77823c41..acdd437e 100644 > > --- a/src/libcamera/pipeline/virtual/virtual.h > > +++ b/src/libcamera/pipeline/virtual/virtual.h > > @@ -14,6 +14,8 @@ > > #include "libcamera/internal/camera.h" > > #include "libcamera/internal/pipeline_handler.h" > > > > +#include "test_pattern_generator.h" > > + > > namespace libcamera { > > > > class VirtualCameraData : public Camera::Private > > @@ -27,6 +29,7 @@ public: > > }; > > struct StreamConfig { > > Stream stream; > > + std::unique_ptr<FrameGenerator> frameGenerator; > > }; > > > > VirtualCameraData(PipelineHandler *pipe, > > @@ -34,6 +37,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 > > + > > # libcamera must be built first as a dependency to the other components. > > subdir('libcamera') > > > > -- > > 2.46.0.598.g6f2099f65c-goog > > -- BR, Harvey Yang
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..d8727b8f --- /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 void 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..f84ab2f3 --- /dev/null +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp @@ -0,0 +1,135 @@ +/* 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; + +void 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, + planes[0].begin(), size.width, + planes[1].begin(), size.width, + size.width, size.height); + if (ret != 0) + LOG(Virtual, Error) << "ARGBToNV12() failed with " << 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 */ + } +} + +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] = { + /* 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..b8bbe0b8 --- /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: + void 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 56e05528..4e9a6973 100644 --- a/src/libcamera/pipeline/virtual/virtual.cpp +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -84,6 +84,8 @@ private: return static_cast<VirtualCameraData *>(camera->_d()); } + void initFrameGenerator(Camera *camera); + DmaBufAllocator dmaBufAllocator_; }; @@ -218,8 +220,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; } @@ -255,9 +259,23 @@ void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera) int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, Request *request) { + VirtualCameraData *data = cameraData(camera); + /* \todo Read from the virtual video if any. */ - for (auto it : request->buffers()) - completeBuffer(request, it.second); + 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; + streamConfig.frameGenerator->generateFrame( + stream->configuration().size, buffer); + completeBuffer(request, buffer); + break; + } + } + ASSERT(found); + } request->metadata().set(controls::SensorTimestamp, currentTimestamp()); completeRequest(request); @@ -299,11 +317,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 77823c41..acdd437e 100644 --- a/src/libcamera/pipeline/virtual/virtual.h +++ b/src/libcamera/pipeline/virtual/virtual.h @@ -14,6 +14,8 @@ #include "libcamera/internal/camera.h" #include "libcamera/internal/pipeline_handler.h" +#include "test_pattern_generator.h" + namespace libcamera { class VirtualCameraData : public Camera::Private @@ -27,6 +29,7 @@ public: }; struct StreamConfig { Stream stream; + std::unique_ptr<FrameGenerator> frameGenerator; }; VirtualCameraData(PipelineHandler *pipe, @@ -34,6 +37,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 + # libcamera must be built first as a dependency to the other components. subdir('libcamera')