[libcamera-devel] [PATCH v2 1/1] Add fake pipeline handler

Harvey Yang chenghaoyang at chromium.org
Fri Oct 14 12:36:12 CEST 2022


---
 meson_options.txt                       |   2 +-
 src/libcamera/pipeline/fake/fake.cpp    | 441 ++++++++++++++++++++++++
 src/libcamera/pipeline/fake/meson.build |   3 +
 test/camera/camera_reconfigure.cpp      |   2 +-
 4 files changed, 446 insertions(+), 2 deletions(-)
 create mode 100644 src/libcamera/pipeline/fake/fake.cpp
 create mode 100644 src/libcamera/pipeline/fake/meson.build

diff --git a/meson_options.txt b/meson_options.txt
index f1d67808..f08dfc5f 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -37,7 +37,7 @@ option('lc-compliance',
 
 option('pipelines',
         type : 'array',
-        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo', 'vimc'],
+        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo', 'vimc', 'fake'],
         description : 'Select which pipeline handlers to include')
 
 option('qcam',
diff --git a/src/libcamera/pipeline/fake/fake.cpp b/src/libcamera/pipeline/fake/fake.cpp
new file mode 100644
index 00000000..1ee24e3b
--- /dev/null
+++ b/src/libcamera/pipeline/fake/fake.cpp
@@ -0,0 +1,441 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * fake.cpp - Pipeline handler for fake cameras
+ */
+
+#include <algorithm>
+#include <iomanip>
+#include <memory>
+#include <queue>
+#include <set>
+#include <vector>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/utils.h>
+
+#include <libcamera/camera_manager.h>
+#include <libcamera/controls.h>
+#include <libcamera/control_ids.h>
+#include <libcamera/formats.h>
+#include <libcamera/property_ids.h>
+#include <libcamera/request.h>
+#include <libcamera/stream.h>
+
+#include "libcamera/internal/camera.h"
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/formats.h"
+#include "libcamera/internal/framebuffer.h"
+#include "libcamera/internal/media_device.h"
+#include "libcamera/internal/pipeline_handler.h"
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(Fake)
+
+uint64_t CurrentTimestamp()
+{
+	struct timespec ts;
+	if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
+		LOG(Fake, Error) << "Get clock time fails";
+		return 0;
+	}
+
+	return ts.tv_sec * 1'000'000'000LL + ts.tv_nsec;
+}
+
+static const ControlInfoMap::Map FakeControls = {
+	{ &controls::draft::PipelineDepth, ControlInfo(2, 3) },
+};
+
+class FakeCameraData : public Camera::Private
+{
+public:
+	struct Resolution {
+		Size size;
+		std::vector<int> frame_rates;
+		std::vector<PixelFormat> formats;
+	};
+
+	FakeCameraData(PipelineHandler *pipe)
+		: Camera::Private(pipe)
+	{
+	}
+
+	std::vector<Resolution> supportedResolutions_;
+
+	Stream outStream_;
+	Stream vfStream_;
+	Stream rawStream_;
+
+	bool started_ = false;
+};
+
+class FakeCameraConfiguration : public CameraConfiguration
+{
+public:
+	static constexpr unsigned int kBufferCount = 4; // 4~6
+	static constexpr unsigned int kMaxStreams = 3;
+
+	FakeCameraConfiguration(FakeCameraData *data);
+
+	Status validate() override;
+
+private:
+	/*
+	 * The FakeCameraData instance is guaranteed to be valid as long as the
+	 * corresponding Camera instance is valid. In order to borrow a
+	 * reference to the camera data, store a new reference to the camera.
+	 */
+	const FakeCameraData *data_;
+};
+
+class PipelineHandlerFake : public PipelineHandler
+{
+public:
+	PipelineHandlerFake(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, const ControlList *controls) override;
+	void stopDevice(Camera *camera) override;
+
+	int queueRequestDevice(Camera *camera, Request *request) override;
+
+	bool match(DeviceEnumerator *enumerator) override;
+
+private:
+	FakeCameraData *cameraData(Camera *camera)
+	{
+		return static_cast<FakeCameraData *>(camera->_d());
+	}
+
+	int registerCameras();
+
+	static bool registered_;
+};
+
+bool PipelineHandlerFake::registered_ = false;
+
+FakeCameraConfiguration::FakeCameraConfiguration(FakeCameraData *data)
+	: CameraConfiguration()
+{
+	data_ = data;
+}
+
+CameraConfiguration::Status FakeCameraConfiguration::validate()
+{
+	Status status = Valid;
+
+	if (config_.empty())
+		return Invalid;
+
+	/* Cap the number of entries to the available streams. */
+	if (config_.size() > kMaxStreams) {
+		config_.resize(kMaxStreams);
+		status = Adjusted;
+	}
+
+	/*
+	 * Validate the requested stream configuration and select the sensor
+	 * format by collecting the maximum RAW stream width and height and
+	 * picking the closest larger match.
+	 *
+	 * If no RAW stream is requested use the one of the largest YUV stream,
+	 * plus margin pixels for the IF and BDS rectangle to downscale.
+	 *
+	 * \todo Clarify the IF and BDS margins requirements.
+	 */
+	unsigned int rawCount = 0;
+	unsigned int yuvCount = 0;
+	Size rawRequirement;
+	Size maxYuvSize;
+	Size rawSize;
+
+	for (const StreamConfiguration &cfg : config_) {
+		const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat);
+
+		if (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW) {
+			rawCount++;
+			rawSize = std::max(rawSize, cfg.size);
+		} else {
+			yuvCount++;
+			maxYuvSize = std::max(maxYuvSize, cfg.size);
+			rawRequirement.expandTo(cfg.size);
+		}
+	}
+
+	// TODO: Check the configuration file.
+	if (rawCount > 1 || yuvCount > 2) {
+		LOG(Fake, Debug) << "Camera configuration not supported";
+		return Invalid;
+	}
+
+	/*
+	 * Adjust the configurations if needed and assign streams while
+	 * iterating them.
+	 */
+	bool mainOutputAvailable = true;
+	for (unsigned int i = 0; i < config_.size(); ++i) {
+		const PixelFormatInfo &info = PixelFormatInfo::info(config_[i].pixelFormat);
+		const StreamConfiguration originalCfg = config_[i];
+		StreamConfiguration *cfg = &config_[i];
+
+		LOG(Fake, Debug) << "Validating stream: " << config_[i].toString();
+
+		if (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW) {
+			/* Initialize the RAW stream with the CIO2 configuration. */
+			cfg->size = rawSize;
+			// TODO: check
+			cfg->pixelFormat = formats::SBGGR10;
+			cfg->bufferCount = FakeCameraConfiguration::kBufferCount;
+			cfg->stride = info.stride(cfg->size.width, 0, 64);
+			cfg->frameSize = info.frameSize(cfg->size, 64);
+			cfg->setStream(const_cast<Stream *>(&data_->rawStream_));
+
+			LOG(Fake, Debug) << "Assigned " << cfg->toString()
+					 << " to the raw stream";
+		} else {
+			/* Assign and configure the main and viewfinder outputs. */
+
+			cfg->pixelFormat = formats::NV12;
+			cfg->bufferCount = kBufferCount;
+			cfg->stride = info.stride(cfg->size.width, 0, 1);
+			cfg->frameSize = info.frameSize(cfg->size, 1);
+
+			/*
+			 * Use the main output stream in case only one stream is
+			 * requested or if the current configuration is the one
+			 * with the maximum YUV output size.
+			 */
+			if (mainOutputAvailable &&
+			    (originalCfg.size == maxYuvSize || yuvCount == 1)) {
+				cfg->setStream(const_cast<Stream *>(&data_->outStream_));
+				mainOutputAvailable = false;
+
+				LOG(Fake, Debug) << "Assigned " << cfg->toString()
+						 << " to the main output";
+			} else {
+				cfg->setStream(const_cast<Stream *>(&data_->vfStream_));
+
+				LOG(Fake, Debug) << "Assigned " << cfg->toString()
+						 << " to the viewfinder output";
+			}
+		}
+
+		if (cfg->pixelFormat != originalCfg.pixelFormat ||
+		    cfg->size != originalCfg.size) {
+			LOG(Fake, Debug)
+				<< "Stream " << i << " configuration adjusted to "
+				<< cfg->toString();
+			status = Adjusted;
+		}
+	}
+
+	return status;
+}
+
+PipelineHandlerFake::PipelineHandlerFake(CameraManager *manager)
+	: PipelineHandler(manager)
+{
+	// TODO: read the fake hal spec file.
+}
+
+CameraConfiguration *PipelineHandlerFake::generateConfiguration(Camera *camera,
+								const StreamRoles &roles)
+{
+	FakeCameraData *data = cameraData(camera);
+	FakeCameraConfiguration *config = new FakeCameraConfiguration(data);
+
+	if (roles.empty())
+		return config;
+
+	Size minSize, sensorResolution;
+	for (const auto& resolution : data->supportedResolutions_) {
+		if (minSize.isNull() || minSize > resolution.size)
+			minSize = resolution.size;
+
+		sensorResolution = std::max(sensorResolution, resolution.size);
+	}
+
+	for (const StreamRole role : roles) {
+		std::map<PixelFormat, std::vector<SizeRange>> streamFormats;
+		unsigned int bufferCount;
+		PixelFormat pixelFormat;
+		Size size;
+
+		switch (role) {
+		case StreamRole::StillCapture:
+			size = sensorResolution;
+			pixelFormat = formats::NV12;
+			bufferCount = FakeCameraConfiguration::kBufferCount;
+			streamFormats[pixelFormat] = { { minSize, size } };
+
+			break;
+
+		case StreamRole::Raw: {
+			// TODO: check
+			pixelFormat = formats::SBGGR10;
+			size = sensorResolution;
+			bufferCount = FakeCameraConfiguration::kBufferCount;
+			streamFormats[pixelFormat] = { { minSize, size } };
+
+			break;
+		}
+
+		case StreamRole::Viewfinder:
+		case StreamRole::VideoRecording: {
+			/*
+			 * Default viewfinder and videorecording to 1280x720,
+			 * capped to the maximum sensor resolution and aligned
+			 * to the ImgU output constraints.
+			 */
+			size = sensorResolution;
+			pixelFormat = formats::NV12;
+			bufferCount = FakeCameraConfiguration::kBufferCount;
+			streamFormats[pixelFormat] = { { minSize, size } };
+
+			break;
+		}
+
+		default:
+			LOG(Fake, Error)
+				<< "Requested stream role not supported: " << role;
+			delete config;
+			return nullptr;
+		}
+
+		StreamFormats formats(streamFormats);
+		StreamConfiguration cfg(formats);
+		cfg.size = size;
+		cfg.pixelFormat = pixelFormat;
+		cfg.bufferCount = bufferCount;
+		config->addConfiguration(cfg);
+	}
+
+	if (config->validate() == CameraConfiguration::Invalid)
+		return {};
+
+	return config;
+}
+
+int PipelineHandlerFake::configure(Camera *camera, CameraConfiguration *c)
+{
+	if (camera || c)
+		return 0;
+	return 0;
+}
+
+int PipelineHandlerFake::exportFrameBuffers(Camera *camera, Stream *stream,
+					    std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+{
+	// Assume it's never called.
+	LOG(Fake, Fatal) << "exportFrameBuffers should never be called";
+	if (camera || stream || buffers)
+		return -EINVAL;
+	return -EINVAL;
+}
+
+int PipelineHandlerFake::start(Camera *camera, [[maybe_unused]] const ControlList *controls)
+{
+	FakeCameraData *data = cameraData(camera);
+	data->started_ = true;
+
+	return 0;
+}
+
+void PipelineHandlerFake::stopDevice(Camera *camera)
+{
+	FakeCameraData *data = cameraData(camera);
+
+	data->started_ = false;
+}
+
+int PipelineHandlerFake::queueRequestDevice(Camera *camera, Request *request)
+{
+	if (!camera)
+		return -EINVAL;
+
+	for (auto it : request->buffers())
+		completeBuffer(request, it.second);
+
+	// TODO: request.metadata()
+	request->metadata().set(controls::SensorTimestamp, CurrentTimestamp());
+	completeRequest(request);
+
+	return 0;
+}
+
+bool PipelineHandlerFake::match(DeviceEnumerator *enumerator)
+{
+	// TODO: exhaust all devices in |enumerator|.
+	if (!enumerator)
+		LOG(Fake, Info) << "Invalid enumerator";
+
+	if (registered_)
+		return false;
+
+	registered_ = true;
+	return registerCameras() == 0;
+}
+
+/**
+ * \brief Initialise ImgU and CIO2 devices associated with cameras
+ *
+ * Initialise the two ImgU instances and create cameras with an associated
+ * CIO2 device instance.
+ *
+ * \return 0 on success or a negative error code for error or if no camera
+ * has been created
+ * \retval -ENODEV no camera has been created
+ */
+int PipelineHandlerFake::registerCameras()
+{
+	std::unique_ptr<FakeCameraData> data =
+		std::make_unique<FakeCameraData>(this);
+	std::set<Stream *> streams = {
+		&data->outStream_,
+		&data->vfStream_,
+		&data->rawStream_,
+	};
+
+	// TODO: Read from config or from IPC.
+	// TODO: Check with Han-lin: Can this function be called more than once?
+	data->supportedResolutions_.resize(2);
+	data->supportedResolutions_[0].size = Size(1920, 1080);
+	data->supportedResolutions_[0].frame_rates.push_back(30);
+	data->supportedResolutions_[0].formats.push_back(formats::NV12);
+	data->supportedResolutions_[0].formats.push_back(formats::MJPEG);
+	data->supportedResolutions_[1].size = Size(1280, 720);
+	data->supportedResolutions_[1].frame_rates.push_back(30);
+	data->supportedResolutions_[1].frame_rates.push_back(60);
+	data->supportedResolutions_[1].formats.push_back(formats::NV12);
+	data->supportedResolutions_[1].formats.push_back(formats::MJPEG);
+
+	// TODO: Assign different locations for different cameras based on config.
+	data->properties_.set(properties::Location, properties::CameraLocationFront);
+	data->properties_.set(properties::PixelArrayActiveAreas, { Rectangle(Size(1920, 1080)) });
+
+	// TODO: Set FrameDurationLimits based on config.
+	ControlInfoMap::Map controls = FakeControls;
+	int64_t min_frame_duration = 30, max_frame_duration = 60;
+	controls[&controls::FrameDurationLimits] = ControlInfo(min_frame_duration, max_frame_duration);
+	data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls);
+
+	std::shared_ptr<Camera> camera =
+		Camera::create(std::move(data), "\\_SB_.PCI0.I2C4.CAM1" /* cameraId */, streams);
+
+	manager_->addCamera(std::move(camera), {});
+
+	return 0;
+}
+
+REGISTER_PIPELINE_HANDLER(PipelineHandlerFake)
+
+} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/fake/meson.build b/src/libcamera/pipeline/fake/meson.build
new file mode 100644
index 00000000..13980b4a
--- /dev/null
+++ b/src/libcamera/pipeline/fake/meson.build
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: CC0-1.0
+
+libcamera_sources += files([ 'fake.cpp' ])
diff --git a/test/camera/camera_reconfigure.cpp b/test/camera/camera_reconfigure.cpp
index d12e2413..06c87730 100644
--- a/test/camera/camera_reconfigure.cpp
+++ b/test/camera/camera_reconfigure.cpp
@@ -98,7 +98,7 @@ private:
 				return TestFail;
 			}
 
-			requests_.push_back(move(request));
+			requests_.push_back(std::move(request));
 		}
 
 		camera_->requestCompleted.connect(this, &CameraReconfigure::requestComplete);
-- 
2.38.0.413.g74048e4d9e-goog



More information about the libcamera-devel mailing list