From patchwork Tue Sep 10 04:40:16 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Harvey Yang X-Patchwork-Id: 21209 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 34B4CC324C for ; Tue, 10 Sep 2024 04:48:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6DA63634F7; Tue, 10 Sep 2024 06:48:52 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="UIBnMvYf"; dkim-atps=neutral Received: from mail-pf1-x42f.google.com (mail-pf1-x42f.google.com [IPv6:2607:f8b0:4864:20::42f]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 54CC6634FF for ; Tue, 10 Sep 2024 06:48:46 +0200 (CEST) Received: by mail-pf1-x42f.google.com with SMTP id d2e1a72fcca58-718d79fb6f7so300591b3a.3 for ; Mon, 09 Sep 2024 21:48:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1725943724; x=1726548524; darn=lists.libcamera.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=GrKFnEIZ+Q4YMS9YNba3hhRVUaayJN3XBQWAq97WnC8=; b=UIBnMvYfJi0rklnlr7Zmvj3JCvctVsneHCx25IbuRzK71eMCv9kBlW80I5ncRZbhNR oNsiZ5yMjUu6Yhu4r7sGtN/jSHuRmRYnr82EaC1mJpHpns8bTyTS3WyfRx0ScOd9wpTH Zhv67h8lV7XSFM5qr99VIadE9Xi7x1F3Giwuc= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1725943724; x=1726548524; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=GrKFnEIZ+Q4YMS9YNba3hhRVUaayJN3XBQWAq97WnC8=; b=I4S/PyF9mB1pZOHR0r8euZ/n+U67sQfDM11PM+4ePU3v94sTuujHY9zgMw1VeHZP3w DuH+pC9qSkMrLbFNcMQh1oFn+xX/JBoId+yhF3Hhit2FAqAFxUTiV3fYXd5dNspwqH+X su/M2FtgvqTgvm635VM5qdIi0E456oS5pVwcse75QJddpA6AhmJaSSfwzq7h9PUyz8aY yGD2mlkNvhDjUWiofaMOJ/rHmNsvNeGmUoMIgv5SbIwtwr5cjxcGg1NGBZL37ODg9sd7 EPg5zA1ObM46C5afg5587Ep7MLiLQnotlf26vW10MmRGrOllbhHZUGnESYpHtUxs7AS3 Z9xQ== X-Gm-Message-State: AOJu0Yz4/xNmR24ch4c3XMJHYCskcsFc3EmtD4MMtBCuBmy+vgzTVMvw D+Kjqdmm0SJuJsACRzQfoOx1Wby+nU07illvpqN1D3d175l0n7Fy9n1ZDcNC2q8f5HNEnflROpA = X-Google-Smtp-Source: AGHT+IHLgF8eerHUmQRa27gGdb6vObvlCh6NOgsCiGOddBAVdJMfrMz6bJqsd2yR1RI8Y817Xlt8Rw== X-Received: by 2002:a05:6a21:398a:b0:1cc:e409:7d0c with SMTP id adf61e73a8af0-1cf2a047376mr10169616637.25.1725943723805; Mon, 09 Sep 2024 21:48:43 -0700 (PDT) Received: from chenghaoyang-low.c.googlers.com.com (0.223.81.34.bc.googleusercontent.com. [34.81.223.0]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-71909092522sm530734b3a.111.2024.09.09.21.48.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 09 Sep 2024 21:48:43 -0700 (PDT) From: Harvey Yang X-Google-Original-From: Harvey Yang To: libcamera-devel@lists.libcamera.org Cc: Harvey Yang , Jacopo Mondi Subject: [PATCH v12 3/7] libcamera: virtual: Add VirtualPipelineHandler Date: Tue, 10 Sep 2024 04:40:16 +0000 Message-ID: <20240910044834.2477701-4-chenghaoyang@google.com> X-Mailer: git-send-email 2.46.0.598.g6f2099f65c-goog In-Reply-To: <20240910044834.2477701-1-chenghaoyang@google.com> References: <20240910044834.2477701-1-chenghaoyang@google.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: Harvey Yang Add VirtualPipelineHandler for more unit tests and verfiy libcamera infrastructure works on devices without using hardware cameras. Signed-off-by: Harvey Yang Reviewed-by: Jacopo Mondi --- meson.build | 1 + meson_options.txt | 3 +- src/libcamera/pipeline/virtual/meson.build | 5 + src/libcamera/pipeline/virtual/virtual.cpp | 309 +++++++++++++++++++++ src/libcamera/pipeline/virtual/virtual.h | 44 +++ 5 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 src/libcamera/pipeline/virtual/meson.build create mode 100644 src/libcamera/pipeline/virtual/virtual.cpp create mode 100644 src/libcamera/pipeline/virtual/virtual.h diff --git a/meson.build b/meson.build index 432ae133..ff9a70cf 100644 --- a/meson.build +++ b/meson.build @@ -214,6 +214,7 @@ pipelines_support = { 'simple': ['any'], 'uvcvideo': ['any'], 'vimc': ['test'], + 'virtual': ['test'], } if pipelines.contains('all') diff --git a/meson_options.txt b/meson_options.txt index 7aa41249..c91cd241 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -53,7 +53,8 @@ option('pipelines', 'rpi/vc4', 'simple', 'uvcvideo', - 'vimc' + 'vimc', + 'virtual' ], description : 'Select which pipeline handlers to build. If this is set to "auto", all the pipelines applicable to the target architecture will be built. If this is set to "all", all the pipelines will be built. If both are selected then "all" will take precedence.') 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..56e05528 --- /dev/null +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -0,0 +1,309 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Google Inc. + * + * virtual.cpp - Pipeline handler for virtual cameras + */ + +#include "virtual.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#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( + 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); + + std::unique_ptr generateConfiguration(Camera *camera, + Span roles) override; + int configure(Camera *camera, CameraConfiguration *config) override; + + int exportFrameBuffers(Camera *camera, Stream *stream, + std::vector> *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(camera->_d()); + } + + DmaBufAllocator dmaBufAllocator_; +}; + +/* static */ +bool PipelineHandlerVirtual::created_ = false; + +VirtualCameraData::VirtualCameraData(PipelineHandler *pipe, + std::vector supportedResolutions) + : Camera::Private(pipe), supportedResolutions_(std::move(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 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; + } + + 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; + + if (cfg.pixelFormat != formats::NV12) { + cfg.pixelFormat = formats::NV12; + LOG(Virtual, Debug) + << "Stream configuration adjusted to " << cfg.toString(); + status = Adjusted; + } + } + + return status; +} + +PipelineHandlerVirtual::PipelineHandlerVirtual(CameraManager *manager) + : PipelineHandler(manager), + dmaBufAllocator_(DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap | + DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap | + DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf) +{ +} + +std::unique_ptr +PipelineHandlerVirtual::generateConfiguration(Camera *camera, + Span roles) +{ + VirtualCameraData *data = cameraData(camera); + auto config = + std::make_unique(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> 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> *buffers) +{ + if (!dmaBufAllocator_.isValid()) + return -ENOBUFS; + + const StreamConfiguration &config = stream->configuration(); + + auto info = PixelFormatInfo::info(config.pixelFormat); + + std::vector 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) +{ + /* \todo Read from the virtual video if any. */ + 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 supportedResolutions; + supportedResolutions.resize(2); + supportedResolutions[0] = { .size = Size(1920, 1080), .frameRates = { 30 } }; + supportedResolutions[1] = { .size = Size(1280, 720), .frameRates = { 30 } }; + + std::unique_ptr data = + std::make_unique(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); + data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls); + + /* Create and register the camera. */ + std::set streams; + for (auto &streamConfig : data->streamConfigs_) + streams.insert(&streamConfig.stream); + + const std::string id = "Virtual0"; + std::shared_ptr camera = Camera::create(std::move(data), id, streams); + registerCamera(std::move(camera)); + + 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..77823c41 --- /dev/null +++ b/src/libcamera/pipeline/virtual/virtual.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Google Inc. + * + * virtual.h - Pipeline handler for virtual cameras + */ + +#pragma once + +#include + +#include + +#include "libcamera/internal/camera.h" +#include "libcamera/internal/pipeline_handler.h" + +namespace libcamera { + +class VirtualCameraData : public Camera::Private +{ +public: + const static unsigned int kMaxStream = 1; + + struct Resolution { + Size size; + std::vector frameRates; + }; + struct StreamConfig { + Stream stream; + }; + + VirtualCameraData(PipelineHandler *pipe, + std::vector supportedResolutions); + + ~VirtualCameraData() = default; + + const std::vector supportedResolutions_; + Size maxResolutionSize_; + Size minResolutionSize_; + + std::vector streamConfigs_; +}; + +} /* namespace libcamera */