[PATCH v10 4/7] libcamera: pipeline: Add test pattern for VirtualPipelineHandler

Harvey Yang chenghaoyang at chromium.org
Thu Aug 29 21:47:43 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.

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>

libcamera: pipeline: Shift test pattern by 1 pixel left every frame

- This write the buffer every frame
- Shifting makes the frame rate dropped from about 160 to 40

Patchset1->2
- Use constant instead of using a magic number

Patchset2->3
- Make shiftLeft() private

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>
---
 src/android/meson.build                       |  19 ---
 .../pipeline/virtual/frame_generator.h        |  29 ++++
 src/libcamera/pipeline/virtual/meson.build    |   3 +
 .../virtual/test_pattern_generator.cpp        | 140 ++++++++++++++++++
 .../pipeline/virtual/test_pattern_generator.h |  57 +++++++
 src/libcamera/pipeline/virtual/virtual.cpp    |  38 ++++-
 src/libcamera/pipeline/virtual/virtual.h      |   7 +
 src/meson.build                               |  19 +++
 8 files changed, 290 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 68646120a..6341ee8b0 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 000000000..d8727b8f3
--- /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 ada1b3358..0c537777f 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 000000000..7094818e5
--- /dev/null
+++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp
@@ -0,0 +1,140 @@
+/* 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
+	}
+}
+
+ColorBarsGenerator::ColorBarsGenerator() = default;
+
+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 */
+		}
+	}
+}
+
+DiagonalLinesGenerator::DiagonalLinesGenerator() = default;
+
+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 000000000..11bcb5f04
--- /dev/null
+++ b/src/libcamera/pipeline/virtual/test_pattern_generator.h
@@ -0,0 +1,57 @@
+/* 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:
+	ColorBarsGenerator();
+
+	/* Generate a template buffer of the color bar test pattern. */
+	void configure(const Size &size) override;
+};
+
+class DiagonalLinesGenerator : public TestPatternGenerator
+{
+public:
+	DiagonalLinesGenerator();
+
+	/* 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 d4e3f60ad..690d72a22 100644
--- a/src/libcamera/pipeline/virtual/virtual.cpp
+++ b/src/libcamera/pipeline/virtual/virtual.cpp
@@ -167,8 +167,11 @@ int PipelineHandlerVirtual::configure(Camera *camera,
 				      CameraConfiguration *config)
 {
 	VirtualCameraData *data = cameraData(camera);
-	for (size_t i = 0; i < config->size(); ++i)
+	for (size_t i = 0; i < config->size(); ++i) {
 		config->at(i).setStream(&data->streamConfigs_[i].stream);
+		data->streamConfigs_[i].frameGenerator->configure(
+			data->streamConfigs_[i].stream.configuration().size);
+	}
 
 	return 0;
 }
@@ -204,9 +207,24 @@ 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;
+			}
+		}
+		if (!found)
+			LOG(Virtual, Fatal) << "Stream not defined";
+	}
 
 	request->metadata().set(controls::SensorTimestamp, currentTimestamp());
 	completeRequest(request);
@@ -251,11 +269,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 fb3dbcad3..79c951a06 100644
--- a/src/libcamera/pipeline/virtual/virtual.h
+++ b/src/libcamera/pipeline/virtual/virtual.h
@@ -16,6 +16,8 @@
 #include "libcamera/internal/dma_buf_allocator.h"
 #include "libcamera/internal/pipeline_handler.h"
 
+#include "test_pattern_generator.h"
+
 namespace libcamera {
 
 class VirtualCameraData : public Camera::Private
@@ -29,6 +31,7 @@ public:
 	};
 	struct StreamConfig {
 		Stream stream;
+		std::unique_ptr<FrameGenerator> frameGenerator;
 	};
 
 	VirtualCameraData(PipelineHandler *pipe,
@@ -36,6 +39,8 @@ public:
 
 	~VirtualCameraData() = default;
 
+	TestPattern testPattern_;
+
 	const std::vector<Resolution> supportedResolutions_;
 	Size maxResolutionSize_;
 	Size minResolutionSize_;
@@ -83,6 +88,8 @@ private:
 		return static_cast<VirtualCameraData *>(camera->_d());
 	}
 
+	void initFrameGenerator(Camera *camera);
+
 	DmaBufAllocator dmaBufAllocator_;
 };
 
diff --git a/src/meson.build b/src/meson.build
index 165a77bb9..91bea7753 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.469.g59c65b2a67-goog



More information about the libcamera-devel mailing list