1From 5a2566de4567404f57b70af4c6fbb09f6f617313 Mon Sep 17 00:00:00 2001
2From: Jeffy Chen <jeffy.chen@rock-chips.com>
3Date: Tue, 29 Nov 2022 17:18:23 +0800
4Subject: [PATCH 3/3] HACK: pipeline: Support custom pipeline
5
6Tested on RK3588 EVB with:
7export LIBCAMERA_CUSTOM_DRIVERS=has:rkisp
8export LIBCAMERA_CUSTOM_DEFAULT=has:mainpath
9export LIBCAMERA_CUSTOM_FORMAT=NV12
10export LIBCAMERA_CUSTOM_BUF_CNT=4
11gst-launch-1.0 libcamerasrc ! waylandsink
12
13Signed-off-by: Jeffy Chen <jeffy.chen@rock-chips.com>
14---
15 meson_options.txt                             |   2 +-
16 src/libcamera/device_enumerator.cpp           |   8 +-
17 src/libcamera/pipeline/custom/custom.cpp      | 415 ++++++++++++++++++
18 src/libcamera/pipeline/custom/meson.build     |   5 +
19 test/pipeline/custom/custom_pipeline_test.cpp | 110 +++++
20 test/pipeline/custom/meson.build              |  14 +
21 test/pipeline/meson.build                     |   1 +
22 7 files changed, 552 insertions(+), 3 deletions(-)
23 create mode 100644 src/libcamera/pipeline/custom/custom.cpp
24 create mode 100644 src/libcamera/pipeline/custom/meson.build
25 create mode 100644 test/pipeline/custom/custom_pipeline_test.cpp
26 create mode 100644 test/pipeline/custom/meson.build
27
28diff --git a/meson_options.txt b/meson_options.txt
29index 7a9aecf..65f3ffc 100644
30--- a/meson_options.txt
31+++ b/meson_options.txt
32@@ -37,7 +37,7 @@ option('lc-compliance',
33
34 option('pipelines',
35         type : 'array',
36-        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo', 'vimc'],
37+        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo', 'vimc', 'custom'],
38         description : 'Select which pipeline handlers to include')
39
40 option('qcam',
41diff --git a/src/libcamera/device_enumerator.cpp b/src/libcamera/device_enumerator.cpp
42index d125805..06d1707 100644
43--- a/src/libcamera/device_enumerator.cpp
44+++ b/src/libcamera/device_enumerator.cpp
45@@ -93,8 +93,12 @@ void DeviceMatch::add(const std::string &entity)
46  */
47 bool DeviceMatch::match(const MediaDevice *device) const
48 {
49-	if (driver_ != device->driver())
50-		return false;
51+	const std::string driver = device->driver();
52+	if (driver_ != driver) {
53+		if (driver_.find("has:") != 0 ||
54+		    (driver.find(driver_.substr(4)) == std::string::npos))
55+			return false;
56+	}
57
58 	for (const std::string &name : entities_) {
59 		bool found = false;
60diff --git a/src/libcamera/pipeline/custom/custom.cpp b/src/libcamera/pipeline/custom/custom.cpp
61new file mode 100644
62index 0000000..bbf5259
63--- /dev/null
64+++ b/src/libcamera/pipeline/custom/custom.cpp
65@@ -0,0 +1,415 @@
66+/* SPDX-License-Identifier: LGPL-2.1-or-later */
67+/*
68+ * Copyright (C) 2019, Google Inc.
69+ * Copyright (C) 2022, Rockchip Electronics Co., Ltd
70+ *
71+ * Based on src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
72+ *
73+ * custom.cpp - Pipeline handler for custom devices
74+ */
75+
76+#include <iostream>
77+#include <string>
78+
79+#include <libcamera/base/log.h>
80+#include <libcamera/base/utils.h>
81+
82+#include <libcamera/camera.h>
83+#include <libcamera/control_ids.h>
84+#include <libcamera/formats.h>
85+#include <libcamera/property_ids.h>
86+#include <libcamera/request.h>
87+#include <libcamera/stream.h>
88+
89+#include "libcamera/internal/camera.h"
90+#include "libcamera/internal/device_enumerator.h"
91+#include "libcamera/internal/media_device.h"
92+#include "libcamera/internal/pipeline_handler.h"
93+#include "libcamera/internal/v4l2_videodevice.h"
94+
95+#define CUSTOM_DRIVERS_ENV "LIBCAMERA_CUSTOM_DRIVERS"
96+#define CUSTOM_DEFAULT_ENV "LIBCAMERA_CUSTOM_DEFAULT"
97+#define CUSTOM_BUF_CNT_ENV "LIBCAMERA_CUSTOM_BUF_CNT"
98+#define CUSTOM_FORMAT_ENV "LIBCAMERA_CUSTOM_FORMAT"
99+#define CUSTOM_FORMAT_NONE formats::R8
100+
101+using namespace std;
102+
103+namespace libcamera {
104+
105+LOG_DEFINE_CATEGORY(Custom)
106+
107+class CustomCameraData : public Camera::Private
108+{
109+public:
110+	CustomCameraData(PipelineHandler *pipe)
111+		: Camera::Private(pipe)
112+	{
113+	}
114+
115+	MediaEntity *getEntity(MediaDevice *media);
116+	int init(MediaDevice *media);
117+	void bufferReady(FrameBuffer *buffer);
118+
119+	std::unique_ptr<V4L2VideoDevice> video_;
120+	Stream stream_;
121+};
122+
123+class CustomCameraConfiguration : public CameraConfiguration
124+{
125+public:
126+	CustomCameraConfiguration(CustomCameraData *data);
127+
128+	Status validate() override;
129+
130+private:
131+	CustomCameraData *data_;
132+};
133+
134+class PipelineHandlerCustom : public PipelineHandler
135+{
136+public:
137+	PipelineHandlerCustom(CameraManager *manager);
138+
139+	CameraConfiguration *generateConfiguration(Camera *camera,
140+		const StreamRoles &roles) override;
141+	int configure(Camera *camera, CameraConfiguration *config) override;
142+
143+	int exportFrameBuffers(Camera *camera, Stream *stream,
144+			       std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;
145+
146+	int start(Camera *camera, const ControlList *controls) override;
147+	void stopDevice(Camera *camera) override;
148+
149+	int queueRequestDevice(Camera *camera, Request *request) override;
150+
151+	bool match(DeviceEnumerator *enumerator) override;
152+
153+private:
154+	CustomCameraData *cameraData(Camera *camera)
155+	{
156+		return static_cast<CustomCameraData *>(camera->_d());
157+	}
158+
159+	int bufferCount_;
160+	PixelFormat pixelFormat_;
161+};
162+
163+CustomCameraConfiguration::CustomCameraConfiguration(CustomCameraData *data)
164+	: CameraConfiguration(), data_(data)
165+{
166+}
167+
168+CameraConfiguration::Status CustomCameraConfiguration::validate()
169+{
170+	Status status = Valid;
171+
172+	if (config_.empty())
173+		return Invalid;
174+
175+	if (transform != Transform::Identity) {
176+		transform = Transform::Identity;
177+		status = Adjusted;
178+	}
179+
180+	/* Cap the number of entries to the available streams. */
181+	if (config_.size() > 1) {
182+		config_.resize(1);
183+		status = Adjusted;
184+	}
185+
186+	StreamConfiguration &cfg = config_[0];
187+	const StreamFormats &formats = cfg.formats();
188+	const PixelFormat pixelFormat = cfg.pixelFormat;
189+	const Size size = cfg.size;
190+
191+	const std::vector<PixelFormat> pixelFormats = formats.pixelformats();
192+	auto iter = std::find(pixelFormats.begin(), pixelFormats.end(), pixelFormat);
193+	if (iter == pixelFormats.end()) {
194+		cfg.pixelFormat = pixelFormats.front();
195+		LOG(Custom, Debug)
196+			<< "Adjusting pixel format from " << pixelFormat
197+			<< " to " << cfg.pixelFormat;
198+		status = Adjusted;
199+	}
200+
201+	const std::vector<Size> &formatSizes = formats.sizes(cfg.pixelFormat);
202+	cfg.size = formatSizes.front();
203+	for (const Size &formatsSize : formatSizes) {
204+		if (formatsSize > size)
205+			break;
206+
207+		cfg.size = formatsSize;
208+	}
209+
210+	if (cfg.size != size) {
211+		LOG(Custom, Debug)
212+			<< "Adjusting size from " << size << " to " << cfg.size;
213+		status = Adjusted;
214+	}
215+
216+	V4L2DeviceFormat format;
217+	format.fourcc = V4L2PixelFormat::fromPixelFormat(cfg.pixelFormat);
218+	format.size = cfg.size;
219+
220+	int ret = data_->video_->tryFormat(&format);
221+	if (ret)
222+		return Invalid;
223+
224+	cfg.stride = format.planes[0].bpl;
225+	cfg.frameSize = format.planes[0].size;
226+
227+	return status;
228+}
229+
230+PipelineHandlerCustom::PipelineHandlerCustom(CameraManager *manager)
231+	: PipelineHandler(manager), bufferCount_(4),
232+	pixelFormat_(CUSTOM_FORMAT_NONE)
233+{
234+	const char *bufferCount = utils::secure_getenv(CUSTOM_BUF_CNT_ENV);
235+	if (bufferCount)
236+		bufferCount_ = atoi(bufferCount);
237+
238+	const char *pixelFormat = utils::secure_getenv(CUSTOM_FORMAT_ENV);
239+	if (pixelFormat) {
240+		if (!strcmp(pixelFormat, "NV12"))
241+			pixelFormat_ = formats::NV12;
242+		else if (!strcmp(pixelFormat, "YUV420"))
243+			pixelFormat_ = formats::YUV420;
244+		else if (!strcmp(pixelFormat, "NV16"))
245+			pixelFormat_ = formats::NV16;
246+		else if (!strcmp(pixelFormat, "YUYV"))
247+			pixelFormat_ = formats::YUYV;
248+	}
249+}
250+
251+CameraConfiguration *PipelineHandlerCustom::generateConfiguration(Camera *camera,
252+	const StreamRoles &roles)
253+{
254+	CustomCameraData *data = cameraData(camera);
255+	CameraConfiguration *config = new CustomCameraConfiguration(data);
256+
257+	if (roles.empty())
258+		return config;
259+
260+	V4L2VideoDevice::Formats v4l2Formats = data->video_->formats();
261+	std::map<PixelFormat, std::vector<SizeRange>> deviceFormats;
262+	for (const auto &format : v4l2Formats) {
263+		PixelFormat pixelFormat = format.first.toPixelFormat();
264+		if (pixelFormat.isValid() &&
265+		    (pixelFormat_ == CUSTOM_FORMAT_NONE ||
266+		     pixelFormat == pixelFormat_))
267+			deviceFormats[pixelFormat] = format.second;
268+	}
269+
270+	StreamFormats formats(deviceFormats);
271+	StreamConfiguration cfg(formats);
272+
273+	if (pixelFormat_ != CUSTOM_FORMAT_NONE)
274+		cfg.pixelFormat = pixelFormat_;
275+	else
276+		cfg.pixelFormat = formats::NV12;
277+
278+	cfg.size = formats.sizes(cfg.pixelFormat).back();
279+	cfg.bufferCount = bufferCount_;
280+
281+	config->addConfiguration(cfg);
282+
283+	config->validate();
284+
285+	return config;
286+}
287+
288+int PipelineHandlerCustom::configure(Camera *camera, CameraConfiguration *config)
289+{
290+	CustomCameraData *data = cameraData(camera);
291+	StreamConfiguration &cfg = config->at(0);
292+	int ret;
293+
294+	V4L2DeviceFormat format;
295+	format.fourcc = V4L2PixelFormat::fromPixelFormat(cfg.pixelFormat);
296+	format.size = cfg.size;
297+
298+	ret = data->video_->setFormat(&format);
299+	if (ret)
300+		return ret;
301+
302+	if (format.size != cfg.size ||
303+	    format.fourcc != V4L2PixelFormat::fromPixelFormat(cfg.pixelFormat))
304+		return -EINVAL;
305+
306+	cfg.setStream(&data->stream_);
307+
308+	return 0;
309+}
310+
311+int PipelineHandlerCustom::exportFrameBuffers(Camera *camera, Stream *stream,
312+					   std::vector<std::unique_ptr<FrameBuffer>> *buffers)
313+{
314+	CustomCameraData *data = cameraData(camera);
315+	unsigned int count = stream->configuration().bufferCount;
316+
317+	return data->video_->exportBuffers(count, buffers);
318+}
319+
320+int PipelineHandlerCustom::start(Camera *camera, [[maybe_unused]] const ControlList *controls)
321+{
322+	CustomCameraData *data = cameraData(camera);
323+	unsigned int count = data->stream_.configuration().bufferCount;
324+
325+	int ret = data->video_->importBuffers(count);
326+	if (ret < 0)
327+		return ret;
328+
329+	ret = data->video_->streamOn();
330+	if (ret < 0) {
331+		data->video_->releaseBuffers();
332+		return ret;
333+	}
334+
335+	return 0;
336+}
337+
338+void PipelineHandlerCustom::stopDevice(Camera *camera)
339+{
340+	CustomCameraData *data = cameraData(camera);
341+	data->video_->streamOff();
342+	data->video_->releaseBuffers();
343+}
344+
345+int PipelineHandlerCustom::queueRequestDevice(Camera *camera, Request *request)
346+{
347+	CustomCameraData *data = cameraData(camera);
348+	int ret;
349+
350+	FrameBuffer *buffer = request->findBuffer(&data->stream_);
351+	if (!buffer) {
352+		LOG(Custom, Error)
353+			<< "Attempt to queue request with invalid stream";
354+
355+		return -ENOENT;
356+	}
357+
358+	ret = data->video_->queueBuffer(buffer);
359+	if (ret < 0)
360+		return ret;
361+
362+	return 0;
363+}
364+
365+bool PipelineHandlerCustom::match(DeviceEnumerator *enumerator)
366+{
367+	MediaDevice *media;
368+	bool found = false;
369+
370+	const char *drivers = utils::secure_getenv(CUSTOM_DRIVERS_ENV);
371+	if (!drivers)
372+		return false;
373+
374+	istringstream in(drivers);
375+	string driver;
376+
377+	while (in >> driver) {
378+		DeviceMatch dm(driver);
379+		media = acquireMediaDevice(enumerator, dm);
380+		if (!media)
381+			continue;
382+
383+		std::unique_ptr<CustomCameraData> data =
384+			std::make_unique<CustomCameraData>(this);
385+
386+		if (data->init(media))
387+			continue;
388+
389+		/* Create and register the camera. */
390+		std::string id = media->model();
391+		if (id.empty()) {
392+			LOG(Custom, Error) << "Failed to get camera ID";
393+			continue;
394+		}
395+
396+		std::set<Stream *> streams{ &data->stream_ };
397+		std::shared_ptr<Camera> camera =
398+			Camera::create(std::move(data), id, streams);
399+		registerCamera(std::move(camera));
400+
401+		found = true;
402+	}
403+
404+	return found;
405+}
406+
407+MediaEntity *CustomCameraData::getEntity(MediaDevice *media)
408+{
409+	const std::vector<MediaEntity *> &entities = media->entities();
410+
411+	if (utils::secure_getenv(CUSTOM_DEFAULT_ENV)) {
412+		auto iter = std::find_if(entities.begin(), entities.end(),
413+					 [](MediaEntity *e) {
414+					 string name = utils::secure_getenv(CUSTOM_DEFAULT_ENV);
415+					 if (e->name() == name) return true;
416+					 if (name.find("has:") != 0) return false;
417+					 return e->name().find(name.substr(4)) != string::npos;
418+					 });
419+		return iter == entities.end() ? NULL : *iter;
420+	} else {
421+		auto iter = std::find_if(entities.begin(), entities.end(),
422+					 [](MediaEntity *e) {
423+					 return e->function() == MEDIA_ENT_F_IO_V4L;
424+					 });
425+		return iter == entities.end() ? NULL : *iter;
426+	}
427+}
428+
429+int CustomCameraData::init(MediaDevice *media)
430+{
431+	/* Locate and initialise the camera data with the default video node. */
432+	MediaEntity *entity = getEntity(media);
433+	if (!entity) {
434+		LOG(Custom, Error) << "Could not find default video device";
435+		return -ENODEV;
436+	}
437+
438+	/* Create and open the video device. */
439+	video_ = std::make_unique<V4L2VideoDevice>(entity);
440+	int ret = video_->open();
441+	if (ret)
442+		return ret;
443+
444+	video_->bufferReady.connect(this, &CustomCameraData::bufferReady);
445+
446+	properties_.set(properties::Model, utils::toAscii(media->model()));
447+
448+	/*
449+	 * Get the current format in order to initialize the sensor array
450+	 * properties.
451+	 */
452+	Size resolution;
453+	for (const auto &it : video_->formats()) {
454+		const std::vector<SizeRange> &sizeRanges = it.second;
455+		for (const SizeRange &sizeRange : sizeRanges) {
456+			if (sizeRange.max > resolution)
457+				resolution = sizeRange.max;
458+		}
459+	}
460+
461+	properties_.set(properties::PixelArraySize, resolution);
462+	properties_.set(properties::PixelArrayActiveAreas, { Rectangle(resolution) });
463+	return 0;
464+}
465+
466+void CustomCameraData::bufferReady(FrameBuffer *buffer)
467+{
468+	Request *request = buffer->request();
469+
470+	/* \todo Use the Custom metadata to calculate a more precise timestamp */
471+	request->metadata().set(controls::SensorTimestamp,
472+				buffer->metadata().timestamp);
473+
474+	pipe()->completeBuffer(request, buffer);
475+	pipe()->completeRequest(request);
476+}
477+
478+REGISTER_PIPELINE_HANDLER(PipelineHandlerCustom)
479+
480+} /* namespace libcamera */
481diff --git a/src/libcamera/pipeline/custom/meson.build b/src/libcamera/pipeline/custom/meson.build
482new file mode 100644
483index 0000000..9d2ee94
484--- /dev/null
485+++ b/src/libcamera/pipeline/custom/meson.build
486@@ -0,0 +1,5 @@
487+# SPDX-License-Identifier: CC0-1.0
488+
489+libcamera_sources += files([
490+    'custom.cpp',
491+])
492diff --git a/test/pipeline/custom/custom_pipeline_test.cpp b/test/pipeline/custom/custom_pipeline_test.cpp
493new file mode 100644
494index 0000000..69d67d9
495--- /dev/null
496+++ b/test/pipeline/custom/custom_pipeline_test.cpp
497@@ -0,0 +1,110 @@
498+/* SPDX-License-Identifier: GPL-2.0-or-later */
499+/*
500+ * Copyright (C) 2022, Rockchip Electronics Co., Ltd
501+ *
502+ * custom_pipeline_test.cpp - Custom pipeline test
503+ */
504+
505+#include <iostream>
506+
507+#include <sys/types.h>
508+#include <unistd.h>
509+
510+#include <libcamera/camera.h>
511+#include <libcamera/camera_manager.h>
512+
513+#include "libcamera/internal/device_enumerator.h"
514+#include "libcamera/internal/media_device.h"
515+
516+#include "test.h"
517+
518+#define CUSTOM_DRIVERS_ENV "LIBCAMERA_CUSTOM_DRIVERS"
519+
520+using namespace std;
521+using namespace libcamera;
522+
523+/*
524+ * Verify that the custom pipeline handler gets matched and cameras
525+ * are enumerated correctly.
526+ *
527+ * The test lists all cameras registered in the system, if any camera is
528+ * available at all.
529+ */
530+class CustomPipelineTest : public Test
531+{
532+protected:
533+	int init();
534+	int run();
535+	void cleanup();
536+
537+private:
538+	CameraManager *cameraManager_;
539+	unsigned int sensors_;
540+};
541+
542+int CustomPipelineTest::init()
543+{
544+	unique_ptr<DeviceEnumerator> enumerator = DeviceEnumerator::create();
545+	if (!enumerator) {
546+		cerr << "Failed to create device enumerator" << endl;
547+		return TestFail;
548+	}
549+
550+	if (enumerator->enumerate()) {
551+		cerr << "Failed to enumerate media devices" << endl;
552+		return TestFail;
553+	}
554+
555+	const char *drivers = utils::secure_getenv(CUSTOM_DRIVERS_ENV);
556+	if (!drivers) {
557+		cerr << "Needs env: " CUSTOM_DRIVERS_ENV << endl;
558+		return TestFail;
559+	}
560+
561+	istringstream in(drivers);
562+	string driver;
563+	bool found = false;
564+
565+	while (in >> driver) {
566+		DeviceMatch dm(driver);
567+		std::shared_ptr<MediaDevice> device = enumerator->search(dm);
568+		if (device)
569+			found = true;
570+	}
571+
572+	if (!found) {
573+		cerr << "Failed to find any camera: test skip" << endl;
574+		return TestSkip;
575+	}
576+
577+	cameraManager_ = new CameraManager();
578+	int ret = cameraManager_->start();
579+	if (ret) {
580+		cerr << "Failed to start the CameraManager" << endl;
581+		return TestFail;
582+	}
583+
584+	return 0;
585+}
586+
587+int CustomPipelineTest::run()
588+{
589+	auto cameras = cameraManager_->cameras();
590+	for (const std::shared_ptr<Camera> &cam : cameras)
591+		cout << "Found camera '" << cam->id() << "'" << endl;
592+
593+	if (!cameras.size()) {
594+		cerr << "no cameras registered" << endl;
595+		return TestFail;
596+	}
597+
598+	return TestPass;
599+}
600+
601+void CustomPipelineTest::cleanup()
602+{
603+	cameraManager_->stop();
604+	delete cameraManager_;
605+}
606+
607+TEST_REGISTER(CustomPipelineTest)
608diff --git a/test/pipeline/custom/meson.build b/test/pipeline/custom/meson.build
609new file mode 100644
610index 0000000..47305c9
611--- /dev/null
612+++ b/test/pipeline/custom/meson.build
613@@ -0,0 +1,14 @@
614+# SPDX-License-Identifier: CC0-1.0
615+
616+custom_test = [
617+    ['custom_pipeline_test',            'custom_pipeline_test.cpp'],
618+]
619+
620+foreach t : custom_test
621+    exe = executable(t[0], t[1],
622+                     dependencies : libcamera_private,
623+                     link_with : test_libraries,
624+                     include_directories : test_includes_internal)
625+
626+    test(t[0], exe, suite : 'custom', is_parallel : false)
627+endforeach
628diff --git a/test/pipeline/meson.build b/test/pipeline/meson.build
629index 6e7901f..a0a3544 100644
630--- a/test/pipeline/meson.build
631+++ b/test/pipeline/meson.build
632@@ -2,3 +2,4 @@
633
634 subdir('ipu3')
635 subdir('rkisp1')
636+subdir('custom')
637--
6382.20.1
639
640