[v12,4/7] libcamera: pipeline: Add test pattern for VirtualPipelineHandler
diff mbox series

Message ID 20240910044834.2477701-5-chenghaoyang@google.com
State Superseded
Headers show
Series
  • Add VirtualPipelineHandler
Related show

Commit Message

Harvey Yang Sept. 10, 2024, 4:40 a.m. UTC
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

Comments

Jacopo Mondi Sept. 26, 2024, 11:18 a.m. UTC | #1
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
>
Cheng-Hao Yang Sept. 26, 2024, 4:32 p.m. UTC | #2
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

Patch
diff mbox series

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')