From patchwork Thu Aug 1 07:30:59 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Harvey Yang X-Patchwork-Id: 20741 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 49C22BDC71 for ; Thu, 1 Aug 2024 07:34:01 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B8C166338A; Thu, 1 Aug 2024 09:34:00 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="jf5YttxN"; 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 5177C63385 for ; Thu, 1 Aug 2024 09:33:52 +0200 (CEST) Received: by mail-pf1-x42f.google.com with SMTP id d2e1a72fcca58-70d162eef54so4733407b3a.3 for ; Thu, 01 Aug 2024 00:33:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1722497630; x=1723102430; 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=qQMW9haoy275D5+UDP32KZdTg4J+OdOIz71Ehvs25Xc=; b=jf5YttxN+OYENWJYjAeUHHtkZfOCfHcqrhD5oxgLAmDVzKH6KomKrBfxVns36QU8FT LwtUyT6NokWqmxdOMkSuvR5D4NvHOToEbubeExNyKIE6snSvQkngeEa56bF2/sQQHhGx xmMBUyl1F9P7IsOUekQEjvPocI0ZbqZ2MmYx0= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1722497630; x=1723102430; 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=qQMW9haoy275D5+UDP32KZdTg4J+OdOIz71Ehvs25Xc=; b=ODU97sNhxSLtFgoao7/NclnvxhwGHvmPBN+QO22u/OUmUA4uN2AbKa9fn9cdV7ePfM 7SQwQTBJDEgaN4EHkyfMs74g1+q8SuMQDwir6WiMk1qmXYh5AL8OcvN5JZxBZxeLbKPR 7t5wBAgGcd1npVaMhnviVHwCoYH4BSp+wy1YNKeZ0NfvuXHt/hs7cYQQ25X/uglI18cK 3/NujS8lCA/ea32JvX0Z0ifDtjCuFnld2LXgCQDybzJpZxoxoJ9PgL57VejW6lQhKLSO Q+fPU5D2RQZR3NP8tVbgtQ58WU2K5q7y7QyqBUDa8x8sMB7Pf9x5hjM/lzJqiPNGMZwF oEdg== X-Gm-Message-State: AOJu0YzYoHstnMy+p7n8gfVqf3IO/UDHvMHV5PVbT4O8wPNGyETlc532 TUxSPwkJjRSt+qdPGM9p0JD1mmQAsWljUfLUVZWODHR2/XDzQT8lZDO2qMvbXwHNSWNZyNanod6 7sQ== X-Google-Smtp-Source: AGHT+IHgOKbp3vBKrrqe5fy8nwfyWT61X4vaOdznC0Q/tyQ7PYqkzpddMq6buyV2XDRAPOo/810+Zw== X-Received: by 2002:a05:6a21:9990:b0:1c0:f0c1:5d45 with SMTP id adf61e73a8af0-1c68d125490mr2247688637.44.1722497630062; Thu, 01 Aug 2024 00:33:50 -0700 (PDT) Received: from chenghaoyang-low.c.googlers.com.com (237.198.80.34.bc.googleusercontent.com. [34.80.198.237]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-1fed7cd3dcbsm131839165ad.73.2024.08.01.00.33.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 01 Aug 2024 00:33:49 -0700 (PDT) From: Harvey Yang X-Google-Original-From: Harvey Yang To: libcamera-devel@lists.libcamera.org Cc: Harvey Yang Subject: [PATCH v7 3/7] libcamera: pipeline: Add VirtualPipelineHandler Date: Thu, 1 Aug 2024 07:30:59 +0000 Message-ID: <20240801073339.4061027-4-chenghaoyang@google.com> X-Mailer: git-send-email 2.46.0.rc1.232.g9752f9e123-goog In-Reply-To: <20240801073339.4061027-1-chenghaoyang@google.com> References: <20240801073339.4061027-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 --- meson.build | 1 + meson_options.txt | 3 +- src/libcamera/pipeline/virtual/meson.build | 5 + src/libcamera/pipeline/virtual/virtual.cpp | 243 +++++++++++++++++++++ src/libcamera/pipeline/virtual/virtual.h | 78 +++++++ 5 files changed, 329 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 f946eba9..3cad3249 100644 --- a/meson.build +++ b/meson.build @@ -222,6 +222,7 @@ pipelines_support = { 'simple': arch_arm, '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..ba7ff754 --- /dev/null +++ b/src/libcamera/pipeline/virtual/meson.build @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: CC0-1.0 + +libcamera_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..8abb74b4 --- /dev/null +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -0,0 +1,243 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Google Inc. + * + * virtual.cpp - Pipeline handler for virtual cameras + */ + +#include "virtual.h" + +#include + +#include +#include +#include +#include +#include + +#include "libcamera/internal/camera.h" +#include "libcamera/internal/formats.h" +#include "libcamera/internal/pipeline_handler.h" + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Virtual) + +namespace { + +uint64_t currentTimestamp() +{ + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) { + LOG(Virtual, Error) << "Get clock time fails"; + return 0; + } + + return ts.tv_sec * 1'000'000'000LL + ts.tv_nsec; +} + +} // namespace + +VirtualCameraConfiguration::VirtualCameraConfiguration(VirtualCameraData *data) + : CameraConfiguration(), data_(data) +{ +} + +CameraConfiguration::Status VirtualCameraConfiguration::validate() +{ + Status status = Valid; + + if (config_.empty()) { + LOG(Virtual, Error) << "Empty config"; + return Invalid; + } + + /* Currently only one stream is supported */ + if (config_.size() > 1) { + config_.resize(1); + status = Adjusted; + } + + Size maxSize; + for (const auto &resolution : data_->supportedResolutions_) + maxSize = std::max(maxSize, resolution.size); + + 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) { + cfg.size = maxSize; + 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.setStream(const_cast(&data_->stream_)); + + cfg.bufferCount = VirtualCameraConfiguration::kBufferCount; + } + + return status; +} + +PipelineHandlerVirtual::PipelineHandlerVirtual(CameraManager *manager) + : PipelineHandler(manager) +{ +} + +std::unique_ptr +PipelineHandlerVirtual::generateConfiguration(Camera *camera, + Span roles) +{ + VirtualCameraData *data = cameraData(camera); + auto config = + std::make_unique(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> streamFormats; + unsigned int bufferCount; + PixelFormat pixelFormat; + + switch (role) { + case StreamRole::StillCapture: + pixelFormat = formats::NV12; + bufferCount = VirtualCameraConfiguration::kBufferCount; + streamFormats[pixelFormat] = { { minSize, sensorResolution } }; + + break; + + case StreamRole::Raw: { + /* \todo check */ + pixelFormat = formats::SBGGR10; + bufferCount = VirtualCameraConfiguration::kBufferCount; + streamFormats[pixelFormat] = { { minSize, sensorResolution } }; + + break; + } + + case StreamRole::Viewfinder: + case StreamRole::VideoRecording: { + pixelFormat = formats::NV12; + bufferCount = VirtualCameraConfiguration::kBufferCount; + streamFormats[pixelFormat] = { { minSize, sensorResolution } }; + + break; + } + + default: + LOG(Virtual, Error) + << "Requested stream role not supported: " << role; + config.reset(); + return config; + } + + StreamFormats formats(streamFormats); + StreamConfiguration cfg(formats); + cfg.size = sensorResolution; + cfg.pixelFormat = pixelFormat; + cfg.bufferCount = bufferCount; + config->addConfiguration(cfg); + } + + if (config->validate() == CameraConfiguration::Invalid) + config.reset(); + + return config; +} + +int PipelineHandlerVirtual::configure( + [[maybe_unused]] Camera *camera, + [[maybe_unused]] CameraConfiguration *config) +{ + // Nothing to be done. + return 0; +} + +int PipelineHandlerVirtual::exportFrameBuffers( + [[maybe_unused]] Camera *camera, + Stream *stream, + std::vector> *buffers) +{ + if (!dmaBufAllocator_.isValid()) + return -ENOBUFS; + + return dmaBufAllocator_.exportFrameBuffers(stream->configuration(), buffers); +} + +int PipelineHandlerVirtual::start([[maybe_unused]] Camera *camera, + [[maybe_unused]] const ControlList *controls) +{ + /* \todo Start reading the virtual video if any. */ + return 0; +} + +void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera) +{ + /* \todo Reset the virtual video if any. */ +} + +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) +{ + /* \todo Add virtual cameras according to a config file. */ + + std::unique_ptr data = std::make_unique(this); + + data->supportedResolutions_.resize(2); + data->supportedResolutions_[0] = { .size = Size(1920, 1080), .frame_rates = { 30 } }; + data->supportedResolutions_[1] = { .size = Size(1280, 720), .frame_rates = { 30, 60 } }; + + 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 = 30, max_frame_duration = 60; + 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{ &data->stream_ }; + const std::string id = "Virtual0"; + std::shared_ptr camera = Camera::create(std::move(data), id, streams); + registerCamera(std::move(camera)); + + return false; // Prevent infinite loops for now +} + +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..6fc6b34d --- /dev/null +++ b/src/libcamera/pipeline/virtual/virtual.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Google Inc. + * + * virtual.h - Pipeline handler for virtual cameras + */ + +#pragma once + +#include + +#include "libcamera/internal/camera.h" +#include "libcamera/internal/dma_buf_allocator.h" +#include "libcamera/internal/pipeline_handler.h" + +namespace libcamera { + +class VirtualCameraData : public Camera::Private +{ +public: + struct Resolution { + Size size; + std::vector frame_rates; + }; + VirtualCameraData(PipelineHandler *pipe) + : Camera::Private(pipe) + { + } + + ~VirtualCameraData() = default; + + std::vector supportedResolutions_; + + Stream stream_; +}; + +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: + VirtualCameraData *cameraData(Camera *camera) + { + return static_cast(camera->_d()); + } + + DmaBufAllocator dmaBufAllocator_; +}; + +} // namespace libcamera