[PATCH v12 4/7] libcamera: pipeline: Add test pattern for VirtualPipelineHandler
Harvey Yang
chenghaoyang at chromium.org
Tue Sep 10 06:40:17 CEST 2024
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.
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 | 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,
+ 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')
--
2.46.0.598.g6f2099f65c-goog
More information about the libcamera-devel
mailing list