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