[libcamera-devel] libcamera: pipelines: Add VIVID pipeline support

Message ID 20200619120249.1084998-1-kieran.bingham@ideasonboard.com
State Not Applicable
Delegated to: Kieran Bingham
Headers show
Series
  • [libcamera-devel] libcamera: pipelines: Add VIVID pipeline support
Related show

Commit Message

Kieran Bingham June 19, 2020, 12:02 p.m. UTC
The VIVID driver supports more pixel formats and properties than the VIMC
driver, and can provide extended testing for libcamera.

The VIMC pipeline handler is duplicated and simplified to support the
VIVID device.

Unfortuantely, the VIVID device can not be handled by either of the
generic UVC or Simple pipeline handlers.

Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
---
 meson_options.txt                        |   2 +-
 src/libcamera/pipeline/vivid/meson.build |   5 +
 src/libcamera/pipeline/vivid/vivid.cpp   | 441 +++++++++++++++++++++++
 3 files changed, 447 insertions(+), 1 deletion(-)
 create mode 100644 src/libcamera/pipeline/vivid/meson.build
 create mode 100644 src/libcamera/pipeline/vivid/vivid.cpp

Comments

Kieran Bingham June 19, 2020, 12:10 p.m. UTC | #1
Hi Kieran,

On 19/06/2020 13:02, Kieran Bingham wrote:
> The VIVID driver supports more pixel formats and properties than the VIMC
> driver, and can provide extended testing for libcamera.
> 
> The VIMC pipeline handler is duplicated and simplified to support the
> VIVID device.
> 
> Unfortuantely, the VIVID device can not be handled by either of the

"Unfortunately"

> generic UVC or Simple pipeline handlers.
> 
> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>

Of course I realise there is resistance to merging more pipeline
handlers at the moment, but this is now the *third* time I've sent this
pipeline out for individuals to use/see - so I'm sending it to the list.


> ---
>  meson_options.txt                        |   2 +-
>  src/libcamera/pipeline/vivid/meson.build |   5 +
>  src/libcamera/pipeline/vivid/vivid.cpp   | 441 +++++++++++++++++++++++
>  3 files changed, 447 insertions(+), 1 deletion(-)
>  create mode 100644 src/libcamera/pipeline/vivid/meson.build
>  create mode 100644 src/libcamera/pipeline/vivid/vivid.cpp
> 
> diff --git a/meson_options.txt b/meson_options.txt
> index badace151bb6..dc4684df49b2 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -16,7 +16,7 @@ option('gstreamer',
>  
>  option('pipelines',
>          type : 'array',
> -        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo', 'vimc'],
> +        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo', 'vimc', 'vivid'],
>          description : 'Select which pipeline handlers to include')
>  
>  option('test',
> diff --git a/src/libcamera/pipeline/vivid/meson.build b/src/libcamera/pipeline/vivid/meson.build
> new file mode 100644
> index 000000000000..086bb825387c
> --- /dev/null
> +++ b/src/libcamera/pipeline/vivid/meson.build
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: CC0-1.0
> +
> +libcamera_sources += files([
> +    'vivid.cpp',
> +])
> diff --git a/src/libcamera/pipeline/vivid/vivid.cpp b/src/libcamera/pipeline/vivid/vivid.cpp
> new file mode 100644
> index 000000000000..b811e33a0299
> --- /dev/null
> +++ b/src/libcamera/pipeline/vivid/vivid.cpp
> @@ -0,0 +1,441 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2018, Google Inc.

This should be 2020 of course,

> + *
> + * vivid.cpp - Pipeline handler for the vivid capture device
> + */
> +
> +#include <algorithm>
> +#include <iomanip>
> +#include <map>
> +#include <math.h>
> +#include <tuple>
> +
> +#include <linux/media-bus-format.h>
> +#include <linux/version.h>
> +
> +#include <libcamera/camera.h>
> +#include <libcamera/control_ids.h>
> +#include <libcamera/controls.h>

> +#include <libcamera/ipa/ipa_interface.h>
> +#include <libcamera/ipa/ipa_module_info.h>

No need for IPA headers of course, as there is no IPA.

> +#include <libcamera/request.h>
> +#include <libcamera/stream.h>
> +
> +#include "libcamera/internal/camera_sensor.h"
> +#include "libcamera/internal/device_enumerator.h"
> +#include "libcamera/internal/ipa_manager.h"
> +#include "libcamera/internal/log.h"
> +#include "libcamera/internal/media_device.h"
> +#include "libcamera/internal/pipeline_handler.h"
> +#include "libcamera/internal/utils.h"
> +#include "libcamera/internal/v4l2_controls.h"
> +#include "libcamera/internal/v4l2_subdevice.h"
> +#include "libcamera/internal/v4l2_videodevice.h"
> +
> +#define VIVID_CID_CUSTOM_BASE           (V4L2_CID_USER_BASE | 0xf000)
> +#define VIVID_CID_BUTTON                (VIVID_CID_CUSTOM_BASE + 0)
> +#define VIVID_CID_BOOLEAN               (VIVID_CID_CUSTOM_BASE + 1)
> +#define VIVID_CID_INTEGER               (VIVID_CID_CUSTOM_BASE + 2)
> +#define VIVID_CID_INTEGER64             (VIVID_CID_CUSTOM_BASE + 3)
> +#define VIVID_CID_MENU                  (VIVID_CID_CUSTOM_BASE + 4)
> +#define VIVID_CID_STRING                (VIVID_CID_CUSTOM_BASE + 5)
> +#define VIVID_CID_BITMASK               (VIVID_CID_CUSTOM_BASE + 6)
> +#define VIVID_CID_INTMENU               (VIVID_CID_CUSTOM_BASE + 7)
> +#define VIVID_CID_U32_ARRAY             (VIVID_CID_CUSTOM_BASE + 8)
> +#define VIVID_CID_U16_MATRIX            (VIVID_CID_CUSTOM_BASE + 9)
> +#define VIVID_CID_U8_4D_ARRAY           (VIVID_CID_CUSTOM_BASE + 10)
> +
> +#define VIVID_CID_VIVID_BASE            (0x00f00000 | 0xf000)
> +#define VIVID_CID_VIVID_CLASS           (0x00f00000 | 1)
> +#define VIVID_CID_TEST_PATTERN          (VIVID_CID_VIVID_BASE + 0)
> +#define VIVID_CID_OSD_TEXT_MODE         (VIVID_CID_VIVID_BASE + 1)
> +#define VIVID_CID_HOR_MOVEMENT          (VIVID_CID_VIVID_BASE + 2)
> +#define VIVID_CID_VERT_MOVEMENT         (VIVID_CID_VIVID_BASE + 3)
> +#define VIVID_CID_SHOW_BORDER           (VIVID_CID_VIVID_BASE + 4)
> +#define VIVID_CID_SHOW_SQUARE           (VIVID_CID_VIVID_BASE + 5)
> +#define VIVID_CID_INSERT_SAV            (VIVID_CID_VIVID_BASE + 6)
> +#define VIVID_CID_INSERT_EAV            (VIVID_CID_VIVID_BASE + 7)
> +#define VIVID_CID_VBI_CAP_INTERLACED    (VIVID_CID_VIVID_BASE + 8)
> +
> +#define VIVID_CID_HFLIP                 (VIVID_CID_VIVID_BASE + 20)
> +#define VIVID_CID_VFLIP                 (VIVID_CID_VIVID_BASE + 21)
> +#define VIVID_CID_STD_ASPECT_RATIO      (VIVID_CID_VIVID_BASE + 22)
> +#define VIVID_CID_DV_TIMINGS_ASPECT_RATIO       (VIVID_CID_VIVID_BASE + 23)
> +#define VIVID_CID_TSTAMP_SRC            (VIVID_CID_VIVID_BASE + 24)
> +#define VIVID_CID_COLORSPACE            (VIVID_CID_VIVID_BASE + 25)
> +#define VIVID_CID_XFER_FUNC             (VIVID_CID_VIVID_BASE + 26)
> +#define VIVID_CID_YCBCR_ENC             (VIVID_CID_VIVID_BASE + 27)
> +#define VIVID_CID_QUANTIZATION          (VIVID_CID_VIVID_BASE + 28)
> +#define VIVID_CID_LIMITED_RGB_RANGE     (VIVID_CID_VIVID_BASE + 29)
> +#define VIVID_CID_ALPHA_MODE            (VIVID_CID_VIVID_BASE + 30)
> +#define VIVID_CID_HAS_CROP_CAP          (VIVID_CID_VIVID_BASE + 31)
> +#define VIVID_CID_HAS_COMPOSE_CAP       (VIVID_CID_VIVID_BASE + 32)
> +#define VIVID_CID_HAS_SCALER_CAP        (VIVID_CID_VIVID_BASE + 33)
> +#define VIVID_CID_HAS_CROP_OUT          (VIVID_CID_VIVID_BASE + 34)
> +#define VIVID_CID_HAS_COMPOSE_OUT       (VIVID_CID_VIVID_BASE + 35)
> +#define VIVID_CID_HAS_SCALER_OUT        (VIVID_CID_VIVID_BASE + 36)
> +#define VIVID_CID_LOOP_VIDEO            (VIVID_CID_VIVID_BASE + 37)
> +#define VIVID_CID_SEQ_WRAP              (VIVID_CID_VIVID_BASE + 38)
> +#define VIVID_CID_TIME_WRAP             (VIVID_CID_VIVID_BASE + 39)
> +#define VIVID_CID_MAX_EDID_BLOCKS       (VIVID_CID_VIVID_BASE + 40)
> +#define VIVID_CID_PERCENTAGE_FILL       (VIVID_CID_VIVID_BASE + 41)
> +#define VIVID_CID_REDUCED_FPS           (VIVID_CID_VIVID_BASE + 42)
> +#define VIVID_CID_HSV_ENC               (VIVID_CID_VIVID_BASE + 43)
> +#define VIVID_CID_DISPLAY_PRESENT       (VIVID_CID_VIVID_BASE + 44)
> +
> +#define VIVID_CID_STD_SIGNAL_MODE       (VIVID_CID_VIVID_BASE + 60)
> +#define VIVID_CID_STANDARD              (VIVID_CID_VIVID_BASE + 61)
> +#define VIVID_CID_DV_TIMINGS_SIGNAL_MODE        (VIVID_CID_VIVID_BASE + 62)
> +#define VIVID_CID_DV_TIMINGS            (VIVID_CID_VIVID_BASE + 63)
> +#define VIVID_CID_PERC_DROPPED          (VIVID_CID_VIVID_BASE + 64)
> +#define VIVID_CID_DISCONNECT            (VIVID_CID_VIVID_BASE + 65)
> +#define VIVID_CID_DQBUF_ERROR           (VIVID_CID_VIVID_BASE + 66)
> +#define VIVID_CID_QUEUE_SETUP_ERROR     (VIVID_CID_VIVID_BASE + 67)
> +#define VIVID_CID_BUF_PREPARE_ERROR     (VIVID_CID_VIVID_BASE + 68)
> +#define VIVID_CID_START_STR_ERROR       (VIVID_CID_VIVID_BASE + 69)
> +#define VIVID_CID_QUEUE_ERROR           (VIVID_CID_VIVID_BASE + 70)
> +#define VIVID_CID_CLEAR_FB              (VIVID_CID_VIVID_BASE + 71)
> +#define VIVID_CID_REQ_VALIDATE_ERROR    (VIVID_CID_VIVID_BASE + 72)
> +
> +#define VIVID_CID_RADIO_SEEK_MODE       (VIVID_CID_VIVID_BASE + 90)
> +#define VIVID_CID_RADIO_SEEK_PROG_LIM   (VIVID_CID_VIVID_BASE + 91)
> +#define VIVID_CID_RADIO_RX_RDS_RBDS     (VIVID_CID_VIVID_BASE + 92)
> +#define VIVID_CID_RADIO_RX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 93)
> +
> +#define VIVID_CID_RADIO_TX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 94)
> +
> +#define VIVID_CID_SDR_CAP_FM_DEVIATION  (VIVID_CID_VIVID_BASE + 110)
> +
> +
> +namespace libcamera {
> +
> +LOG_DEFINE_CATEGORY(VIVID)
> +
> +class VividCameraData : public CameraData
> +{
> +public:
> +	VividCameraData(PipelineHandler *pipe, MediaDevice *media)
> +		: CameraData(pipe), media_(media), video_(nullptr)
> +	{
> +	}
> +
> +	~VividCameraData()
> +	{
> +		delete video_;
> +	}
> +
> +	int init();
> +	void bufferReady(FrameBuffer *buffer);
> +
> +	MediaDevice *media_;
> +	V4L2VideoDevice *video_;
> +	Stream stream_;
> +};
> +
> +class VividCameraConfiguration : public CameraConfiguration
> +{
> +public:
> +	VividCameraConfiguration();
> +
> +	Status validate() override;
> +};
> +
> +class PipelineHandlerVivid : public PipelineHandler
> +{
> +public:
> +	PipelineHandlerVivid(CameraManager *manager);
> +
> +	CameraConfiguration *generateConfiguration(Camera *camera,
> +						   const StreamRoles &roles) override;
> +	int configure(Camera *camera, CameraConfiguration *config) override;
> +
> +	int exportFrameBuffers(Camera *camera, Stream *stream,
> +			       std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;
> +
> +	int start(Camera *camera) override;
> +	void stop(Camera *camera) override;
> +
> +	int queueRequestDevice(Camera *camera, Request *request) override;
> +
> +	bool match(DeviceEnumerator *enumerator) override;
> +
> +private:
> +	int processControls(VividCameraData *data, Request *request);
> +
> +	VividCameraData *cameraData(const Camera *camera)
> +	{
> +		return static_cast<VividCameraData *>(
> +			PipelineHandler::cameraData(camera));
> +	}
> +};
> +
> +VividCameraConfiguration::VividCameraConfiguration()
> +	: CameraConfiguration()
> +{
> +}
> +
> +CameraConfiguration::Status VividCameraConfiguration::validate()
> +{
> +	Status status = Valid;
> +
> +	if (config_.empty())
> +		return Invalid;
> +
> +	/* Cap the number of entries to the available streams. */
> +	if (config_.size() > 1) {
> +		config_.resize(1);
> +		status = Adjusted;
> +	}
> +
> +	StreamConfiguration &cfg = config_[0];
> +
> +	/* Adjust the pixel format. */
> +	const std::vector<libcamera::PixelFormat> formats = cfg.formats().pixelformats();
> +	if (std::find(formats.begin(), formats.end(), cfg.pixelFormat) == formats.end()) {
> +		cfg.pixelFormat = cfg.formats().pixelformats()[0];
> +		LOG(VIVID, Debug) << "Adjusting format to " << cfg.pixelFormat.toString();
> +		status = Adjusted;
> +	}
> +
> +	cfg.bufferCount = 4;
> +
> +	return status;
> +}
> +
> +PipelineHandlerVivid::PipelineHandlerVivid(CameraManager *manager)
> +	: PipelineHandler(manager)
> +{
> +}
> +
> +CameraConfiguration *PipelineHandlerVivid::generateConfiguration(Camera *camera,
> +								 const StreamRoles &roles)
> +{
> +	CameraConfiguration *config = new VividCameraConfiguration();
> +	VividCameraData *data = cameraData(camera);
> +
> +	if (roles.empty())
> +		return config;
> +
> +	std::map<V4L2PixelFormat, std::vector<SizeRange>> v4l2Formats =
> +		data->video_->formats();
> +	std::map<PixelFormat, std::vector<SizeRange>> deviceFormats;
> +	std::transform(v4l2Formats.begin(), v4l2Formats.end(),
> +		       std::inserter(deviceFormats, deviceFormats.begin()),
> +		       [&](const decltype(v4l2Formats)::value_type &format) {
> +			       return decltype(deviceFormats)::value_type{
> +				       format.first.toPixelFormat(),
> +				       format.second
> +			       };
> +		       });
> +
> +	StreamFormats formats(deviceFormats);
> +	StreamConfiguration cfg(formats);
> +
> +	cfg.pixelFormat = PixelFormat(DRM_FORMAT_BGR888);
> +	cfg.size = { 1280, 720 };
> +	cfg.bufferCount = 4;
> +
> +	config->addConfiguration(cfg);
> +
> +	config->validate();
> +
> +	return config;
> +}
> +
> +int PipelineHandlerVivid::configure(Camera *camera, CameraConfiguration *config)
> +{
> +	VividCameraData *data = cameraData(camera);
> +	StreamConfiguration &cfg = config->at(0);
> +	int ret;
> +
> +	V4L2DeviceFormat format = {};
> +	format.fourcc = data->video_->toV4L2PixelFormat(cfg.pixelFormat);
> +	format.size = cfg.size;
> +
> +	ret = data->video_->setFormat(&format);
> +	if (ret)
> +		return ret;
> +
> +	if (format.size != cfg.size ||
> +	    format.fourcc != data->video_->toV4L2PixelFormat(cfg.pixelFormat))
> +		return -EINVAL;
> +
> +	cfg.setStream(&data->stream_);
> +	cfg.stride = format.planes[0].bpl;
> +
> +	return 0;
> +}
> +
> +int PipelineHandlerVivid::exportFrameBuffers(Camera *camera, Stream *stream,
> +					     std::vector<std::unique_ptr<FrameBuffer>> *buffers)
> +{
> +	VividCameraData *data = cameraData(camera);
> +	unsigned int count = stream->configuration().bufferCount;
> +
> +	return data->video_->exportBuffers(count, buffers);
> +}
> +
> +int PipelineHandlerVivid::start(Camera *camera)
> +{
> +	VividCameraData *data = cameraData(camera);
> +	unsigned int count = data->stream_.configuration().bufferCount;
> +
> +	int ret = data->video_->importBuffers(count);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = data->video_->streamOn();
> +	if (ret < 0) {
> +		data->ipa_->stop();
> +		data->video_->releaseBuffers();
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +void PipelineHandlerVivid::stop(Camera *camera)
> +{
> +	VividCameraData *data = cameraData(camera);
> +	data->video_->streamOff();
> +	data->video_->releaseBuffers();
> +}
> +
> +int PipelineHandlerVivid::processControls(VividCameraData *data, Request *request)
> +{
> +	ControlList controls(data->video_->controls());
> +
> +	for (auto it : request->controls()) {
> +		unsigned int id = it.first;
> +		unsigned int offset;
> +		uint32_t cid;
> +
> +		if (id == controls::Brightness) {
> +			cid = V4L2_CID_BRIGHTNESS;
> +			offset = 128;
> +		} else if (id == controls::Contrast) {
> +			cid = V4L2_CID_CONTRAST;
> +			offset = 0;
> +		} else if (id == controls::Saturation) {
> +			cid = V4L2_CID_SATURATION;
> +			offset = 0;
> +		} else {
> +			continue;
> +		}
> +
> +		int32_t value = lroundf(it.second.get<float>() * 128 + offset);
> +		controls.set(cid, utils::clamp(value, 0, 255));
> +	}
> +
> +	for (const auto &ctrl : controls)
> +		LOG(VIVID, Debug)
> +			<< "Setting control " << utils::hex(ctrl.first)
> +			<< " to " << ctrl.second.toString();
> +
> +	int ret = data->video_->setControls(&controls);
> +	if (ret) {
> +		LOG(VIVID, Error) << "Failed to set controls: " << ret;
> +		return ret < 0 ? ret : -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +int PipelineHandlerVivid::queueRequestDevice(Camera *camera, Request *request)
> +{
> +	VividCameraData *data = cameraData(camera);
> +	FrameBuffer *buffer = request->findBuffer(&data->stream_);
> +	if (!buffer) {
> +		LOG(VIVID, Error)
> +			<< "Attempt to queue request with invalid stream";
> +
> +		return -ENOENT;
> +	}
> +
> +	int ret = processControls(data, request);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = data->video_->queueBuffer(buffer);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +bool PipelineHandlerVivid::match(DeviceEnumerator *enumerator)
> +{
> +	DeviceMatch dm("vivid");
> +	dm.add("vivid-000-vid-cap");
> +
> +	MediaDevice *media = acquireMediaDevice(enumerator, dm);
> +	if (!media)
> +		return false;
> +
> +	std::unique_ptr<VividCameraData> data = std::make_unique<VividCameraData>(this, media);
> +
> +	/* Locate and open the capture video node. */
> +	if (data->init())
> +		return false;
> +
> +	/* Create and register the camera. */
> +	std::set<Stream *> streams{ &data->stream_ };
> +	std::shared_ptr<Camera> camera = Camera::create(this, data->video_->deviceName(), streams);
> +	registerCamera(std::move(camera), std::move(data));
> +
> +	return true;
> +}
> +
> +int VividCameraData::init()
> +{
> +	video_ = new V4L2VideoDevice(media_->getEntityByName("vivid-000-vid-cap"));
> +	if (video_->open())
> +		return -ENODEV;
> +
> +	video_->bufferReady.connect(this, &VividCameraData::bufferReady);
> +
> +	/* Initialise the supported controls. */
> +	const ControlInfoMap &controls = video_->controls();
> +	ControlInfoMap::Map ctrls;
> +
> +	for (const auto &ctrl : controls) {
> +		const ControlId *id;
> +		ControlInfo info;
> +
> +		switch (ctrl.first->id()) {
> +		case V4L2_CID_BRIGHTNESS:
> +			id = &controls::Brightness;
> +			info = ControlInfo{ { -1.0f }, { 1.0f }, { 0.0f } };
> +			break;
> +		case V4L2_CID_CONTRAST:
> +			id = &controls::Contrast;
> +			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
> +			break;
> +		case V4L2_CID_SATURATION:
> +			id = &controls::Saturation;
> +			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
> +			break;
> +		default:
> +			continue;
> +		}
> +
> +		ctrls.emplace(id, info);
> +	}
> +
> +	controlInfo_ = std::move(ctrls);
> +
> +	return 0;
> +}
> +
> +void VividCameraData::bufferReady(FrameBuffer *buffer)
> +{
> +	Request *request = buffer->request();
> +
> +	pipe_->completeBuffer(camera_, request, buffer);
> +	pipe_->completeRequest(camera_, request);
> +}
> +
> +REGISTER_PIPELINE_HANDLER(PipelineHandlerVivid);
> +
> +} /* namespace libcamera */
>
Kieran Bingham June 19, 2020, 12:14 p.m. UTC | #2
On 19/06/2020 13:10, Kieran Bingham wrote:
> Hi Kieran,
> 
> On 19/06/2020 13:02, Kieran Bingham wrote:
>> The VIVID driver supports more pixel formats and properties than the VIMC
>> driver, and can provide extended testing for libcamera.
>>
>> The VIMC pipeline handler is duplicated and simplified to support the
>> VIVID device.
>>
>> Unfortuantely, the VIVID device can not be handled by either of the
> 
> "Unfortunately"
> 
>> generic UVC or Simple pipeline handlers.

Perhaps an alternative route to supporting this however would be to make
the simple pipeline handler support pipelines which *don't* have a
sensor object... though the VIVID controls could be useful to support
directly.

Perhaps - inheriting the simple-pipeline handler should be possible, to
provide some 'device specific' adjustments for things like device
specific controls.


>> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> 
> Of course I realise there is resistance to merging more pipeline
> handlers at the moment, but this is now the *third* time I've sent this
> pipeline out for individuals to use/see - so I'm sending it to the list.
> 
> 
>> ---
>>  meson_options.txt                        |   2 +-
>>  src/libcamera/pipeline/vivid/meson.build |   5 +
>>  src/libcamera/pipeline/vivid/vivid.cpp   | 441 +++++++++++++++++++++++
>>  3 files changed, 447 insertions(+), 1 deletion(-)
>>  create mode 100644 src/libcamera/pipeline/vivid/meson.build
>>  create mode 100644 src/libcamera/pipeline/vivid/vivid.cpp
>>
>> diff --git a/meson_options.txt b/meson_options.txt
>> index badace151bb6..dc4684df49b2 100644
>> --- a/meson_options.txt
>> +++ b/meson_options.txt
>> @@ -16,7 +16,7 @@ option('gstreamer',
>>  
>>  option('pipelines',
>>          type : 'array',
>> -        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo', 'vimc'],
>> +        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo', 'vimc', 'vivid'],
>>          description : 'Select which pipeline handlers to include')
>>  
>>  option('test',
>> diff --git a/src/libcamera/pipeline/vivid/meson.build b/src/libcamera/pipeline/vivid/meson.build
>> new file mode 100644
>> index 000000000000..086bb825387c
>> --- /dev/null
>> +++ b/src/libcamera/pipeline/vivid/meson.build
>> @@ -0,0 +1,5 @@
>> +# SPDX-License-Identifier: CC0-1.0
>> +
>> +libcamera_sources += files([
>> +    'vivid.cpp',
>> +])
>> diff --git a/src/libcamera/pipeline/vivid/vivid.cpp b/src/libcamera/pipeline/vivid/vivid.cpp
>> new file mode 100644
>> index 000000000000..b811e33a0299
>> --- /dev/null
>> +++ b/src/libcamera/pipeline/vivid/vivid.cpp
>> @@ -0,0 +1,441 @@
>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
>> +/*
>> + * Copyright (C) 2018, Google Inc.
> 
> This should be 2020 of course,
> 
>> + *
>> + * vivid.cpp - Pipeline handler for the vivid capture device
>> + */
>> +
>> +#include <algorithm>
>> +#include <iomanip>
>> +#include <map>
>> +#include <math.h>
>> +#include <tuple>
>> +
>> +#include <linux/media-bus-format.h>
>> +#include <linux/version.h>
>> +
>> +#include <libcamera/camera.h>
>> +#include <libcamera/control_ids.h>
>> +#include <libcamera/controls.h>
> 
>> +#include <libcamera/ipa/ipa_interface.h>
>> +#include <libcamera/ipa/ipa_module_info.h>
> 
> No need for IPA headers of course, as there is no IPA.
> 
>> +#include <libcamera/request.h>
>> +#include <libcamera/stream.h>
>> +
>> +#include "libcamera/internal/camera_sensor.h"
>> +#include "libcamera/internal/device_enumerator.h"
>> +#include "libcamera/internal/ipa_manager.h"
>> +#include "libcamera/internal/log.h"
>> +#include "libcamera/internal/media_device.h"
>> +#include "libcamera/internal/pipeline_handler.h"
>> +#include "libcamera/internal/utils.h"
>> +#include "libcamera/internal/v4l2_controls.h"
>> +#include "libcamera/internal/v4l2_subdevice.h"
>> +#include "libcamera/internal/v4l2_videodevice.h"
>> +
>> +#define VIVID_CID_CUSTOM_BASE           (V4L2_CID_USER_BASE | 0xf000)
>> +#define VIVID_CID_BUTTON                (VIVID_CID_CUSTOM_BASE + 0)
>> +#define VIVID_CID_BOOLEAN               (VIVID_CID_CUSTOM_BASE + 1)
>> +#define VIVID_CID_INTEGER               (VIVID_CID_CUSTOM_BASE + 2)
>> +#define VIVID_CID_INTEGER64             (VIVID_CID_CUSTOM_BASE + 3)
>> +#define VIVID_CID_MENU                  (VIVID_CID_CUSTOM_BASE + 4)
>> +#define VIVID_CID_STRING                (VIVID_CID_CUSTOM_BASE + 5)
>> +#define VIVID_CID_BITMASK               (VIVID_CID_CUSTOM_BASE + 6)
>> +#define VIVID_CID_INTMENU               (VIVID_CID_CUSTOM_BASE + 7)
>> +#define VIVID_CID_U32_ARRAY             (VIVID_CID_CUSTOM_BASE + 8)
>> +#define VIVID_CID_U16_MATRIX            (VIVID_CID_CUSTOM_BASE + 9)
>> +#define VIVID_CID_U8_4D_ARRAY           (VIVID_CID_CUSTOM_BASE + 10)
>> +
>> +#define VIVID_CID_VIVID_BASE            (0x00f00000 | 0xf000)
>> +#define VIVID_CID_VIVID_CLASS           (0x00f00000 | 1)
>> +#define VIVID_CID_TEST_PATTERN          (VIVID_CID_VIVID_BASE + 0)
>> +#define VIVID_CID_OSD_TEXT_MODE         (VIVID_CID_VIVID_BASE + 1)
>> +#define VIVID_CID_HOR_MOVEMENT          (VIVID_CID_VIVID_BASE + 2)
>> +#define VIVID_CID_VERT_MOVEMENT         (VIVID_CID_VIVID_BASE + 3)
>> +#define VIVID_CID_SHOW_BORDER           (VIVID_CID_VIVID_BASE + 4)
>> +#define VIVID_CID_SHOW_SQUARE           (VIVID_CID_VIVID_BASE + 5)
>> +#define VIVID_CID_INSERT_SAV            (VIVID_CID_VIVID_BASE + 6)
>> +#define VIVID_CID_INSERT_EAV            (VIVID_CID_VIVID_BASE + 7)
>> +#define VIVID_CID_VBI_CAP_INTERLACED    (VIVID_CID_VIVID_BASE + 8)
>> +
>> +#define VIVID_CID_HFLIP                 (VIVID_CID_VIVID_BASE + 20)
>> +#define VIVID_CID_VFLIP                 (VIVID_CID_VIVID_BASE + 21)
>> +#define VIVID_CID_STD_ASPECT_RATIO      (VIVID_CID_VIVID_BASE + 22)
>> +#define VIVID_CID_DV_TIMINGS_ASPECT_RATIO       (VIVID_CID_VIVID_BASE + 23)
>> +#define VIVID_CID_TSTAMP_SRC            (VIVID_CID_VIVID_BASE + 24)
>> +#define VIVID_CID_COLORSPACE            (VIVID_CID_VIVID_BASE + 25)
>> +#define VIVID_CID_XFER_FUNC             (VIVID_CID_VIVID_BASE + 26)
>> +#define VIVID_CID_YCBCR_ENC             (VIVID_CID_VIVID_BASE + 27)
>> +#define VIVID_CID_QUANTIZATION          (VIVID_CID_VIVID_BASE + 28)
>> +#define VIVID_CID_LIMITED_RGB_RANGE     (VIVID_CID_VIVID_BASE + 29)
>> +#define VIVID_CID_ALPHA_MODE            (VIVID_CID_VIVID_BASE + 30)
>> +#define VIVID_CID_HAS_CROP_CAP          (VIVID_CID_VIVID_BASE + 31)
>> +#define VIVID_CID_HAS_COMPOSE_CAP       (VIVID_CID_VIVID_BASE + 32)
>> +#define VIVID_CID_HAS_SCALER_CAP        (VIVID_CID_VIVID_BASE + 33)
>> +#define VIVID_CID_HAS_CROP_OUT          (VIVID_CID_VIVID_BASE + 34)
>> +#define VIVID_CID_HAS_COMPOSE_OUT       (VIVID_CID_VIVID_BASE + 35)
>> +#define VIVID_CID_HAS_SCALER_OUT        (VIVID_CID_VIVID_BASE + 36)
>> +#define VIVID_CID_LOOP_VIDEO            (VIVID_CID_VIVID_BASE + 37)
>> +#define VIVID_CID_SEQ_WRAP              (VIVID_CID_VIVID_BASE + 38)
>> +#define VIVID_CID_TIME_WRAP             (VIVID_CID_VIVID_BASE + 39)
>> +#define VIVID_CID_MAX_EDID_BLOCKS       (VIVID_CID_VIVID_BASE + 40)
>> +#define VIVID_CID_PERCENTAGE_FILL       (VIVID_CID_VIVID_BASE + 41)
>> +#define VIVID_CID_REDUCED_FPS           (VIVID_CID_VIVID_BASE + 42)
>> +#define VIVID_CID_HSV_ENC               (VIVID_CID_VIVID_BASE + 43)
>> +#define VIVID_CID_DISPLAY_PRESENT       (VIVID_CID_VIVID_BASE + 44)
>> +
>> +#define VIVID_CID_STD_SIGNAL_MODE       (VIVID_CID_VIVID_BASE + 60)
>> +#define VIVID_CID_STANDARD              (VIVID_CID_VIVID_BASE + 61)
>> +#define VIVID_CID_DV_TIMINGS_SIGNAL_MODE        (VIVID_CID_VIVID_BASE + 62)
>> +#define VIVID_CID_DV_TIMINGS            (VIVID_CID_VIVID_BASE + 63)
>> +#define VIVID_CID_PERC_DROPPED          (VIVID_CID_VIVID_BASE + 64)
>> +#define VIVID_CID_DISCONNECT            (VIVID_CID_VIVID_BASE + 65)
>> +#define VIVID_CID_DQBUF_ERROR           (VIVID_CID_VIVID_BASE + 66)
>> +#define VIVID_CID_QUEUE_SETUP_ERROR     (VIVID_CID_VIVID_BASE + 67)
>> +#define VIVID_CID_BUF_PREPARE_ERROR     (VIVID_CID_VIVID_BASE + 68)
>> +#define VIVID_CID_START_STR_ERROR       (VIVID_CID_VIVID_BASE + 69)
>> +#define VIVID_CID_QUEUE_ERROR           (VIVID_CID_VIVID_BASE + 70)
>> +#define VIVID_CID_CLEAR_FB              (VIVID_CID_VIVID_BASE + 71)
>> +#define VIVID_CID_REQ_VALIDATE_ERROR    (VIVID_CID_VIVID_BASE + 72)
>> +
>> +#define VIVID_CID_RADIO_SEEK_MODE       (VIVID_CID_VIVID_BASE + 90)
>> +#define VIVID_CID_RADIO_SEEK_PROG_LIM   (VIVID_CID_VIVID_BASE + 91)
>> +#define VIVID_CID_RADIO_RX_RDS_RBDS     (VIVID_CID_VIVID_BASE + 92)
>> +#define VIVID_CID_RADIO_RX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 93)
>> +
>> +#define VIVID_CID_RADIO_TX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 94)
>> +
>> +#define VIVID_CID_SDR_CAP_FM_DEVIATION  (VIVID_CID_VIVID_BASE + 110)
>> +
>> +
>> +namespace libcamera {
>> +
>> +LOG_DEFINE_CATEGORY(VIVID)
>> +
>> +class VividCameraData : public CameraData
>> +{
>> +public:
>> +	VividCameraData(PipelineHandler *pipe, MediaDevice *media)
>> +		: CameraData(pipe), media_(media), video_(nullptr)
>> +	{
>> +	}
>> +
>> +	~VividCameraData()
>> +	{
>> +		delete video_;
>> +	}
>> +
>> +	int init();
>> +	void bufferReady(FrameBuffer *buffer);
>> +
>> +	MediaDevice *media_;
>> +	V4L2VideoDevice *video_;
>> +	Stream stream_;
>> +};
>> +
>> +class VividCameraConfiguration : public CameraConfiguration
>> +{
>> +public:
>> +	VividCameraConfiguration();
>> +
>> +	Status validate() override;
>> +};
>> +
>> +class PipelineHandlerVivid : public PipelineHandler
>> +{
>> +public:
>> +	PipelineHandlerVivid(CameraManager *manager);
>> +
>> +	CameraConfiguration *generateConfiguration(Camera *camera,
>> +						   const StreamRoles &roles) override;
>> +	int configure(Camera *camera, CameraConfiguration *config) override;
>> +
>> +	int exportFrameBuffers(Camera *camera, Stream *stream,
>> +			       std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;
>> +
>> +	int start(Camera *camera) override;
>> +	void stop(Camera *camera) override;
>> +
>> +	int queueRequestDevice(Camera *camera, Request *request) override;
>> +
>> +	bool match(DeviceEnumerator *enumerator) override;
>> +
>> +private:
>> +	int processControls(VividCameraData *data, Request *request);
>> +
>> +	VividCameraData *cameraData(const Camera *camera)
>> +	{
>> +		return static_cast<VividCameraData *>(
>> +			PipelineHandler::cameraData(camera));
>> +	}
>> +};
>> +
>> +VividCameraConfiguration::VividCameraConfiguration()
>> +	: CameraConfiguration()
>> +{
>> +}
>> +
>> +CameraConfiguration::Status VividCameraConfiguration::validate()
>> +{
>> +	Status status = Valid;
>> +
>> +	if (config_.empty())
>> +		return Invalid;
>> +
>> +	/* Cap the number of entries to the available streams. */
>> +	if (config_.size() > 1) {
>> +		config_.resize(1);
>> +		status = Adjusted;
>> +	}
>> +
>> +	StreamConfiguration &cfg = config_[0];
>> +
>> +	/* Adjust the pixel format. */
>> +	const std::vector<libcamera::PixelFormat> formats = cfg.formats().pixelformats();
>> +	if (std::find(formats.begin(), formats.end(), cfg.pixelFormat) == formats.end()) {
>> +		cfg.pixelFormat = cfg.formats().pixelformats()[0];
>> +		LOG(VIVID, Debug) << "Adjusting format to " << cfg.pixelFormat.toString();
>> +		status = Adjusted;
>> +	}
>> +
>> +	cfg.bufferCount = 4;
>> +
>> +	return status;
>> +}
>> +
>> +PipelineHandlerVivid::PipelineHandlerVivid(CameraManager *manager)
>> +	: PipelineHandler(manager)
>> +{
>> +}
>> +
>> +CameraConfiguration *PipelineHandlerVivid::generateConfiguration(Camera *camera,
>> +								 const StreamRoles &roles)
>> +{
>> +	CameraConfiguration *config = new VividCameraConfiguration();
>> +	VividCameraData *data = cameraData(camera);
>> +
>> +	if (roles.empty())
>> +		return config;
>> +
>> +	std::map<V4L2PixelFormat, std::vector<SizeRange>> v4l2Formats =
>> +		data->video_->formats();
>> +	std::map<PixelFormat, std::vector<SizeRange>> deviceFormats;
>> +	std::transform(v4l2Formats.begin(), v4l2Formats.end(),
>> +		       std::inserter(deviceFormats, deviceFormats.begin()),
>> +		       [&](const decltype(v4l2Formats)::value_type &format) {
>> +			       return decltype(deviceFormats)::value_type{
>> +				       format.first.toPixelFormat(),
>> +				       format.second
>> +			       };
>> +		       });
>> +
>> +	StreamFormats formats(deviceFormats);
>> +	StreamConfiguration cfg(formats);
>> +
>> +	cfg.pixelFormat = PixelFormat(DRM_FORMAT_BGR888);
>> +	cfg.size = { 1280, 720 };
>> +	cfg.bufferCount = 4;
>> +
>> +	config->addConfiguration(cfg);
>> +
>> +	config->validate();
>> +
>> +	return config;
>> +}
>> +
>> +int PipelineHandlerVivid::configure(Camera *camera, CameraConfiguration *config)
>> +{
>> +	VividCameraData *data = cameraData(camera);
>> +	StreamConfiguration &cfg = config->at(0);
>> +	int ret;
>> +
>> +	V4L2DeviceFormat format = {};
>> +	format.fourcc = data->video_->toV4L2PixelFormat(cfg.pixelFormat);
>> +	format.size = cfg.size;
>> +
>> +	ret = data->video_->setFormat(&format);
>> +	if (ret)
>> +		return ret;
>> +
>> +	if (format.size != cfg.size ||
>> +	    format.fourcc != data->video_->toV4L2PixelFormat(cfg.pixelFormat))
>> +		return -EINVAL;
>> +
>> +	cfg.setStream(&data->stream_);
>> +	cfg.stride = format.planes[0].bpl;
>> +
>> +	return 0;
>> +}
>> +
>> +int PipelineHandlerVivid::exportFrameBuffers(Camera *camera, Stream *stream,
>> +					     std::vector<std::unique_ptr<FrameBuffer>> *buffers)
>> +{
>> +	VividCameraData *data = cameraData(camera);
>> +	unsigned int count = stream->configuration().bufferCount;
>> +
>> +	return data->video_->exportBuffers(count, buffers);
>> +}
>> +
>> +int PipelineHandlerVivid::start(Camera *camera)
>> +{
>> +	VividCameraData *data = cameraData(camera);
>> +	unsigned int count = data->stream_.configuration().bufferCount;
>> +
>> +	int ret = data->video_->importBuffers(count);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	ret = data->video_->streamOn();
>> +	if (ret < 0) {
>> +		data->ipa_->stop();
>> +		data->video_->releaseBuffers();
>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +void PipelineHandlerVivid::stop(Camera *camera)
>> +{
>> +	VividCameraData *data = cameraData(camera);
>> +	data->video_->streamOff();
>> +	data->video_->releaseBuffers();
>> +}
>> +
>> +int PipelineHandlerVivid::processControls(VividCameraData *data, Request *request)
>> +{
>> +	ControlList controls(data->video_->controls());
>> +
>> +	for (auto it : request->controls()) {
>> +		unsigned int id = it.first;
>> +		unsigned int offset;
>> +		uint32_t cid;
>> +
>> +		if (id == controls::Brightness) {
>> +			cid = V4L2_CID_BRIGHTNESS;
>> +			offset = 128;
>> +		} else if (id == controls::Contrast) {
>> +			cid = V4L2_CID_CONTRAST;
>> +			offset = 0;
>> +		} else if (id == controls::Saturation) {
>> +			cid = V4L2_CID_SATURATION;
>> +			offset = 0;
>> +		} else {
>> +			continue;
>> +		}
>> +
>> +		int32_t value = lroundf(it.second.get<float>() * 128 + offset);
>> +		controls.set(cid, utils::clamp(value, 0, 255));
>> +	}
>> +
>> +	for (const auto &ctrl : controls)
>> +		LOG(VIVID, Debug)
>> +			<< "Setting control " << utils::hex(ctrl.first)
>> +			<< " to " << ctrl.second.toString();
>> +
>> +	int ret = data->video_->setControls(&controls);
>> +	if (ret) {
>> +		LOG(VIVID, Error) << "Failed to set controls: " << ret;
>> +		return ret < 0 ? ret : -EINVAL;
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +int PipelineHandlerVivid::queueRequestDevice(Camera *camera, Request *request)
>> +{
>> +	VividCameraData *data = cameraData(camera);
>> +	FrameBuffer *buffer = request->findBuffer(&data->stream_);
>> +	if (!buffer) {
>> +		LOG(VIVID, Error)
>> +			<< "Attempt to queue request with invalid stream";
>> +
>> +		return -ENOENT;
>> +	}
>> +
>> +	int ret = processControls(data, request);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	ret = data->video_->queueBuffer(buffer);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	return 0;
>> +}
>> +
>> +bool PipelineHandlerVivid::match(DeviceEnumerator *enumerator)
>> +{
>> +	DeviceMatch dm("vivid");
>> +	dm.add("vivid-000-vid-cap");
>> +
>> +	MediaDevice *media = acquireMediaDevice(enumerator, dm);
>> +	if (!media)
>> +		return false;
>> +
>> +	std::unique_ptr<VividCameraData> data = std::make_unique<VividCameraData>(this, media);
>> +
>> +	/* Locate and open the capture video node. */
>> +	if (data->init())
>> +		return false;
>> +
>> +	/* Create and register the camera. */
>> +	std::set<Stream *> streams{ &data->stream_ };
>> +	std::shared_ptr<Camera> camera = Camera::create(this, data->video_->deviceName(), streams);
>> +	registerCamera(std::move(camera), std::move(data));
>> +
>> +	return true;
>> +}
>> +
>> +int VividCameraData::init()
>> +{
>> +	video_ = new V4L2VideoDevice(media_->getEntityByName("vivid-000-vid-cap"));
>> +	if (video_->open())
>> +		return -ENODEV;
>> +
>> +	video_->bufferReady.connect(this, &VividCameraData::bufferReady);
>> +
>> +	/* Initialise the supported controls. */
>> +	const ControlInfoMap &controls = video_->controls();
>> +	ControlInfoMap::Map ctrls;
>> +
>> +	for (const auto &ctrl : controls) {
>> +		const ControlId *id;
>> +		ControlInfo info;
>> +
>> +		switch (ctrl.first->id()) {
>> +		case V4L2_CID_BRIGHTNESS:
>> +			id = &controls::Brightness;
>> +			info = ControlInfo{ { -1.0f }, { 1.0f }, { 0.0f } };
>> +			break;
>> +		case V4L2_CID_CONTRAST:
>> +			id = &controls::Contrast;
>> +			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
>> +			break;
>> +		case V4L2_CID_SATURATION:
>> +			id = &controls::Saturation;
>> +			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
>> +			break;
>> +		default:
>> +			continue;
>> +		}
>> +
>> +		ctrls.emplace(id, info);
>> +	}
>> +
>> +	controlInfo_ = std::move(ctrls);
>> +
>> +	return 0;
>> +}
>> +
>> +void VividCameraData::bufferReady(FrameBuffer *buffer)
>> +{
>> +	Request *request = buffer->request();
>> +
>> +	pipe_->completeBuffer(camera_, request, buffer);
>> +	pipe_->completeRequest(camera_, request);
>> +}
>> +
>> +REGISTER_PIPELINE_HANDLER(PipelineHandlerVivid);
>> +
>> +} /* namespace libcamera */
>>
>
Nicolas Dufresne June 19, 2020, 3:34 p.m. UTC | #3
Le vendredi 19 juin 2020 à 13:02 +0100, Kieran Bingham a écrit :
> The VIVID driver supports more pixel formats and properties than the VIMC
> driver, and can provide extended testing for libcamera.
> 
> The VIMC pipeline handler is duplicated and simplified to support the
> VIVID device.
> 
> Unfortuantely, the VIVID device can not be handled by either of the
> generic UVC or Simple pipeline handlers.

Can you extend on that ?

> 
> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> ---
>  meson_options.txt                        |   2 +-
>  src/libcamera/pipeline/vivid/meson.build |   5 +
>  src/libcamera/pipeline/vivid/vivid.cpp   | 441 +++++++++++++++++++++++
>  3 files changed, 447 insertions(+), 1 deletion(-)
>  create mode 100644 src/libcamera/pipeline/vivid/meson.build
>  create mode 100644 src/libcamera/pipeline/vivid/vivid.cpp
> 
> diff --git a/meson_options.txt b/meson_options.txt
> index badace151bb6..dc4684df49b2 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -16,7 +16,7 @@ option('gstreamer',
>  
>  option('pipelines',
>          type : 'array',
> -        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo',
> 'vimc'],
> +        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo',
> 'vimc', 'vivid'],
>          description : 'Select which pipeline handlers to include')
>  
>  option('test',
> diff --git a/src/libcamera/pipeline/vivid/meson.build
> b/src/libcamera/pipeline/vivid/meson.build
> new file mode 100644
> index 000000000000..086bb825387c
> --- /dev/null
> +++ b/src/libcamera/pipeline/vivid/meson.build
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: CC0-1.0
> +
> +libcamera_sources += files([
> +    'vivid.cpp',
> +])
> diff --git a/src/libcamera/pipeline/vivid/vivid.cpp
> b/src/libcamera/pipeline/vivid/vivid.cpp
> new file mode 100644
> index 000000000000..b811e33a0299
> --- /dev/null
> +++ b/src/libcamera/pipeline/vivid/vivid.cpp
> @@ -0,0 +1,441 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2018, Google Inc.
> + *
> + * vivid.cpp - Pipeline handler for the vivid capture device
> + */
> +
> +#include <algorithm>
> +#include <iomanip>
> +#include <map>
> +#include <math.h>
> +#include <tuple>
> +
> +#include <linux/media-bus-format.h>
> +#include <linux/version.h>
> +
> +#include <libcamera/camera.h>
> +#include <libcamera/control_ids.h>
> +#include <libcamera/controls.h>
> +#include <libcamera/ipa/ipa_interface.h>
> +#include <libcamera/ipa/ipa_module_info.h>
> +#include <libcamera/request.h>
> +#include <libcamera/stream.h>
> +
> +#include "libcamera/internal/camera_sensor.h"
> +#include "libcamera/internal/device_enumerator.h"
> +#include "libcamera/internal/ipa_manager.h"
> +#include "libcamera/internal/log.h"
> +#include "libcamera/internal/media_device.h"
> +#include "libcamera/internal/pipeline_handler.h"
> +#include "libcamera/internal/utils.h"
> +#include "libcamera/internal/v4l2_controls.h"
> +#include "libcamera/internal/v4l2_subdevice.h"
> +#include "libcamera/internal/v4l2_videodevice.h"
> +
> +#define VIVID_CID_CUSTOM_BASE           (V4L2_CID_USER_BASE | 0xf000)
> +#define VIVID_CID_BUTTON                (VIVID_CID_CUSTOM_BASE + 0)
> +#define VIVID_CID_BOOLEAN               (VIVID_CID_CUSTOM_BASE + 1)
> +#define VIVID_CID_INTEGER               (VIVID_CID_CUSTOM_BASE + 2)
> +#define VIVID_CID_INTEGER64             (VIVID_CID_CUSTOM_BASE + 3)
> +#define VIVID_CID_MENU                  (VIVID_CID_CUSTOM_BASE + 4)
> +#define VIVID_CID_STRING                (VIVID_CID_CUSTOM_BASE + 5)
> +#define VIVID_CID_BITMASK               (VIVID_CID_CUSTOM_BASE + 6)
> +#define VIVID_CID_INTMENU               (VIVID_CID_CUSTOM_BASE + 7)
> +#define VIVID_CID_U32_ARRAY             (VIVID_CID_CUSTOM_BASE + 8)
> +#define VIVID_CID_U16_MATRIX            (VIVID_CID_CUSTOM_BASE + 9)
> +#define VIVID_CID_U8_4D_ARRAY           (VIVID_CID_CUSTOM_BASE + 10)
> +
> +#define VIVID_CID_VIVID_BASE            (0x00f00000 | 0xf000)
> +#define VIVID_CID_VIVID_CLASS           (0x00f00000 | 1)
> +#define VIVID_CID_TEST_PATTERN          (VIVID_CID_VIVID_BASE + 0)
> +#define VIVID_CID_OSD_TEXT_MODE         (VIVID_CID_VIVID_BASE + 1)
> +#define VIVID_CID_HOR_MOVEMENT          (VIVID_CID_VIVID_BASE + 2)
> +#define VIVID_CID_VERT_MOVEMENT         (VIVID_CID_VIVID_BASE + 3)
> +#define VIVID_CID_SHOW_BORDER           (VIVID_CID_VIVID_BASE + 4)
> +#define VIVID_CID_SHOW_SQUARE           (VIVID_CID_VIVID_BASE + 5)
> +#define VIVID_CID_INSERT_SAV            (VIVID_CID_VIVID_BASE + 6)
> +#define VIVID_CID_INSERT_EAV            (VIVID_CID_VIVID_BASE + 7)
> +#define VIVID_CID_VBI_CAP_INTERLACED    (VIVID_CID_VIVID_BASE + 8)
> +
> +#define VIVID_CID_HFLIP                 (VIVID_CID_VIVID_BASE + 20)
> +#define VIVID_CID_VFLIP                 (VIVID_CID_VIVID_BASE + 21)
> +#define VIVID_CID_STD_ASPECT_RATIO      (VIVID_CID_VIVID_BASE + 22)
> +#define VIVID_CID_DV_TIMINGS_ASPECT_RATIO       (VIVID_CID_VIVID_BASE + 23)
> +#define VIVID_CID_TSTAMP_SRC            (VIVID_CID_VIVID_BASE + 24)
> +#define VIVID_CID_COLORSPACE            (VIVID_CID_VIVID_BASE + 25)
> +#define VIVID_CID_XFER_FUNC             (VIVID_CID_VIVID_BASE + 26)
> +#define VIVID_CID_YCBCR_ENC             (VIVID_CID_VIVID_BASE + 27)
> +#define VIVID_CID_QUANTIZATION          (VIVID_CID_VIVID_BASE + 28)
> +#define VIVID_CID_LIMITED_RGB_RANGE     (VIVID_CID_VIVID_BASE + 29)
> +#define VIVID_CID_ALPHA_MODE            (VIVID_CID_VIVID_BASE + 30)
> +#define VIVID_CID_HAS_CROP_CAP          (VIVID_CID_VIVID_BASE + 31)
> +#define VIVID_CID_HAS_COMPOSE_CAP       (VIVID_CID_VIVID_BASE + 32)
> +#define VIVID_CID_HAS_SCALER_CAP        (VIVID_CID_VIVID_BASE + 33)
> +#define VIVID_CID_HAS_CROP_OUT          (VIVID_CID_VIVID_BASE + 34)
> +#define VIVID_CID_HAS_COMPOSE_OUT       (VIVID_CID_VIVID_BASE + 35)
> +#define VIVID_CID_HAS_SCALER_OUT        (VIVID_CID_VIVID_BASE + 36)
> +#define VIVID_CID_LOOP_VIDEO            (VIVID_CID_VIVID_BASE + 37)
> +#define VIVID_CID_SEQ_WRAP              (VIVID_CID_VIVID_BASE + 38)
> +#define VIVID_CID_TIME_WRAP             (VIVID_CID_VIVID_BASE + 39)
> +#define VIVID_CID_MAX_EDID_BLOCKS       (VIVID_CID_VIVID_BASE + 40)
> +#define VIVID_CID_PERCENTAGE_FILL       (VIVID_CID_VIVID_BASE + 41)
> +#define VIVID_CID_REDUCED_FPS           (VIVID_CID_VIVID_BASE + 42)
> +#define VIVID_CID_HSV_ENC               (VIVID_CID_VIVID_BASE + 43)
> +#define VIVID_CID_DISPLAY_PRESENT       (VIVID_CID_VIVID_BASE + 44)
> +
> +#define VIVID_CID_STD_SIGNAL_MODE       (VIVID_CID_VIVID_BASE + 60)
> +#define VIVID_CID_STANDARD              (VIVID_CID_VIVID_BASE + 61)
> +#define VIVID_CID_DV_TIMINGS_SIGNAL_MODE        (VIVID_CID_VIVID_BASE + 62)
> +#define VIVID_CID_DV_TIMINGS            (VIVID_CID_VIVID_BASE + 63)
> +#define VIVID_CID_PERC_DROPPED          (VIVID_CID_VIVID_BASE + 64)
> +#define VIVID_CID_DISCONNECT            (VIVID_CID_VIVID_BASE + 65)
> +#define VIVID_CID_DQBUF_ERROR           (VIVID_CID_VIVID_BASE + 66)
> +#define VIVID_CID_QUEUE_SETUP_ERROR     (VIVID_CID_VIVID_BASE + 67)
> +#define VIVID_CID_BUF_PREPARE_ERROR     (VIVID_CID_VIVID_BASE + 68)
> +#define VIVID_CID_START_STR_ERROR       (VIVID_CID_VIVID_BASE + 69)
> +#define VIVID_CID_QUEUE_ERROR           (VIVID_CID_VIVID_BASE + 70)
> +#define VIVID_CID_CLEAR_FB              (VIVID_CID_VIVID_BASE + 71)
> +#define VIVID_CID_REQ_VALIDATE_ERROR    (VIVID_CID_VIVID_BASE + 72)
> +
> +#define VIVID_CID_RADIO_SEEK_MODE       (VIVID_CID_VIVID_BASE + 90)
> +#define VIVID_CID_RADIO_SEEK_PROG_LIM   (VIVID_CID_VIVID_BASE + 91)
> +#define VIVID_CID_RADIO_RX_RDS_RBDS     (VIVID_CID_VIVID_BASE + 92)
> +#define VIVID_CID_RADIO_RX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 93)
> +
> +#define VIVID_CID_RADIO_TX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 94)
> +
> +#define VIVID_CID_SDR_CAP_FM_DEVIATION  (VIVID_CID_VIVID_BASE + 110)
> +
> +
> +namespace libcamera {
> +
> +LOG_DEFINE_CATEGORY(VIVID)
> +
> +class VividCameraData : public CameraData
> +{
> +public:
> +	VividCameraData(PipelineHandler *pipe, MediaDevice *media)
> +		: CameraData(pipe), media_(media), video_(nullptr)
> +	{
> +	}
> +
> +	~VividCameraData()
> +	{
> +		delete video_;
> +	}
> +
> +	int init();
> +	void bufferReady(FrameBuffer *buffer);
> +
> +	MediaDevice *media_;
> +	V4L2VideoDevice *video_;
> +	Stream stream_;
> +};
> +
> +class VividCameraConfiguration : public CameraConfiguration
> +{
> +public:
> +	VividCameraConfiguration();
> +
> +	Status validate() override;
> +};
> +
> +class PipelineHandlerVivid : public PipelineHandler
> +{
> +public:
> +	PipelineHandlerVivid(CameraManager *manager);
> +
> +	CameraConfiguration *generateConfiguration(Camera *camera,
> +						   const StreamRoles &roles)
> override;
> +	int configure(Camera *camera, CameraConfiguration *config) override;
> +
> +	int exportFrameBuffers(Camera *camera, Stream *stream,
> +			       std::vector<std::unique_ptr<FrameBuffer>>
> *buffers) override;
> +
> +	int start(Camera *camera) override;
> +	void stop(Camera *camera) override;
> +
> +	int queueRequestDevice(Camera *camera, Request *request) override;
> +
> +	bool match(DeviceEnumerator *enumerator) override;
> +
> +private:
> +	int processControls(VividCameraData *data, Request *request);
> +
> +	VividCameraData *cameraData(const Camera *camera)
> +	{
> +		return static_cast<VividCameraData *>(
> +			PipelineHandler::cameraData(camera));
> +	}
> +};
> +
> +VividCameraConfiguration::VividCameraConfiguration()
> +	: CameraConfiguration()
> +{
> +}
> +
> +CameraConfiguration::Status VividCameraConfiguration::validate()
> +{
> +	Status status = Valid;
> +
> +	if (config_.empty())
> +		return Invalid;
> +
> +	/* Cap the number of entries to the available streams. */
> +	if (config_.size() > 1) {
> +		config_.resize(1);
> +		status = Adjusted;
> +	}
> +
> +	StreamConfiguration &cfg = config_[0];
> +
> +	/* Adjust the pixel format. */
> +	const std::vector<libcamera::PixelFormat> formats =
> cfg.formats().pixelformats();
> +	if (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==
> formats.end()) {
> +		cfg.pixelFormat = cfg.formats().pixelformats()[0];
> +		LOG(VIVID, Debug) << "Adjusting format to " <<
> cfg.pixelFormat.toString();
> +		status = Adjusted;
> +	}
> +
> +	cfg.bufferCount = 4;
> +
> +	return status;
> +}
> +
> +PipelineHandlerVivid::PipelineHandlerVivid(CameraManager *manager)
> +	: PipelineHandler(manager)
> +{
> +}
> +
> +CameraConfiguration *PipelineHandlerVivid::generateConfiguration(Camera
> *camera,
> +								 const
> StreamRoles &roles)
> +{
> +	CameraConfiguration *config = new VividCameraConfiguration();
> +	VividCameraData *data = cameraData(camera);
> +
> +	if (roles.empty())
> +		return config;
> +
> +	std::map<V4L2PixelFormat, std::vector<SizeRange>> v4l2Formats =
> +		data->video_->formats();
> +	std::map<PixelFormat, std::vector<SizeRange>> deviceFormats;
> +	std::transform(v4l2Formats.begin(), v4l2Formats.end(),
> +		       std::inserter(deviceFormats, deviceFormats.begin()),
> +		       [&](const decltype(v4l2Formats)::value_type &format) {
> +			       return decltype(deviceFormats)::value_type{
> +				       format.first.toPixelFormat(),
> +				       format.second
> +			       };
> +		       });
> +
> +	StreamFormats formats(deviceFormats);
> +	StreamConfiguration cfg(formats);
> +
> +	cfg.pixelFormat = PixelFormat(DRM_FORMAT_BGR888);
> +	cfg.size = { 1280, 720 };
> +	cfg.bufferCount = 4;
> +
> +	config->addConfiguration(cfg);
> +
> +	config->validate();
> +
> +	return config;
> +}
> +
> +int PipelineHandlerVivid::configure(Camera *camera, CameraConfiguration
> *config)
> +{
> +	VividCameraData *data = cameraData(camera);
> +	StreamConfiguration &cfg = config->at(0);
> +	int ret;
> +
> +	V4L2DeviceFormat format = {};
> +	format.fourcc = data->video_->toV4L2PixelFormat(cfg.pixelFormat);
> +	format.size = cfg.size;
> +
> +	ret = data->video_->setFormat(&format);
> +	if (ret)
> +		return ret;
> +
> +	if (format.size != cfg.size ||
> +	    format.fourcc != data->video_->toV4L2PixelFormat(cfg.pixelFormat))
> +		return -EINVAL;
> +
> +	cfg.setStream(&data->stream_);
> +	cfg.stride = format.planes[0].bpl;
> +
> +	return 0;
> +}
> +
> +int PipelineHandlerVivid::exportFrameBuffers(Camera *camera, Stream *stream,
> +					     std::vector<std::unique_ptr<FrameBu
> ffer>> *buffers)
> +{
> +	VividCameraData *data = cameraData(camera);
> +	unsigned int count = stream->configuration().bufferCount;
> +
> +	return data->video_->exportBuffers(count, buffers);
> +}
> +
> +int PipelineHandlerVivid::start(Camera *camera)
> +{
> +	VividCameraData *data = cameraData(camera);
> +	unsigned int count = data->stream_.configuration().bufferCount;
> +
> +	int ret = data->video_->importBuffers(count);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = data->video_->streamOn();
> +	if (ret < 0) {
> +		data->ipa_->stop();
> +		data->video_->releaseBuffers();
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +void PipelineHandlerVivid::stop(Camera *camera)
> +{
> +	VividCameraData *data = cameraData(camera);
> +	data->video_->streamOff();
> +	data->video_->releaseBuffers();
> +}
> +
> +int PipelineHandlerVivid::processControls(VividCameraData *data, Request
> *request)
> +{
> +	ControlList controls(data->video_->controls());
> +
> +	for (auto it : request->controls()) {
> +		unsigned int id = it.first;
> +		unsigned int offset;
> +		uint32_t cid;
> +
> +		if (id == controls::Brightness) {
> +			cid = V4L2_CID_BRIGHTNESS;
> +			offset = 128;
> +		} else if (id == controls::Contrast) {
> +			cid = V4L2_CID_CONTRAST;
> +			offset = 0;
> +		} else if (id == controls::Saturation) {
> +			cid = V4L2_CID_SATURATION;
> +			offset = 0;
> +		} else {
> +			continue;
> +		}
> +
> +		int32_t value = lroundf(it.second.get<float>() * 128 + offset);
> +		controls.set(cid, utils::clamp(value, 0, 255));
> +	}
> +
> +	for (const auto &ctrl : controls)
> +		LOG(VIVID, Debug)
> +			<< "Setting control " << utils::hex(ctrl.first)
> +			<< " to " << ctrl.second.toString();
> +
> +	int ret = data->video_->setControls(&controls);
> +	if (ret) {
> +		LOG(VIVID, Error) << "Failed to set controls: " << ret;
> +		return ret < 0 ? ret : -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +int PipelineHandlerVivid::queueRequestDevice(Camera *camera, Request
> *request)
> +{
> +	VividCameraData *data = cameraData(camera);
> +	FrameBuffer *buffer = request->findBuffer(&data->stream_);
> +	if (!buffer) {
> +		LOG(VIVID, Error)
> +			<< "Attempt to queue request with invalid stream";
> +
> +		return -ENOENT;
> +	}
> +
> +	int ret = processControls(data, request);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = data->video_->queueBuffer(buffer);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +bool PipelineHandlerVivid::match(DeviceEnumerator *enumerator)
> +{
> +	DeviceMatch dm("vivid");
> +	dm.add("vivid-000-vid-cap");
> +
> +	MediaDevice *media = acquireMediaDevice(enumerator, dm);
> +	if (!media)
> +		return false;
> +
> +	std::unique_ptr<VividCameraData> data =
> std::make_unique<VividCameraData>(this, media);
> +
> +	/* Locate and open the capture video node. */
> +	if (data->init())
> +		return false;
> +
> +	/* Create and register the camera. */
> +	std::set<Stream *> streams{ &data->stream_ };
> +	std::shared_ptr<Camera> camera = Camera::create(this, data->video_-
> >deviceName(), streams);
> +	registerCamera(std::move(camera), std::move(data));
> +
> +	return true;
> +}
> +
> +int VividCameraData::init()
> +{
> +	video_ = new V4L2VideoDevice(media_->getEntityByName("vivid-000-vid-
> cap"));
> +	if (video_->open())
> +		return -ENODEV;
> +
> +	video_->bufferReady.connect(this, &VividCameraData::bufferReady);
> +
> +	/* Initialise the supported controls. */
> +	const ControlInfoMap &controls = video_->controls();
> +	ControlInfoMap::Map ctrls;
> +
> +	for (const auto &ctrl : controls) {
> +		const ControlId *id;
> +		ControlInfo info;
> +
> +		switch (ctrl.first->id()) {
> +		case V4L2_CID_BRIGHTNESS:
> +			id = &controls::Brightness;
> +			info = ControlInfo{ { -1.0f }, { 1.0f }, { 0.0f } };
> +			break;
> +		case V4L2_CID_CONTRAST:
> +			id = &controls::Contrast;
> +			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
> +			break;
> +		case V4L2_CID_SATURATION:
> +			id = &controls::Saturation;
> +			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
> +			break;
> +		default:
> +			continue;
> +		}
> +
> +		ctrls.emplace(id, info);
> +	}
> +
> +	controlInfo_ = std::move(ctrls);
> +
> +	return 0;
> +}
> +
> +void VividCameraData::bufferReady(FrameBuffer *buffer)
> +{
> +	Request *request = buffer->request();
> +
> +	pipe_->completeBuffer(camera_, request, buffer);
> +	pipe_->completeRequest(camera_, request);
> +}
> +
> +REGISTER_PIPELINE_HANDLER(PipelineHandlerVivid);
> +
> +} /* namespace libcamera */
Kieran Bingham June 19, 2020, 4:19 p.m. UTC | #4
Hi Nicolas,

On 19/06/2020 16:34, Nicolas Dufresne wrote:
> Le vendredi 19 juin 2020 à 13:02 +0100, Kieran Bingham a écrit :
>> The VIVID driver supports more pixel formats and properties than the VIMC
>> driver, and can provide extended testing for libcamera.
>>
>> The VIMC pipeline handler is duplicated and simplified to support the
>> VIVID device.
>>
>> Unfortuantely, the VIVID device can not be handled by either of the
>> generic UVC or Simple pipeline handlers.
> 
> Can you extend on that ?

I actually disagreed with myself in my latest reply (@13:14 in my timezone).


> Perhaps an alternative route to supporting this however would be to make
> the simple pipeline handler support pipelines which *don't* have a
> sensor object... though the VIVID controls could be useful to support
> directly.
> 
> Perhaps - inheriting the simple-pipeline handler should be possible, to
> provide some 'device specific' adjustments for things like device
> specific controls.

The simple pipeline expects to walk a media graph and find a sensor.
VIVID doesn't expose a sensor, nor desire to. It's just a plain video
device node.

Perhaps we could extend the simple pipeline handler to support devices
which don't have a sensor ... or create a "simple-simple-pipeline
handler" ? :-D

Equally - there could then be quite a lot of commonality between the UVC
pipeline handler, though we kept that separate because of expected
differences with UVC devices ... (in particular the controls for UVC
have some specific variations already handled there).

So for testing with vivid, I took the easy option of creating a new
pipeline for VIVID. But there is so much code duplication already, I'm
sure we could find some reuse somewhere ...

--
Kieran


>> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
>> ---
>>  meson_options.txt                        |   2 +-
>>  src/libcamera/pipeline/vivid/meson.build |   5 +
>>  src/libcamera/pipeline/vivid/vivid.cpp   | 441 +++++++++++++++++++++++
>>  3 files changed, 447 insertions(+), 1 deletion(-)
>>  create mode 100644 src/libcamera/pipeline/vivid/meson.build
>>  create mode 100644 src/libcamera/pipeline/vivid/vivid.cpp
>>
>> diff --git a/meson_options.txt b/meson_options.txt
>> index badace151bb6..dc4684df49b2 100644
>> --- a/meson_options.txt
>> +++ b/meson_options.txt
>> @@ -16,7 +16,7 @@ option('gstreamer',
>>  
>>  option('pipelines',
>>          type : 'array',
>> -        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo',
>> 'vimc'],
>> +        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo',
>> 'vimc', 'vivid'],
>>          description : 'Select which pipeline handlers to include')
>>  
>>  option('test',
>> diff --git a/src/libcamera/pipeline/vivid/meson.build
>> b/src/libcamera/pipeline/vivid/meson.build
>> new file mode 100644
>> index 000000000000..086bb825387c
>> --- /dev/null
>> +++ b/src/libcamera/pipeline/vivid/meson.build
>> @@ -0,0 +1,5 @@
>> +# SPDX-License-Identifier: CC0-1.0
>> +
>> +libcamera_sources += files([
>> +    'vivid.cpp',
>> +])
>> diff --git a/src/libcamera/pipeline/vivid/vivid.cpp
>> b/src/libcamera/pipeline/vivid/vivid.cpp
>> new file mode 100644
>> index 000000000000..b811e33a0299
>> --- /dev/null
>> +++ b/src/libcamera/pipeline/vivid/vivid.cpp
>> @@ -0,0 +1,441 @@
>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
>> +/*
>> + * Copyright (C) 2018, Google Inc.
>> + *
>> + * vivid.cpp - Pipeline handler for the vivid capture device
>> + */
>> +
>> +#include <algorithm>
>> +#include <iomanip>
>> +#include <map>
>> +#include <math.h>
>> +#include <tuple>
>> +
>> +#include <linux/media-bus-format.h>
>> +#include <linux/version.h>
>> +
>> +#include <libcamera/camera.h>
>> +#include <libcamera/control_ids.h>
>> +#include <libcamera/controls.h>
>> +#include <libcamera/ipa/ipa_interface.h>
>> +#include <libcamera/ipa/ipa_module_info.h>
>> +#include <libcamera/request.h>
>> +#include <libcamera/stream.h>
>> +
>> +#include "libcamera/internal/camera_sensor.h"
>> +#include "libcamera/internal/device_enumerator.h"
>> +#include "libcamera/internal/ipa_manager.h"
>> +#include "libcamera/internal/log.h"
>> +#include "libcamera/internal/media_device.h"
>> +#include "libcamera/internal/pipeline_handler.h"
>> +#include "libcamera/internal/utils.h"
>> +#include "libcamera/internal/v4l2_controls.h"
>> +#include "libcamera/internal/v4l2_subdevice.h"
>> +#include "libcamera/internal/v4l2_videodevice.h"
>> +
>> +#define VIVID_CID_CUSTOM_BASE           (V4L2_CID_USER_BASE | 0xf000)
>> +#define VIVID_CID_BUTTON                (VIVID_CID_CUSTOM_BASE + 0)
>> +#define VIVID_CID_BOOLEAN               (VIVID_CID_CUSTOM_BASE + 1)
>> +#define VIVID_CID_INTEGER               (VIVID_CID_CUSTOM_BASE + 2)
>> +#define VIVID_CID_INTEGER64             (VIVID_CID_CUSTOM_BASE + 3)
>> +#define VIVID_CID_MENU                  (VIVID_CID_CUSTOM_BASE + 4)
>> +#define VIVID_CID_STRING                (VIVID_CID_CUSTOM_BASE + 5)
>> +#define VIVID_CID_BITMASK               (VIVID_CID_CUSTOM_BASE + 6)
>> +#define VIVID_CID_INTMENU               (VIVID_CID_CUSTOM_BASE + 7)
>> +#define VIVID_CID_U32_ARRAY             (VIVID_CID_CUSTOM_BASE + 8)
>> +#define VIVID_CID_U16_MATRIX            (VIVID_CID_CUSTOM_BASE + 9)
>> +#define VIVID_CID_U8_4D_ARRAY           (VIVID_CID_CUSTOM_BASE + 10)
>> +
>> +#define VIVID_CID_VIVID_BASE            (0x00f00000 | 0xf000)
>> +#define VIVID_CID_VIVID_CLASS           (0x00f00000 | 1)
>> +#define VIVID_CID_TEST_PATTERN          (VIVID_CID_VIVID_BASE + 0)
>> +#define VIVID_CID_OSD_TEXT_MODE         (VIVID_CID_VIVID_BASE + 1)
>> +#define VIVID_CID_HOR_MOVEMENT          (VIVID_CID_VIVID_BASE + 2)
>> +#define VIVID_CID_VERT_MOVEMENT         (VIVID_CID_VIVID_BASE + 3)
>> +#define VIVID_CID_SHOW_BORDER           (VIVID_CID_VIVID_BASE + 4)
>> +#define VIVID_CID_SHOW_SQUARE           (VIVID_CID_VIVID_BASE + 5)
>> +#define VIVID_CID_INSERT_SAV            (VIVID_CID_VIVID_BASE + 6)
>> +#define VIVID_CID_INSERT_EAV            (VIVID_CID_VIVID_BASE + 7)
>> +#define VIVID_CID_VBI_CAP_INTERLACED    (VIVID_CID_VIVID_BASE + 8)
>> +
>> +#define VIVID_CID_HFLIP                 (VIVID_CID_VIVID_BASE + 20)
>> +#define VIVID_CID_VFLIP                 (VIVID_CID_VIVID_BASE + 21)
>> +#define VIVID_CID_STD_ASPECT_RATIO      (VIVID_CID_VIVID_BASE + 22)
>> +#define VIVID_CID_DV_TIMINGS_ASPECT_RATIO       (VIVID_CID_VIVID_BASE + 23)
>> +#define VIVID_CID_TSTAMP_SRC            (VIVID_CID_VIVID_BASE + 24)
>> +#define VIVID_CID_COLORSPACE            (VIVID_CID_VIVID_BASE + 25)
>> +#define VIVID_CID_XFER_FUNC             (VIVID_CID_VIVID_BASE + 26)
>> +#define VIVID_CID_YCBCR_ENC             (VIVID_CID_VIVID_BASE + 27)
>> +#define VIVID_CID_QUANTIZATION          (VIVID_CID_VIVID_BASE + 28)
>> +#define VIVID_CID_LIMITED_RGB_RANGE     (VIVID_CID_VIVID_BASE + 29)
>> +#define VIVID_CID_ALPHA_MODE            (VIVID_CID_VIVID_BASE + 30)
>> +#define VIVID_CID_HAS_CROP_CAP          (VIVID_CID_VIVID_BASE + 31)
>> +#define VIVID_CID_HAS_COMPOSE_CAP       (VIVID_CID_VIVID_BASE + 32)
>> +#define VIVID_CID_HAS_SCALER_CAP        (VIVID_CID_VIVID_BASE + 33)
>> +#define VIVID_CID_HAS_CROP_OUT          (VIVID_CID_VIVID_BASE + 34)
>> +#define VIVID_CID_HAS_COMPOSE_OUT       (VIVID_CID_VIVID_BASE + 35)
>> +#define VIVID_CID_HAS_SCALER_OUT        (VIVID_CID_VIVID_BASE + 36)
>> +#define VIVID_CID_LOOP_VIDEO            (VIVID_CID_VIVID_BASE + 37)
>> +#define VIVID_CID_SEQ_WRAP              (VIVID_CID_VIVID_BASE + 38)
>> +#define VIVID_CID_TIME_WRAP             (VIVID_CID_VIVID_BASE + 39)
>> +#define VIVID_CID_MAX_EDID_BLOCKS       (VIVID_CID_VIVID_BASE + 40)
>> +#define VIVID_CID_PERCENTAGE_FILL       (VIVID_CID_VIVID_BASE + 41)
>> +#define VIVID_CID_REDUCED_FPS           (VIVID_CID_VIVID_BASE + 42)
>> +#define VIVID_CID_HSV_ENC               (VIVID_CID_VIVID_BASE + 43)
>> +#define VIVID_CID_DISPLAY_PRESENT       (VIVID_CID_VIVID_BASE + 44)
>> +
>> +#define VIVID_CID_STD_SIGNAL_MODE       (VIVID_CID_VIVID_BASE + 60)
>> +#define VIVID_CID_STANDARD              (VIVID_CID_VIVID_BASE + 61)
>> +#define VIVID_CID_DV_TIMINGS_SIGNAL_MODE        (VIVID_CID_VIVID_BASE + 62)
>> +#define VIVID_CID_DV_TIMINGS            (VIVID_CID_VIVID_BASE + 63)
>> +#define VIVID_CID_PERC_DROPPED          (VIVID_CID_VIVID_BASE + 64)
>> +#define VIVID_CID_DISCONNECT            (VIVID_CID_VIVID_BASE + 65)
>> +#define VIVID_CID_DQBUF_ERROR           (VIVID_CID_VIVID_BASE + 66)
>> +#define VIVID_CID_QUEUE_SETUP_ERROR     (VIVID_CID_VIVID_BASE + 67)
>> +#define VIVID_CID_BUF_PREPARE_ERROR     (VIVID_CID_VIVID_BASE + 68)
>> +#define VIVID_CID_START_STR_ERROR       (VIVID_CID_VIVID_BASE + 69)
>> +#define VIVID_CID_QUEUE_ERROR           (VIVID_CID_VIVID_BASE + 70)
>> +#define VIVID_CID_CLEAR_FB              (VIVID_CID_VIVID_BASE + 71)
>> +#define VIVID_CID_REQ_VALIDATE_ERROR    (VIVID_CID_VIVID_BASE + 72)
>> +
>> +#define VIVID_CID_RADIO_SEEK_MODE       (VIVID_CID_VIVID_BASE + 90)
>> +#define VIVID_CID_RADIO_SEEK_PROG_LIM   (VIVID_CID_VIVID_BASE + 91)
>> +#define VIVID_CID_RADIO_RX_RDS_RBDS     (VIVID_CID_VIVID_BASE + 92)
>> +#define VIVID_CID_RADIO_RX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 93)
>> +
>> +#define VIVID_CID_RADIO_TX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 94)
>> +
>> +#define VIVID_CID_SDR_CAP_FM_DEVIATION  (VIVID_CID_VIVID_BASE + 110)
>> +
>> +
>> +namespace libcamera {
>> +
>> +LOG_DEFINE_CATEGORY(VIVID)
>> +
>> +class VividCameraData : public CameraData
>> +{
>> +public:
>> +	VividCameraData(PipelineHandler *pipe, MediaDevice *media)
>> +		: CameraData(pipe), media_(media), video_(nullptr)
>> +	{
>> +	}
>> +
>> +	~VividCameraData()
>> +	{
>> +		delete video_;
>> +	}
>> +
>> +	int init();
>> +	void bufferReady(FrameBuffer *buffer);
>> +
>> +	MediaDevice *media_;
>> +	V4L2VideoDevice *video_;
>> +	Stream stream_;
>> +};
>> +
>> +class VividCameraConfiguration : public CameraConfiguration
>> +{
>> +public:
>> +	VividCameraConfiguration();
>> +
>> +	Status validate() override;
>> +};
>> +
>> +class PipelineHandlerVivid : public PipelineHandler
>> +{
>> +public:
>> +	PipelineHandlerVivid(CameraManager *manager);
>> +
>> +	CameraConfiguration *generateConfiguration(Camera *camera,
>> +						   const StreamRoles &roles)
>> override;
>> +	int configure(Camera *camera, CameraConfiguration *config) override;
>> +
>> +	int exportFrameBuffers(Camera *camera, Stream *stream,
>> +			       std::vector<std::unique_ptr<FrameBuffer>>
>> *buffers) override;
>> +
>> +	int start(Camera *camera) override;
>> +	void stop(Camera *camera) override;
>> +
>> +	int queueRequestDevice(Camera *camera, Request *request) override;
>> +
>> +	bool match(DeviceEnumerator *enumerator) override;
>> +
>> +private:
>> +	int processControls(VividCameraData *data, Request *request);
>> +
>> +	VividCameraData *cameraData(const Camera *camera)
>> +	{
>> +		return static_cast<VividCameraData *>(
>> +			PipelineHandler::cameraData(camera));
>> +	}
>> +};
>> +
>> +VividCameraConfiguration::VividCameraConfiguration()
>> +	: CameraConfiguration()
>> +{
>> +}
>> +
>> +CameraConfiguration::Status VividCameraConfiguration::validate()
>> +{
>> +	Status status = Valid;
>> +
>> +	if (config_.empty())
>> +		return Invalid;
>> +
>> +	/* Cap the number of entries to the available streams. */
>> +	if (config_.size() > 1) {
>> +		config_.resize(1);
>> +		status = Adjusted;
>> +	}
>> +
>> +	StreamConfiguration &cfg = config_[0];
>> +
>> +	/* Adjust the pixel format. */
>> +	const std::vector<libcamera::PixelFormat> formats =
>> cfg.formats().pixelformats();
>> +	if (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==
>> formats.end()) {
>> +		cfg.pixelFormat = cfg.formats().pixelformats()[0];
>> +		LOG(VIVID, Debug) << "Adjusting format to " <<
>> cfg.pixelFormat.toString();
>> +		status = Adjusted;
>> +	}
>> +
>> +	cfg.bufferCount = 4;
>> +
>> +	return status;
>> +}
>> +
>> +PipelineHandlerVivid::PipelineHandlerVivid(CameraManager *manager)
>> +	: PipelineHandler(manager)
>> +{
>> +}
>> +
>> +CameraConfiguration *PipelineHandlerVivid::generateConfiguration(Camera
>> *camera,
>> +								 const
>> StreamRoles &roles)
>> +{
>> +	CameraConfiguration *config = new VividCameraConfiguration();
>> +	VividCameraData *data = cameraData(camera);
>> +
>> +	if (roles.empty())
>> +		return config;
>> +
>> +	std::map<V4L2PixelFormat, std::vector<SizeRange>> v4l2Formats =
>> +		data->video_->formats();
>> +	std::map<PixelFormat, std::vector<SizeRange>> deviceFormats;
>> +	std::transform(v4l2Formats.begin(), v4l2Formats.end(),
>> +		       std::inserter(deviceFormats, deviceFormats.begin()),
>> +		       [&](const decltype(v4l2Formats)::value_type &format) {
>> +			       return decltype(deviceFormats)::value_type{
>> +				       format.first.toPixelFormat(),
>> +				       format.second
>> +			       };
>> +		       });
>> +
>> +	StreamFormats formats(deviceFormats);
>> +	StreamConfiguration cfg(formats);
>> +
>> +	cfg.pixelFormat = PixelFormat(DRM_FORMAT_BGR888);
>> +	cfg.size = { 1280, 720 };
>> +	cfg.bufferCount = 4;
>> +
>> +	config->addConfiguration(cfg);
>> +
>> +	config->validate();
>> +
>> +	return config;
>> +}
>> +
>> +int PipelineHandlerVivid::configure(Camera *camera, CameraConfiguration
>> *config)
>> +{
>> +	VividCameraData *data = cameraData(camera);
>> +	StreamConfiguration &cfg = config->at(0);
>> +	int ret;
>> +
>> +	V4L2DeviceFormat format = {};
>> +	format.fourcc = data->video_->toV4L2PixelFormat(cfg.pixelFormat);
>> +	format.size = cfg.size;
>> +
>> +	ret = data->video_->setFormat(&format);
>> +	if (ret)
>> +		return ret;
>> +
>> +	if (format.size != cfg.size ||
>> +	    format.fourcc != data->video_->toV4L2PixelFormat(cfg.pixelFormat))
>> +		return -EINVAL;
>> +
>> +	cfg.setStream(&data->stream_);
>> +	cfg.stride = format.planes[0].bpl;
>> +
>> +	return 0;
>> +}
>> +
>> +int PipelineHandlerVivid::exportFrameBuffers(Camera *camera, Stream *stream,
>> +					     std::vector<std::unique_ptr<FrameBu
>> ffer>> *buffers)
>> +{
>> +	VividCameraData *data = cameraData(camera);
>> +	unsigned int count = stream->configuration().bufferCount;
>> +
>> +	return data->video_->exportBuffers(count, buffers);
>> +}
>> +
>> +int PipelineHandlerVivid::start(Camera *camera)
>> +{
>> +	VividCameraData *data = cameraData(camera);
>> +	unsigned int count = data->stream_.configuration().bufferCount;
>> +
>> +	int ret = data->video_->importBuffers(count);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	ret = data->video_->streamOn();
>> +	if (ret < 0) {
>> +		data->ipa_->stop();
>> +		data->video_->releaseBuffers();
>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +void PipelineHandlerVivid::stop(Camera *camera)
>> +{
>> +	VividCameraData *data = cameraData(camera);
>> +	data->video_->streamOff();
>> +	data->video_->releaseBuffers();
>> +}
>> +
>> +int PipelineHandlerVivid::processControls(VividCameraData *data, Request
>> *request)
>> +{
>> +	ControlList controls(data->video_->controls());
>> +
>> +	for (auto it : request->controls()) {
>> +		unsigned int id = it.first;
>> +		unsigned int offset;
>> +		uint32_t cid;
>> +
>> +		if (id == controls::Brightness) {
>> +			cid = V4L2_CID_BRIGHTNESS;
>> +			offset = 128;
>> +		} else if (id == controls::Contrast) {
>> +			cid = V4L2_CID_CONTRAST;
>> +			offset = 0;
>> +		} else if (id == controls::Saturation) {
>> +			cid = V4L2_CID_SATURATION;
>> +			offset = 0;
>> +		} else {
>> +			continue;
>> +		}
>> +
>> +		int32_t value = lroundf(it.second.get<float>() * 128 + offset);
>> +		controls.set(cid, utils::clamp(value, 0, 255));
>> +	}
>> +
>> +	for (const auto &ctrl : controls)
>> +		LOG(VIVID, Debug)
>> +			<< "Setting control " << utils::hex(ctrl.first)
>> +			<< " to " << ctrl.second.toString();
>> +
>> +	int ret = data->video_->setControls(&controls);
>> +	if (ret) {
>> +		LOG(VIVID, Error) << "Failed to set controls: " << ret;
>> +		return ret < 0 ? ret : -EINVAL;
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +int PipelineHandlerVivid::queueRequestDevice(Camera *camera, Request
>> *request)
>> +{
>> +	VividCameraData *data = cameraData(camera);
>> +	FrameBuffer *buffer = request->findBuffer(&data->stream_);
>> +	if (!buffer) {
>> +		LOG(VIVID, Error)
>> +			<< "Attempt to queue request with invalid stream";
>> +
>> +		return -ENOENT;
>> +	}
>> +
>> +	int ret = processControls(data, request);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	ret = data->video_->queueBuffer(buffer);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	return 0;
>> +}
>> +
>> +bool PipelineHandlerVivid::match(DeviceEnumerator *enumerator)
>> +{
>> +	DeviceMatch dm("vivid");
>> +	dm.add("vivid-000-vid-cap");
>> +
>> +	MediaDevice *media = acquireMediaDevice(enumerator, dm);
>> +	if (!media)
>> +		return false;
>> +
>> +	std::unique_ptr<VividCameraData> data =
>> std::make_unique<VividCameraData>(this, media);
>> +
>> +	/* Locate and open the capture video node. */
>> +	if (data->init())
>> +		return false;
>> +
>> +	/* Create and register the camera. */
>> +	std::set<Stream *> streams{ &data->stream_ };
>> +	std::shared_ptr<Camera> camera = Camera::create(this, data->video_-
>>> deviceName(), streams);
>> +	registerCamera(std::move(camera), std::move(data));
>> +
>> +	return true;
>> +}
>> +
>> +int VividCameraData::init()
>> +{
>> +	video_ = new V4L2VideoDevice(media_->getEntityByName("vivid-000-vid-
>> cap"));
>> +	if (video_->open())
>> +		return -ENODEV;
>> +
>> +	video_->bufferReady.connect(this, &VividCameraData::bufferReady);
>> +
>> +	/* Initialise the supported controls. */
>> +	const ControlInfoMap &controls = video_->controls();
>> +	ControlInfoMap::Map ctrls;
>> +
>> +	for (const auto &ctrl : controls) {
>> +		const ControlId *id;
>> +		ControlInfo info;
>> +
>> +		switch (ctrl.first->id()) {
>> +		case V4L2_CID_BRIGHTNESS:
>> +			id = &controls::Brightness;
>> +			info = ControlInfo{ { -1.0f }, { 1.0f }, { 0.0f } };
>> +			break;
>> +		case V4L2_CID_CONTRAST:
>> +			id = &controls::Contrast;
>> +			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
>> +			break;
>> +		case V4L2_CID_SATURATION:
>> +			id = &controls::Saturation;
>> +			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
>> +			break;
>> +		default:
>> +			continue;
>> +		}
>> +
>> +		ctrls.emplace(id, info);
>> +	}
>> +
>> +	controlInfo_ = std::move(ctrls);
>> +
>> +	return 0;
>> +}
>> +
>> +void VividCameraData::bufferReady(FrameBuffer *buffer)
>> +{
>> +	Request *request = buffer->request();
>> +
>> +	pipe_->completeBuffer(camera_, request, buffer);
>> +	pipe_->completeRequest(camera_, request);
>> +}
>> +
>> +REGISTER_PIPELINE_HANDLER(PipelineHandlerVivid);
>> +
>> +} /* namespace libcamera */
>
Nicolas Dufresne June 19, 2020, 4:35 p.m. UTC | #5
Le vendredi 19 juin 2020 à 17:19 +0100, Kieran Bingham a écrit :
> Hi Nicolas,
> 
> On 19/06/2020 16:34, Nicolas Dufresne wrote:
> > Le vendredi 19 juin 2020 à 13:02 +0100, Kieran Bingham a écrit :
> > > The VIVID driver supports more pixel formats and properties than the VIMC
> > > driver, and can provide extended testing for libcamera.
> > > 
> > > The VIMC pipeline handler is duplicated and simplified to support the
> > > VIVID device.
> > > 
> > > Unfortuantely, the VIVID device can not be handled by either of the
> > > generic UVC or Simple pipeline handlers.
> > 
> > Can you extend on that ?
> 
> I actually disagreed with myself in my latest reply (@13:14 in my timezone).
> 
> 
> > Perhaps an alternative route to supporting this however would be to make
> > the simple pipeline handler support pipelines which *don't* have a
> > sensor object... though the VIVID controls could be useful to support
> > directly.
> > 
> > Perhaps - inheriting the simple-pipeline handler should be possible, to
> > provide some 'device specific' adjustments for things like device
> > specific controls.
> 
> The simple pipeline expects to walk a media graph and find a sensor.
> VIVID doesn't expose a sensor, nor desire to. It's just a plain video
> device node.

Indeed, the simulated input is meant to be HDMI, SDI, RCA etc. Making these
sensor would be mis-leading. But it has a "webcam" mode too, but I think all
this control clashes too much.

> 
> Perhaps we could extend the simple pipeline handler to support devices
> which don't have a sensor ... or create a "simple-simple-pipeline
> handler" ? :-D
> 
> Equally - there could then be quite a lot of commonality between the UVC
> pipeline handler, though we kept that separate because of expected
> differences with UVC devices ... (in particular the controls for UVC
> have some specific variations already handled there).
> 
> So for testing with vivid, I took the easy option of creating a new
> pipeline for VIVID. But there is so much code duplication already, I'm
> sure we could find some reuse somewhere ...

Thanks, seems fair, often my question are just to make sure we leave a trace, so
in few years for now, if we want to merge it we know exactly why it was split in
the first place.

> 
> --
> Kieran
> 
> 
> > > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > > ---
> > >  meson_options.txt                        |   2 +-
> > >  src/libcamera/pipeline/vivid/meson.build |   5 +
> > >  src/libcamera/pipeline/vivid/vivid.cpp   | 441 +++++++++++++++++++++++
> > >  3 files changed, 447 insertions(+), 1 deletion(-)
> > >  create mode 100644 src/libcamera/pipeline/vivid/meson.build
> > >  create mode 100644 src/libcamera/pipeline/vivid/vivid.cpp
> > > 
> > > diff --git a/meson_options.txt b/meson_options.txt
> > > index badace151bb6..dc4684df49b2 100644
> > > --- a/meson_options.txt
> > > +++ b/meson_options.txt
> > > @@ -16,7 +16,7 @@ option('gstreamer',
> > >  
> > >  option('pipelines',
> > >          type : 'array',
> > > -        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo',
> > > 'vimc'],
> > > +        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo',
> > > 'vimc', 'vivid'],
> > >          description : 'Select which pipeline handlers to include')
> > >  
> > >  option('test',
> > > diff --git a/src/libcamera/pipeline/vivid/meson.build
> > > b/src/libcamera/pipeline/vivid/meson.build
> > > new file mode 100644
> > > index 000000000000..086bb825387c
> > > --- /dev/null
> > > +++ b/src/libcamera/pipeline/vivid/meson.build
> > > @@ -0,0 +1,5 @@
> > > +# SPDX-License-Identifier: CC0-1.0
> > > +
> > > +libcamera_sources += files([
> > > +    'vivid.cpp',
> > > +])
> > > diff --git a/src/libcamera/pipeline/vivid/vivid.cpp
> > > b/src/libcamera/pipeline/vivid/vivid.cpp
> > > new file mode 100644
> > > index 000000000000..b811e33a0299
> > > --- /dev/null
> > > +++ b/src/libcamera/pipeline/vivid/vivid.cpp
> > > @@ -0,0 +1,441 @@
> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > > +/*
> > > + * Copyright (C) 2018, Google Inc.
> > > + *
> > > + * vivid.cpp - Pipeline handler for the vivid capture device
> > > + */
> > > +
> > > +#include <algorithm>
> > > +#include <iomanip>
> > > +#include <map>
> > > +#include <math.h>
> > > +#include <tuple>
> > > +
> > > +#include <linux/media-bus-format.h>
> > > +#include <linux/version.h>
> > > +
> > > +#include <libcamera/camera.h>
> > > +#include <libcamera/control_ids.h>
> > > +#include <libcamera/controls.h>
> > > +#include <libcamera/ipa/ipa_interface.h>
> > > +#include <libcamera/ipa/ipa_module_info.h>
> > > +#include <libcamera/request.h>
> > > +#include <libcamera/stream.h>
> > > +
> > > +#include "libcamera/internal/camera_sensor.h"
> > > +#include "libcamera/internal/device_enumerator.h"
> > > +#include "libcamera/internal/ipa_manager.h"
> > > +#include "libcamera/internal/log.h"
> > > +#include "libcamera/internal/media_device.h"
> > > +#include "libcamera/internal/pipeline_handler.h"
> > > +#include "libcamera/internal/utils.h"
> > > +#include "libcamera/internal/v4l2_controls.h"
> > > +#include "libcamera/internal/v4l2_subdevice.h"
> > > +#include "libcamera/internal/v4l2_videodevice.h"
> > > +
> > > +#define VIVID_CID_CUSTOM_BASE           (V4L2_CID_USER_BASE | 0xf000)
> > > +#define VIVID_CID_BUTTON                (VIVID_CID_CUSTOM_BASE + 0)
> > > +#define VIVID_CID_BOOLEAN               (VIVID_CID_CUSTOM_BASE + 1)
> > > +#define VIVID_CID_INTEGER               (VIVID_CID_CUSTOM_BASE + 2)
> > > +#define VIVID_CID_INTEGER64             (VIVID_CID_CUSTOM_BASE + 3)
> > > +#define VIVID_CID_MENU                  (VIVID_CID_CUSTOM_BASE + 4)
> > > +#define VIVID_CID_STRING                (VIVID_CID_CUSTOM_BASE + 5)
> > > +#define VIVID_CID_BITMASK               (VIVID_CID_CUSTOM_BASE + 6)
> > > +#define VIVID_CID_INTMENU               (VIVID_CID_CUSTOM_BASE + 7)
> > > +#define VIVID_CID_U32_ARRAY             (VIVID_CID_CUSTOM_BASE + 8)
> > > +#define VIVID_CID_U16_MATRIX            (VIVID_CID_CUSTOM_BASE + 9)
> > > +#define VIVID_CID_U8_4D_ARRAY           (VIVID_CID_CUSTOM_BASE + 10)
> > > +
> > > +#define VIVID_CID_VIVID_BASE            (0x00f00000 | 0xf000)
> > > +#define VIVID_CID_VIVID_CLASS           (0x00f00000 | 1)
> > > +#define VIVID_CID_TEST_PATTERN          (VIVID_CID_VIVID_BASE + 0)
> > > +#define VIVID_CID_OSD_TEXT_MODE         (VIVID_CID_VIVID_BASE + 1)
> > > +#define VIVID_CID_HOR_MOVEMENT          (VIVID_CID_VIVID_BASE + 2)
> > > +#define VIVID_CID_VERT_MOVEMENT         (VIVID_CID_VIVID_BASE + 3)
> > > +#define VIVID_CID_SHOW_BORDER           (VIVID_CID_VIVID_BASE + 4)
> > > +#define VIVID_CID_SHOW_SQUARE           (VIVID_CID_VIVID_BASE + 5)
> > > +#define VIVID_CID_INSERT_SAV            (VIVID_CID_VIVID_BASE + 6)
> > > +#define VIVID_CID_INSERT_EAV            (VIVID_CID_VIVID_BASE + 7)
> > > +#define VIVID_CID_VBI_CAP_INTERLACED    (VIVID_CID_VIVID_BASE + 8)
> > > +
> > > +#define VIVID_CID_HFLIP                 (VIVID_CID_VIVID_BASE + 20)
> > > +#define VIVID_CID_VFLIP                 (VIVID_CID_VIVID_BASE + 21)
> > > +#define VIVID_CID_STD_ASPECT_RATIO      (VIVID_CID_VIVID_BASE + 22)
> > > +#define VIVID_CID_DV_TIMINGS_ASPECT_RATIO       (VIVID_CID_VIVID_BASE +
> > > 23)
> > > +#define VIVID_CID_TSTAMP_SRC            (VIVID_CID_VIVID_BASE + 24)
> > > +#define VIVID_CID_COLORSPACE            (VIVID_CID_VIVID_BASE + 25)
> > > +#define VIVID_CID_XFER_FUNC             (VIVID_CID_VIVID_BASE + 26)
> > > +#define VIVID_CID_YCBCR_ENC             (VIVID_CID_VIVID_BASE + 27)
> > > +#define VIVID_CID_QUANTIZATION          (VIVID_CID_VIVID_BASE + 28)
> > > +#define VIVID_CID_LIMITED_RGB_RANGE     (VIVID_CID_VIVID_BASE + 29)
> > > +#define VIVID_CID_ALPHA_MODE            (VIVID_CID_VIVID_BASE + 30)
> > > +#define VIVID_CID_HAS_CROP_CAP          (VIVID_CID_VIVID_BASE + 31)
> > > +#define VIVID_CID_HAS_COMPOSE_CAP       (VIVID_CID_VIVID_BASE + 32)
> > > +#define VIVID_CID_HAS_SCALER_CAP        (VIVID_CID_VIVID_BASE + 33)
> > > +#define VIVID_CID_HAS_CROP_OUT          (VIVID_CID_VIVID_BASE + 34)
> > > +#define VIVID_CID_HAS_COMPOSE_OUT       (VIVID_CID_VIVID_BASE + 35)
> > > +#define VIVID_CID_HAS_SCALER_OUT        (VIVID_CID_VIVID_BASE + 36)
> > > +#define VIVID_CID_LOOP_VIDEO            (VIVID_CID_VIVID_BASE + 37)
> > > +#define VIVID_CID_SEQ_WRAP              (VIVID_CID_VIVID_BASE + 38)
> > > +#define VIVID_CID_TIME_WRAP             (VIVID_CID_VIVID_BASE + 39)
> > > +#define VIVID_CID_MAX_EDID_BLOCKS       (VIVID_CID_VIVID_BASE + 40)
> > > +#define VIVID_CID_PERCENTAGE_FILL       (VIVID_CID_VIVID_BASE + 41)
> > > +#define VIVID_CID_REDUCED_FPS           (VIVID_CID_VIVID_BASE + 42)
> > > +#define VIVID_CID_HSV_ENC               (VIVID_CID_VIVID_BASE + 43)
> > > +#define VIVID_CID_DISPLAY_PRESENT       (VIVID_CID_VIVID_BASE + 44)
> > > +
> > > +#define VIVID_CID_STD_SIGNAL_MODE       (VIVID_CID_VIVID_BASE + 60)
> > > +#define VIVID_CID_STANDARD              (VIVID_CID_VIVID_BASE + 61)
> > > +#define VIVID_CID_DV_TIMINGS_SIGNAL_MODE        (VIVID_CID_VIVID_BASE +
> > > 62)
> > > +#define VIVID_CID_DV_TIMINGS            (VIVID_CID_VIVID_BASE + 63)
> > > +#define VIVID_CID_PERC_DROPPED          (VIVID_CID_VIVID_BASE + 64)
> > > +#define VIVID_CID_DISCONNECT            (VIVID_CID_VIVID_BASE + 65)
> > > +#define VIVID_CID_DQBUF_ERROR           (VIVID_CID_VIVID_BASE + 66)
> > > +#define VIVID_CID_QUEUE_SETUP_ERROR     (VIVID_CID_VIVID_BASE + 67)
> > > +#define VIVID_CID_BUF_PREPARE_ERROR     (VIVID_CID_VIVID_BASE + 68)
> > > +#define VIVID_CID_START_STR_ERROR       (VIVID_CID_VIVID_BASE + 69)
> > > +#define VIVID_CID_QUEUE_ERROR           (VIVID_CID_VIVID_BASE + 70)
> > > +#define VIVID_CID_CLEAR_FB              (VIVID_CID_VIVID_BASE + 71)
> > > +#define VIVID_CID_REQ_VALIDATE_ERROR    (VIVID_CID_VIVID_BASE + 72)
> > > +
> > > +#define VIVID_CID_RADIO_SEEK_MODE       (VIVID_CID_VIVID_BASE + 90)
> > > +#define VIVID_CID_RADIO_SEEK_PROG_LIM   (VIVID_CID_VIVID_BASE + 91)
> > > +#define VIVID_CID_RADIO_RX_RDS_RBDS     (VIVID_CID_VIVID_BASE + 92)
> > > +#define VIVID_CID_RADIO_RX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 93)
> > > +
> > > +#define VIVID_CID_RADIO_TX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 94)
> > > +
> > > +#define VIVID_CID_SDR_CAP_FM_DEVIATION  (VIVID_CID_VIVID_BASE + 110)
> > > +
> > > +
> > > +namespace libcamera {
> > > +
> > > +LOG_DEFINE_CATEGORY(VIVID)
> > > +
> > > +class VividCameraData : public CameraData
> > > +{
> > > +public:
> > > +	VividCameraData(PipelineHandler *pipe, MediaDevice *media)
> > > +		: CameraData(pipe), media_(media), video_(nullptr)
> > > +	{
> > > +	}
> > > +
> > > +	~VividCameraData()
> > > +	{
> > > +		delete video_;
> > > +	}
> > > +
> > > +	int init();
> > > +	void bufferReady(FrameBuffer *buffer);
> > > +
> > > +	MediaDevice *media_;
> > > +	V4L2VideoDevice *video_;
> > > +	Stream stream_;
> > > +};
> > > +
> > > +class VividCameraConfiguration : public CameraConfiguration
> > > +{
> > > +public:
> > > +	VividCameraConfiguration();
> > > +
> > > +	Status validate() override;
> > > +};
> > > +
> > > +class PipelineHandlerVivid : public PipelineHandler
> > > +{
> > > +public:
> > > +	PipelineHandlerVivid(CameraManager *manager);
> > > +
> > > +	CameraConfiguration *generateConfiguration(Camera *camera,
> > > +						   const StreamRoles &roles)
> > > override;
> > > +	int configure(Camera *camera, CameraConfiguration *config) override;
> > > +
> > > +	int exportFrameBuffers(Camera *camera, Stream *stream,
> > > +			       std::vector<std::unique_ptr<FrameBuffer>>
> > > *buffers) override;
> > > +
> > > +	int start(Camera *camera) override;
> > > +	void stop(Camera *camera) override;
> > > +
> > > +	int queueRequestDevice(Camera *camera, Request *request) override;
> > > +
> > > +	bool match(DeviceEnumerator *enumerator) override;
> > > +
> > > +private:
> > > +	int processControls(VividCameraData *data, Request *request);
> > > +
> > > +	VividCameraData *cameraData(const Camera *camera)
> > > +	{
> > > +		return static_cast<VividCameraData *>(
> > > +			PipelineHandler::cameraData(camera));
> > > +	}
> > > +};
> > > +
> > > +VividCameraConfiguration::VividCameraConfiguration()
> > > +	: CameraConfiguration()
> > > +{
> > > +}
> > > +
> > > +CameraConfiguration::Status VividCameraConfiguration::validate()
> > > +{
> > > +	Status status = Valid;
> > > +
> > > +	if (config_.empty())
> > > +		return Invalid;
> > > +
> > > +	/* Cap the number of entries to the available streams. */
> > > +	if (config_.size() > 1) {
> > > +		config_.resize(1);
> > > +		status = Adjusted;
> > > +	}
> > > +
> > > +	StreamConfiguration &cfg = config_[0];
> > > +
> > > +	/* Adjust the pixel format. */
> > > +	const std::vector<libcamera::PixelFormat> formats =
> > > cfg.formats().pixelformats();
> > > +	if (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==
> > > formats.end()) {
> > > +		cfg.pixelFormat = cfg.formats().pixelformats()[0];
> > > +		LOG(VIVID, Debug) << "Adjusting format to " <<
> > > cfg.pixelFormat.toString();
> > > +		status = Adjusted;
> > > +	}
> > > +
> > > +	cfg.bufferCount = 4;
> > > +
> > > +	return status;
> > > +}
> > > +
> > > +PipelineHandlerVivid::PipelineHandlerVivid(CameraManager *manager)
> > > +	: PipelineHandler(manager)
> > > +{
> > > +}
> > > +
> > > +CameraConfiguration *PipelineHandlerVivid::generateConfiguration(Camera
> > > *camera,
> > > +								 const
> > > StreamRoles &roles)
> > > +{
> > > +	CameraConfiguration *config = new VividCameraConfiguration();
> > > +	VividCameraData *data = cameraData(camera);
> > > +
> > > +	if (roles.empty())
> > > +		return config;
> > > +
> > > +	std::map<V4L2PixelFormat, std::vector<SizeRange>> v4l2Formats =
> > > +		data->video_->formats();
> > > +	std::map<PixelFormat, std::vector<SizeRange>> deviceFormats;
> > > +	std::transform(v4l2Formats.begin(), v4l2Formats.end(),
> > > +		       std::inserter(deviceFormats, deviceFormats.begin()),
> > > +		       [&](const decltype(v4l2Formats)::value_type &format) {
> > > +			       return decltype(deviceFormats)::value_type{
> > > +				       format.first.toPixelFormat(),
> > > +				       format.second
> > > +			       };
> > > +		       });
> > > +
> > > +	StreamFormats formats(deviceFormats);
> > > +	StreamConfiguration cfg(formats);
> > > +
> > > +	cfg.pixelFormat = PixelFormat(DRM_FORMAT_BGR888);
> > > +	cfg.size = { 1280, 720 };
> > > +	cfg.bufferCount = 4;
> > > +
> > > +	config->addConfiguration(cfg);
> > > +
> > > +	config->validate();
> > > +
> > > +	return config;
> > > +}
> > > +
> > > +int PipelineHandlerVivid::configure(Camera *camera, CameraConfiguration
> > > *config)
> > > +{
> > > +	VividCameraData *data = cameraData(camera);
> > > +	StreamConfiguration &cfg = config->at(0);
> > > +	int ret;
> > > +
> > > +	V4L2DeviceFormat format = {};
> > > +	format.fourcc = data->video_->toV4L2PixelFormat(cfg.pixelFormat);
> > > +	format.size = cfg.size;
> > > +
> > > +	ret = data->video_->setFormat(&format);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	if (format.size != cfg.size ||
> > > +	    format.fourcc != data->video_->toV4L2PixelFormat(cfg.pixelFormat))
> > > +		return -EINVAL;
> > > +
> > > +	cfg.setStream(&data->stream_);
> > > +	cfg.stride = format.planes[0].bpl;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +int PipelineHandlerVivid::exportFrameBuffers(Camera *camera, Stream
> > > *stream,
> > > +					     std::vector<std::unique_ptr<FrameBu
> > > ffer>> *buffers)
> > > +{
> > > +	VividCameraData *data = cameraData(camera);
> > > +	unsigned int count = stream->configuration().bufferCount;
> > > +
> > > +	return data->video_->exportBuffers(count, buffers);
> > > +}
> > > +
> > > +int PipelineHandlerVivid::start(Camera *camera)
> > > +{
> > > +	VividCameraData *data = cameraData(camera);
> > > +	unsigned int count = data->stream_.configuration().bufferCount;
> > > +
> > > +	int ret = data->video_->importBuffers(count);
> > > +	if (ret < 0)
> > > +		return ret;
> > > +
> > > +	ret = data->video_->streamOn();
> > > +	if (ret < 0) {
> > > +		data->ipa_->stop();
> > > +		data->video_->releaseBuffers();
> > > +		return ret;
> > > +	}
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +void PipelineHandlerVivid::stop(Camera *camera)
> > > +{
> > > +	VividCameraData *data = cameraData(camera);
> > > +	data->video_->streamOff();
> > > +	data->video_->releaseBuffers();
> > > +}
> > > +
> > > +int PipelineHandlerVivid::processControls(VividCameraData *data, Request
> > > *request)
> > > +{
> > > +	ControlList controls(data->video_->controls());
> > > +
> > > +	for (auto it : request->controls()) {
> > > +		unsigned int id = it.first;
> > > +		unsigned int offset;
> > > +		uint32_t cid;
> > > +
> > > +		if (id == controls::Brightness) {
> > > +			cid = V4L2_CID_BRIGHTNESS;
> > > +			offset = 128;
> > > +		} else if (id == controls::Contrast) {
> > > +			cid = V4L2_CID_CONTRAST;
> > > +			offset = 0;
> > > +		} else if (id == controls::Saturation) {
> > > +			cid = V4L2_CID_SATURATION;
> > > +			offset = 0;
> > > +		} else {
> > > +			continue;
> > > +		}
> > > +
> > > +		int32_t value = lroundf(it.second.get<float>() * 128 + offset);
> > > +		controls.set(cid, utils::clamp(value, 0, 255));
> > > +	}
> > > +
> > > +	for (const auto &ctrl : controls)
> > > +		LOG(VIVID, Debug)
> > > +			<< "Setting control " << utils::hex(ctrl.first)
> > > +			<< " to " << ctrl.second.toString();
> > > +
> > > +	int ret = data->video_->setControls(&controls);
> > > +	if (ret) {
> > > +		LOG(VIVID, Error) << "Failed to set controls: " << ret;
> > > +		return ret < 0 ? ret : -EINVAL;
> > > +	}
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +int PipelineHandlerVivid::queueRequestDevice(Camera *camera, Request
> > > *request)
> > > +{
> > > +	VividCameraData *data = cameraData(camera);
> > > +	FrameBuffer *buffer = request->findBuffer(&data->stream_);
> > > +	if (!buffer) {
> > > +		LOG(VIVID, Error)
> > > +			<< "Attempt to queue request with invalid stream";
> > > +
> > > +		return -ENOENT;
> > > +	}
> > > +
> > > +	int ret = processControls(data, request);
> > > +	if (ret < 0)
> > > +		return ret;
> > > +
> > > +	ret = data->video_->queueBuffer(buffer);
> > > +	if (ret < 0)
> > > +		return ret;
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +bool PipelineHandlerVivid::match(DeviceEnumerator *enumerator)
> > > +{
> > > +	DeviceMatch dm("vivid");
> > > +	dm.add("vivid-000-vid-cap");
> > > +
> > > +	MediaDevice *media = acquireMediaDevice(enumerator, dm);
> > > +	if (!media)
> > > +		return false;
> > > +
> > > +	std::unique_ptr<VividCameraData> data =
> > > std::make_unique<VividCameraData>(this, media);
> > > +
> > > +	/* Locate and open the capture video node. */
> > > +	if (data->init())
> > > +		return false;
> > > +
> > > +	/* Create and register the camera. */
> > > +	std::set<Stream *> streams{ &data->stream_ };
> > > +	std::shared_ptr<Camera> camera = Camera::create(this, data->video_-
> > > > deviceName(), streams);
> > > +	registerCamera(std::move(camera), std::move(data));
> > > +
> > > +	return true;
> > > +}
> > > +
> > > +int VividCameraData::init()
> > > +{
> > > +	video_ = new V4L2VideoDevice(media_->getEntityByName("vivid-000-vid-
> > > cap"));
> > > +	if (video_->open())
> > > +		return -ENODEV;
> > > +
> > > +	video_->bufferReady.connect(this, &VividCameraData::bufferReady);
> > > +
> > > +	/* Initialise the supported controls. */
> > > +	const ControlInfoMap &controls = video_->controls();
> > > +	ControlInfoMap::Map ctrls;
> > > +
> > > +	for (const auto &ctrl : controls) {
> > > +		const ControlId *id;
> > > +		ControlInfo info;
> > > +
> > > +		switch (ctrl.first->id()) {
> > > +		case V4L2_CID_BRIGHTNESS:
> > > +			id = &controls::Brightness;
> > > +			info = ControlInfo{ { -1.0f }, { 1.0f }, { 0.0f } };
> > > +			break;
> > > +		case V4L2_CID_CONTRAST:
> > > +			id = &controls::Contrast;
> > > +			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
> > > +			break;
> > > +		case V4L2_CID_SATURATION:
> > > +			id = &controls::Saturation;
> > > +			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
> > > +			break;
> > > +		default:
> > > +			continue;
> > > +		}
> > > +
> > > +		ctrls.emplace(id, info);
> > > +	}
> > > +
> > > +	controlInfo_ = std::move(ctrls);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +void VividCameraData::bufferReady(FrameBuffer *buffer)
> > > +{
> > > +	Request *request = buffer->request();
> > > +
> > > +	pipe_->completeBuffer(camera_, request, buffer);
> > > +	pipe_->completeRequest(camera_, request);
> > > +}
> > > +
> > > +REGISTER_PIPELINE_HANDLER(PipelineHandlerVivid);
> > > +
> > > +} /* namespace libcamera */
Laurent Pinchart June 22, 2020, 1:45 a.m. UTC | #6
Hi Kieran,

On Fri, Jun 19, 2020 at 05:19:55PM +0100, Kieran Bingham wrote:
> On 19/06/2020 16:34, Nicolas Dufresne wrote:
> > Le vendredi 19 juin 2020 à 13:02 +0100, Kieran Bingham a écrit :
> >> The VIVID driver supports more pixel formats and properties than the VIMC
> >> driver, and can provide extended testing for libcamera.
> >>
> >> The VIMC pipeline handler is duplicated and simplified to support the
> >> VIVID device.
> >>
> >> Unfortuantely, the VIVID device can not be handled by either of the
> >> generic UVC or Simple pipeline handlers.
> > 
> > Can you extend on that ?
> 
> I actually disagreed with myself in my latest reply (@13:14 in my timezone).
> 
> > Perhaps an alternative route to supporting this however would be to make
> > the simple pipeline handler support pipelines which *don't* have a
> > sensor object... though the VIVID controls could be useful to support
> > directly.
> > 
> > Perhaps - inheriting the simple-pipeline handler should be possible, to
> > provide some 'device specific' adjustments for things like device
> > specific controls.
> 
> The simple pipeline expects to walk a media graph and find a sensor.
> VIVID doesn't expose a sensor, nor desire to. It's just a plain video
> device node.
> 
> Perhaps we could extend the simple pipeline handler to support devices
> which don't have a sensor ... or create a "simple-simple-pipeline
> handler" ? :-D
> 
> Equally - there could then be quite a lot of commonality between the UVC
> pipeline handler, though we kept that separate because of expected
> differences with UVC devices ... (in particular the controls for UVC
> have some specific variations already handled there).
> 
> So for testing with vivid, I took the easy option of creating a new
> pipeline for VIVID. But there is so much code duplication already, I'm
> sure we could find some reuse somewhere ...

Thank you for posting this patch. I think it's useful, as vimc currently
has a fairly limited set of features compared to vivid. In particular,
the lack of YUV format support in vimc (when configuring the pipeline
with a Bayer camera sensor as a source) limits our ability to test any
use case that requires YUV without real hardware.

Ideally, I would like vimc to gain support for these features, and avoid
adding another pipeline handler for vivid that would duplicate code. We
can discuss how to minimize code duplication, either with the simple
pipeline handler or the UVC pipeline handler, but the effort to add YUV
support in vimc seems fairly low to me, possibly even lower than
developing vivid support in libcamera with reduced code duplication. I'm
curious to know your opinion on long-term support for vivid in
libcamera, compared to enhancing the vimc driver.

We could merge this pipeline handler as-is and deal with code sharing
later (or possibly even drop it later when vimc support will be better),
but we also need to consider that every new pipeline handler increases
the maintenance effort, and makes changes to the pipeline handler API
more difficult. We need to consider the pros and cons.

Regarding code sharing, without having studied the question in details,
I feel that the UVC pipeline handler would be a better target, as vimc
is media controller-centric, while UVC and vivid are video node-centric.
I'm even considering supporting vimc with the simple pipeline handler
instead of a dedicated pipeline handler, but that would likely require
changes in both the simple pipeline handler and the vimc driver (most
notably the vimc scaler, which hardcodes a downscaling ratio of 3).

> >> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> >> ---
> >>  meson_options.txt                        |   2 +-
> >>  src/libcamera/pipeline/vivid/meson.build |   5 +
> >>  src/libcamera/pipeline/vivid/vivid.cpp   | 441 +++++++++++++++++++++++
> >>  3 files changed, 447 insertions(+), 1 deletion(-)
> >>  create mode 100644 src/libcamera/pipeline/vivid/meson.build
> >>  create mode 100644 src/libcamera/pipeline/vivid/vivid.cpp
> >>
> >> diff --git a/meson_options.txt b/meson_options.txt
> >> index badace151bb6..dc4684df49b2 100644
> >> --- a/meson_options.txt
> >> +++ b/meson_options.txt
> >> @@ -16,7 +16,7 @@ option('gstreamer',
> >>  
> >>  option('pipelines',
> >>          type : 'array',
> >> -        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo',
> >> 'vimc'],
> >> +        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo',
> >> 'vimc', 'vivid'],
> >>          description : 'Select which pipeline handlers to include')
> >>  
> >>  option('test',
> >> diff --git a/src/libcamera/pipeline/vivid/meson.build
> >> b/src/libcamera/pipeline/vivid/meson.build
> >> new file mode 100644
> >> index 000000000000..086bb825387c
> >> --- /dev/null
> >> +++ b/src/libcamera/pipeline/vivid/meson.build
> >> @@ -0,0 +1,5 @@
> >> +# SPDX-License-Identifier: CC0-1.0
> >> +
> >> +libcamera_sources += files([
> >> +    'vivid.cpp',
> >> +])
> >> diff --git a/src/libcamera/pipeline/vivid/vivid.cpp
> >> b/src/libcamera/pipeline/vivid/vivid.cpp
> >> new file mode 100644
> >> index 000000000000..b811e33a0299
> >> --- /dev/null
> >> +++ b/src/libcamera/pipeline/vivid/vivid.cpp
> >> @@ -0,0 +1,441 @@
> >> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> >> +/*
> >> + * Copyright (C) 2018, Google Inc.
> >> + *
> >> + * vivid.cpp - Pipeline handler for the vivid capture device
> >> + */
> >> +
> >> +#include <algorithm>
> >> +#include <iomanip>
> >> +#include <map>
> >> +#include <math.h>
> >> +#include <tuple>
> >> +
> >> +#include <linux/media-bus-format.h>
> >> +#include <linux/version.h>
> >> +
> >> +#include <libcamera/camera.h>
> >> +#include <libcamera/control_ids.h>
> >> +#include <libcamera/controls.h>
> >> +#include <libcamera/ipa/ipa_interface.h>
> >> +#include <libcamera/ipa/ipa_module_info.h>
> >> +#include <libcamera/request.h>
> >> +#include <libcamera/stream.h>
> >> +
> >> +#include "libcamera/internal/camera_sensor.h"
> >> +#include "libcamera/internal/device_enumerator.h"
> >> +#include "libcamera/internal/ipa_manager.h"
> >> +#include "libcamera/internal/log.h"
> >> +#include "libcamera/internal/media_device.h"
> >> +#include "libcamera/internal/pipeline_handler.h"
> >> +#include "libcamera/internal/utils.h"
> >> +#include "libcamera/internal/v4l2_controls.h"
> >> +#include "libcamera/internal/v4l2_subdevice.h"
> >> +#include "libcamera/internal/v4l2_videodevice.h"
> >> +
> >> +#define VIVID_CID_CUSTOM_BASE           (V4L2_CID_USER_BASE | 0xf000)
> >> +#define VIVID_CID_BUTTON                (VIVID_CID_CUSTOM_BASE + 0)
> >> +#define VIVID_CID_BOOLEAN               (VIVID_CID_CUSTOM_BASE + 1)
> >> +#define VIVID_CID_INTEGER               (VIVID_CID_CUSTOM_BASE + 2)
> >> +#define VIVID_CID_INTEGER64             (VIVID_CID_CUSTOM_BASE + 3)
> >> +#define VIVID_CID_MENU                  (VIVID_CID_CUSTOM_BASE + 4)
> >> +#define VIVID_CID_STRING                (VIVID_CID_CUSTOM_BASE + 5)
> >> +#define VIVID_CID_BITMASK               (VIVID_CID_CUSTOM_BASE + 6)
> >> +#define VIVID_CID_INTMENU               (VIVID_CID_CUSTOM_BASE + 7)
> >> +#define VIVID_CID_U32_ARRAY             (VIVID_CID_CUSTOM_BASE + 8)
> >> +#define VIVID_CID_U16_MATRIX            (VIVID_CID_CUSTOM_BASE + 9)
> >> +#define VIVID_CID_U8_4D_ARRAY           (VIVID_CID_CUSTOM_BASE + 10)
> >> +
> >> +#define VIVID_CID_VIVID_BASE            (0x00f00000 | 0xf000)
> >> +#define VIVID_CID_VIVID_CLASS           (0x00f00000 | 1)
> >> +#define VIVID_CID_TEST_PATTERN          (VIVID_CID_VIVID_BASE + 0)
> >> +#define VIVID_CID_OSD_TEXT_MODE         (VIVID_CID_VIVID_BASE + 1)
> >> +#define VIVID_CID_HOR_MOVEMENT          (VIVID_CID_VIVID_BASE + 2)
> >> +#define VIVID_CID_VERT_MOVEMENT         (VIVID_CID_VIVID_BASE + 3)
> >> +#define VIVID_CID_SHOW_BORDER           (VIVID_CID_VIVID_BASE + 4)
> >> +#define VIVID_CID_SHOW_SQUARE           (VIVID_CID_VIVID_BASE + 5)
> >> +#define VIVID_CID_INSERT_SAV            (VIVID_CID_VIVID_BASE + 6)
> >> +#define VIVID_CID_INSERT_EAV            (VIVID_CID_VIVID_BASE + 7)
> >> +#define VIVID_CID_VBI_CAP_INTERLACED    (VIVID_CID_VIVID_BASE + 8)
> >> +
> >> +#define VIVID_CID_HFLIP                 (VIVID_CID_VIVID_BASE + 20)
> >> +#define VIVID_CID_VFLIP                 (VIVID_CID_VIVID_BASE + 21)
> >> +#define VIVID_CID_STD_ASPECT_RATIO      (VIVID_CID_VIVID_BASE + 22)
> >> +#define VIVID_CID_DV_TIMINGS_ASPECT_RATIO       (VIVID_CID_VIVID_BASE + 23)
> >> +#define VIVID_CID_TSTAMP_SRC            (VIVID_CID_VIVID_BASE + 24)
> >> +#define VIVID_CID_COLORSPACE            (VIVID_CID_VIVID_BASE + 25)
> >> +#define VIVID_CID_XFER_FUNC             (VIVID_CID_VIVID_BASE + 26)
> >> +#define VIVID_CID_YCBCR_ENC             (VIVID_CID_VIVID_BASE + 27)
> >> +#define VIVID_CID_QUANTIZATION          (VIVID_CID_VIVID_BASE + 28)
> >> +#define VIVID_CID_LIMITED_RGB_RANGE     (VIVID_CID_VIVID_BASE + 29)
> >> +#define VIVID_CID_ALPHA_MODE            (VIVID_CID_VIVID_BASE + 30)
> >> +#define VIVID_CID_HAS_CROP_CAP          (VIVID_CID_VIVID_BASE + 31)
> >> +#define VIVID_CID_HAS_COMPOSE_CAP       (VIVID_CID_VIVID_BASE + 32)
> >> +#define VIVID_CID_HAS_SCALER_CAP        (VIVID_CID_VIVID_BASE + 33)
> >> +#define VIVID_CID_HAS_CROP_OUT          (VIVID_CID_VIVID_BASE + 34)
> >> +#define VIVID_CID_HAS_COMPOSE_OUT       (VIVID_CID_VIVID_BASE + 35)
> >> +#define VIVID_CID_HAS_SCALER_OUT        (VIVID_CID_VIVID_BASE + 36)
> >> +#define VIVID_CID_LOOP_VIDEO            (VIVID_CID_VIVID_BASE + 37)
> >> +#define VIVID_CID_SEQ_WRAP              (VIVID_CID_VIVID_BASE + 38)
> >> +#define VIVID_CID_TIME_WRAP             (VIVID_CID_VIVID_BASE + 39)
> >> +#define VIVID_CID_MAX_EDID_BLOCKS       (VIVID_CID_VIVID_BASE + 40)
> >> +#define VIVID_CID_PERCENTAGE_FILL       (VIVID_CID_VIVID_BASE + 41)
> >> +#define VIVID_CID_REDUCED_FPS           (VIVID_CID_VIVID_BASE + 42)
> >> +#define VIVID_CID_HSV_ENC               (VIVID_CID_VIVID_BASE + 43)
> >> +#define VIVID_CID_DISPLAY_PRESENT       (VIVID_CID_VIVID_BASE + 44)
> >> +
> >> +#define VIVID_CID_STD_SIGNAL_MODE       (VIVID_CID_VIVID_BASE + 60)
> >> +#define VIVID_CID_STANDARD              (VIVID_CID_VIVID_BASE + 61)
> >> +#define VIVID_CID_DV_TIMINGS_SIGNAL_MODE        (VIVID_CID_VIVID_BASE + 62)
> >> +#define VIVID_CID_DV_TIMINGS            (VIVID_CID_VIVID_BASE + 63)
> >> +#define VIVID_CID_PERC_DROPPED          (VIVID_CID_VIVID_BASE + 64)
> >> +#define VIVID_CID_DISCONNECT            (VIVID_CID_VIVID_BASE + 65)
> >> +#define VIVID_CID_DQBUF_ERROR           (VIVID_CID_VIVID_BASE + 66)
> >> +#define VIVID_CID_QUEUE_SETUP_ERROR     (VIVID_CID_VIVID_BASE + 67)
> >> +#define VIVID_CID_BUF_PREPARE_ERROR     (VIVID_CID_VIVID_BASE + 68)
> >> +#define VIVID_CID_START_STR_ERROR       (VIVID_CID_VIVID_BASE + 69)
> >> +#define VIVID_CID_QUEUE_ERROR           (VIVID_CID_VIVID_BASE + 70)
> >> +#define VIVID_CID_CLEAR_FB              (VIVID_CID_VIVID_BASE + 71)
> >> +#define VIVID_CID_REQ_VALIDATE_ERROR    (VIVID_CID_VIVID_BASE + 72)
> >> +
> >> +#define VIVID_CID_RADIO_SEEK_MODE       (VIVID_CID_VIVID_BASE + 90)
> >> +#define VIVID_CID_RADIO_SEEK_PROG_LIM   (VIVID_CID_VIVID_BASE + 91)
> >> +#define VIVID_CID_RADIO_RX_RDS_RBDS     (VIVID_CID_VIVID_BASE + 92)
> >> +#define VIVID_CID_RADIO_RX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 93)
> >> +
> >> +#define VIVID_CID_RADIO_TX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 94)
> >> +
> >> +#define VIVID_CID_SDR_CAP_FM_DEVIATION  (VIVID_CID_VIVID_BASE + 110)
> >> +
> >> +
> >> +namespace libcamera {
> >> +
> >> +LOG_DEFINE_CATEGORY(VIVID)
> >> +
> >> +class VividCameraData : public CameraData
> >> +{
> >> +public:
> >> +	VividCameraData(PipelineHandler *pipe, MediaDevice *media)
> >> +		: CameraData(pipe), media_(media), video_(nullptr)
> >> +	{
> >> +	}
> >> +
> >> +	~VividCameraData()
> >> +	{
> >> +		delete video_;
> >> +	}
> >> +
> >> +	int init();
> >> +	void bufferReady(FrameBuffer *buffer);
> >> +
> >> +	MediaDevice *media_;
> >> +	V4L2VideoDevice *video_;
> >> +	Stream stream_;
> >> +};
> >> +
> >> +class VividCameraConfiguration : public CameraConfiguration
> >> +{
> >> +public:
> >> +	VividCameraConfiguration();
> >> +
> >> +	Status validate() override;
> >> +};
> >> +
> >> +class PipelineHandlerVivid : public PipelineHandler
> >> +{
> >> +public:
> >> +	PipelineHandlerVivid(CameraManager *manager);
> >> +
> >> +	CameraConfiguration *generateConfiguration(Camera *camera,
> >> +						   const StreamRoles &roles)
> >> override;
> >> +	int configure(Camera *camera, CameraConfiguration *config) override;
> >> +
> >> +	int exportFrameBuffers(Camera *camera, Stream *stream,
> >> +			       std::vector<std::unique_ptr<FrameBuffer>>
> >> *buffers) override;
> >> +
> >> +	int start(Camera *camera) override;
> >> +	void stop(Camera *camera) override;
> >> +
> >> +	int queueRequestDevice(Camera *camera, Request *request) override;
> >> +
> >> +	bool match(DeviceEnumerator *enumerator) override;
> >> +
> >> +private:
> >> +	int processControls(VividCameraData *data, Request *request);
> >> +
> >> +	VividCameraData *cameraData(const Camera *camera)
> >> +	{
> >> +		return static_cast<VividCameraData *>(
> >> +			PipelineHandler::cameraData(camera));
> >> +	}
> >> +};
> >> +
> >> +VividCameraConfiguration::VividCameraConfiguration()
> >> +	: CameraConfiguration()
> >> +{
> >> +}
> >> +
> >> +CameraConfiguration::Status VividCameraConfiguration::validate()
> >> +{
> >> +	Status status = Valid;
> >> +
> >> +	if (config_.empty())
> >> +		return Invalid;
> >> +
> >> +	/* Cap the number of entries to the available streams. */
> >> +	if (config_.size() > 1) {
> >> +		config_.resize(1);
> >> +		status = Adjusted;
> >> +	}
> >> +
> >> +	StreamConfiguration &cfg = config_[0];
> >> +
> >> +	/* Adjust the pixel format. */
> >> +	const std::vector<libcamera::PixelFormat> formats =
> >> cfg.formats().pixelformats();
> >> +	if (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==
> >> formats.end()) {
> >> +		cfg.pixelFormat = cfg.formats().pixelformats()[0];
> >> +		LOG(VIVID, Debug) << "Adjusting format to " <<
> >> cfg.pixelFormat.toString();
> >> +		status = Adjusted;
> >> +	}
> >> +
> >> +	cfg.bufferCount = 4;
> >> +
> >> +	return status;
> >> +}
> >> +
> >> +PipelineHandlerVivid::PipelineHandlerVivid(CameraManager *manager)
> >> +	: PipelineHandler(manager)
> >> +{
> >> +}
> >> +
> >> +CameraConfiguration *PipelineHandlerVivid::generateConfiguration(Camera
> >> *camera,
> >> +								 const
> >> StreamRoles &roles)
> >> +{
> >> +	CameraConfiguration *config = new VividCameraConfiguration();
> >> +	VividCameraData *data = cameraData(camera);
> >> +
> >> +	if (roles.empty())
> >> +		return config;
> >> +
> >> +	std::map<V4L2PixelFormat, std::vector<SizeRange>> v4l2Formats =
> >> +		data->video_->formats();
> >> +	std::map<PixelFormat, std::vector<SizeRange>> deviceFormats;
> >> +	std::transform(v4l2Formats.begin(), v4l2Formats.end(),
> >> +		       std::inserter(deviceFormats, deviceFormats.begin()),
> >> +		       [&](const decltype(v4l2Formats)::value_type &format) {
> >> +			       return decltype(deviceFormats)::value_type{
> >> +				       format.first.toPixelFormat(),
> >> +				       format.second
> >> +			       };
> >> +		       });
> >> +
> >> +	StreamFormats formats(deviceFormats);
> >> +	StreamConfiguration cfg(formats);
> >> +
> >> +	cfg.pixelFormat = PixelFormat(DRM_FORMAT_BGR888);
> >> +	cfg.size = { 1280, 720 };
> >> +	cfg.bufferCount = 4;
> >> +
> >> +	config->addConfiguration(cfg);
> >> +
> >> +	config->validate();
> >> +
> >> +	return config;
> >> +}
> >> +
> >> +int PipelineHandlerVivid::configure(Camera *camera, CameraConfiguration
> >> *config)
> >> +{
> >> +	VividCameraData *data = cameraData(camera);
> >> +	StreamConfiguration &cfg = config->at(0);
> >> +	int ret;
> >> +
> >> +	V4L2DeviceFormat format = {};
> >> +	format.fourcc = data->video_->toV4L2PixelFormat(cfg.pixelFormat);
> >> +	format.size = cfg.size;
> >> +
> >> +	ret = data->video_->setFormat(&format);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	if (format.size != cfg.size ||
> >> +	    format.fourcc != data->video_->toV4L2PixelFormat(cfg.pixelFormat))
> >> +		return -EINVAL;
> >> +
> >> +	cfg.setStream(&data->stream_);
> >> +	cfg.stride = format.planes[0].bpl;
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +int PipelineHandlerVivid::exportFrameBuffers(Camera *camera, Stream *stream,
> >> +					     std::vector<std::unique_ptr<FrameBu
> >> ffer>> *buffers)
> >> +{
> >> +	VividCameraData *data = cameraData(camera);
> >> +	unsigned int count = stream->configuration().bufferCount;
> >> +
> >> +	return data->video_->exportBuffers(count, buffers);
> >> +}
> >> +
> >> +int PipelineHandlerVivid::start(Camera *camera)
> >> +{
> >> +	VividCameraData *data = cameraData(camera);
> >> +	unsigned int count = data->stream_.configuration().bufferCount;
> >> +
> >> +	int ret = data->video_->importBuffers(count);
> >> +	if (ret < 0)
> >> +		return ret;
> >> +
> >> +	ret = data->video_->streamOn();
> >> +	if (ret < 0) {
> >> +		data->ipa_->stop();
> >> +		data->video_->releaseBuffers();
> >> +		return ret;
> >> +	}
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +void PipelineHandlerVivid::stop(Camera *camera)
> >> +{
> >> +	VividCameraData *data = cameraData(camera);
> >> +	data->video_->streamOff();
> >> +	data->video_->releaseBuffers();
> >> +}
> >> +
> >> +int PipelineHandlerVivid::processControls(VividCameraData *data, Request
> >> *request)
> >> +{
> >> +	ControlList controls(data->video_->controls());
> >> +
> >> +	for (auto it : request->controls()) {
> >> +		unsigned int id = it.first;
> >> +		unsigned int offset;
> >> +		uint32_t cid;
> >> +
> >> +		if (id == controls::Brightness) {
> >> +			cid = V4L2_CID_BRIGHTNESS;
> >> +			offset = 128;
> >> +		} else if (id == controls::Contrast) {
> >> +			cid = V4L2_CID_CONTRAST;
> >> +			offset = 0;
> >> +		} else if (id == controls::Saturation) {
> >> +			cid = V4L2_CID_SATURATION;
> >> +			offset = 0;
> >> +		} else {
> >> +			continue;
> >> +		}
> >> +
> >> +		int32_t value = lroundf(it.second.get<float>() * 128 + offset);
> >> +		controls.set(cid, utils::clamp(value, 0, 255));
> >> +	}
> >> +
> >> +	for (const auto &ctrl : controls)
> >> +		LOG(VIVID, Debug)
> >> +			<< "Setting control " << utils::hex(ctrl.first)
> >> +			<< " to " << ctrl.second.toString();
> >> +
> >> +	int ret = data->video_->setControls(&controls);
> >> +	if (ret) {
> >> +		LOG(VIVID, Error) << "Failed to set controls: " << ret;
> >> +		return ret < 0 ? ret : -EINVAL;
> >> +	}
> >> +
> >> +	return ret;
> >> +}
> >> +
> >> +int PipelineHandlerVivid::queueRequestDevice(Camera *camera, Request
> >> *request)
> >> +{
> >> +	VividCameraData *data = cameraData(camera);
> >> +	FrameBuffer *buffer = request->findBuffer(&data->stream_);
> >> +	if (!buffer) {
> >> +		LOG(VIVID, Error)
> >> +			<< "Attempt to queue request with invalid stream";
> >> +
> >> +		return -ENOENT;
> >> +	}
> >> +
> >> +	int ret = processControls(data, request);
> >> +	if (ret < 0)
> >> +		return ret;
> >> +
> >> +	ret = data->video_->queueBuffer(buffer);
> >> +	if (ret < 0)
> >> +		return ret;
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +bool PipelineHandlerVivid::match(DeviceEnumerator *enumerator)
> >> +{
> >> +	DeviceMatch dm("vivid");
> >> +	dm.add("vivid-000-vid-cap");
> >> +
> >> +	MediaDevice *media = acquireMediaDevice(enumerator, dm);
> >> +	if (!media)
> >> +		return false;
> >> +
> >> +	std::unique_ptr<VividCameraData> data =
> >> std::make_unique<VividCameraData>(this, media);
> >> +
> >> +	/* Locate and open the capture video node. */
> >> +	if (data->init())
> >> +		return false;
> >> +
> >> +	/* Create and register the camera. */
> >> +	std::set<Stream *> streams{ &data->stream_ };
> >> +	std::shared_ptr<Camera> camera = Camera::create(this, data->video_-
> >>> deviceName(), streams);
> >> +	registerCamera(std::move(camera), std::move(data));
> >> +
> >> +	return true;
> >> +}
> >> +
> >> +int VividCameraData::init()
> >> +{
> >> +	video_ = new V4L2VideoDevice(media_->getEntityByName("vivid-000-vid-
> >> cap"));
> >> +	if (video_->open())
> >> +		return -ENODEV;
> >> +
> >> +	video_->bufferReady.connect(this, &VividCameraData::bufferReady);
> >> +
> >> +	/* Initialise the supported controls. */
> >> +	const ControlInfoMap &controls = video_->controls();
> >> +	ControlInfoMap::Map ctrls;
> >> +
> >> +	for (const auto &ctrl : controls) {
> >> +		const ControlId *id;
> >> +		ControlInfo info;
> >> +
> >> +		switch (ctrl.first->id()) {
> >> +		case V4L2_CID_BRIGHTNESS:
> >> +			id = &controls::Brightness;
> >> +			info = ControlInfo{ { -1.0f }, { 1.0f }, { 0.0f } };
> >> +			break;
> >> +		case V4L2_CID_CONTRAST:
> >> +			id = &controls::Contrast;
> >> +			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
> >> +			break;
> >> +		case V4L2_CID_SATURATION:
> >> +			id = &controls::Saturation;
> >> +			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
> >> +			break;
> >> +		default:
> >> +			continue;
> >> +		}
> >> +
> >> +		ctrls.emplace(id, info);
> >> +	}
> >> +
> >> +	controlInfo_ = std::move(ctrls);
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +void VividCameraData::bufferReady(FrameBuffer *buffer)
> >> +{
> >> +	Request *request = buffer->request();
> >> +
> >> +	pipe_->completeBuffer(camera_, request, buffer);
> >> +	pipe_->completeRequest(camera_, request);
> >> +}
> >> +
> >> +REGISTER_PIPELINE_HANDLER(PipelineHandlerVivid);
> >> +
> >> +} /* namespace libcamera */
Kieran Bingham June 22, 2020, 9:47 a.m. UTC | #7
Hi Laurent,

On 22/06/2020 02:45, Laurent Pinchart wrote:
> Hi Kieran,
> 
> On Fri, Jun 19, 2020 at 05:19:55PM +0100, Kieran Bingham wrote:
>> On 19/06/2020 16:34, Nicolas Dufresne wrote:
>>> Le vendredi 19 juin 2020 à 13:02 +0100, Kieran Bingham a écrit :
>>>> The VIVID driver supports more pixel formats and properties than the VIMC
>>>> driver, and can provide extended testing for libcamera.
>>>>
>>>> The VIMC pipeline handler is duplicated and simplified to support the
>>>> VIVID device.
>>>>
>>>> Unfortuantely, the VIVID device can not be handled by either of the
>>>> generic UVC or Simple pipeline handlers.
>>>
>>> Can you extend on that ?
>>
>> I actually disagreed with myself in my latest reply (@13:14 in my timezone).
>>
>>> Perhaps an alternative route to supporting this however would be to make
>>> the simple pipeline handler support pipelines which *don't* have a
>>> sensor object... though the VIVID controls could be useful to support
>>> directly.
>>>
>>> Perhaps - inheriting the simple-pipeline handler should be possible, to
>>> provide some 'device specific' adjustments for things like device
>>> specific controls.
>>
>> The simple pipeline expects to walk a media graph and find a sensor.
>> VIVID doesn't expose a sensor, nor desire to. It's just a plain video
>> device node.
>>
>> Perhaps we could extend the simple pipeline handler to support devices
>> which don't have a sensor ... or create a "simple-simple-pipeline
>> handler" ? :-D
>>
>> Equally - there could then be quite a lot of commonality between the UVC
>> pipeline handler, though we kept that separate because of expected
>> differences with UVC devices ... (in particular the controls for UVC
>> have some specific variations already handled there).
>>
>> So for testing with vivid, I took the easy option of creating a new
>> pipeline for VIVID. But there is so much code duplication already, I'm
>> sure we could find some reuse somewhere ...
> 
> Thank you for posting this patch. I think it's useful, as vimc currently
> has a fairly limited set of features compared to vivid. In particular,
> the lack of YUV format support in vimc (when configuring the pipeline
> with a Bayer camera sensor as a source) limits our ability to test any
> use case that requires YUV without real hardware.

The extra format support really was the driving factor here, and vivid
has much greater coverage for V4L2, so I suspect it might have
further/different benefits for testing the v4l2 compatibility layers ...

Which is of course why it got posted, because Paul had asked there, and
I had the vivid handler ready to hand.

> Ideally, I would like vimc to gain support for these features, and avoid
> adding another pipeline handler for vivid that would duplicate code. We
> can discuss how to minimize code duplication, either with the simple
> pipeline handler or the UVC pipeline handler, but the effort to add YUV
> support in vimc seems fairly low to me, possibly even lower than
> developing vivid support in libcamera with reduced code duplication. I'm

I don't yet know what's involved in adding extra format support to VIMC,
but weren't Collabora also looking at such a topic ?


> curious to know your opinion on long-term support for vivid in
> libcamera, compared to enhancing the vimc driver.
> 
> We could merge this pipeline handler as-is and deal with code sharing
> later (or possibly even drop it later when vimc support will be better),

I think that's an important consideration. - We can 'drop' it later too
if required test functionality becomes available elsewhere...


> but we also need to consider that every new pipeline handler increases
> the maintenance effort, and makes changes to the pipeline handler API
> more difficult. We need to consider the pros and cons.

Of course, that's why initially I hadn't posted it when /I/ needed it.
But upon the third send out - I figured that was enough motivation to
include the mailing-list.


> Regarding code sharing, without having studied the question in details,
> I feel that the UVC pipeline handler would be a better target, as vimc
> is media controller-centric, while UVC and vivid are video node-centric.

Yes, there are definitely similarities with UVC, but there are likely
differences in controls ... but I suspect for VIVID we don't /actually/
care about those differences anyway.


> I'm even considering supporting vimc with the simple pipeline handler
> instead of a dedicated pipeline handler, but that would likely require

That's interesting, but how would you handle having the 'test vimc' IPA?
I suspect VIMC would end up staying external unless perhaps the simple
pipeline handler would also support IPA matching?


> changes in both the simple pipeline handler and the vimc driver (most
> notably the vimc scaler, which hardcodes a downscaling ratio of 3).

Yes, that's a painful arbitrary limitation.


Overall, I don't particularly mind too much if this gets integrated or not.

I think it can provide some help for development of libcamera in virtual
systems which, while we have low hardware support might be more beneficial.

It might be an easy way to provide testing of things like 'multiple
cameras' on a single pipeline handler too - as you can easily have extra
capture devices with fairly arbitrary sizes for testing.

Without wide format coverage in VIMC it could help with other plumbing
aspects too - but equally even if we do integrate it - I could easily
see a point in the future where it becomes simpler to remove it.

--
Kieran


>>>> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
>>>> ---
>>>>  meson_options.txt                        |   2 +-
>>>>  src/libcamera/pipeline/vivid/meson.build |   5 +
>>>>  src/libcamera/pipeline/vivid/vivid.cpp   | 441 +++++++++++++++++++++++
>>>>  3 files changed, 447 insertions(+), 1 deletion(-)
>>>>  create mode 100644 src/libcamera/pipeline/vivid/meson.build
>>>>  create mode 100644 src/libcamera/pipeline/vivid/vivid.cpp
>>>>
>>>> diff --git a/meson_options.txt b/meson_options.txt
>>>> index badace151bb6..dc4684df49b2 100644
>>>> --- a/meson_options.txt
>>>> +++ b/meson_options.txt
>>>> @@ -16,7 +16,7 @@ option('gstreamer',
>>>>  
>>>>  option('pipelines',
>>>>          type : 'array',
>>>> -        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo',
>>>> 'vimc'],
>>>> +        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo',
>>>> 'vimc', 'vivid'],
>>>>          description : 'Select which pipeline handlers to include')
>>>>  
>>>>  option('test',
>>>> diff --git a/src/libcamera/pipeline/vivid/meson.build
>>>> b/src/libcamera/pipeline/vivid/meson.build
>>>> new file mode 100644
>>>> index 000000000000..086bb825387c
>>>> --- /dev/null
>>>> +++ b/src/libcamera/pipeline/vivid/meson.build
>>>> @@ -0,0 +1,5 @@
>>>> +# SPDX-License-Identifier: CC0-1.0
>>>> +
>>>> +libcamera_sources += files([
>>>> +    'vivid.cpp',
>>>> +])
>>>> diff --git a/src/libcamera/pipeline/vivid/vivid.cpp
>>>> b/src/libcamera/pipeline/vivid/vivid.cpp
>>>> new file mode 100644
>>>> index 000000000000..b811e33a0299
>>>> --- /dev/null
>>>> +++ b/src/libcamera/pipeline/vivid/vivid.cpp
>>>> @@ -0,0 +1,441 @@
>>>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
>>>> +/*
>>>> + * Copyright (C) 2018, Google Inc.
>>>> + *
>>>> + * vivid.cpp - Pipeline handler for the vivid capture device
>>>> + */
>>>> +
>>>> +#include <algorithm>
>>>> +#include <iomanip>
>>>> +#include <map>
>>>> +#include <math.h>
>>>> +#include <tuple>
>>>> +
>>>> +#include <linux/media-bus-format.h>
>>>> +#include <linux/version.h>
>>>> +
>>>> +#include <libcamera/camera.h>
>>>> +#include <libcamera/control_ids.h>
>>>> +#include <libcamera/controls.h>
>>>> +#include <libcamera/ipa/ipa_interface.h>
>>>> +#include <libcamera/ipa/ipa_module_info.h>
>>>> +#include <libcamera/request.h>
>>>> +#include <libcamera/stream.h>
>>>> +
>>>> +#include "libcamera/internal/camera_sensor.h"
>>>> +#include "libcamera/internal/device_enumerator.h"
>>>> +#include "libcamera/internal/ipa_manager.h"
>>>> +#include "libcamera/internal/log.h"
>>>> +#include "libcamera/internal/media_device.h"
>>>> +#include "libcamera/internal/pipeline_handler.h"
>>>> +#include "libcamera/internal/utils.h"
>>>> +#include "libcamera/internal/v4l2_controls.h"
>>>> +#include "libcamera/internal/v4l2_subdevice.h"
>>>> +#include "libcamera/internal/v4l2_videodevice.h"
>>>> +
>>>> +#define VIVID_CID_CUSTOM_BASE           (V4L2_CID_USER_BASE | 0xf000)
>>>> +#define VIVID_CID_BUTTON                (VIVID_CID_CUSTOM_BASE + 0)
>>>> +#define VIVID_CID_BOOLEAN               (VIVID_CID_CUSTOM_BASE + 1)
>>>> +#define VIVID_CID_INTEGER               (VIVID_CID_CUSTOM_BASE + 2)
>>>> +#define VIVID_CID_INTEGER64             (VIVID_CID_CUSTOM_BASE + 3)
>>>> +#define VIVID_CID_MENU                  (VIVID_CID_CUSTOM_BASE + 4)
>>>> +#define VIVID_CID_STRING                (VIVID_CID_CUSTOM_BASE + 5)
>>>> +#define VIVID_CID_BITMASK               (VIVID_CID_CUSTOM_BASE + 6)
>>>> +#define VIVID_CID_INTMENU               (VIVID_CID_CUSTOM_BASE + 7)
>>>> +#define VIVID_CID_U32_ARRAY             (VIVID_CID_CUSTOM_BASE + 8)
>>>> +#define VIVID_CID_U16_MATRIX            (VIVID_CID_CUSTOM_BASE + 9)
>>>> +#define VIVID_CID_U8_4D_ARRAY           (VIVID_CID_CUSTOM_BASE + 10)
>>>> +
>>>> +#define VIVID_CID_VIVID_BASE            (0x00f00000 | 0xf000)
>>>> +#define VIVID_CID_VIVID_CLASS           (0x00f00000 | 1)
>>>> +#define VIVID_CID_TEST_PATTERN          (VIVID_CID_VIVID_BASE + 0)
>>>> +#define VIVID_CID_OSD_TEXT_MODE         (VIVID_CID_VIVID_BASE + 1)
>>>> +#define VIVID_CID_HOR_MOVEMENT          (VIVID_CID_VIVID_BASE + 2)
>>>> +#define VIVID_CID_VERT_MOVEMENT         (VIVID_CID_VIVID_BASE + 3)
>>>> +#define VIVID_CID_SHOW_BORDER           (VIVID_CID_VIVID_BASE + 4)
>>>> +#define VIVID_CID_SHOW_SQUARE           (VIVID_CID_VIVID_BASE + 5)
>>>> +#define VIVID_CID_INSERT_SAV            (VIVID_CID_VIVID_BASE + 6)
>>>> +#define VIVID_CID_INSERT_EAV            (VIVID_CID_VIVID_BASE + 7)
>>>> +#define VIVID_CID_VBI_CAP_INTERLACED    (VIVID_CID_VIVID_BASE + 8)
>>>> +
>>>> +#define VIVID_CID_HFLIP                 (VIVID_CID_VIVID_BASE + 20)
>>>> +#define VIVID_CID_VFLIP                 (VIVID_CID_VIVID_BASE + 21)
>>>> +#define VIVID_CID_STD_ASPECT_RATIO      (VIVID_CID_VIVID_BASE + 22)
>>>> +#define VIVID_CID_DV_TIMINGS_ASPECT_RATIO       (VIVID_CID_VIVID_BASE + 23)
>>>> +#define VIVID_CID_TSTAMP_SRC            (VIVID_CID_VIVID_BASE + 24)
>>>> +#define VIVID_CID_COLORSPACE            (VIVID_CID_VIVID_BASE + 25)
>>>> +#define VIVID_CID_XFER_FUNC             (VIVID_CID_VIVID_BASE + 26)
>>>> +#define VIVID_CID_YCBCR_ENC             (VIVID_CID_VIVID_BASE + 27)
>>>> +#define VIVID_CID_QUANTIZATION          (VIVID_CID_VIVID_BASE + 28)
>>>> +#define VIVID_CID_LIMITED_RGB_RANGE     (VIVID_CID_VIVID_BASE + 29)
>>>> +#define VIVID_CID_ALPHA_MODE            (VIVID_CID_VIVID_BASE + 30)
>>>> +#define VIVID_CID_HAS_CROP_CAP          (VIVID_CID_VIVID_BASE + 31)
>>>> +#define VIVID_CID_HAS_COMPOSE_CAP       (VIVID_CID_VIVID_BASE + 32)
>>>> +#define VIVID_CID_HAS_SCALER_CAP        (VIVID_CID_VIVID_BASE + 33)
>>>> +#define VIVID_CID_HAS_CROP_OUT          (VIVID_CID_VIVID_BASE + 34)
>>>> +#define VIVID_CID_HAS_COMPOSE_OUT       (VIVID_CID_VIVID_BASE + 35)
>>>> +#define VIVID_CID_HAS_SCALER_OUT        (VIVID_CID_VIVID_BASE + 36)
>>>> +#define VIVID_CID_LOOP_VIDEO            (VIVID_CID_VIVID_BASE + 37)
>>>> +#define VIVID_CID_SEQ_WRAP              (VIVID_CID_VIVID_BASE + 38)
>>>> +#define VIVID_CID_TIME_WRAP             (VIVID_CID_VIVID_BASE + 39)
>>>> +#define VIVID_CID_MAX_EDID_BLOCKS       (VIVID_CID_VIVID_BASE + 40)
>>>> +#define VIVID_CID_PERCENTAGE_FILL       (VIVID_CID_VIVID_BASE + 41)
>>>> +#define VIVID_CID_REDUCED_FPS           (VIVID_CID_VIVID_BASE + 42)
>>>> +#define VIVID_CID_HSV_ENC               (VIVID_CID_VIVID_BASE + 43)
>>>> +#define VIVID_CID_DISPLAY_PRESENT       (VIVID_CID_VIVID_BASE + 44)
>>>> +
>>>> +#define VIVID_CID_STD_SIGNAL_MODE       (VIVID_CID_VIVID_BASE + 60)
>>>> +#define VIVID_CID_STANDARD              (VIVID_CID_VIVID_BASE + 61)
>>>> +#define VIVID_CID_DV_TIMINGS_SIGNAL_MODE        (VIVID_CID_VIVID_BASE + 62)
>>>> +#define VIVID_CID_DV_TIMINGS            (VIVID_CID_VIVID_BASE + 63)
>>>> +#define VIVID_CID_PERC_DROPPED          (VIVID_CID_VIVID_BASE + 64)
>>>> +#define VIVID_CID_DISCONNECT            (VIVID_CID_VIVID_BASE + 65)
>>>> +#define VIVID_CID_DQBUF_ERROR           (VIVID_CID_VIVID_BASE + 66)
>>>> +#define VIVID_CID_QUEUE_SETUP_ERROR     (VIVID_CID_VIVID_BASE + 67)
>>>> +#define VIVID_CID_BUF_PREPARE_ERROR     (VIVID_CID_VIVID_BASE + 68)
>>>> +#define VIVID_CID_START_STR_ERROR       (VIVID_CID_VIVID_BASE + 69)
>>>> +#define VIVID_CID_QUEUE_ERROR           (VIVID_CID_VIVID_BASE + 70)
>>>> +#define VIVID_CID_CLEAR_FB              (VIVID_CID_VIVID_BASE + 71)
>>>> +#define VIVID_CID_REQ_VALIDATE_ERROR    (VIVID_CID_VIVID_BASE + 72)
>>>> +
>>>> +#define VIVID_CID_RADIO_SEEK_MODE       (VIVID_CID_VIVID_BASE + 90)
>>>> +#define VIVID_CID_RADIO_SEEK_PROG_LIM   (VIVID_CID_VIVID_BASE + 91)
>>>> +#define VIVID_CID_RADIO_RX_RDS_RBDS     (VIVID_CID_VIVID_BASE + 92)
>>>> +#define VIVID_CID_RADIO_RX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 93)
>>>> +
>>>> +#define VIVID_CID_RADIO_TX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 94)
>>>> +
>>>> +#define VIVID_CID_SDR_CAP_FM_DEVIATION  (VIVID_CID_VIVID_BASE + 110)
>>>> +
>>>> +
>>>> +namespace libcamera {
>>>> +
>>>> +LOG_DEFINE_CATEGORY(VIVID)
>>>> +
>>>> +class VividCameraData : public CameraData
>>>> +{
>>>> +public:
>>>> +	VividCameraData(PipelineHandler *pipe, MediaDevice *media)
>>>> +		: CameraData(pipe), media_(media), video_(nullptr)
>>>> +	{
>>>> +	}
>>>> +
>>>> +	~VividCameraData()
>>>> +	{
>>>> +		delete video_;
>>>> +	}
>>>> +
>>>> +	int init();
>>>> +	void bufferReady(FrameBuffer *buffer);
>>>> +
>>>> +	MediaDevice *media_;
>>>> +	V4L2VideoDevice *video_;
>>>> +	Stream stream_;
>>>> +};
>>>> +
>>>> +class VividCameraConfiguration : public CameraConfiguration
>>>> +{
>>>> +public:
>>>> +	VividCameraConfiguration();
>>>> +
>>>> +	Status validate() override;
>>>> +};
>>>> +
>>>> +class PipelineHandlerVivid : public PipelineHandler
>>>> +{
>>>> +public:
>>>> +	PipelineHandlerVivid(CameraManager *manager);
>>>> +
>>>> +	CameraConfiguration *generateConfiguration(Camera *camera,
>>>> +						   const StreamRoles &roles)
>>>> override;
>>>> +	int configure(Camera *camera, CameraConfiguration *config) override;
>>>> +
>>>> +	int exportFrameBuffers(Camera *camera, Stream *stream,
>>>> +			       std::vector<std::unique_ptr<FrameBuffer>>
>>>> *buffers) override;
>>>> +
>>>> +	int start(Camera *camera) override;
>>>> +	void stop(Camera *camera) override;
>>>> +
>>>> +	int queueRequestDevice(Camera *camera, Request *request) override;
>>>> +
>>>> +	bool match(DeviceEnumerator *enumerator) override;
>>>> +
>>>> +private:
>>>> +	int processControls(VividCameraData *data, Request *request);
>>>> +
>>>> +	VividCameraData *cameraData(const Camera *camera)
>>>> +	{
>>>> +		return static_cast<VividCameraData *>(
>>>> +			PipelineHandler::cameraData(camera));
>>>> +	}
>>>> +};
>>>> +
>>>> +VividCameraConfiguration::VividCameraConfiguration()
>>>> +	: CameraConfiguration()
>>>> +{
>>>> +}
>>>> +
>>>> +CameraConfiguration::Status VividCameraConfiguration::validate()
>>>> +{
>>>> +	Status status = Valid;
>>>> +
>>>> +	if (config_.empty())
>>>> +		return Invalid;
>>>> +
>>>> +	/* Cap the number of entries to the available streams. */
>>>> +	if (config_.size() > 1) {
>>>> +		config_.resize(1);
>>>> +		status = Adjusted;
>>>> +	}
>>>> +
>>>> +	StreamConfiguration &cfg = config_[0];
>>>> +
>>>> +	/* Adjust the pixel format. */
>>>> +	const std::vector<libcamera::PixelFormat> formats =
>>>> cfg.formats().pixelformats();
>>>> +	if (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==
>>>> formats.end()) {
>>>> +		cfg.pixelFormat = cfg.formats().pixelformats()[0];
>>>> +		LOG(VIVID, Debug) << "Adjusting format to " <<
>>>> cfg.pixelFormat.toString();
>>>> +		status = Adjusted;
>>>> +	}
>>>> +
>>>> +	cfg.bufferCount = 4;
>>>> +
>>>> +	return status;
>>>> +}
>>>> +
>>>> +PipelineHandlerVivid::PipelineHandlerVivid(CameraManager *manager)
>>>> +	: PipelineHandler(manager)
>>>> +{
>>>> +}
>>>> +
>>>> +CameraConfiguration *PipelineHandlerVivid::generateConfiguration(Camera
>>>> *camera,
>>>> +								 const
>>>> StreamRoles &roles)
>>>> +{
>>>> +	CameraConfiguration *config = new VividCameraConfiguration();
>>>> +	VividCameraData *data = cameraData(camera);
>>>> +
>>>> +	if (roles.empty())
>>>> +		return config;
>>>> +
>>>> +	std::map<V4L2PixelFormat, std::vector<SizeRange>> v4l2Formats =
>>>> +		data->video_->formats();
>>>> +	std::map<PixelFormat, std::vector<SizeRange>> deviceFormats;
>>>> +	std::transform(v4l2Formats.begin(), v4l2Formats.end(),
>>>> +		       std::inserter(deviceFormats, deviceFormats.begin()),
>>>> +		       [&](const decltype(v4l2Formats)::value_type &format) {
>>>> +			       return decltype(deviceFormats)::value_type{
>>>> +				       format.first.toPixelFormat(),
>>>> +				       format.second
>>>> +			       };
>>>> +		       });
>>>> +
>>>> +	StreamFormats formats(deviceFormats);
>>>> +	StreamConfiguration cfg(formats);
>>>> +
>>>> +	cfg.pixelFormat = PixelFormat(DRM_FORMAT_BGR888);
>>>> +	cfg.size = { 1280, 720 };
>>>> +	cfg.bufferCount = 4;
>>>> +
>>>> +	config->addConfiguration(cfg);
>>>> +
>>>> +	config->validate();
>>>> +
>>>> +	return config;
>>>> +}
>>>> +
>>>> +int PipelineHandlerVivid::configure(Camera *camera, CameraConfiguration
>>>> *config)
>>>> +{
>>>> +	VividCameraData *data = cameraData(camera);
>>>> +	StreamConfiguration &cfg = config->at(0);
>>>> +	int ret;
>>>> +
>>>> +	V4L2DeviceFormat format = {};
>>>> +	format.fourcc = data->video_->toV4L2PixelFormat(cfg.pixelFormat);
>>>> +	format.size = cfg.size;
>>>> +
>>>> +	ret = data->video_->setFormat(&format);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	if (format.size != cfg.size ||
>>>> +	    format.fourcc != data->video_->toV4L2PixelFormat(cfg.pixelFormat))
>>>> +		return -EINVAL;
>>>> +
>>>> +	cfg.setStream(&data->stream_);
>>>> +	cfg.stride = format.planes[0].bpl;
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +int PipelineHandlerVivid::exportFrameBuffers(Camera *camera, Stream *stream,
>>>> +					     std::vector<std::unique_ptr<FrameBu
>>>> ffer>> *buffers)
>>>> +{
>>>> +	VividCameraData *data = cameraData(camera);
>>>> +	unsigned int count = stream->configuration().bufferCount;
>>>> +
>>>> +	return data->video_->exportBuffers(count, buffers);
>>>> +}
>>>> +
>>>> +int PipelineHandlerVivid::start(Camera *camera)
>>>> +{
>>>> +	VividCameraData *data = cameraData(camera);
>>>> +	unsigned int count = data->stream_.configuration().bufferCount;
>>>> +
>>>> +	int ret = data->video_->importBuffers(count);
>>>> +	if (ret < 0)
>>>> +		return ret;
>>>> +
>>>> +	ret = data->video_->streamOn();
>>>> +	if (ret < 0) {
>>>> +		data->ipa_->stop();
>>>> +		data->video_->releaseBuffers();
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +void PipelineHandlerVivid::stop(Camera *camera)
>>>> +{
>>>> +	VividCameraData *data = cameraData(camera);
>>>> +	data->video_->streamOff();
>>>> +	data->video_->releaseBuffers();
>>>> +}
>>>> +
>>>> +int PipelineHandlerVivid::processControls(VividCameraData *data, Request
>>>> *request)
>>>> +{
>>>> +	ControlList controls(data->video_->controls());
>>>> +
>>>> +	for (auto it : request->controls()) {
>>>> +		unsigned int id = it.first;
>>>> +		unsigned int offset;
>>>> +		uint32_t cid;
>>>> +
>>>> +		if (id == controls::Brightness) {
>>>> +			cid = V4L2_CID_BRIGHTNESS;
>>>> +			offset = 128;
>>>> +		} else if (id == controls::Contrast) {
>>>> +			cid = V4L2_CID_CONTRAST;
>>>> +			offset = 0;
>>>> +		} else if (id == controls::Saturation) {
>>>> +			cid = V4L2_CID_SATURATION;
>>>> +			offset = 0;
>>>> +		} else {
>>>> +			continue;
>>>> +		}
>>>> +
>>>> +		int32_t value = lroundf(it.second.get<float>() * 128 + offset);
>>>> +		controls.set(cid, utils::clamp(value, 0, 255));
>>>> +	}
>>>> +
>>>> +	for (const auto &ctrl : controls)
>>>> +		LOG(VIVID, Debug)
>>>> +			<< "Setting control " << utils::hex(ctrl.first)
>>>> +			<< " to " << ctrl.second.toString();
>>>> +
>>>> +	int ret = data->video_->setControls(&controls);
>>>> +	if (ret) {
>>>> +		LOG(VIVID, Error) << "Failed to set controls: " << ret;
>>>> +		return ret < 0 ? ret : -EINVAL;
>>>> +	}
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +int PipelineHandlerVivid::queueRequestDevice(Camera *camera, Request
>>>> *request)
>>>> +{
>>>> +	VividCameraData *data = cameraData(camera);
>>>> +	FrameBuffer *buffer = request->findBuffer(&data->stream_);
>>>> +	if (!buffer) {
>>>> +		LOG(VIVID, Error)
>>>> +			<< "Attempt to queue request with invalid stream";
>>>> +
>>>> +		return -ENOENT;
>>>> +	}
>>>> +
>>>> +	int ret = processControls(data, request);
>>>> +	if (ret < 0)
>>>> +		return ret;
>>>> +
>>>> +	ret = data->video_->queueBuffer(buffer);
>>>> +	if (ret < 0)
>>>> +		return ret;
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +bool PipelineHandlerVivid::match(DeviceEnumerator *enumerator)
>>>> +{
>>>> +	DeviceMatch dm("vivid");
>>>> +	dm.add("vivid-000-vid-cap");
>>>> +
>>>> +	MediaDevice *media = acquireMediaDevice(enumerator, dm);
>>>> +	if (!media)
>>>> +		return false;
>>>> +
>>>> +	std::unique_ptr<VividCameraData> data =
>>>> std::make_unique<VividCameraData>(this, media);
>>>> +
>>>> +	/* Locate and open the capture video node. */
>>>> +	if (data->init())
>>>> +		return false;
>>>> +
>>>> +	/* Create and register the camera. */
>>>> +	std::set<Stream *> streams{ &data->stream_ };
>>>> +	std::shared_ptr<Camera> camera = Camera::create(this, data->video_-
>>>>> deviceName(), streams);
>>>> +	registerCamera(std::move(camera), std::move(data));
>>>> +
>>>> +	return true;
>>>> +}
>>>> +
>>>> +int VividCameraData::init()
>>>> +{
>>>> +	video_ = new V4L2VideoDevice(media_->getEntityByName("vivid-000-vid-
>>>> cap"));
>>>> +	if (video_->open())
>>>> +		return -ENODEV;
>>>> +
>>>> +	video_->bufferReady.connect(this, &VividCameraData::bufferReady);
>>>> +
>>>> +	/* Initialise the supported controls. */
>>>> +	const ControlInfoMap &controls = video_->controls();
>>>> +	ControlInfoMap::Map ctrls;
>>>> +
>>>> +	for (const auto &ctrl : controls) {
>>>> +		const ControlId *id;
>>>> +		ControlInfo info;
>>>> +
>>>> +		switch (ctrl.first->id()) {
>>>> +		case V4L2_CID_BRIGHTNESS:
>>>> +			id = &controls::Brightness;
>>>> +			info = ControlInfo{ { -1.0f }, { 1.0f }, { 0.0f } };
>>>> +			break;
>>>> +		case V4L2_CID_CONTRAST:
>>>> +			id = &controls::Contrast;
>>>> +			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
>>>> +			break;
>>>> +		case V4L2_CID_SATURATION:
>>>> +			id = &controls::Saturation;
>>>> +			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
>>>> +			break;
>>>> +		default:
>>>> +			continue;
>>>> +		}
>>>> +
>>>> +		ctrls.emplace(id, info);
>>>> +	}
>>>> +
>>>> +	controlInfo_ = std::move(ctrls);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +void VividCameraData::bufferReady(FrameBuffer *buffer)
>>>> +{
>>>> +	Request *request = buffer->request();
>>>> +
>>>> +	pipe_->completeBuffer(camera_, request, buffer);
>>>> +	pipe_->completeRequest(camera_, request);
>>>> +}
>>>> +
>>>> +REGISTER_PIPELINE_HANDLER(PipelineHandlerVivid);
>>>> +
>>>> +} /* namespace libcamera */
>
Laurent Pinchart June 22, 2020, 10:29 p.m. UTC | #8
Hi Kieran,

On Mon, Jun 22, 2020 at 10:47:39AM +0100, Kieran Bingham wrote:
> On 22/06/2020 02:45, Laurent Pinchart wrote:
> > On Fri, Jun 19, 2020 at 05:19:55PM +0100, Kieran Bingham wrote:
> >> On 19/06/2020 16:34, Nicolas Dufresne wrote:
> >>> Le vendredi 19 juin 2020 à 13:02 +0100, Kieran Bingham a écrit :
> >>>> The VIVID driver supports more pixel formats and properties than the VIMC
> >>>> driver, and can provide extended testing for libcamera.
> >>>>
> >>>> The VIMC pipeline handler is duplicated and simplified to support the
> >>>> VIVID device.
> >>>>
> >>>> Unfortuantely, the VIVID device can not be handled by either of the
> >>>> generic UVC or Simple pipeline handlers.
> >>>
> >>> Can you extend on that ?
> >>
> >> I actually disagreed with myself in my latest reply (@13:14 in my timezone).
> >>
> >>> Perhaps an alternative route to supporting this however would be to make
> >>> the simple pipeline handler support pipelines which *don't* have a
> >>> sensor object... though the VIVID controls could be useful to support
> >>> directly.
> >>>
> >>> Perhaps - inheriting the simple-pipeline handler should be possible, to
> >>> provide some 'device specific' adjustments for things like device
> >>> specific controls.
> >>
> >> The simple pipeline expects to walk a media graph and find a sensor.
> >> VIVID doesn't expose a sensor, nor desire to. It's just a plain video
> >> device node.
> >>
> >> Perhaps we could extend the simple pipeline handler to support devices
> >> which don't have a sensor ... or create a "simple-simple-pipeline
> >> handler" ? :-D
> >>
> >> Equally - there could then be quite a lot of commonality between the UVC
> >> pipeline handler, though we kept that separate because of expected
> >> differences with UVC devices ... (in particular the controls for UVC
> >> have some specific variations already handled there).
> >>
> >> So for testing with vivid, I took the easy option of creating a new
> >> pipeline for VIVID. But there is so much code duplication already, I'm
> >> sure we could find some reuse somewhere ...
> > 
> > Thank you for posting this patch. I think it's useful, as vimc currently
> > has a fairly limited set of features compared to vivid. In particular,
> > the lack of YUV format support in vimc (when configuring the pipeline
> > with a Bayer camera sensor as a source) limits our ability to test any
> > use case that requires YUV without real hardware.
> 
> The extra format support really was the driving factor here, and vivid
> has much greater coverage for V4L2, so I suspect it might have
> further/different benefits for testing the v4l2 compatibility layers ...
> 
> Which is of course why it got posted, because Paul had asked there, and
> I had the vivid handler ready to hand.
> 
> > Ideally, I would like vimc to gain support for these features, and avoid
> > adding another pipeline handler for vivid that would duplicate code. We
> > can discuss how to minimize code duplication, either with the simple
> > pipeline handler or the UVC pipeline handler, but the effort to add YUV
> > support in vimc seems fairly low to me, possibly even lower than
> > developing vivid support in libcamera with reduced code duplication. I'm
> 
> I don't yet know what's involved in adding extra format support to VIMC,
> but weren't Collabora also looking at such a topic ?

We need to synchronize with Helen and Shuah here.

> > curious to know your opinion on long-term support for vivid in
> > libcamera, compared to enhancing the vimc driver.
> > 
> > We could merge this pipeline handler as-is and deal with code sharing
> > later (or possibly even drop it later when vimc support will be better),
> 
> I think that's an important consideration. - We can 'drop' it later too
> if required test functionality becomes available elsewhere...
> 
> > but we also need to consider that every new pipeline handler increases
> > the maintenance effort, and makes changes to the pipeline handler API
> > more difficult. We need to consider the pros and cons.
> 
> Of course, that's why initially I hadn't posted it when /I/ needed it.
> But upon the third send out - I figured that was enough motivation to
> include the mailing-list.
> 
> > Regarding code sharing, without having studied the question in details,
> > I feel that the UVC pipeline handler would be a better target, as vimc
> > is media controller-centric, while UVC and vivid are video node-centric.
> 
> Yes, there are definitely similarities with UVC, but there are likely
> differences in controls ... but I suspect for VIVID we don't /actually/
> care about those differences anyway.
> 
> > I'm even considering supporting vimc with the simple pipeline handler
> > instead of a dedicated pipeline handler, but that would likely require
> 
> That's interesting, but how would you handle having the 'test vimc' IPA?
> I suspect VIMC would end up staying external unless perhaps the simple
> pipeline handler would also support IPA matching?

I foresee a need for IPAs for the simple pipeline handler, coupled with
GPU processing of Bayer data (a poor man's ISP in a way). The vimc IPA
could then be integrated with whatever mechanism we implement in the
simple pipeline handler to load IPAs.

> > changes in both the simple pipeline handler and the vimc driver (most
> > notably the vimc scaler, which hardcodes a downscaling ratio of 3).
> 
> Yes, that's a painful arbitrary limitation.
> 
> Overall, I don't particularly mind too much if this gets integrated or not.
> 
> I think it can provide some help for development of libcamera in virtual
> systems which, while we have low hardware support might be more beneficial.
> 
> It might be an easy way to provide testing of things like 'multiple
> cameras' on a single pipeline handler too - as you can easily have extra
> capture devices with fairly arbitrary sizes for testing.
> 
> Without wide format coverage in VIMC it could help with other plumbing
> aspects too - but equally even if we do integrate it - I could easily
> see a point in the future where it becomes simpler to remove it.
> 
> >>>> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> >>>> ---
> >>>>  meson_options.txt                        |   2 +-
> >>>>  src/libcamera/pipeline/vivid/meson.build |   5 +
> >>>>  src/libcamera/pipeline/vivid/vivid.cpp   | 441 +++++++++++++++++++++++
> >>>>  3 files changed, 447 insertions(+), 1 deletion(-)
> >>>>  create mode 100644 src/libcamera/pipeline/vivid/meson.build
> >>>>  create mode 100644 src/libcamera/pipeline/vivid/vivid.cpp
> >>>>
> >>>> diff --git a/meson_options.txt b/meson_options.txt
> >>>> index badace151bb6..dc4684df49b2 100644
> >>>> --- a/meson_options.txt
> >>>> +++ b/meson_options.txt
> >>>> @@ -16,7 +16,7 @@ option('gstreamer',
> >>>>  
> >>>>  option('pipelines',
> >>>>          type : 'array',
> >>>> -        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo',
> >>>> 'vimc'],
> >>>> +        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo',
> >>>> 'vimc', 'vivid'],
> >>>>          description : 'Select which pipeline handlers to include')
> >>>>  
> >>>>  option('test',
> >>>> diff --git a/src/libcamera/pipeline/vivid/meson.build
> >>>> b/src/libcamera/pipeline/vivid/meson.build
> >>>> new file mode 100644
> >>>> index 000000000000..086bb825387c
> >>>> --- /dev/null
> >>>> +++ b/src/libcamera/pipeline/vivid/meson.build
> >>>> @@ -0,0 +1,5 @@
> >>>> +# SPDX-License-Identifier: CC0-1.0
> >>>> +
> >>>> +libcamera_sources += files([
> >>>> +    'vivid.cpp',
> >>>> +])
> >>>> diff --git a/src/libcamera/pipeline/vivid/vivid.cpp
> >>>> b/src/libcamera/pipeline/vivid/vivid.cpp
> >>>> new file mode 100644
> >>>> index 000000000000..b811e33a0299
> >>>> --- /dev/null
> >>>> +++ b/src/libcamera/pipeline/vivid/vivid.cpp
> >>>> @@ -0,0 +1,441 @@
> >>>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> >>>> +/*
> >>>> + * Copyright (C) 2018, Google Inc.
> >>>> + *
> >>>> + * vivid.cpp - Pipeline handler for the vivid capture device
> >>>> + */
> >>>> +
> >>>> +#include <algorithm>
> >>>> +#include <iomanip>
> >>>> +#include <map>
> >>>> +#include <math.h>
> >>>> +#include <tuple>
> >>>> +
> >>>> +#include <linux/media-bus-format.h>
> >>>> +#include <linux/version.h>
> >>>> +
> >>>> +#include <libcamera/camera.h>
> >>>> +#include <libcamera/control_ids.h>
> >>>> +#include <libcamera/controls.h>
> >>>> +#include <libcamera/ipa/ipa_interface.h>
> >>>> +#include <libcamera/ipa/ipa_module_info.h>
> >>>> +#include <libcamera/request.h>
> >>>> +#include <libcamera/stream.h>
> >>>> +
> >>>> +#include "libcamera/internal/camera_sensor.h"
> >>>> +#include "libcamera/internal/device_enumerator.h"
> >>>> +#include "libcamera/internal/ipa_manager.h"
> >>>> +#include "libcamera/internal/log.h"
> >>>> +#include "libcamera/internal/media_device.h"
> >>>> +#include "libcamera/internal/pipeline_handler.h"
> >>>> +#include "libcamera/internal/utils.h"
> >>>> +#include "libcamera/internal/v4l2_controls.h"
> >>>> +#include "libcamera/internal/v4l2_subdevice.h"
> >>>> +#include "libcamera/internal/v4l2_videodevice.h"
> >>>> +
> >>>> +#define VIVID_CID_CUSTOM_BASE           (V4L2_CID_USER_BASE | 0xf000)
> >>>> +#define VIVID_CID_BUTTON                (VIVID_CID_CUSTOM_BASE + 0)
> >>>> +#define VIVID_CID_BOOLEAN               (VIVID_CID_CUSTOM_BASE + 1)
> >>>> +#define VIVID_CID_INTEGER               (VIVID_CID_CUSTOM_BASE + 2)
> >>>> +#define VIVID_CID_INTEGER64             (VIVID_CID_CUSTOM_BASE + 3)
> >>>> +#define VIVID_CID_MENU                  (VIVID_CID_CUSTOM_BASE + 4)
> >>>> +#define VIVID_CID_STRING                (VIVID_CID_CUSTOM_BASE + 5)
> >>>> +#define VIVID_CID_BITMASK               (VIVID_CID_CUSTOM_BASE + 6)
> >>>> +#define VIVID_CID_INTMENU               (VIVID_CID_CUSTOM_BASE + 7)
> >>>> +#define VIVID_CID_U32_ARRAY             (VIVID_CID_CUSTOM_BASE + 8)
> >>>> +#define VIVID_CID_U16_MATRIX            (VIVID_CID_CUSTOM_BASE + 9)
> >>>> +#define VIVID_CID_U8_4D_ARRAY           (VIVID_CID_CUSTOM_BASE + 10)
> >>>> +
> >>>> +#define VIVID_CID_VIVID_BASE            (0x00f00000 | 0xf000)
> >>>> +#define VIVID_CID_VIVID_CLASS           (0x00f00000 | 1)
> >>>> +#define VIVID_CID_TEST_PATTERN          (VIVID_CID_VIVID_BASE + 0)
> >>>> +#define VIVID_CID_OSD_TEXT_MODE         (VIVID_CID_VIVID_BASE + 1)
> >>>> +#define VIVID_CID_HOR_MOVEMENT          (VIVID_CID_VIVID_BASE + 2)
> >>>> +#define VIVID_CID_VERT_MOVEMENT         (VIVID_CID_VIVID_BASE + 3)
> >>>> +#define VIVID_CID_SHOW_BORDER           (VIVID_CID_VIVID_BASE + 4)
> >>>> +#define VIVID_CID_SHOW_SQUARE           (VIVID_CID_VIVID_BASE + 5)
> >>>> +#define VIVID_CID_INSERT_SAV            (VIVID_CID_VIVID_BASE + 6)
> >>>> +#define VIVID_CID_INSERT_EAV            (VIVID_CID_VIVID_BASE + 7)
> >>>> +#define VIVID_CID_VBI_CAP_INTERLACED    (VIVID_CID_VIVID_BASE + 8)
> >>>> +
> >>>> +#define VIVID_CID_HFLIP                 (VIVID_CID_VIVID_BASE + 20)
> >>>> +#define VIVID_CID_VFLIP                 (VIVID_CID_VIVID_BASE + 21)
> >>>> +#define VIVID_CID_STD_ASPECT_RATIO      (VIVID_CID_VIVID_BASE + 22)
> >>>> +#define VIVID_CID_DV_TIMINGS_ASPECT_RATIO       (VIVID_CID_VIVID_BASE + 23)
> >>>> +#define VIVID_CID_TSTAMP_SRC            (VIVID_CID_VIVID_BASE + 24)
> >>>> +#define VIVID_CID_COLORSPACE            (VIVID_CID_VIVID_BASE + 25)
> >>>> +#define VIVID_CID_XFER_FUNC             (VIVID_CID_VIVID_BASE + 26)
> >>>> +#define VIVID_CID_YCBCR_ENC             (VIVID_CID_VIVID_BASE + 27)
> >>>> +#define VIVID_CID_QUANTIZATION          (VIVID_CID_VIVID_BASE + 28)
> >>>> +#define VIVID_CID_LIMITED_RGB_RANGE     (VIVID_CID_VIVID_BASE + 29)
> >>>> +#define VIVID_CID_ALPHA_MODE            (VIVID_CID_VIVID_BASE + 30)
> >>>> +#define VIVID_CID_HAS_CROP_CAP          (VIVID_CID_VIVID_BASE + 31)
> >>>> +#define VIVID_CID_HAS_COMPOSE_CAP       (VIVID_CID_VIVID_BASE + 32)
> >>>> +#define VIVID_CID_HAS_SCALER_CAP        (VIVID_CID_VIVID_BASE + 33)
> >>>> +#define VIVID_CID_HAS_CROP_OUT          (VIVID_CID_VIVID_BASE + 34)
> >>>> +#define VIVID_CID_HAS_COMPOSE_OUT       (VIVID_CID_VIVID_BASE + 35)
> >>>> +#define VIVID_CID_HAS_SCALER_OUT        (VIVID_CID_VIVID_BASE + 36)
> >>>> +#define VIVID_CID_LOOP_VIDEO            (VIVID_CID_VIVID_BASE + 37)
> >>>> +#define VIVID_CID_SEQ_WRAP              (VIVID_CID_VIVID_BASE + 38)
> >>>> +#define VIVID_CID_TIME_WRAP             (VIVID_CID_VIVID_BASE + 39)
> >>>> +#define VIVID_CID_MAX_EDID_BLOCKS       (VIVID_CID_VIVID_BASE + 40)
> >>>> +#define VIVID_CID_PERCENTAGE_FILL       (VIVID_CID_VIVID_BASE + 41)
> >>>> +#define VIVID_CID_REDUCED_FPS           (VIVID_CID_VIVID_BASE + 42)
> >>>> +#define VIVID_CID_HSV_ENC               (VIVID_CID_VIVID_BASE + 43)
> >>>> +#define VIVID_CID_DISPLAY_PRESENT       (VIVID_CID_VIVID_BASE + 44)
> >>>> +
> >>>> +#define VIVID_CID_STD_SIGNAL_MODE       (VIVID_CID_VIVID_BASE + 60)
> >>>> +#define VIVID_CID_STANDARD              (VIVID_CID_VIVID_BASE + 61)
> >>>> +#define VIVID_CID_DV_TIMINGS_SIGNAL_MODE        (VIVID_CID_VIVID_BASE + 62)
> >>>> +#define VIVID_CID_DV_TIMINGS            (VIVID_CID_VIVID_BASE + 63)
> >>>> +#define VIVID_CID_PERC_DROPPED          (VIVID_CID_VIVID_BASE + 64)
> >>>> +#define VIVID_CID_DISCONNECT            (VIVID_CID_VIVID_BASE + 65)
> >>>> +#define VIVID_CID_DQBUF_ERROR           (VIVID_CID_VIVID_BASE + 66)
> >>>> +#define VIVID_CID_QUEUE_SETUP_ERROR     (VIVID_CID_VIVID_BASE + 67)
> >>>> +#define VIVID_CID_BUF_PREPARE_ERROR     (VIVID_CID_VIVID_BASE + 68)
> >>>> +#define VIVID_CID_START_STR_ERROR       (VIVID_CID_VIVID_BASE + 69)
> >>>> +#define VIVID_CID_QUEUE_ERROR           (VIVID_CID_VIVID_BASE + 70)
> >>>> +#define VIVID_CID_CLEAR_FB              (VIVID_CID_VIVID_BASE + 71)
> >>>> +#define VIVID_CID_REQ_VALIDATE_ERROR    (VIVID_CID_VIVID_BASE + 72)
> >>>> +
> >>>> +#define VIVID_CID_RADIO_SEEK_MODE       (VIVID_CID_VIVID_BASE + 90)
> >>>> +#define VIVID_CID_RADIO_SEEK_PROG_LIM   (VIVID_CID_VIVID_BASE + 91)
> >>>> +#define VIVID_CID_RADIO_RX_RDS_RBDS     (VIVID_CID_VIVID_BASE + 92)
> >>>> +#define VIVID_CID_RADIO_RX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 93)
> >>>> +
> >>>> +#define VIVID_CID_RADIO_TX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 94)
> >>>> +
> >>>> +#define VIVID_CID_SDR_CAP_FM_DEVIATION  (VIVID_CID_VIVID_BASE + 110)
> >>>> +
> >>>> +
> >>>> +namespace libcamera {
> >>>> +
> >>>> +LOG_DEFINE_CATEGORY(VIVID)
> >>>> +
> >>>> +class VividCameraData : public CameraData
> >>>> +{
> >>>> +public:
> >>>> +	VividCameraData(PipelineHandler *pipe, MediaDevice *media)
> >>>> +		: CameraData(pipe), media_(media), video_(nullptr)
> >>>> +	{
> >>>> +	}
> >>>> +
> >>>> +	~VividCameraData()
> >>>> +	{
> >>>> +		delete video_;
> >>>> +	}
> >>>> +
> >>>> +	int init();
> >>>> +	void bufferReady(FrameBuffer *buffer);
> >>>> +
> >>>> +	MediaDevice *media_;
> >>>> +	V4L2VideoDevice *video_;
> >>>> +	Stream stream_;
> >>>> +};
> >>>> +
> >>>> +class VividCameraConfiguration : public CameraConfiguration
> >>>> +{
> >>>> +public:
> >>>> +	VividCameraConfiguration();
> >>>> +
> >>>> +	Status validate() override;
> >>>> +};
> >>>> +
> >>>> +class PipelineHandlerVivid : public PipelineHandler
> >>>> +{
> >>>> +public:
> >>>> +	PipelineHandlerVivid(CameraManager *manager);
> >>>> +
> >>>> +	CameraConfiguration *generateConfiguration(Camera *camera,
> >>>> +						   const StreamRoles &roles)
> >>>> override;
> >>>> +	int configure(Camera *camera, CameraConfiguration *config) override;
> >>>> +
> >>>> +	int exportFrameBuffers(Camera *camera, Stream *stream,
> >>>> +			       std::vector<std::unique_ptr<FrameBuffer>>
> >>>> *buffers) override;
> >>>> +
> >>>> +	int start(Camera *camera) override;
> >>>> +	void stop(Camera *camera) override;
> >>>> +
> >>>> +	int queueRequestDevice(Camera *camera, Request *request) override;
> >>>> +
> >>>> +	bool match(DeviceEnumerator *enumerator) override;
> >>>> +
> >>>> +private:
> >>>> +	int processControls(VividCameraData *data, Request *request);
> >>>> +
> >>>> +	VividCameraData *cameraData(const Camera *camera)
> >>>> +	{
> >>>> +		return static_cast<VividCameraData *>(
> >>>> +			PipelineHandler::cameraData(camera));
> >>>> +	}
> >>>> +};
> >>>> +
> >>>> +VividCameraConfiguration::VividCameraConfiguration()
> >>>> +	: CameraConfiguration()
> >>>> +{
> >>>> +}
> >>>> +
> >>>> +CameraConfiguration::Status VividCameraConfiguration::validate()
> >>>> +{
> >>>> +	Status status = Valid;
> >>>> +
> >>>> +	if (config_.empty())
> >>>> +		return Invalid;
> >>>> +
> >>>> +	/* Cap the number of entries to the available streams. */
> >>>> +	if (config_.size() > 1) {
> >>>> +		config_.resize(1);
> >>>> +		status = Adjusted;
> >>>> +	}
> >>>> +
> >>>> +	StreamConfiguration &cfg = config_[0];
> >>>> +
> >>>> +	/* Adjust the pixel format. */
> >>>> +	const std::vector<libcamera::PixelFormat> formats =
> >>>> cfg.formats().pixelformats();
> >>>> +	if (std::find(formats.begin(), formats.end(), cfg.pixelFormat) ==
> >>>> formats.end()) {
> >>>> +		cfg.pixelFormat = cfg.formats().pixelformats()[0];
> >>>> +		LOG(VIVID, Debug) << "Adjusting format to " <<
> >>>> cfg.pixelFormat.toString();
> >>>> +		status = Adjusted;
> >>>> +	}
> >>>> +
> >>>> +	cfg.bufferCount = 4;
> >>>> +
> >>>> +	return status;
> >>>> +}
> >>>> +
> >>>> +PipelineHandlerVivid::PipelineHandlerVivid(CameraManager *manager)
> >>>> +	: PipelineHandler(manager)
> >>>> +{
> >>>> +}
> >>>> +
> >>>> +CameraConfiguration *PipelineHandlerVivid::generateConfiguration(Camera
> >>>> *camera,
> >>>> +								 const
> >>>> StreamRoles &roles)
> >>>> +{
> >>>> +	CameraConfiguration *config = new VividCameraConfiguration();
> >>>> +	VividCameraData *data = cameraData(camera);
> >>>> +
> >>>> +	if (roles.empty())
> >>>> +		return config;
> >>>> +
> >>>> +	std::map<V4L2PixelFormat, std::vector<SizeRange>> v4l2Formats =
> >>>> +		data->video_->formats();
> >>>> +	std::map<PixelFormat, std::vector<SizeRange>> deviceFormats;
> >>>> +	std::transform(v4l2Formats.begin(), v4l2Formats.end(),
> >>>> +		       std::inserter(deviceFormats, deviceFormats.begin()),
> >>>> +		       [&](const decltype(v4l2Formats)::value_type &format) {
> >>>> +			       return decltype(deviceFormats)::value_type{
> >>>> +				       format.first.toPixelFormat(),
> >>>> +				       format.second
> >>>> +			       };
> >>>> +		       });
> >>>> +
> >>>> +	StreamFormats formats(deviceFormats);
> >>>> +	StreamConfiguration cfg(formats);
> >>>> +
> >>>> +	cfg.pixelFormat = PixelFormat(DRM_FORMAT_BGR888);
> >>>> +	cfg.size = { 1280, 720 };
> >>>> +	cfg.bufferCount = 4;
> >>>> +
> >>>> +	config->addConfiguration(cfg);
> >>>> +
> >>>> +	config->validate();
> >>>> +
> >>>> +	return config;
> >>>> +}
> >>>> +
> >>>> +int PipelineHandlerVivid::configure(Camera *camera, CameraConfiguration
> >>>> *config)
> >>>> +{
> >>>> +	VividCameraData *data = cameraData(camera);
> >>>> +	StreamConfiguration &cfg = config->at(0);
> >>>> +	int ret;
> >>>> +
> >>>> +	V4L2DeviceFormat format = {};
> >>>> +	format.fourcc = data->video_->toV4L2PixelFormat(cfg.pixelFormat);
> >>>> +	format.size = cfg.size;
> >>>> +
> >>>> +	ret = data->video_->setFormat(&format);
> >>>> +	if (ret)
> >>>> +		return ret;
> >>>> +
> >>>> +	if (format.size != cfg.size ||
> >>>> +	    format.fourcc != data->video_->toV4L2PixelFormat(cfg.pixelFormat))
> >>>> +		return -EINVAL;
> >>>> +
> >>>> +	cfg.setStream(&data->stream_);
> >>>> +	cfg.stride = format.planes[0].bpl;
> >>>> +
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>> +int PipelineHandlerVivid::exportFrameBuffers(Camera *camera, Stream *stream,
> >>>> +					     std::vector<std::unique_ptr<FrameBu
> >>>> ffer>> *buffers)
> >>>> +{
> >>>> +	VividCameraData *data = cameraData(camera);
> >>>> +	unsigned int count = stream->configuration().bufferCount;
> >>>> +
> >>>> +	return data->video_->exportBuffers(count, buffers);
> >>>> +}
> >>>> +
> >>>> +int PipelineHandlerVivid::start(Camera *camera)
> >>>> +{
> >>>> +	VividCameraData *data = cameraData(camera);
> >>>> +	unsigned int count = data->stream_.configuration().bufferCount;
> >>>> +
> >>>> +	int ret = data->video_->importBuffers(count);
> >>>> +	if (ret < 0)
> >>>> +		return ret;
> >>>> +
> >>>> +	ret = data->video_->streamOn();
> >>>> +	if (ret < 0) {
> >>>> +		data->ipa_->stop();
> >>>> +		data->video_->releaseBuffers();
> >>>> +		return ret;
> >>>> +	}
> >>>> +
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>> +void PipelineHandlerVivid::stop(Camera *camera)
> >>>> +{
> >>>> +	VividCameraData *data = cameraData(camera);
> >>>> +	data->video_->streamOff();
> >>>> +	data->video_->releaseBuffers();
> >>>> +}
> >>>> +
> >>>> +int PipelineHandlerVivid::processControls(VividCameraData *data, Request
> >>>> *request)
> >>>> +{
> >>>> +	ControlList controls(data->video_->controls());
> >>>> +
> >>>> +	for (auto it : request->controls()) {
> >>>> +		unsigned int id = it.first;
> >>>> +		unsigned int offset;
> >>>> +		uint32_t cid;
> >>>> +
> >>>> +		if (id == controls::Brightness) {
> >>>> +			cid = V4L2_CID_BRIGHTNESS;
> >>>> +			offset = 128;
> >>>> +		} else if (id == controls::Contrast) {
> >>>> +			cid = V4L2_CID_CONTRAST;
> >>>> +			offset = 0;
> >>>> +		} else if (id == controls::Saturation) {
> >>>> +			cid = V4L2_CID_SATURATION;
> >>>> +			offset = 0;
> >>>> +		} else {
> >>>> +			continue;
> >>>> +		}
> >>>> +
> >>>> +		int32_t value = lroundf(it.second.get<float>() * 128 + offset);
> >>>> +		controls.set(cid, utils::clamp(value, 0, 255));
> >>>> +	}
> >>>> +
> >>>> +	for (const auto &ctrl : controls)
> >>>> +		LOG(VIVID, Debug)
> >>>> +			<< "Setting control " << utils::hex(ctrl.first)
> >>>> +			<< " to " << ctrl.second.toString();
> >>>> +
> >>>> +	int ret = data->video_->setControls(&controls);
> >>>> +	if (ret) {
> >>>> +		LOG(VIVID, Error) << "Failed to set controls: " << ret;
> >>>> +		return ret < 0 ? ret : -EINVAL;
> >>>> +	}
> >>>> +
> >>>> +	return ret;
> >>>> +}
> >>>> +
> >>>> +int PipelineHandlerVivid::queueRequestDevice(Camera *camera, Request
> >>>> *request)
> >>>> +{
> >>>> +	VividCameraData *data = cameraData(camera);
> >>>> +	FrameBuffer *buffer = request->findBuffer(&data->stream_);
> >>>> +	if (!buffer) {
> >>>> +		LOG(VIVID, Error)
> >>>> +			<< "Attempt to queue request with invalid stream";
> >>>> +
> >>>> +		return -ENOENT;
> >>>> +	}
> >>>> +
> >>>> +	int ret = processControls(data, request);
> >>>> +	if (ret < 0)
> >>>> +		return ret;
> >>>> +
> >>>> +	ret = data->video_->queueBuffer(buffer);
> >>>> +	if (ret < 0)
> >>>> +		return ret;
> >>>> +
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>> +bool PipelineHandlerVivid::match(DeviceEnumerator *enumerator)
> >>>> +{
> >>>> +	DeviceMatch dm("vivid");
> >>>> +	dm.add("vivid-000-vid-cap");
> >>>> +
> >>>> +	MediaDevice *media = acquireMediaDevice(enumerator, dm);
> >>>> +	if (!media)
> >>>> +		return false;
> >>>> +
> >>>> +	std::unique_ptr<VividCameraData> data =
> >>>> std::make_unique<VividCameraData>(this, media);
> >>>> +
> >>>> +	/* Locate and open the capture video node. */
> >>>> +	if (data->init())
> >>>> +		return false;
> >>>> +
> >>>> +	/* Create and register the camera. */
> >>>> +	std::set<Stream *> streams{ &data->stream_ };
> >>>> +	std::shared_ptr<Camera> camera = Camera::create(this, data->video_-
> >>>>> deviceName(), streams);
> >>>> +	registerCamera(std::move(camera), std::move(data));
> >>>> +
> >>>> +	return true;
> >>>> +}
> >>>> +
> >>>> +int VividCameraData::init()
> >>>> +{
> >>>> +	video_ = new V4L2VideoDevice(media_->getEntityByName("vivid-000-vid-
> >>>> cap"));
> >>>> +	if (video_->open())
> >>>> +		return -ENODEV;
> >>>> +
> >>>> +	video_->bufferReady.connect(this, &VividCameraData::bufferReady);
> >>>> +
> >>>> +	/* Initialise the supported controls. */
> >>>> +	const ControlInfoMap &controls = video_->controls();
> >>>> +	ControlInfoMap::Map ctrls;
> >>>> +
> >>>> +	for (const auto &ctrl : controls) {
> >>>> +		const ControlId *id;
> >>>> +		ControlInfo info;
> >>>> +
> >>>> +		switch (ctrl.first->id()) {
> >>>> +		case V4L2_CID_BRIGHTNESS:
> >>>> +			id = &controls::Brightness;
> >>>> +			info = ControlInfo{ { -1.0f }, { 1.0f }, { 0.0f } };
> >>>> +			break;
> >>>> +		case V4L2_CID_CONTRAST:
> >>>> +			id = &controls::Contrast;
> >>>> +			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
> >>>> +			break;
> >>>> +		case V4L2_CID_SATURATION:
> >>>> +			id = &controls::Saturation;
> >>>> +			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
> >>>> +			break;
> >>>> +		default:
> >>>> +			continue;
> >>>> +		}
> >>>> +
> >>>> +		ctrls.emplace(id, info);
> >>>> +	}
> >>>> +
> >>>> +	controlInfo_ = std::move(ctrls);
> >>>> +
> >>>> +	return 0;
> >>>> +}
> >>>> +
> >>>> +void VividCameraData::bufferReady(FrameBuffer *buffer)
> >>>> +{
> >>>> +	Request *request = buffer->request();
> >>>> +
> >>>> +	pipe_->completeBuffer(camera_, request, buffer);
> >>>> +	pipe_->completeRequest(camera_, request);
> >>>> +}
> >>>> +
> >>>> +REGISTER_PIPELINE_HANDLER(PipelineHandlerVivid);
> >>>> +
> >>>> +} /* namespace libcamera */

Patch

diff --git a/meson_options.txt b/meson_options.txt
index badace151bb6..dc4684df49b2 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -16,7 +16,7 @@  option('gstreamer',
 
 option('pipelines',
         type : 'array',
-        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo', 'vimc'],
+        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo', 'vimc', 'vivid'],
         description : 'Select which pipeline handlers to include')
 
 option('test',
diff --git a/src/libcamera/pipeline/vivid/meson.build b/src/libcamera/pipeline/vivid/meson.build
new file mode 100644
index 000000000000..086bb825387c
--- /dev/null
+++ b/src/libcamera/pipeline/vivid/meson.build
@@ -0,0 +1,5 @@ 
+# SPDX-License-Identifier: CC0-1.0
+
+libcamera_sources += files([
+    'vivid.cpp',
+])
diff --git a/src/libcamera/pipeline/vivid/vivid.cpp b/src/libcamera/pipeline/vivid/vivid.cpp
new file mode 100644
index 000000000000..b811e33a0299
--- /dev/null
+++ b/src/libcamera/pipeline/vivid/vivid.cpp
@@ -0,0 +1,441 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2018, Google Inc.
+ *
+ * vivid.cpp - Pipeline handler for the vivid capture device
+ */
+
+#include <algorithm>
+#include <iomanip>
+#include <map>
+#include <math.h>
+#include <tuple>
+
+#include <linux/media-bus-format.h>
+#include <linux/version.h>
+
+#include <libcamera/camera.h>
+#include <libcamera/control_ids.h>
+#include <libcamera/controls.h>
+#include <libcamera/ipa/ipa_interface.h>
+#include <libcamera/ipa/ipa_module_info.h>
+#include <libcamera/request.h>
+#include <libcamera/stream.h>
+
+#include "libcamera/internal/camera_sensor.h"
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/ipa_manager.h"
+#include "libcamera/internal/log.h"
+#include "libcamera/internal/media_device.h"
+#include "libcamera/internal/pipeline_handler.h"
+#include "libcamera/internal/utils.h"
+#include "libcamera/internal/v4l2_controls.h"
+#include "libcamera/internal/v4l2_subdevice.h"
+#include "libcamera/internal/v4l2_videodevice.h"
+
+#define VIVID_CID_CUSTOM_BASE           (V4L2_CID_USER_BASE | 0xf000)
+#define VIVID_CID_BUTTON                (VIVID_CID_CUSTOM_BASE + 0)
+#define VIVID_CID_BOOLEAN               (VIVID_CID_CUSTOM_BASE + 1)
+#define VIVID_CID_INTEGER               (VIVID_CID_CUSTOM_BASE + 2)
+#define VIVID_CID_INTEGER64             (VIVID_CID_CUSTOM_BASE + 3)
+#define VIVID_CID_MENU                  (VIVID_CID_CUSTOM_BASE + 4)
+#define VIVID_CID_STRING                (VIVID_CID_CUSTOM_BASE + 5)
+#define VIVID_CID_BITMASK               (VIVID_CID_CUSTOM_BASE + 6)
+#define VIVID_CID_INTMENU               (VIVID_CID_CUSTOM_BASE + 7)
+#define VIVID_CID_U32_ARRAY             (VIVID_CID_CUSTOM_BASE + 8)
+#define VIVID_CID_U16_MATRIX            (VIVID_CID_CUSTOM_BASE + 9)
+#define VIVID_CID_U8_4D_ARRAY           (VIVID_CID_CUSTOM_BASE + 10)
+
+#define VIVID_CID_VIVID_BASE            (0x00f00000 | 0xf000)
+#define VIVID_CID_VIVID_CLASS           (0x00f00000 | 1)
+#define VIVID_CID_TEST_PATTERN          (VIVID_CID_VIVID_BASE + 0)
+#define VIVID_CID_OSD_TEXT_MODE         (VIVID_CID_VIVID_BASE + 1)
+#define VIVID_CID_HOR_MOVEMENT          (VIVID_CID_VIVID_BASE + 2)
+#define VIVID_CID_VERT_MOVEMENT         (VIVID_CID_VIVID_BASE + 3)
+#define VIVID_CID_SHOW_BORDER           (VIVID_CID_VIVID_BASE + 4)
+#define VIVID_CID_SHOW_SQUARE           (VIVID_CID_VIVID_BASE + 5)
+#define VIVID_CID_INSERT_SAV            (VIVID_CID_VIVID_BASE + 6)
+#define VIVID_CID_INSERT_EAV            (VIVID_CID_VIVID_BASE + 7)
+#define VIVID_CID_VBI_CAP_INTERLACED    (VIVID_CID_VIVID_BASE + 8)
+
+#define VIVID_CID_HFLIP                 (VIVID_CID_VIVID_BASE + 20)
+#define VIVID_CID_VFLIP                 (VIVID_CID_VIVID_BASE + 21)
+#define VIVID_CID_STD_ASPECT_RATIO      (VIVID_CID_VIVID_BASE + 22)
+#define VIVID_CID_DV_TIMINGS_ASPECT_RATIO       (VIVID_CID_VIVID_BASE + 23)
+#define VIVID_CID_TSTAMP_SRC            (VIVID_CID_VIVID_BASE + 24)
+#define VIVID_CID_COLORSPACE            (VIVID_CID_VIVID_BASE + 25)
+#define VIVID_CID_XFER_FUNC             (VIVID_CID_VIVID_BASE + 26)
+#define VIVID_CID_YCBCR_ENC             (VIVID_CID_VIVID_BASE + 27)
+#define VIVID_CID_QUANTIZATION          (VIVID_CID_VIVID_BASE + 28)
+#define VIVID_CID_LIMITED_RGB_RANGE     (VIVID_CID_VIVID_BASE + 29)
+#define VIVID_CID_ALPHA_MODE            (VIVID_CID_VIVID_BASE + 30)
+#define VIVID_CID_HAS_CROP_CAP          (VIVID_CID_VIVID_BASE + 31)
+#define VIVID_CID_HAS_COMPOSE_CAP       (VIVID_CID_VIVID_BASE + 32)
+#define VIVID_CID_HAS_SCALER_CAP        (VIVID_CID_VIVID_BASE + 33)
+#define VIVID_CID_HAS_CROP_OUT          (VIVID_CID_VIVID_BASE + 34)
+#define VIVID_CID_HAS_COMPOSE_OUT       (VIVID_CID_VIVID_BASE + 35)
+#define VIVID_CID_HAS_SCALER_OUT        (VIVID_CID_VIVID_BASE + 36)
+#define VIVID_CID_LOOP_VIDEO            (VIVID_CID_VIVID_BASE + 37)
+#define VIVID_CID_SEQ_WRAP              (VIVID_CID_VIVID_BASE + 38)
+#define VIVID_CID_TIME_WRAP             (VIVID_CID_VIVID_BASE + 39)
+#define VIVID_CID_MAX_EDID_BLOCKS       (VIVID_CID_VIVID_BASE + 40)
+#define VIVID_CID_PERCENTAGE_FILL       (VIVID_CID_VIVID_BASE + 41)
+#define VIVID_CID_REDUCED_FPS           (VIVID_CID_VIVID_BASE + 42)
+#define VIVID_CID_HSV_ENC               (VIVID_CID_VIVID_BASE + 43)
+#define VIVID_CID_DISPLAY_PRESENT       (VIVID_CID_VIVID_BASE + 44)
+
+#define VIVID_CID_STD_SIGNAL_MODE       (VIVID_CID_VIVID_BASE + 60)
+#define VIVID_CID_STANDARD              (VIVID_CID_VIVID_BASE + 61)
+#define VIVID_CID_DV_TIMINGS_SIGNAL_MODE        (VIVID_CID_VIVID_BASE + 62)
+#define VIVID_CID_DV_TIMINGS            (VIVID_CID_VIVID_BASE + 63)
+#define VIVID_CID_PERC_DROPPED          (VIVID_CID_VIVID_BASE + 64)
+#define VIVID_CID_DISCONNECT            (VIVID_CID_VIVID_BASE + 65)
+#define VIVID_CID_DQBUF_ERROR           (VIVID_CID_VIVID_BASE + 66)
+#define VIVID_CID_QUEUE_SETUP_ERROR     (VIVID_CID_VIVID_BASE + 67)
+#define VIVID_CID_BUF_PREPARE_ERROR     (VIVID_CID_VIVID_BASE + 68)
+#define VIVID_CID_START_STR_ERROR       (VIVID_CID_VIVID_BASE + 69)
+#define VIVID_CID_QUEUE_ERROR           (VIVID_CID_VIVID_BASE + 70)
+#define VIVID_CID_CLEAR_FB              (VIVID_CID_VIVID_BASE + 71)
+#define VIVID_CID_REQ_VALIDATE_ERROR    (VIVID_CID_VIVID_BASE + 72)
+
+#define VIVID_CID_RADIO_SEEK_MODE       (VIVID_CID_VIVID_BASE + 90)
+#define VIVID_CID_RADIO_SEEK_PROG_LIM   (VIVID_CID_VIVID_BASE + 91)
+#define VIVID_CID_RADIO_RX_RDS_RBDS     (VIVID_CID_VIVID_BASE + 92)
+#define VIVID_CID_RADIO_RX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 93)
+
+#define VIVID_CID_RADIO_TX_RDS_BLOCKIO  (VIVID_CID_VIVID_BASE + 94)
+
+#define VIVID_CID_SDR_CAP_FM_DEVIATION  (VIVID_CID_VIVID_BASE + 110)
+
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(VIVID)
+
+class VividCameraData : public CameraData
+{
+public:
+	VividCameraData(PipelineHandler *pipe, MediaDevice *media)
+		: CameraData(pipe), media_(media), video_(nullptr)
+	{
+	}
+
+	~VividCameraData()
+	{
+		delete video_;
+	}
+
+	int init();
+	void bufferReady(FrameBuffer *buffer);
+
+	MediaDevice *media_;
+	V4L2VideoDevice *video_;
+	Stream stream_;
+};
+
+class VividCameraConfiguration : public CameraConfiguration
+{
+public:
+	VividCameraConfiguration();
+
+	Status validate() override;
+};
+
+class PipelineHandlerVivid : public PipelineHandler
+{
+public:
+	PipelineHandlerVivid(CameraManager *manager);
+
+	CameraConfiguration *generateConfiguration(Camera *camera,
+						   const StreamRoles &roles) override;
+	int configure(Camera *camera, CameraConfiguration *config) override;
+
+	int exportFrameBuffers(Camera *camera, Stream *stream,
+			       std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;
+
+	int start(Camera *camera) override;
+	void stop(Camera *camera) override;
+
+	int queueRequestDevice(Camera *camera, Request *request) override;
+
+	bool match(DeviceEnumerator *enumerator) override;
+
+private:
+	int processControls(VividCameraData *data, Request *request);
+
+	VividCameraData *cameraData(const Camera *camera)
+	{
+		return static_cast<VividCameraData *>(
+			PipelineHandler::cameraData(camera));
+	}
+};
+
+VividCameraConfiguration::VividCameraConfiguration()
+	: CameraConfiguration()
+{
+}
+
+CameraConfiguration::Status VividCameraConfiguration::validate()
+{
+	Status status = Valid;
+
+	if (config_.empty())
+		return Invalid;
+
+	/* Cap the number of entries to the available streams. */
+	if (config_.size() > 1) {
+		config_.resize(1);
+		status = Adjusted;
+	}
+
+	StreamConfiguration &cfg = config_[0];
+
+	/* Adjust the pixel format. */
+	const std::vector<libcamera::PixelFormat> formats = cfg.formats().pixelformats();
+	if (std::find(formats.begin(), formats.end(), cfg.pixelFormat) == formats.end()) {
+		cfg.pixelFormat = cfg.formats().pixelformats()[0];
+		LOG(VIVID, Debug) << "Adjusting format to " << cfg.pixelFormat.toString();
+		status = Adjusted;
+	}
+
+	cfg.bufferCount = 4;
+
+	return status;
+}
+
+PipelineHandlerVivid::PipelineHandlerVivid(CameraManager *manager)
+	: PipelineHandler(manager)
+{
+}
+
+CameraConfiguration *PipelineHandlerVivid::generateConfiguration(Camera *camera,
+								 const StreamRoles &roles)
+{
+	CameraConfiguration *config = new VividCameraConfiguration();
+	VividCameraData *data = cameraData(camera);
+
+	if (roles.empty())
+		return config;
+
+	std::map<V4L2PixelFormat, std::vector<SizeRange>> v4l2Formats =
+		data->video_->formats();
+	std::map<PixelFormat, std::vector<SizeRange>> deviceFormats;
+	std::transform(v4l2Formats.begin(), v4l2Formats.end(),
+		       std::inserter(deviceFormats, deviceFormats.begin()),
+		       [&](const decltype(v4l2Formats)::value_type &format) {
+			       return decltype(deviceFormats)::value_type{
+				       format.first.toPixelFormat(),
+				       format.second
+			       };
+		       });
+
+	StreamFormats formats(deviceFormats);
+	StreamConfiguration cfg(formats);
+
+	cfg.pixelFormat = PixelFormat(DRM_FORMAT_BGR888);
+	cfg.size = { 1280, 720 };
+	cfg.bufferCount = 4;
+
+	config->addConfiguration(cfg);
+
+	config->validate();
+
+	return config;
+}
+
+int PipelineHandlerVivid::configure(Camera *camera, CameraConfiguration *config)
+{
+	VividCameraData *data = cameraData(camera);
+	StreamConfiguration &cfg = config->at(0);
+	int ret;
+
+	V4L2DeviceFormat format = {};
+	format.fourcc = data->video_->toV4L2PixelFormat(cfg.pixelFormat);
+	format.size = cfg.size;
+
+	ret = data->video_->setFormat(&format);
+	if (ret)
+		return ret;
+
+	if (format.size != cfg.size ||
+	    format.fourcc != data->video_->toV4L2PixelFormat(cfg.pixelFormat))
+		return -EINVAL;
+
+	cfg.setStream(&data->stream_);
+	cfg.stride = format.planes[0].bpl;
+
+	return 0;
+}
+
+int PipelineHandlerVivid::exportFrameBuffers(Camera *camera, Stream *stream,
+					     std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+{
+	VividCameraData *data = cameraData(camera);
+	unsigned int count = stream->configuration().bufferCount;
+
+	return data->video_->exportBuffers(count, buffers);
+}
+
+int PipelineHandlerVivid::start(Camera *camera)
+{
+	VividCameraData *data = cameraData(camera);
+	unsigned int count = data->stream_.configuration().bufferCount;
+
+	int ret = data->video_->importBuffers(count);
+	if (ret < 0)
+		return ret;
+
+	ret = data->video_->streamOn();
+	if (ret < 0) {
+		data->ipa_->stop();
+		data->video_->releaseBuffers();
+		return ret;
+	}
+
+	return 0;
+}
+
+void PipelineHandlerVivid::stop(Camera *camera)
+{
+	VividCameraData *data = cameraData(camera);
+	data->video_->streamOff();
+	data->video_->releaseBuffers();
+}
+
+int PipelineHandlerVivid::processControls(VividCameraData *data, Request *request)
+{
+	ControlList controls(data->video_->controls());
+
+	for (auto it : request->controls()) {
+		unsigned int id = it.first;
+		unsigned int offset;
+		uint32_t cid;
+
+		if (id == controls::Brightness) {
+			cid = V4L2_CID_BRIGHTNESS;
+			offset = 128;
+		} else if (id == controls::Contrast) {
+			cid = V4L2_CID_CONTRAST;
+			offset = 0;
+		} else if (id == controls::Saturation) {
+			cid = V4L2_CID_SATURATION;
+			offset = 0;
+		} else {
+			continue;
+		}
+
+		int32_t value = lroundf(it.second.get<float>() * 128 + offset);
+		controls.set(cid, utils::clamp(value, 0, 255));
+	}
+
+	for (const auto &ctrl : controls)
+		LOG(VIVID, Debug)
+			<< "Setting control " << utils::hex(ctrl.first)
+			<< " to " << ctrl.second.toString();
+
+	int ret = data->video_->setControls(&controls);
+	if (ret) {
+		LOG(VIVID, Error) << "Failed to set controls: " << ret;
+		return ret < 0 ? ret : -EINVAL;
+	}
+
+	return ret;
+}
+
+int PipelineHandlerVivid::queueRequestDevice(Camera *camera, Request *request)
+{
+	VividCameraData *data = cameraData(camera);
+	FrameBuffer *buffer = request->findBuffer(&data->stream_);
+	if (!buffer) {
+		LOG(VIVID, Error)
+			<< "Attempt to queue request with invalid stream";
+
+		return -ENOENT;
+	}
+
+	int ret = processControls(data, request);
+	if (ret < 0)
+		return ret;
+
+	ret = data->video_->queueBuffer(buffer);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+bool PipelineHandlerVivid::match(DeviceEnumerator *enumerator)
+{
+	DeviceMatch dm("vivid");
+	dm.add("vivid-000-vid-cap");
+
+	MediaDevice *media = acquireMediaDevice(enumerator, dm);
+	if (!media)
+		return false;
+
+	std::unique_ptr<VividCameraData> data = std::make_unique<VividCameraData>(this, media);
+
+	/* Locate and open the capture video node. */
+	if (data->init())
+		return false;
+
+	/* Create and register the camera. */
+	std::set<Stream *> streams{ &data->stream_ };
+	std::shared_ptr<Camera> camera = Camera::create(this, data->video_->deviceName(), streams);
+	registerCamera(std::move(camera), std::move(data));
+
+	return true;
+}
+
+int VividCameraData::init()
+{
+	video_ = new V4L2VideoDevice(media_->getEntityByName("vivid-000-vid-cap"));
+	if (video_->open())
+		return -ENODEV;
+
+	video_->bufferReady.connect(this, &VividCameraData::bufferReady);
+
+	/* Initialise the supported controls. */
+	const ControlInfoMap &controls = video_->controls();
+	ControlInfoMap::Map ctrls;
+
+	for (const auto &ctrl : controls) {
+		const ControlId *id;
+		ControlInfo info;
+
+		switch (ctrl.first->id()) {
+		case V4L2_CID_BRIGHTNESS:
+			id = &controls::Brightness;
+			info = ControlInfo{ { -1.0f }, { 1.0f }, { 0.0f } };
+			break;
+		case V4L2_CID_CONTRAST:
+			id = &controls::Contrast;
+			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
+			break;
+		case V4L2_CID_SATURATION:
+			id = &controls::Saturation;
+			info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } };
+			break;
+		default:
+			continue;
+		}
+
+		ctrls.emplace(id, info);
+	}
+
+	controlInfo_ = std::move(ctrls);
+
+	return 0;
+}
+
+void VividCameraData::bufferReady(FrameBuffer *buffer)
+{
+	Request *request = buffer->request();
+
+	pipe_->completeBuffer(camera_, request, buffer);
+	pipe_->completeRequest(camera_, request);
+}
+
+REGISTER_PIPELINE_HANDLER(PipelineHandlerVivid);
+
+} /* namespace libcamera */