summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHarvey Yang <chenghaoyang@chromium.org>2024-10-22 07:43:39 +0000
committerKieran Bingham <kieran.bingham@ideasonboard.com>2024-11-20 10:47:27 +0000
commit3a884ebfe4cdd0a0fd9b9d3202d3369c737e381b (patch)
tree8e074ec6ac57418e1ea031592737b8a73b445cde /src
parent670bbf3dc2b1099bd12cdcea0e63a26637db6d00 (diff)
libcamera: virtual: Add VirtualPipelineHandler
Add VirtualPipelineHandler for more unit tests and verfiy libcamera infrastructure works on devices without using hardware cameras. Signed-off-by: Harvey Yang <chenghaoyang@chromium.org> Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Diffstat (limited to 'src')
-rw-r--r--src/libcamera/pipeline/virtual/meson.build5
-rw-r--r--src/libcamera/pipeline/virtual/virtual.cpp337
-rw-r--r--src/libcamera/pipeline/virtual/virtual.h45
3 files changed, 387 insertions, 0 deletions
diff --git a/src/libcamera/pipeline/virtual/meson.build b/src/libcamera/pipeline/virtual/meson.build
new file mode 100644
index 00000000..ada1b335
--- /dev/null
+++ b/src/libcamera/pipeline/virtual/meson.build
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: CC0-1.0
+
+libcamera_internal_sources += files([
+ 'virtual.cpp',
+])
diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp
new file mode 100644
index 00000000..13107874
--- /dev/null
+++ b/src/libcamera/pipeline/virtual/virtual.cpp
@@ -0,0 +1,337 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Google Inc.
+ *
+ * Pipeline handler for virtual cameras
+ */
+
+#include "virtual.h"
+
+#include <algorithm>
+#include <array>
+#include <chrono>
+#include <errno.h>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <stdint.h>
+#include <string>
+#include <time.h>
+#include <utility>
+#include <vector>
+
+#include <libcamera/base/flags.h>
+#include <libcamera/base/log.h>
+
+#include <libcamera/control_ids.h>
+#include <libcamera/controls.h>
+#include <libcamera/formats.h>
+#include <libcamera/pixel_format.h>
+#include <libcamera/property_ids.h>
+#include <libcamera/request.h>
+
+#include "libcamera/internal/camera.h"
+#include "libcamera/internal/dma_buf_allocator.h"
+#include "libcamera/internal/formats.h"
+#include "libcamera/internal/pipeline_handler.h"
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(Virtual)
+
+namespace {
+
+uint64_t currentTimestamp()
+{
+ const auto now = std::chrono::steady_clock::now();
+ auto nsecs = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ now.time_since_epoch());
+
+ return nsecs.count();
+}
+
+} /* namespace */
+
+class VirtualCameraConfiguration : public CameraConfiguration
+{
+public:
+ static constexpr unsigned int kBufferCount = 4;
+
+ VirtualCameraConfiguration(VirtualCameraData *data);
+
+ Status validate() override;
+
+private:
+ const VirtualCameraData *data_;
+};
+
+class PipelineHandlerVirtual : public PipelineHandler
+{
+public:
+ PipelineHandlerVirtual(CameraManager *manager);
+ ~PipelineHandlerVirtual();
+
+ std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,
+ Span<const StreamRole> 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:
+ static bool created_;
+
+ VirtualCameraData *cameraData(Camera *camera)
+ {
+ return static_cast<VirtualCameraData *>(camera->_d());
+ }
+
+ DmaBufAllocator dmaBufAllocator_;
+
+ bool resetCreated_ = false;
+};
+
+VirtualCameraData::VirtualCameraData(PipelineHandler *pipe,
+ const std::vector<Resolution> &supportedResolutions)
+ : Camera::Private(pipe), supportedResolutions_(supportedResolutions)
+{
+ for (const auto &resolution : supportedResolutions_) {
+ if (minResolutionSize_.isNull() || minResolutionSize_ > resolution.size)
+ minResolutionSize_ = resolution.size;
+
+ maxResolutionSize_ = std::max(maxResolutionSize_, resolution.size);
+ }
+
+ /* \todo Support multiple streams and pass multi_stream_test */
+ streamConfigs_.resize(kMaxStream);
+}
+
+VirtualCameraConfiguration::VirtualCameraConfiguration(VirtualCameraData *data)
+ : CameraConfiguration(), data_(data)
+{
+}
+
+CameraConfiguration::Status VirtualCameraConfiguration::validate()
+{
+ Status status = Valid;
+
+ if (config_.empty()) {
+ LOG(Virtual, Error) << "Empty config";
+ return Invalid;
+ }
+
+ /* Only one stream is supported */
+ if (config_.size() > VirtualCameraData::kMaxStream) {
+ config_.resize(VirtualCameraData::kMaxStream);
+ status = Adjusted;
+ }
+
+ for (StreamConfiguration &cfg : config_) {
+ bool adjusted = false;
+ bool found = false;
+ for (const auto &resolution : data_->supportedResolutions_) {
+ if (resolution.size.width == cfg.size.width &&
+ resolution.size.height == cfg.size.height) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ /*
+ * \todo It's a pipeline's decision to choose a
+ * resolution when the exact one is not supported.
+ * Defining the default logic in PipelineHandler to
+ * find the closest resolution would be nice.
+ */
+ cfg.size = data_->maxResolutionSize_;
+ status = Adjusted;
+ adjusted = true;
+ }
+
+ if (cfg.pixelFormat != formats::NV12) {
+ cfg.pixelFormat = formats::NV12;
+ status = Adjusted;
+ adjusted = true;
+ }
+
+ if (adjusted)
+ LOG(Virtual, Info)
+ << "Stream configuration adjusted to " << cfg.toString();
+
+ const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat);
+ cfg.stride = info.stride(cfg.size.width, 0, 1);
+ cfg.frameSize = info.frameSize(cfg.size, 1);
+
+ cfg.bufferCount = VirtualCameraConfiguration::kBufferCount;
+ }
+
+ return status;
+}
+
+/* static */
+bool PipelineHandlerVirtual::created_ = false;
+
+PipelineHandlerVirtual::PipelineHandlerVirtual(CameraManager *manager)
+ : PipelineHandler(manager),
+ dmaBufAllocator_(DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap |
+ DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap |
+ DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf)
+{
+}
+
+PipelineHandlerVirtual::~PipelineHandlerVirtual()
+{
+ if (resetCreated_)
+ created_ = false;
+}
+
+std::unique_ptr<CameraConfiguration>
+PipelineHandlerVirtual::generateConfiguration(Camera *camera,
+ Span<const StreamRole> roles)
+{
+ VirtualCameraData *data = cameraData(camera);
+ auto config = std::make_unique<VirtualCameraConfiguration>(data);
+
+ if (roles.empty())
+ return config;
+
+ for (const StreamRole role : roles) {
+ switch (role) {
+ case StreamRole::StillCapture:
+ case StreamRole::VideoRecording:
+ case StreamRole::Viewfinder:
+ break;
+
+ case StreamRole::Raw:
+ default:
+ LOG(Virtual, Error)
+ << "Requested stream role not supported: " << role;
+ config.reset();
+ return config;
+ }
+
+ std::map<PixelFormat, std::vector<SizeRange>> streamFormats;
+ PixelFormat pixelFormat = formats::NV12;
+ streamFormats[pixelFormat] = { { data->minResolutionSize_,
+ data->maxResolutionSize_ } };
+ StreamFormats formats(streamFormats);
+ StreamConfiguration cfg(formats);
+ cfg.pixelFormat = pixelFormat;
+ cfg.size = data->maxResolutionSize_;
+ cfg.bufferCount = VirtualCameraConfiguration::kBufferCount;
+
+ config->addConfiguration(cfg);
+ }
+
+ ASSERT(config->validate() != CameraConfiguration::Invalid);
+
+ return config;
+}
+
+int PipelineHandlerVirtual::configure(Camera *camera,
+ CameraConfiguration *config)
+{
+ VirtualCameraData *data = cameraData(camera);
+ for (auto [i, c] : utils::enumerate(*config))
+ c.setStream(&data->streamConfigs_[i].stream);
+
+ return 0;
+}
+
+int PipelineHandlerVirtual::exportFrameBuffers([[maybe_unused]] Camera *camera,
+ Stream *stream,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+{
+ if (!dmaBufAllocator_.isValid())
+ return -ENOBUFS;
+
+ const StreamConfiguration &config = stream->configuration();
+
+ auto info = PixelFormatInfo::info(config.pixelFormat);
+
+ std::vector<unsigned int> planeSizes;
+ for (size_t i = 0; i < info.planes.size(); ++i)
+ planeSizes.push_back(info.planeSize(config.size, i));
+
+ return dmaBufAllocator_.exportBuffers(config.bufferCount, planeSizes, buffers);
+}
+
+int PipelineHandlerVirtual::start([[maybe_unused]] Camera *camera,
+ [[maybe_unused]] const ControlList *controls)
+{
+ return 0;
+}
+
+void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera)
+{
+}
+
+int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera,
+ Request *request)
+{
+ for (auto it : request->buffers())
+ completeBuffer(request, it.second);
+
+ request->metadata().set(controls::SensorTimestamp, currentTimestamp());
+ completeRequest(request);
+
+ return 0;
+}
+
+bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator *enumerator)
+{
+ if (created_)
+ return false;
+
+ created_ = true;
+
+ /* \todo Add virtual cameras according to a config file. */
+
+ std::vector<VirtualCameraData::Resolution> supportedResolutions;
+ supportedResolutions.resize(2);
+ supportedResolutions[0] = { .size = Size(1920, 1080), .frameRates = { 30 } };
+ supportedResolutions[1] = { .size = Size(1280, 720), .frameRates = { 30 } };
+
+ std::unique_ptr<VirtualCameraData> data =
+ std::make_unique<VirtualCameraData>(this, supportedResolutions);
+
+ data->properties_.set(properties::Location, properties::CameraLocationFront);
+ data->properties_.set(properties::Model, "Virtual Video Device");
+ data->properties_.set(properties::PixelArrayActiveAreas, { Rectangle(Size(1920, 1080)) });
+
+ /* \todo Set FrameDurationLimits based on config. */
+ ControlInfoMap::Map controls;
+ int64_t min_frame_duration = 33333, max_frame_duration = 33333;
+ controls[&controls::FrameDurationLimits] = ControlInfo(min_frame_duration, max_frame_duration);
+ std::vector<ControlValue> supportedFaceDetectModes{
+ static_cast<int32_t>(controls::draft::FaceDetectModeOff),
+ };
+ controls[&controls::draft::FaceDetectMode] = ControlInfo(supportedFaceDetectModes);
+ data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls);
+
+ /* Create and register the camera. */
+ std::set<Stream *> streams;
+ for (auto &streamConfig : data->streamConfigs_)
+ streams.insert(&streamConfig.stream);
+
+ const std::string id = "Virtual0";
+ std::shared_ptr<Camera> camera = Camera::create(std::move(data), id, streams);
+ registerCamera(std::move(camera));
+
+ resetCreated_ = true;
+
+ return true;
+}
+
+REGISTER_PIPELINE_HANDLER(PipelineHandlerVirtual, "virtual")
+
+} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h
new file mode 100644
index 00000000..f6cacd27
--- /dev/null
+++ b/src/libcamera/pipeline/virtual/virtual.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Google Inc.
+ *
+ * Pipeline handler for virtual cameras
+ */
+
+#pragma once
+
+#include <vector>
+
+#include <libcamera/geometry.h>
+#include <libcamera/stream.h>
+
+#include "libcamera/internal/camera.h"
+#include "libcamera/internal/pipeline_handler.h"
+
+namespace libcamera {
+
+class VirtualCameraData : public Camera::Private
+{
+public:
+ const static unsigned int kMaxStream = 3;
+
+ struct Resolution {
+ Size size;
+ std::vector<int> frameRates;
+ };
+ struct StreamConfig {
+ Stream stream;
+ };
+
+ VirtualCameraData(PipelineHandler *pipe,
+ const std::vector<Resolution> &supportedResolutions);
+
+ ~VirtualCameraData() = default;
+
+ const std::vector<Resolution> supportedResolutions_;
+ Size maxResolutionSize_;
+ Size minResolutionSize_;
+
+ std::vector<StreamConfig> streamConfigs_;
+};
+
+} /* namespace libcamera */