mirror of
https://git.libcamera.org/libcamera/libcamera.git
synced 2025-07-13 07:19:45 +03:00
libcamera: pipeline: Add test pattern for VirtualPipelineHandler
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> Signed-off-by: Harvey Yang <chenghaoyang@chromium.org> Co-developed-by: Yunke Cao <yunkec@chromium.org> Signed-off-by: Yunke Cao <yunkec@chromium.org> Co-developed-by: Tomasz Figa <tfiga@chromium.org> Signed-off-by: Tomasz Figa <tfiga@chromium.org> Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
This commit is contained in:
parent
3a884ebfe4
commit
eeaa7de21b
8 changed files with 282 additions and 22 deletions
|
@ -15,25 +15,6 @@ foreach dep : android_deps
|
||||||
endif
|
endif
|
||||||
endforeach
|
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_deps += [libyuv_dep]
|
||||||
|
|
||||||
android_hal_sources = files([
|
android_hal_sources = files([
|
||||||
|
|
29
src/libcamera/pipeline/virtual/frame_generator.h
Normal file
29
src/libcamera/pipeline/virtual/frame_generator.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024, Google Inc.
|
||||||
|
*
|
||||||
|
* Virtual cameras helper to generate frames
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <libcamera/framebuffer.h>
|
||||||
|
#include <libcamera/geometry.h>
|
||||||
|
|
||||||
|
namespace libcamera {
|
||||||
|
|
||||||
|
class FrameGenerator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~FrameGenerator() = default;
|
||||||
|
|
||||||
|
virtual void configure(const Size &size) = 0;
|
||||||
|
|
||||||
|
virtual int generateFrame(const Size &size,
|
||||||
|
const FrameBuffer *buffer) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
FrameGenerator() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} /* namespace libcamera */
|
|
@ -1,5 +1,8 @@
|
||||||
# SPDX-License-Identifier: CC0-1.0
|
# SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
libcamera_internal_sources += files([
|
libcamera_internal_sources += files([
|
||||||
|
'test_pattern_generator.cpp',
|
||||||
'virtual.cpp',
|
'virtual.cpp',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
libcamera_deps += [libyuv_dep]
|
||||||
|
|
136
src/libcamera/pipeline/virtual/test_pattern_generator.cpp
Normal file
136
src/libcamera/pipeline/virtual/test_pattern_generator.cpp
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024, Google Inc.
|
||||||
|
*
|
||||||
|
* Derived class of FrameGenerator for generating test patterns
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "test_pattern_generator.h"
|
||||||
|
|
||||||
|
#include <libcamera/base/log.h>
|
||||||
|
|
||||||
|
#include "libcamera/internal/mapped_framebuffer.h"
|
||||||
|
|
||||||
|
#include <libyuv/convert_from_argb.h>
|
||||||
|
|
||||||
|
namespace libcamera {
|
||||||
|
|
||||||
|
LOG_DECLARE_CATEGORY(Virtual)
|
||||||
|
|
||||||
|
static const unsigned int kARGBSize = 4;
|
||||||
|
|
||||||
|
int TestPatternGenerator::generateFrame(const Size &size,
|
||||||
|
const FrameBuffer *buffer)
|
||||||
|
{
|
||||||
|
MappedFrameBuffer mappedFrameBuffer(buffer,
|
||||||
|
MappedFrameBuffer::MapFlag::Write);
|
||||||
|
|
||||||
|
auto planes = mappedFrameBuffer.planes();
|
||||||
|
|
||||||
|
shiftLeft(size);
|
||||||
|
|
||||||
|
/* Convert the template_ to the frame buffer */
|
||||||
|
int ret = libyuv::ARGBToNV12(template_.get(), size.width * kARGBSize,
|
||||||
|
planes[0].begin(), size.width,
|
||||||
|
planes[1].begin(), size.width,
|
||||||
|
size.width, size.height);
|
||||||
|
if (ret != 0)
|
||||||
|
LOG(Virtual, Error) << "ARGBToNV12() failed with " << ret;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestPatternGenerator::shiftLeft(const Size &size)
|
||||||
|
{
|
||||||
|
/* Store the first column temporarily */
|
||||||
|
auto firstColumn = std::make_unique<uint8_t[]>(size.height * kARGBSize);
|
||||||
|
for (size_t h = 0; h < size.height; h++) {
|
||||||
|
unsigned int index = h * size.width * kARGBSize;
|
||||||
|
unsigned int index1 = h * kARGBSize;
|
||||||
|
firstColumn[index1] = template_[index];
|
||||||
|
firstColumn[index1 + 1] = template_[index + 1];
|
||||||
|
firstColumn[index1 + 2] = template_[index + 2];
|
||||||
|
firstColumn[index1 + 3] = 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Overwrite template_ */
|
||||||
|
uint8_t *buf = template_.get();
|
||||||
|
for (size_t h = 0; h < size.height; h++) {
|
||||||
|
for (size_t w = 0; w < size.width - 1; w++) {
|
||||||
|
/* Overwrite with the pixel on the right */
|
||||||
|
unsigned int index = (h * size.width + w + 1) * kARGBSize;
|
||||||
|
*buf++ = template_[index]; /* B */
|
||||||
|
*buf++ = template_[index + 1]; /* G */
|
||||||
|
*buf++ = template_[index + 2]; /* R */
|
||||||
|
*buf++ = 0x00; /* A */
|
||||||
|
}
|
||||||
|
/* Overwrite the new last column with the original first column */
|
||||||
|
unsigned int index1 = h * kARGBSize;
|
||||||
|
*buf++ = firstColumn[index1]; /* B */
|
||||||
|
*buf++ = firstColumn[index1 + 1]; /* G */
|
||||||
|
*buf++ = firstColumn[index1 + 2]; /* R */
|
||||||
|
*buf++ = 0x00; /* A */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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[2][3] = {
|
||||||
|
/* R, G, B */
|
||||||
|
{ 0xff, 0xff, 0xff }, /* White */
|
||||||
|
{ 0x00, 0x00, 0x00 }, /* Black */
|
||||||
|
};
|
||||||
|
|
||||||
|
template_ = std::make_unique<uint8_t[]>(
|
||||||
|
size.width * size.height * kARGBSize);
|
||||||
|
|
||||||
|
unsigned int lineWidth = size.width / 10;
|
||||||
|
|
||||||
|
uint8_t *buf = template_.get();
|
||||||
|
for (size_t h = 0; h < size.height; h++) {
|
||||||
|
for (size_t w = 0; w < size.width; w++) {
|
||||||
|
/* Repeat when the width is exceed */
|
||||||
|
int index = ((w + h) / lineWidth) % 2;
|
||||||
|
|
||||||
|
*buf++ = kColorBar[index][2]; /* B */
|
||||||
|
*buf++ = kColorBar[index][1]; /* G */
|
||||||
|
*buf++ = kColorBar[index][0]; /* R */
|
||||||
|
*buf++ = 0x00; /* A */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace libcamera */
|
52
src/libcamera/pipeline/virtual/test_pattern_generator.h
Normal file
52
src/libcamera/pipeline/virtual/test_pattern_generator.h
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024, Google Inc.
|
||||||
|
*
|
||||||
|
* Derived class of FrameGenerator for generating test patterns
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <libcamera/framebuffer.h>
|
||||||
|
#include <libcamera/geometry.h>
|
||||||
|
|
||||||
|
#include "frame_generator.h"
|
||||||
|
|
||||||
|
namespace libcamera {
|
||||||
|
|
||||||
|
enum class TestPattern : char {
|
||||||
|
ColorBars = 0,
|
||||||
|
DiagonalLines = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
class TestPatternGenerator : public FrameGenerator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int generateFrame(const Size &size, const FrameBuffer *buffer) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/* Buffer of test pattern template */
|
||||||
|
std::unique_ptr<uint8_t[]> template_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/* Shift the buffer by 1 pixel left each frame */
|
||||||
|
void shiftLeft(const Size &size);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ColorBarsGenerator : public TestPatternGenerator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/* Generate a template buffer of the color bar test pattern. */
|
||||||
|
void configure(const Size &size) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DiagonalLinesGenerator : public TestPatternGenerator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/* Generate a template buffer of the diagonal lines test pattern. */
|
||||||
|
void configure(const Size &size) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} /* namespace libcamera */
|
|
@ -34,6 +34,7 @@
|
||||||
#include "libcamera/internal/camera.h"
|
#include "libcamera/internal/camera.h"
|
||||||
#include "libcamera/internal/dma_buf_allocator.h"
|
#include "libcamera/internal/dma_buf_allocator.h"
|
||||||
#include "libcamera/internal/formats.h"
|
#include "libcamera/internal/formats.h"
|
||||||
|
#include "libcamera/internal/framebuffer.h"
|
||||||
#include "libcamera/internal/pipeline_handler.h"
|
#include "libcamera/internal/pipeline_handler.h"
|
||||||
|
|
||||||
namespace libcamera {
|
namespace libcamera {
|
||||||
|
@ -94,6 +95,8 @@ private:
|
||||||
return static_cast<VirtualCameraData *>(camera->_d());
|
return static_cast<VirtualCameraData *>(camera->_d());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void initFrameGenerator(Camera *camera);
|
||||||
|
|
||||||
DmaBufAllocator dmaBufAllocator_;
|
DmaBufAllocator dmaBufAllocator_;
|
||||||
|
|
||||||
bool resetCreated_ = false;
|
bool resetCreated_ = false;
|
||||||
|
@ -241,8 +244,10 @@ int PipelineHandlerVirtual::configure(Camera *camera,
|
||||||
CameraConfiguration *config)
|
CameraConfiguration *config)
|
||||||
{
|
{
|
||||||
VirtualCameraData *data = cameraData(camera);
|
VirtualCameraData *data = cameraData(camera);
|
||||||
for (auto [i, c] : utils::enumerate(*config))
|
for (auto [i, c] : utils::enumerate(*config)) {
|
||||||
c.setStream(&data->streamConfigs_[i].stream);
|
c.setStream(&data->streamConfigs_[i].stream);
|
||||||
|
data->streamConfigs_[i].frameGenerator->configure(c.size);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -278,8 +283,24 @@ void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera)
|
||||||
int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera,
|
int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera,
|
||||||
Request *request)
|
Request *request)
|
||||||
{
|
{
|
||||||
for (auto it : request->buffers())
|
VirtualCameraData *data = cameraData(camera);
|
||||||
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;
|
||||||
|
if (streamConfig.frameGenerator->generateFrame(
|
||||||
|
stream->configuration().size, buffer))
|
||||||
|
buffer->_d()->cancel();
|
||||||
|
|
||||||
|
completeBuffer(request, buffer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ASSERT(found);
|
||||||
|
}
|
||||||
|
|
||||||
request->metadata().set(controls::SensorTimestamp, currentTimestamp());
|
request->metadata().set(controls::SensorTimestamp, currentTimestamp());
|
||||||
completeRequest(request);
|
completeRequest(request);
|
||||||
|
@ -325,6 +346,9 @@ bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator *enumerator
|
||||||
|
|
||||||
const std::string id = "Virtual0";
|
const std::string id = "Virtual0";
|
||||||
std::shared_ptr<Camera> camera = Camera::create(std::move(data), id, streams);
|
std::shared_ptr<Camera> camera = Camera::create(std::move(data), id, streams);
|
||||||
|
|
||||||
|
initFrameGenerator(camera.get());
|
||||||
|
|
||||||
registerCamera(std::move(camera));
|
registerCamera(std::move(camera));
|
||||||
|
|
||||||
resetCreated_ = true;
|
resetCreated_ = true;
|
||||||
|
@ -332,6 +356,17 @@ bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator *enumerator
|
||||||
return true;
|
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")
|
REGISTER_PIPELINE_HANDLER(PipelineHandlerVirtual, "virtual")
|
||||||
|
|
||||||
} /* namespace libcamera */
|
} /* namespace libcamera */
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
#include "libcamera/internal/camera.h"
|
#include "libcamera/internal/camera.h"
|
||||||
#include "libcamera/internal/pipeline_handler.h"
|
#include "libcamera/internal/pipeline_handler.h"
|
||||||
|
|
||||||
|
#include "test_pattern_generator.h"
|
||||||
|
|
||||||
namespace libcamera {
|
namespace libcamera {
|
||||||
|
|
||||||
class VirtualCameraData : public Camera::Private
|
class VirtualCameraData : public Camera::Private
|
||||||
|
@ -28,6 +30,7 @@ public:
|
||||||
};
|
};
|
||||||
struct StreamConfig {
|
struct StreamConfig {
|
||||||
Stream stream;
|
Stream stream;
|
||||||
|
std::unique_ptr<FrameGenerator> frameGenerator;
|
||||||
};
|
};
|
||||||
|
|
||||||
VirtualCameraData(PipelineHandler *pipe,
|
VirtualCameraData(PipelineHandler *pipe,
|
||||||
|
@ -35,6 +38,8 @@ public:
|
||||||
|
|
||||||
~VirtualCameraData() = default;
|
~VirtualCameraData() = default;
|
||||||
|
|
||||||
|
TestPattern testPattern_ = TestPattern::ColorBars;
|
||||||
|
|
||||||
const std::vector<Resolution> supportedResolutions_;
|
const std::vector<Resolution> supportedResolutions_;
|
||||||
Size maxResolutionSize_;
|
Size maxResolutionSize_;
|
||||||
Size minResolutionSize_;
|
Size minResolutionSize_;
|
||||||
|
|
|
@ -27,6 +27,25 @@ else
|
||||||
ipa_sign_module = false
|
ipa_sign_module = false
|
||||||
endif
|
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.
|
# libcamera must be built first as a dependency to the other components.
|
||||||
subdir('libcamera')
|
subdir('libcamera')
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue