From patchwork Tue Aug 20 16:23:32 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Harvey Yang X-Patchwork-Id: 20968 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 A7EA4C323E for ; Tue, 20 Aug 2024 17:22:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 42961633DA; Tue, 20 Aug 2024 19:22:22 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="QTiHx+sT"; dkim-atps=neutral Received: from mail-wm1-x32a.google.com (mail-wm1-x32a.google.com [IPv6:2a00:1450:4864:20::32a]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2B322633B9 for ; Tue, 20 Aug 2024 19:22:16 +0200 (CEST) Received: by mail-wm1-x32a.google.com with SMTP id 5b1f17b1804b1-4280bbdad3dso45736665e9.0 for ; Tue, 20 Aug 2024 10:22:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1724174535; x=1724779335; 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=xhJ1TpNfniDD0lgMah61wWiI0vruYwsEg3JQnpBROMs=; b=QTiHx+sTibzYudyenUtC3JettTDUhA1a3S3/EwHWVYKRRjoBYEQZj6i2tRVLy6TrB1 WhJfPivYbCVOnxLTgXFHtk68N5toyVdlAV8hSRmCGiVsnrYvTBcEAfA1riU9Pu7pmpYY fsfEjBaD6yAatQ9Vf8Q111wjQ192k/U6qT7ZE= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724174535; x=1724779335; 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=xhJ1TpNfniDD0lgMah61wWiI0vruYwsEg3JQnpBROMs=; b=Lh4jJXbBA3i9wSO6BGuLEvHFvvx5sK66Tku5+a2bPE1+9c1z8VGqngDIkVN7SlvlA4 6hO//39El/655Hpc/bodbnzNOJWo8q3imjoFknzMD3bgDW4pJI4vxWBkfGPc18E3rLf2 hLseF7d+gSnP86eSm5gZVQREzX4eLe+HdZM80D9cD41xKd60GWbLvFwK7Lmgs8ioWngC oapTkCzV1txTxfVZYo/R0M5hrGq1DSmbJlP6ZkLxCwhNoUpTJGXB3fUOkJ5YmSyNb5jm lmZP+hJJEaVa0tDXKrGq1SnUcpnVYt26QX75wbnOpMbOc8zJUqVlnzLoe/9cxtAVnPk3 6Bxw== X-Gm-Message-State: AOJu0YzDa14XT8yKmgflvR/shGnJnP9C6lQGljPgEEaTqYVAQI9yA/lN lye26F33vDTnFeFsvL/y+8M2Hp86V1jJIrmNyMOEYcp0FKzePI7Tp/upHO8MStPaDWHkQwiOuid jfO3rMGY= X-Google-Smtp-Source: AGHT+IGonbZmSHTbLGIz2b10s+Wy5//AT3oBumPhjOlHMWUOu2bR1D+cxyqvSPJ2aozdV1BBzkyD6g== X-Received: by 2002:a05:600c:46ce:b0:426:593c:935d with SMTP id 5b1f17b1804b1-42abd21f59fmr493395e9.5.1724174534668; Tue, 20 Aug 2024 10:22:14 -0700 (PDT) Received: from chenghaoyang-germany.c.googlers.com.com (161.126.77.34.bc.googleusercontent.com. [34.77.126.161]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-42ab6e90db5sm30328575e9.0.2024.08.20.10.22.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 20 Aug 2024 10:22:14 -0700 (PDT) From: Harvey Yang X-Google-Original-From: Harvey Yang To: libcamera-devel@lists.libcamera.org Cc: Harvey Yang , Harvey Yang Subject: [PATCH v9 1/8] libcamera: add DmaBufAllocator::exportBuffers() Date: Tue, 20 Aug 2024 16:23:32 +0000 Message-ID: <20240820172202.526547-2-chenghaoyang@google.com> X-Mailer: git-send-email 2.46.0.184.g6999bdac58-goog In-Reply-To: <20240820172202.526547-1-chenghaoyang@google.com> References: <20240820172202.526547-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" Add a helper function exportBuffers in DmaBufAllocator to make it easier to use. It'll be used in Virtual Pipeline Handler and SoftwareIsp. Signed-off-by: Harvey Yang --- .../libcamera/internal/dma_buf_allocator.h | 12 ++++ src/libcamera/dma_buf_allocator.cpp | 64 ++++++++++++++++++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/include/libcamera/internal/dma_buf_allocator.h b/include/libcamera/internal/dma_buf_allocator.h index 36ec1696b..3a9b56b1c 100644 --- a/include/libcamera/internal/dma_buf_allocator.h +++ b/include/libcamera/internal/dma_buf_allocator.h @@ -8,12 +8,16 @@ #pragma once #include +#include #include #include namespace libcamera { +class FrameBuffer; +struct StreamConfiguration; + class DmaBufAllocator { public: @@ -30,7 +34,15 @@ public: bool isValid() const { return providerHandle_.isValid(); } UniqueFD alloc(const char *name, std::size_t size); + int exportBuffers( + std::size_t count, + std::vector frameSize, + std::vector> *buffers); + private: + std::unique_ptr createBuffer( + std::string name, std::vector frameSizes); + UniqueFD allocFromHeap(const char *name, std::size_t size); UniqueFD allocFromUDmaBuf(const char *name, std::size_t size); UniqueFD providerHandle_; diff --git a/src/libcamera/dma_buf_allocator.cpp b/src/libcamera/dma_buf_allocator.cpp index c06eca7d0..2d88b8b2b 100644 --- a/src/libcamera/dma_buf_allocator.cpp +++ b/src/libcamera/dma_buf_allocator.cpp @@ -23,6 +23,11 @@ #include +#include +#include + +#include "libcamera/internal/formats.h" + /** * \file dma_buf_allocator.cpp * \brief dma-buf allocator @@ -130,8 +135,8 @@ DmaBufAllocator::~DmaBufAllocator() = default; /* uClibc doesn't provide the file sealing API. */ #ifndef __DOXYGEN__ #if not HAVE_FILE_SEALS -#define F_ADD_SEALS 1033 -#define F_SEAL_SHRINK 0x0002 +#define F_ADD_SEALS 1033 +#define F_SEAL_SHRINK 0x0002 #endif #endif @@ -243,4 +248,59 @@ UniqueFD DmaBufAllocator::alloc(const char *name, std::size_t size) return allocFromHeap(name, size); } +/** + * \brief Allocate and export buffers for \a stream from the DmaBufAllocator + * \param[in] count The number of FrameBuffers required + * \param[in] frameSizes The sizes of planes in the FrameBuffer + * \param[out] buffers Array of buffers successfully allocated + * + * \return The number of allocated buffers on success or a negative error code + * otherwise + */ +int DmaBufAllocator::exportBuffers( + std::size_t count, + std::vector frameSizes, + std::vector> *buffers) +{ + for (unsigned i = 0; i < count; ++i) { + std::unique_ptr buffer = + createBuffer("frame-" + std::to_string(i), frameSizes); + if (!buffer) { + LOG(DmaBufAllocator, Error) << "Unable to create buffer"; + + buffers->clear(); + return -EINVAL; + } + + buffers->push_back(std::move(buffer)); + } + + return count; +} + +std::unique_ptr DmaBufAllocator::createBuffer( + std::string name, std::vector frameSizes) +{ + std::vector planes; + + std::size_t bufferSize = 0, offset = 0; + for (auto frameSize : frameSizes) + bufferSize += frameSize; + + SharedFD fd(alloc(name.c_str(), bufferSize)); + if (!fd.isValid()) + return nullptr; + + for (auto frameSize : frameSizes) { + FrameBuffer::Plane plane; + plane.fd = fd; + plane.offset = offset; + plane.length = frameSize; + planes.push_back(std::move(plane)); + offset += plane.length; + } + + return std::make_unique(planes); +} + } /* namespace libcamera */ From patchwork Tue Aug 20 16:23:33 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Harvey Yang X-Patchwork-Id: 20969 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 2CD77C323E for ; Tue, 20 Aug 2024 17:22:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C3705633CE; Tue, 20 Aug 2024 19:22:24 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="l9CXATQi"; dkim-atps=neutral Received: from mail-wm1-x32f.google.com (mail-wm1-x32f.google.com [IPv6:2a00:1450:4864:20::32f]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 41502633CC for ; Tue, 20 Aug 2024 19:22:16 +0200 (CEST) Received: by mail-wm1-x32f.google.com with SMTP id 5b1f17b1804b1-428101fa30aso46610075e9.3 for ; Tue, 20 Aug 2024 10:22:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1724174535; x=1724779335; 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=J4lnb82gImewC8slrhsxvkvivBhZnq9isCozxZOsaF4=; b=l9CXATQiDidu8OaYTq8ek4ChoQ2VAd6H9Dyy1fzVdA7nMz9+MXNCp+rjDbBKHEornp AP8cSNXQjlWGrzjiF4L9riV4m1VnRyE3p242amlwPptnqX8HGCBuvvemVglXOV/wDh3D ItKynZFgCPZC9PMEpV9nQuC6q8fBdCU9hIqko= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724174535; x=1724779335; 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=J4lnb82gImewC8slrhsxvkvivBhZnq9isCozxZOsaF4=; b=xUxQ8gC/zwaaHQVp/gwIzg05QRJMgR3lKIAK2/y2HOFLEBwR87+QCWEHRIjQ2+9+1o vrBrw9aJ8HrnvpsFN2zytu4Or51rFbSIy8D7qiWhxA366vQ4cWQmMSigJ3FJYp2z61/w 087pcuS4H30A2leGm4X4R7KcWcCzT084koOYR6KC/atcwaJ0dO1Nvo6c/HuIZmkdtSRk gg/S7gBCIeELcDCPcQmYs9SsJC84eG2VBACtflVwmrVk1kKf/c+h4YdfHfDIeO3d6/Cb g8NIXMuWoOjr4fiIqL/VIFSvPzQ+m9+fd113Qk4gVypQ50cAfKqTC04Z4OfEtBPz+jVL mQlA== X-Gm-Message-State: AOJu0Yw63rb/0A1akOc8nSP5xjCeAwl9imA46/nAfMsUofKYRJU/2N0x 8vGnkLn2+UAUhQOqwMbmzUpLzhmWsNb6WcJoRpbA1OIytlXJzToV+e925u+ZnNnK+qz3pqho/sT qy3u/oq8= X-Google-Smtp-Source: AGHT+IH23RfHsspLkyfA/hpQOAxys7tKWopOfyc0+g2iUqn+ZgYHH3VETRQmvbwrdqvAqX1TlnFblw== X-Received: by 2002:a05:600c:1c02:b0:429:d0cb:950f with SMTP id 5b1f17b1804b1-42abd112184mr566945e9.2.1724174535368; Tue, 20 Aug 2024 10:22:15 -0700 (PDT) Received: from chenghaoyang-germany.c.googlers.com.com (161.126.77.34.bc.googleusercontent.com. [34.77.126.161]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-42ab6e90db5sm30328575e9.0.2024.08.20.10.22.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 20 Aug 2024 10:22:14 -0700 (PDT) From: Harvey Yang X-Google-Original-From: Harvey Yang To: libcamera-devel@lists.libcamera.org Cc: Harvey Yang , Harvey Yang , Laurent Pinchart Subject: [PATCH v9 2/8] libcamera: Remove PipelineHandler Fatal check of non-empty MediaDevices Date: Tue, 20 Aug 2024 16:23:33 +0000 Message-ID: <20240820172202.526547-3-chenghaoyang@google.com> X-Mailer: git-send-email 2.46.0.184.g6999bdac58-goog In-Reply-To: <20240820172202.526547-1-chenghaoyang@google.com> References: <20240820172202.526547-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" The Fatal check of having at least one MediaDevice was to prevent pipeline handler implementations searching and owning media devices with custom conventions, instead of using the base function |acquireMediaDevice|. It also has the assumption that there's at least one media device to make a camera work. Now that the assumption will be broken by the virtual pipeline handler added in the following patches, and developers should be aware of the available functions in the base class to handle media devices, the Fatal check is no longer needed. Signed-off-by: Harvey Yang Reviewed-by: Laurent Pinchart --- src/libcamera/pipeline_handler.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp index 5ea2ca780..125cf9d5e 100644 --- a/src/libcamera/pipeline_handler.cpp +++ b/src/libcamera/pipeline_handler.cpp @@ -605,9 +605,14 @@ void PipelineHandler::registerCamera(std::shared_ptr camera) { cameras_.push_back(camera); - if (mediaDevices_.empty()) - LOG(Pipeline, Fatal) - << "Registering camera with no media devices!"; + if (mediaDevices_.empty()) { + /* + * For virtual devices with no MediaDevice, there are no system + * devices to register. + */ + manager_->_d()->addCamera(std::move(camera)); + return; + } /* * Walk the entity list and map the devnums of all capture video nodes From patchwork Tue Aug 20 16:23:34 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Harvey Yang X-Patchwork-Id: 20970 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 8B296C323E for ; Tue, 20 Aug 2024 17:22:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 65AC1633DC; Tue, 20 Aug 2024 19:22:26 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="ZhmTOzvk"; dkim-atps=neutral Received: from mail-wm1-x32e.google.com (mail-wm1-x32e.google.com [IPv6:2a00:1450:4864:20::32e]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id EEDE1633B2 for ; Tue, 20 Aug 2024 19:22:16 +0200 (CEST) Received: by mail-wm1-x32e.google.com with SMTP id 5b1f17b1804b1-42809d6e719so48947225e9.3 for ; Tue, 20 Aug 2024 10:22:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1724174536; x=1724779336; 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=HqeP+IawON4r8zA7ZMgJEE9V7481f59c8GJ32O5JIPQ=; b=ZhmTOzvkxHa8oyr48fZch8pIGkDk6QH+Xefl5QApUi5ENu4VWrheDRC+Anrwz6do18 ZP7T8/0cNYBdTiaw0zjlyr/5R38eGgPtYdIZ4Ja50XE437ciqCZu8qHNI7xceXE09b6q A583xzlSQRSCuCNhcKIOp+M+MUdA/909RnKuE= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724174536; x=1724779336; 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=HqeP+IawON4r8zA7ZMgJEE9V7481f59c8GJ32O5JIPQ=; b=VOVZEUd0B+brgW077YUmYiw+/dB2/Kps8C1TPRpOIp2wf94PTW2J0uSzlvOEQ2g+up PnHowkf80QgmdCU/uSRugpaTPAc02HXyCffNxYfO7/6a+YtOtGzfYGHv+E9SKPcfrPIR snWcD3CTrQRihTth+eu7tDTFD9eTvOEE8kjWSwAvnDTSqx3alkdoVrSoUTUr1N2uDBLH +OEvSeeX10EBXj7a7yr6gy2ZOtkLrKMYmt5ThOqHG7mBonGKHBDdArECjm5HjBOsmliR dWUujT5+JRQlfuHXvdLg0Z3vDNKiOmlmPJEMO1PEYNcJXHnJhvNZ9WdiC8trSVBw+8/g koVQ== X-Gm-Message-State: AOJu0Yxk3VJp/VVbOraSQYvU99EM+NeKw0Mpu9z8P33Fj4glJ63QR63I 3zDwOwluP0q9/siShgOF76G/6p/XbMpxONHVVVAugdv+lA98jsFZrjQ2mNN8/g3YugvY9T8thLD BXSKxFyA= X-Google-Smtp-Source: AGHT+IEafQ9bURodxZTC6Dy8PffwlCZwHlBI8AdA4H+p9VrK9WafNCpH0UYzklrIFlIHbo3FnMytKA== X-Received: by 2002:a05:600c:3d13:b0:426:55a3:71af with SMTP id 5b1f17b1804b1-42abd263ee4mr297685e9.33.1724174536121; Tue, 20 Aug 2024 10:22:16 -0700 (PDT) Received: from chenghaoyang-germany.c.googlers.com.com (161.126.77.34.bc.googleusercontent.com. [34.77.126.161]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-42ab6e90db5sm30328575e9.0.2024.08.20.10.22.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 20 Aug 2024 10:22:15 -0700 (PDT) From: Harvey Yang X-Google-Original-From: Harvey Yang To: libcamera-devel@lists.libcamera.org Cc: Harvey Yang Subject: [PATCH v9 3/8] libcamera: pipeline: Add VirtualPipelineHandler Date: Tue, 20 Aug 2024 16:23:34 +0000 Message-ID: <20240820172202.526547-4-chenghaoyang@google.com> X-Mailer: git-send-email 2.46.0.184.g6999bdac58-goog In-Reply-To: <20240820172202.526547-1-chenghaoyang@google.com> References: <20240820172202.526547-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 | 251 +++++++++++++++++++++ src/libcamera/pipeline/virtual/virtual.h | 78 +++++++ 5 files changed, 337 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 f946eba94..3cad3249a 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 7aa412491..c91cd241a 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 000000000..ba7ff754e --- /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 000000000..74eb8c7ad --- /dev/null +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -0,0 +1,251 @@ +/* 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; + + 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) +{ + /* \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 000000000..6fc6b34d8 --- /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 From patchwork Tue Aug 20 16:23:35 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Harvey Yang X-Patchwork-Id: 20972 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 076F4C32A9 for ; Tue, 20 Aug 2024 17:22:30 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1130C633CE; Tue, 20 Aug 2024 19:22:29 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="Bt2gJQwL"; dkim-atps=neutral Received: from mail-wr1-x42b.google.com (mail-wr1-x42b.google.com [IPv6:2a00:1450:4864:20::42b]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A0EFF633D1 for ; Tue, 20 Aug 2024 19:22:17 +0200 (CEST) Received: by mail-wr1-x42b.google.com with SMTP id ffacd0b85a97d-3718706cf8aso3572107f8f.3 for ; Tue, 20 Aug 2024 10:22:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1724174537; x=1724779337; 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=0zBA+T2Bn4jMOL2ggU7WXrBRpoKtYkDL3N71bwwLyho=; b=Bt2gJQwLPzy0DOcHfCY+2+be3KeSZ1Rf31zh4zdARiB5lj4wcZsQIVZWiew3j0K85d QFqseXaaoyBhw6YPikgOn8+YoAT3Ca/QwkNSSqSf3JvgHngEp8gxYi0HM9NOgeU4ZeoL MULMDyLJoSRzGXmqgx+LOO51wRT600WUc2/Cs= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724174537; x=1724779337; 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=0zBA+T2Bn4jMOL2ggU7WXrBRpoKtYkDL3N71bwwLyho=; b=XsASpSXYRuIJbno4nhr4JXv7tVZJe24Z9dYO40PkCxfOZM5iNfkhQjFicRS85j/WVm TY1JTVKA0emg3tLOZQg5b9fTyQuzpO6f7bU10aiW8rlsVKYdpKevjwklyHYPuiF5/m6X WuYPuDJzN7qGLnVEZdSXMpe+cfgIlyUfkFkd7qhDVNiqAjMw5qwCSv26ZqSNVDvKHT2K /gYDkzKuk8GhCFC3z6keWWmGvqL1nwUHmb2E7ewoEYFkxpS1o6C/8HyujIFKqHuX0ErY JId//pX02TPajKVbmVrySNPTZXWAGZSqDAyV5Pm9743XF7aC8TnoqhWSttF4HueFihrc npLA== X-Gm-Message-State: AOJu0Yy2j9IbFWbqNhg3sazHB0MkT8DjtD+uRXJyP2L2uoBt8cnvcVFp mhJwNVPlqnys+QT0KeoW5rn0gooaX7aJvRT5cjHEQWOSauQ5n7sAo0KawpVVOkwHiZVc2UzkDZN VpNm+Hgg= X-Google-Smtp-Source: AGHT+IGq742r/AyS2YSBP3a78zY+h7g7dDV+2PVTyMB5IsMX7RDuE1YvOp6H/F8bOeF7wjKlSvtfJw== X-Received: by 2002:adf:f6c6:0:b0:371:899b:5c5 with SMTP id ffacd0b85a97d-37194659e95mr8696910f8f.27.1724174536753; Tue, 20 Aug 2024 10:22:16 -0700 (PDT) Received: from chenghaoyang-germany.c.googlers.com.com (161.126.77.34.bc.googleusercontent.com. [34.77.126.161]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-42ab6e90db5sm30328575e9.0.2024.08.20.10.22.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 20 Aug 2024 10:22:16 -0700 (PDT) From: Harvey Yang X-Google-Original-From: Harvey Yang To: libcamera-devel@lists.libcamera.org Cc: Konami Shu , Harvey Yang , Yunke Cao , Tomasz Figa Subject: [PATCH v9 4/8] libcamera: pipeline: Add test pattern for VirtualPipelineHandler Date: Tue, 20 Aug 2024 16:23:35 +0000 Message-ID: <20240820172202.526547-5-chenghaoyang@google.com> X-Mailer: git-send-email 2.46.0.184.g6999bdac58-goog In-Reply-To: <20240820172202.526547-1-chenghaoyang@google.com> References: <20240820172202.526547-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: Konami Shu - There are two test patterns: color bars and diagonal lines - Add class for generating test patterns - Add libyuv to build dependencies - Make VirtualPipelineHandler show the test pattern - Format the code - Rename test_pattern to frame_generator - reflect comment - Fix const variable name - Use #pragma once - Make configure() private Signed-off-by: Konami Shu Co-developed-by: Harvey Yang Co-developed-by: Yunke Cao Co-developed-by: Tomasz Figa --- .../pipeline/virtual/frame_generator.h | 33 ++++++ src/libcamera/pipeline/virtual/meson.build | 22 ++++ .../virtual/test_pattern_generator.cpp | 112 ++++++++++++++++++ .../pipeline/virtual/test_pattern_generator.h | 58 +++++++++ src/libcamera/pipeline/virtual/virtual.cpp | 28 ++++- src/libcamera/pipeline/virtual/virtual.h | 8 ++ 6 files changed, 258 insertions(+), 3 deletions(-) create mode 100644 src/libcamera/pipeline/virtual/frame_generator.h create mode 100644 src/libcamera/pipeline/virtual/test_pattern_generator.cpp create mode 100644 src/libcamera/pipeline/virtual/test_pattern_generator.h diff --git a/src/libcamera/pipeline/virtual/frame_generator.h b/src/libcamera/pipeline/virtual/frame_generator.h new file mode 100644 index 000000000..9699af7a4 --- /dev/null +++ b/src/libcamera/pipeline/virtual/frame_generator.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Google Inc. + * + * frame_generator.h - Virtual cameras helper to generate frames + */ + +#pragma once + +#include +#include + +namespace libcamera { + +class FrameGenerator +{ +public: + virtual ~FrameGenerator() = default; + + /* Create buffers for using them in `generateFrame` */ + virtual void configure(const Size &size) = 0; + + /** Fill the output frame buffer. + * Use the frame at the frameCount of image frames + */ + virtual void generateFrame(const Size &size, + const FrameBuffer *buffer) = 0; + +protected: + FrameGenerator() {} +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/meson.build b/src/libcamera/pipeline/virtual/meson.build index ba7ff754e..e1e65e68d 100644 --- a/src/libcamera/pipeline/virtual/meson.build +++ b/src/libcamera/pipeline/virtual/meson.build @@ -2,4 +2,26 @@ libcamera_sources += files([ 'virtual.cpp', + 'test_pattern_generator.cpp', ]) + +libyuv_dep = dependency('libyuv', required : false) + +# Fallback to a subproject if libyuv isn't found, as it's typically not +# provided by distributions. +if not libyuv_dep.found() + cmake = import('cmake') + + libyuv_vars = cmake.subproject_options() + libyuv_vars.add_cmake_defines({'CMAKE_POSITION_INDEPENDENT_CODE': 'ON'}) + libyuv_vars.set_override_option('cpp_std', 'c++17') + libyuv_vars.append_compile_args('cpp', + '-Wno-sign-compare', + '-Wno-unused-variable', + '-Wno-unused-parameter') + libyuv_vars.append_link_args('-ljpeg') + libyuv = cmake.subproject('libyuv', options : libyuv_vars) + libyuv_dep = libyuv.dependency('yuv') +endif + +libcamera_deps += [libyuv_dep] diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp new file mode 100644 index 000000000..8dfe626e5 --- /dev/null +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Google Inc. + * + * test_pattern_generator.cpp - Derived class of FrameGenerator for + * generating test patterns + */ + +#include "test_pattern_generator.h" + +#include + +#include "libcamera/internal/mapped_framebuffer.h" + +#include "libyuv/convert_from_argb.h" +namespace libcamera { + +LOG_DECLARE_CATEGORY(Virtual) + +static const unsigned int kARGBSize = 4; + +void TestPatternGenerator::generateFrame( + const Size &size, + const FrameBuffer *buffer) +{ + MappedFrameBuffer mappedFrameBuffer(buffer, + MappedFrameBuffer::MapFlag::Write); + + auto planes = mappedFrameBuffer.planes(); + + /* Convert the template_ to the frame buffer */ + int ret = libyuv::ARGBToNV12( + template_.get(), /*src_stride_argb=*/size.width * kARGBSize, + planes[0].begin(), size.width, + planes[1].begin(), size.width, + size.width, size.height); + if (ret != 0) { + LOG(Virtual, Error) << "ARGBToNV12() failed with " << ret; + } +} + +std::unique_ptr ColorBarsGenerator::create() +{ + return std::make_unique(); +} + +void ColorBarsGenerator::configure(const Size &size) +{ + constexpr uint8_t kColorBar[8][3] = { + // R, G, B + { 0xff, 0xff, 0xff }, // White + { 0xff, 0xff, 0x00 }, // Yellow + { 0x00, 0xff, 0xff }, // Cyan + { 0x00, 0xff, 0x00 }, // Green + { 0xff, 0x00, 0xff }, // Magenta + { 0xff, 0x00, 0x00 }, // Red + { 0x00, 0x00, 0xff }, // Blue + { 0x00, 0x00, 0x00 }, // Black + }; + + template_ = std::make_unique( + size.width * size.height * kARGBSize); + + unsigned int colorBarWidth = size.width / std::size(kColorBar); + + uint8_t *buf = template_.get(); + for (size_t h = 0; h < size.height; h++) { + for (size_t w = 0; w < size.width; w++) { + // repeat when the width is exceed + int index = (w / colorBarWidth) % std::size(kColorBar); + + *buf++ = kColorBar[index][2]; // B + *buf++ = kColorBar[index][1]; // G + *buf++ = kColorBar[index][0]; // R + *buf++ = 0x00; // A + } + } +} + +std::unique_ptr DiagonalLinesGenerator::create() +{ + return std::make_unique(); +} + +void DiagonalLinesGenerator::configure(const Size &size) +{ + constexpr uint8_t kColorBar[8][3] = { + // R, G, B + { 0xff, 0xff, 0xff }, // White + { 0x00, 0x00, 0x00 }, // Black + }; + + template_ = std::make_unique( + size.width * size.height * kARGBSize); + + unsigned int lineWidth = size.width / 10; + + uint8_t *buf = template_.get(); + for (size_t h = 0; h < size.height; h++) { + for (size_t w = 0; w < size.width; w++) { + // repeat when the width is exceed + int index = ((w + h) / lineWidth) % 2; + + *buf++ = kColorBar[index][2]; // B + *buf++ = kColorBar[index][1]; // G + *buf++ = kColorBar[index][0]; // R + *buf++ = 0x00; // A + } + } +} + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.h b/src/libcamera/pipeline/virtual/test_pattern_generator.h new file mode 100644 index 000000000..ed8d4e43b --- /dev/null +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Google Inc. + * + * test_pattern_generator.h - Derived class of FrameGenerator for + * generating test patterns + */ + +#pragma once + +#include + +#include +#include + +#include "frame_generator.h" + +namespace libcamera { + +enum class TestPattern : char { + ColorBars = 0, + DiagonalLines = 1, +}; + +class TestPatternGenerator : public FrameGenerator +{ +private: + void generateFrame(const Size &size, + const FrameBuffer *buffer) override; + +protected: + /* Shift the buffer by 1 pixel left each frame */ + void shiftLeft(const Size &size); + /* Buffer of test pattern template */ + std::unique_ptr template_; +}; + +class ColorBarsGenerator : public TestPatternGenerator +{ +public: + static std::unique_ptr create(); + +private: + /* Generate a template buffer of the color bar test pattern. */ + void configure(const Size &size) override; +}; + +class DiagonalLinesGenerator : public TestPatternGenerator +{ +public: + static std::unique_ptr create(); + +private: + /* Generate a template buffer of the diagonal lines test pattern. */ + void configure(const Size &size) override; +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp index 74eb8c7ad..357fdd035 100644 --- a/src/libcamera/pipeline/virtual/virtual.cpp +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -192,10 +192,14 @@ int PipelineHandlerVirtual::exportFrameBuffers( return dmaBufAllocator_.exportBuffers(config.bufferCount, planeSizes, buffers); } -int PipelineHandlerVirtual::start([[maybe_unused]] Camera *camera, +int PipelineHandlerVirtual::start(Camera *camera, [[maybe_unused]] const ControlList *controls) { /* \todo Start reading the virtual video if any. */ + VirtualCameraData *data = cameraData(camera); + + data->frameGenerator_->configure(data->stream_.configuration().size); + return 0; } @@ -207,9 +211,14 @@ void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera) int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, Request *request) { + VirtualCameraData *data = cameraData(camera); + /* \todo Read from the virtual video if any. */ - for (auto it : request->buffers()) - completeBuffer(request, it.second); + for (auto const &[stream, buffer] : request->buffers()) { + /* map buffer and fill test patterns */ + data->frameGenerator_->generateFrame(stream->configuration().size, buffer); + completeBuffer(request, buffer); + } request->metadata().set(controls::SensorTimestamp, currentTimestamp()); completeRequest(request); @@ -241,11 +250,24 @@ bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator *enumerator std::set streams{ &data->stream_ }; const std::string id = "Virtual0"; std::shared_ptr camera = Camera::create(std::move(data), id, streams); + + initFrameGenerator(camera.get()); + registerCamera(std::move(camera)); return false; // Prevent infinite loops for now } +void PipelineHandlerVirtual::initFrameGenerator(Camera *camera) +{ + auto data = cameraData(camera); + if (data->testPattern_ == TestPattern::DiagonalLines) { + data->frameGenerator_ = DiagonalLinesGenerator::create(); + } else { + data->frameGenerator_ = ColorBarsGenerator::create(); + } +} + REGISTER_PIPELINE_HANDLER(PipelineHandlerVirtual, "virtual") } /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h index 6fc6b34d8..fecd9fa6f 100644 --- a/src/libcamera/pipeline/virtual/virtual.h +++ b/src/libcamera/pipeline/virtual/virtual.h @@ -13,6 +13,8 @@ #include "libcamera/internal/dma_buf_allocator.h" #include "libcamera/internal/pipeline_handler.h" +#include "test_pattern_generator.h" + namespace libcamera { class VirtualCameraData : public Camera::Private @@ -29,9 +31,13 @@ public: ~VirtualCameraData() = default; + TestPattern testPattern_; + std::vector supportedResolutions_; Stream stream_; + + std::unique_ptr frameGenerator_; }; class VirtualCameraConfiguration : public CameraConfiguration @@ -72,6 +78,8 @@ private: return static_cast(camera->_d()); } + void initFrameGenerator(Camera *camera); + DmaBufAllocator dmaBufAllocator_; }; From patchwork Tue Aug 20 16:23:36 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Harvey Yang X-Patchwork-Id: 20971 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 82BCBC323E for ; Tue, 20 Aug 2024 17:22:29 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 941B96340B; Tue, 20 Aug 2024 19:22:27 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="Hp59HjFI"; dkim-atps=neutral Received: from mail-wr1-x42d.google.com (mail-wr1-x42d.google.com [IPv6:2a00:1450:4864:20::42d]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3C3C7633D2 for ; Tue, 20 Aug 2024 19:22:18 +0200 (CEST) Received: by mail-wr1-x42d.google.com with SMTP id ffacd0b85a97d-3719753d365so3147396f8f.2 for ; Tue, 20 Aug 2024 10:22:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1724174537; x=1724779337; 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=2Ay//vNC4qtVEWwzRLa7yylN/5+6F1GspCmgnRirwsU=; b=Hp59HjFIr9uyxDEWXWUUskobFd3qgtzA9JC+X0kE8kuSfAhww2fQwoDf89u+Vrd+Dz U9Gi+et+MNctkumluJlzxV/2mx7iERyHpbMBmqhX2txRnbOhv0ZF+xmkhHzel2thtJOR NopjEFwzYblQG5yHyxU/1JaW1Ov9wS1CMJXmY= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724174537; x=1724779337; 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=2Ay//vNC4qtVEWwzRLa7yylN/5+6F1GspCmgnRirwsU=; b=mv9ZduSlbbAt4n4tZwZqGchBNeScoR0duPB5LBW6NYRj8Gf6MTJppypdDAZI3VxoYK S44e9n/O+xsl1LhHSqZ5iZgkR98NAGDsqFggB0IOWoNsAFLAVNTY4ka0LwfHOBZq0Yi3 /JlDaNlJaVEHYoe4hlt5qFj+qdR32y/4l9dKDZiVLRnKhd+CR5F99egbdlcMwayhAb8Y 0lRI8Ic2KohvIPZdZi3vcprFAabeA1McGTd3V7vhPW1ukBQp3bdL8VGYDttf+P26vigJ qZIjLsQ3bDAV+GxUeM0unnjnd3MMF3M6p4X081ZfwTccHtloTnZC/pdfuxJlylGG1nT0 hdwQ== X-Gm-Message-State: AOJu0YzayMypfybwLBdzydUsHSCqS69wG/yGuXT+unCwLK80dSJQqOpm ukW7JTFhKIs3eQ5nNIxvPafrw6rmbGU71xMuOD+PLzrTfX7dz4Qbs63DsLIdXwMkjiMakfncSI5 AzMBwa1M= X-Google-Smtp-Source: AGHT+IFIcxNLCZJTBf6g6tHR2TQooZF7X8Wptxq3VaM/q1/XlByCWmIjXH64IPTe2SUljJoxtqvE8w== X-Received: by 2002:adf:f0c9:0:b0:368:2f01:307a with SMTP id ffacd0b85a97d-3719469f97cmr9846690f8f.46.1724174537249; Tue, 20 Aug 2024 10:22:17 -0700 (PDT) Received: from chenghaoyang-germany.c.googlers.com.com (161.126.77.34.bc.googleusercontent.com. [34.77.126.161]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-42ab6e90db5sm30328575e9.0.2024.08.20.10.22.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 20 Aug 2024 10:22:16 -0700 (PDT) From: Harvey Yang X-Google-Original-From: Harvey Yang To: libcamera-devel@lists.libcamera.org Cc: Konami Shu , Harvey Yang , Yunke Cao , Tomasz Figa Subject: [PATCH v9 5/8] libcamera: pipeline: Read config and register cameras based on the config Date: Tue, 20 Aug 2024 16:23:36 +0000 Message-ID: <20240820172202.526547-6-chenghaoyang@google.com> X-Mailer: git-send-email 2.46.0.184.g6999bdac58-goog In-Reply-To: <20240820172202.526547-1-chenghaoyang@google.com> References: <20240820172202.526547-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: Konami Shu - Use Yaml::Parser to parse the config - Add config file at "virtual/data/virtual.yaml" - README.md contains the documentation for the format of config file and the implementation of Parser class. - Add Parser class to parse config file in Virtual pipeline handler - Add header file of virtual.cpp to use VirtualCameraData class in Parser class - Parse test patterns - Raise Error when the width of a resolution is an odd number Signed-off-by: Konami Shu Co-developed-by: Harvey Yang Co-developed-by: Yunke Cao Co-developed-by: Tomasz Figa --- src/libcamera/pipeline/virtual/README.md | 68 ++++++ .../pipeline/virtual/data/virtual.yaml | 49 +++++ src/libcamera/pipeline/virtual/meson.build | 1 + src/libcamera/pipeline/virtual/parser.cpp | 198 ++++++++++++++++++ src/libcamera/pipeline/virtual/parser.h | 45 ++++ src/libcamera/pipeline/virtual/virtual.cpp | 47 +++-- src/libcamera/pipeline/virtual/virtual.h | 6 +- 7 files changed, 389 insertions(+), 25 deletions(-) create mode 100644 src/libcamera/pipeline/virtual/README.md create mode 100644 src/libcamera/pipeline/virtual/data/virtual.yaml create mode 100644 src/libcamera/pipeline/virtual/parser.cpp create mode 100644 src/libcamera/pipeline/virtual/parser.h diff --git a/src/libcamera/pipeline/virtual/README.md b/src/libcamera/pipeline/virtual/README.md new file mode 100644 index 000000000..27d6283df --- /dev/null +++ b/src/libcamera/pipeline/virtual/README.md @@ -0,0 +1,68 @@ +# Virtual Pipeline Handler + +Virtual pipeline handler emulates fake external camera(s) on ChromeOS for testing. + +## Parse config file and register cameras + +- The config file is located at `src/libcamera/pipeline/virtual/data/virtual.yaml` + +### Config File Format +The config file contains the information about cameras' properties to register. +The config file should be a yaml file with dictionary of the cameraIds +associated with their properties as top level. The default value will be applied when any property is empty. + +Each camera block is a dictionary, containing the following keys: +- `supported_formats` (list of `VirtualCameraData::Resolution`, optional) : List of supported resolution and frame rates of the emulated camera + - `width` (`unsigned int`, default=1920): Width of the window resolution. This needs to be even. + - `height` (`unsigned int`, default=1080): Height of the window resolution. + - `frame_rates` (list of `int`, default=`[30,60]` ): Range of the frame rate. The list has to be two values of the lower bound and the upper bound of the frame rate. +- `test_pattern` (`string`, default="bars"): Which test pattern to use as frames. The options are "bars", "lines". +- `location` (`string`, default="front"): The location of the camera. Support "front" and "back". This is displayed in qcam camera selection window but this does not change the output. +- `model` (`string`, default="Unknown"): The model name of the camera. This is displayed in qcam camera selection window but this does not change the output. + +A sample config file: +``` +--- +"Virtual0": + supported_formats: + - width: 1920 + height: 1080 + frame_rates: + - 30 + - 60 + - width: 1680 + height: 1050 + frame_rates: + - 70 + - 80 + test_pattern: "bars" + location: "front" + model: "Virtual Video Device" +"Virtual1": + supported_formats: + - width: 800 + test_pattern: "lines" + location: "back" + model: "Virtual Video Device1" +"Virtual2": +``` + +### Implementation + +`Parser` class provides methods to parse the config file to register cameras +in Virtual Pipeline Handler. `parseConfigFile()` is exposed to use in +Virtual Pipeline Handler. + +This is the procedure of the Parser class: +1. `parseConfigFile()` parses the config file to `YamlObject` using `YamlParser::parse()`. + - Parse the top level of config file which are the camera ids and look into each camera properties. +2. For each camera, `parseCameraConfigData()` returns a camera with the configuration. + - The methods in the next step fill the data with the pointer to the Camera object. + - If the config file contains invalid configuration, this method returns nullptr. The camera will be skipped. +3. Parse each property and register the data. + - `parseSupportedFormats()`: Parses `supported_formats` in the config, which contains resolutions and frame rates. + - `parseTestPattern()`: Parses `test_pattern` in the config. + - `parseLocation()`: Parses `location` in the config. + - `parseModel()`: Parses `model` in the config. +4. Back to `parseConfigFile()` and append the camera configuration. +5. Returns a list of camera configurations. diff --git a/src/libcamera/pipeline/virtual/data/virtual.yaml b/src/libcamera/pipeline/virtual/data/virtual.yaml new file mode 100644 index 000000000..4eb239e24 --- /dev/null +++ b/src/libcamera/pipeline/virtual/data/virtual.yaml @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: CC0-1.0 +%YAML 1.1 +--- +"Virtual0": + supported_formats: + - width: 1920 + height: 1080 + frame_rates: + - 30 + - 60 + - width: 1680 + height: 1050 + frame_rates: + - 70 + - 80 + test_pattern: "lines" + location: "front" + model: "Virtual Video Device" +"Virtual1": + supported_formats: + - width: 800 + height: 600 + frame_rates: + - 30 + - 60 + test_pattern: + location: "back" + model: "Virtual Video Device1" +"Virtual2": + supported_formats: + - width: 100 + height: 100 + test_pattern: "lines" + location: "front" + model: "Virtual Video Device2" +"Virtual3": + supported_formats: + - width: 100 + height: 100 + - width: 800 + height: 600 + - width: 1920 + height: 1080 + frame_rates: + - 20 + - 30 + location: "a" + model: "Virtual Video Device3" +"Virtual4": diff --git a/src/libcamera/pipeline/virtual/meson.build b/src/libcamera/pipeline/virtual/meson.build index e1e65e68d..2e82e64cb 100644 --- a/src/libcamera/pipeline/virtual/meson.build +++ b/src/libcamera/pipeline/virtual/meson.build @@ -3,6 +3,7 @@ libcamera_sources += files([ 'virtual.cpp', 'test_pattern_generator.cpp', + 'parser.cpp', ]) libyuv_dep = dependency('libyuv', required : false) diff --git a/src/libcamera/pipeline/virtual/parser.cpp b/src/libcamera/pipeline/virtual/parser.cpp new file mode 100644 index 000000000..032c0cd9d --- /dev/null +++ b/src/libcamera/pipeline/virtual/parser.cpp @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Google Inc. + * + * parser.cpp - Virtual cameras helper to parse config file + */ + +#include "parser.h" + +#include +#include + +#include + +#include +#include + +#include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/yaml_parser.h" + +#include "virtual.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Virtual) + +std::vector> Parser::parseConfigFile( + File &file, PipelineHandler *pipe) +{ + std::vector> configurations; + + std::unique_ptr cameras = YamlParser::parse(file); + if (!cameras) { + LOG(Virtual, Error) << "Failed to pass config file."; + return configurations; + } + + if (!cameras->isDictionary()) { + LOG(Virtual, Error) << "Config file is not a dictionary at the top level."; + return configurations; + } + + /* Look into the configuration of each camera */ + for (const auto &[cameraId, cameraConfigData] : cameras->asDict()) { + std::unique_ptr data = + parseCameraConfigData(cameraConfigData, pipe); + /* Parse configData to data*/ + if (!data) { + /* Skip the camera if it has invalid config */ + LOG(Virtual, Error) << "Failed to parse config of the camera: " + << cameraId; + continue; + } + + data->id_ = cameraId; + ControlInfoMap::Map controls; + /* todo: Check which resolution's frame rate to be reported */ + controls[&controls::FrameDurationLimits] = + ControlInfo(int64_t(1000 / data->supportedResolutions_[0].frameRates[1]), + int64_t(1000 / data->supportedResolutions_[0].frameRates[0])); + data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls); + configurations.push_back(std::move(data)); + } + return configurations; +} + +std::unique_ptr Parser::parseCameraConfigData( + const YamlObject &cameraConfigData, PipelineHandler *pipe) +{ + std::unique_ptr data = std::make_unique(pipe); + + if (parseSupportedFormats(cameraConfigData, data.get())) + return nullptr; + + if (parseTestPattern(cameraConfigData, data.get())) + return nullptr; + + if (parseLocation(cameraConfigData, data.get())) + return nullptr; + + if (parseModel(cameraConfigData, data.get())) + return nullptr; + + return data; +} + +int Parser::parseSupportedFormats( + const YamlObject &cameraConfigData, VirtualCameraData *data) +{ + Size activeResolution{ 0, 0 }; + if (cameraConfigData.contains("supported_formats")) { + const YamlObject &supportedResolutions = cameraConfigData["supported_formats"]; + + for (const YamlObject &supportedResolution : supportedResolutions.asList()) { + unsigned int width = supportedResolution["width"].get(1920); + unsigned int height = supportedResolution["height"].get(1080); + if (width <= 0 || height <= 0) { + LOG(Virtual, Error) << "Invalid width or/and height"; + return -EINVAL; + } + if (width % 2 != 0) { + LOG(Virtual, Error) << "Invalid width: width needs to be even"; + return -EINVAL; + } + + std::vector frameRates; + if (supportedResolution.contains("frame_rates")) { + auto frameRatesList = + supportedResolution["frame_rates"].getList().value(); + if (frameRatesList.size() != 2) { + LOG(Virtual, Error) << "frame_rates needs to be the two edge values of a range"; + return -EINVAL; + } + if (frameRatesList[0] > frameRatesList[1]) { + LOG(Virtual, Error) << "frame_rates's first value(lower bound) is higher than the second value(upper bound)"; + return -EINVAL; + } + frameRates.push_back(frameRatesList[0]); + frameRates.push_back(frameRatesList[1]); + } else { + frameRates.push_back(30); + frameRates.push_back(60); + } + + data->supportedResolutions_.emplace_back( + VirtualCameraData::Resolution{ Size{ width, height }, + frameRates }); + + activeResolution = std::max(activeResolution, Size{ width, height }); + } + } else { + data->supportedResolutions_.emplace_back( + VirtualCameraData::Resolution{ Size{ 1920, 1080 }, + { 30, 60 } }); + activeResolution = Size(1920, 1080); + } + + data->properties_.set(properties::PixelArrayActiveAreas, + { Rectangle(activeResolution) }); + + return 0; +} + +int Parser::parseTestPattern( + const YamlObject &cameraConfigData, VirtualCameraData *data) +{ + std::string testPattern = cameraConfigData["test_pattern"].get().value(); + + /* Default value is "bars" */ + if (testPattern == "bars" || testPattern == "") { + data->testPattern_ = TestPattern::ColorBars; + } else if (testPattern == "lines") { + data->testPattern_ = TestPattern::DiagonalLines; + } else { + LOG(Virtual, Error) << "Test pattern: " << testPattern + << "is not supported"; + return -EINVAL; + } + return 0; +} + +int Parser::parseLocation( + const YamlObject &cameraConfigData, VirtualCameraData *data) +{ + std::string location = cameraConfigData["location"].get().value(); + + /* Default value is properties::CameraLocationFront */ + if (location == "front" || location == "") { + data->properties_.set(properties::Location, + properties::CameraLocationFront); + } else if (location == "back") { + data->properties_.set(properties::Location, + properties::CameraLocationBack); + } else { + LOG(Virtual, Error) << "location: " << location + << " is not supported"; + return -EINVAL; + } + + return 0; +} + +int Parser::parseModel( + const YamlObject &cameraConfigData, VirtualCameraData *data) +{ + std::string model = + cameraConfigData["model"].get().value(); + + /* Default value is "Unknown" */ + if (model == "") + data->properties_.set(properties::Model, "Unknown"); + else + data->properties_.set(properties::Model, model); + + return 0; +} + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/parser.h b/src/libcamera/pipeline/virtual/parser.h new file mode 100644 index 000000000..a377d8aa1 --- /dev/null +++ b/src/libcamera/pipeline/virtual/parser.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Google Inc. + * + * parser.h - Virtual cameras helper to parse config file + */ + +#pragma once + +#include +#include + +#include + +#include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/yaml_parser.h" + +#include "virtual.h" + +namespace libcamera { + +class Parser +{ +public: + Parser() {} + ~Parser() = default; + + std::vector> + parseConfigFile(File &file, PipelineHandler *pipe); + +private: + std::unique_ptr parseCameraConfigData( + const YamlObject &cameraConfigData, PipelineHandler *pipe); + + int parseSupportedFormats( + const YamlObject &cameraConfigData, VirtualCameraData *data); + int parseTestPattern( + const YamlObject &cameraConfigData, VirtualCameraData *data); + int parseLocation( + const YamlObject &cameraConfigData, VirtualCameraData *data); + int parseModel( + const YamlObject &cameraConfigData, VirtualCameraData *data); +}; + +} // namespace libcamera diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp index 357fdd035..0fe471f00 100644 --- a/src/libcamera/pipeline/virtual/virtual.cpp +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -18,6 +18,10 @@ #include "libcamera/internal/camera.h" #include "libcamera/internal/formats.h" #include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/yaml_parser.h" + +#include "frame_generator.h" +#include "parser.h" namespace libcamera { @@ -228,32 +232,31 @@ int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, 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)) }); + File file(configurationFile("virtual", "virtual.yaml")); + bool isOpen = file.open(File::OpenModeFlag::ReadOnly); + if (!isOpen) { + LOG(Virtual, Error) << "Failed to open config file: " << file.fileName(); + return false; + } - /* \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); + Parser parser; + auto configData = parser.parseConfigFile(file, this); + if (configData.size() == 0) { + LOG(Virtual, Error) << "Failed to parse any cameras from the config file: " + << file.fileName(); + return false; + } - /* 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); + /* Configure and register cameras with configData */ + for (auto &data : configData) { + std::set streams{ &data->stream_ }; + std::string id = data->id_; + std::shared_ptr camera = Camera::create(std::move(data), id, streams); - initFrameGenerator(camera.get()); + initFrameGenerator(camera.get()); - registerCamera(std::move(camera)); + registerCamera(std::move(camera)); + } return false; // Prevent infinite loops for now } diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h index fecd9fa6f..c1ac4eb90 100644 --- a/src/libcamera/pipeline/virtual/virtual.h +++ b/src/libcamera/pipeline/virtual/virtual.h @@ -22,7 +22,7 @@ class VirtualCameraData : public Camera::Private public: struct Resolution { Size size; - std::vector frame_rates; + std::vector frameRates; }; VirtualCameraData(PipelineHandler *pipe) : Camera::Private(pipe) @@ -31,9 +31,9 @@ public: ~VirtualCameraData() = default; - TestPattern testPattern_; - + std::string id_; std::vector supportedResolutions_; + TestPattern testPattern_; Stream stream_; From patchwork Tue Aug 20 16:23:37 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Harvey Yang X-Patchwork-Id: 20973 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 6AA14C323E for ; Tue, 20 Aug 2024 17:22:32 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AFF3463419; Tue, 20 Aug 2024 19:22:31 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="Cvek/Pa0"; dkim-atps=neutral Received: from mail-wm1-x330.google.com (mail-wm1-x330.google.com [IPv6:2a00:1450:4864:20::330]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 410F6633D3 for ; Tue, 20 Aug 2024 19:22:19 +0200 (CEST) Received: by mail-wm1-x330.google.com with SMTP id 5b1f17b1804b1-4281faefea9so45950805e9.2 for ; Tue, 20 Aug 2024 10:22:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1724174538; x=1724779338; 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=Pr2RjT5zL83ETuScO4bS+zvWmin1yvLtrtGvqaSpiWw=; b=Cvek/Pa0avygXUlKeQhjA6mqhz7vLsQzFTiWpRvjsh3ZrNHggt2hpDFLH/k6o0mY09 gihJjfbVOTptLHxcM3tIdIKynMwPfDaX76Odzeuh8B76k/PYdNiBIwPQs+FbZTniR6DZ R3xYxP9fsKfIHUPszETqUQET/iwu8l0xTaSAg= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724174538; x=1724779338; 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=Pr2RjT5zL83ETuScO4bS+zvWmin1yvLtrtGvqaSpiWw=; b=M2S/o04DgKYmBhqncU3M+DRfb+y0EluEuQ/BnTMdtV0L5l7ZX2OGzD5/EMtvTA70bG 1g/xrCUGZXcnbtHMNpKIAp1hiVEQwAfTMjZtrgudsxn8FQffNqSaQi30T40wv+B0hewt 7aLI1Sfya+uu0NqrfLEHfHwXg2OQ6zpmrwcCnPDx/UV2Ui+golQaH0Mytxc17eiaA7H/ Wo17qJtrPzhuv61VF4yvO1iiyIjEtgvH9iQLYxvbWAVmjilVK25rQjnN5wRhbH2Qqmb9 42tYEZaDMDro+//I+ttlHYhPeTcRqITr6/6NX+ukS4fyaZ13hgcYRS2P2kliIzh2PAAA 3r2w== X-Gm-Message-State: AOJu0YwNcVqd7oVwi1bsXptfchPX/N20CxUdwsQMJxWCveUZ8aqvCMTH 6YUP/VvRj9EGB8BJbOjs/k8aO3xQv/FnDZBb22+joAOZi8q8GF4SoHobPFMV2bTQr3RUFDrJFsE wJScI4ZE= X-Google-Smtp-Source: AGHT+IHjtFvlEtl1gqgBVdLTrTLzusepXezFVRk0EBWLS2h6OdRHU//8vMB4GTQ3ObGob9K87bHXpw== X-Received: by 2002:a05:600c:1d83:b0:428:c0a:27ea with SMTP id 5b1f17b1804b1-42abd212819mr556645e9.12.1724174538528; Tue, 20 Aug 2024 10:22:18 -0700 (PDT) Received: from chenghaoyang-germany.c.googlers.com.com (161.126.77.34.bc.googleusercontent.com. [34.77.126.161]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-42ab6e90db5sm30328575e9.0.2024.08.20.10.22.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 20 Aug 2024 10:22:18 -0700 (PDT) From: Harvey Yang X-Google-Original-From: Harvey Yang To: libcamera-devel@lists.libcamera.org Cc: Konami Shu , Harvey Yang , Yunke Cao , Tomasz Figa Subject: [PATCH v9 6/8] libcamera: pipeline: Shift test pattern by 1 pixel left every frame Date: Tue, 20 Aug 2024 16:23:37 +0000 Message-ID: <20240820172202.526547-7-chenghaoyang@google.com> X-Mailer: git-send-email 2.46.0.184.g6999bdac58-goog In-Reply-To: <20240820172202.526547-1-chenghaoyang@google.com> References: <20240820172202.526547-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: Konami Shu - This write the buffer every frame - Shifting makes the frame rate dropped from about 160 to 40 Patchset1->2 - Use constant instead of using a magic number Patchset2->3 - Make shiftLeft() private Signed-off-by: Konami Shu Co-developed-by: Harvey Yang Co-developed-by: Yunke Cao Co-developed-by: Tomasz Figa --- .../pipeline/virtual/data/virtual.yaml | 4 +-- .../virtual/test_pattern_generator.cpp | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/libcamera/pipeline/virtual/data/virtual.yaml b/src/libcamera/pipeline/virtual/data/virtual.yaml index 4eb239e24..b8956f2d4 100644 --- a/src/libcamera/pipeline/virtual/data/virtual.yaml +++ b/src/libcamera/pipeline/virtual/data/virtual.yaml @@ -28,8 +28,8 @@ model: "Virtual Video Device1" "Virtual2": supported_formats: - - width: 100 - height: 100 + - width: 400 + height: 300 test_pattern: "lines" location: "front" model: "Virtual Video Device2" diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp index 8dfe626e5..6df9b31e9 100644 --- a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp @@ -28,6 +28,9 @@ void TestPatternGenerator::generateFrame( auto planes = mappedFrameBuffer.planes(); + /* TODO: select whether to do shifting or not */ + shiftLeft(size); + /* Convert the template_ to the frame buffer */ int ret = libyuv::ARGBToNV12( template_.get(), /*src_stride_argb=*/size.width * kARGBSize, @@ -39,6 +42,39 @@ void TestPatternGenerator::generateFrame( } } +void TestPatternGenerator::shiftLeft(const Size &size) +{ + /* Store the first column temporarily */ + uint8_t firstColumn[size.height * kARGBSize]; + for (size_t h = 0; h < size.height; h++) { + unsigned int index = h * size.width * kARGBSize; + unsigned int index1 = h * kARGBSize; + firstColumn[index1] = template_[index]; + firstColumn[index1 + 1] = template_[index + 1]; + firstColumn[index1 + 2] = template_[index + 2]; + firstColumn[index1 + 3] = 0x00; + } + + /* Overwrite template_ */ + uint8_t *buf = template_.get(); + for (size_t h = 0; h < size.height; h++) { + for (size_t w = 0; w < size.width - 1; w++) { + /* Overwrite with the pixel on the right */ + unsigned int index = (h * size.width + w + 1) * kARGBSize; + *buf++ = template_[index]; // B + *buf++ = template_[index + 1]; // G + *buf++ = template_[index + 2]; // R + *buf++ = 0x00; // A + } + /* Overwrite the new last column with the original first column */ + unsigned int index1 = h * kARGBSize; + *buf++ = firstColumn[index1]; // B + *buf++ = firstColumn[index1 + 1]; // G + *buf++ = firstColumn[index1 + 2]; // R + *buf++ = 0x00; // A + } +} + std::unique_ptr ColorBarsGenerator::create() { return std::make_unique(); From patchwork Tue Aug 20 16:23:38 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Harvey Yang X-Patchwork-Id: 20974 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 4A4BAC32A9 for ; Tue, 20 Aug 2024 17:22:34 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D06A5633DC; Tue, 20 Aug 2024 19:22:33 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="kk5lOgTg"; dkim-atps=neutral Received: from mail-wm1-x332.google.com (mail-wm1-x332.google.com [IPv6:2a00:1450:4864:20::332]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 78D07633D4 for ; Tue, 20 Aug 2024 19:22:20 +0200 (CEST) Received: by mail-wm1-x332.google.com with SMTP id 5b1f17b1804b1-4281faefea9so45950895e9.2 for ; Tue, 20 Aug 2024 10:22:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1724174540; x=1724779340; 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=EMjej9H41FgV3cYdc+aKrEQY/wouaLeHJHp1lz774hE=; b=kk5lOgTgH5Y4nbjthcEICmYdXU1QUAEIMZr04iQINcqNTPBcdH6pjmnoa24og/lBPE MtijkRAQ/FdPYI+gXI5KKcpjR7kyH3dl7ZCtFsaeZMmt7J3GBw2aY+0qDzJX5BFoRjd6 JsyLwSLjznol+6Tq0kKUjorRbaiIqcGNXpjdQ= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724174540; x=1724779340; 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=EMjej9H41FgV3cYdc+aKrEQY/wouaLeHJHp1lz774hE=; b=PR0/JHY1q5o2obwUx3HoTTFfLISS4j4psVAaH+3do1tggrPnhqRHx99iN7r7kVb5QB i8w9TAbTk+lOQ8Ekm7apD25260Jwil7yS4ImQguEwVX3MKnkPD4hwcgp8IpyOPQM5jrq GUkr1yDbp6n96G7hvCqEpRkCt+Y1thY6rTc/k6aIhHQcDB5kvBeHciWttRNQq53Snjfz nAlB9FLc3J6GsmHwR3v6yLRpMXeZdrddGceb52huqCNuDDfFQJJ8jYMGNxXyMaJd/NbO zuzAoUC6ZuPvj+jv+U4Jribya+K5hCajaa8ECWtVrmpRmA7pog2Q39j63A/rkn/XPA6H H05Q== X-Gm-Message-State: AOJu0YxwP0g1cZYceJTsofmKGDGppOatjZRvRCc5Z7LJ+3JBcLhHf1/x S5uZnp26dYEK/UA8Q00qsimgsyYT5uCcvIaHCLTDvMjnFBgMLmr4WLQLcXwSp5bQoMbRvXhzAh4 v0hQh8ZY= X-Google-Smtp-Source: AGHT+IFAJfm+86+s83SNFXJ9sqSU9dVneVH3zHjryC0V4u1zPGpdpxQIev6IwKnNZ0m6rIkzjmz5Dw== X-Received: by 2002:a05:600c:198c:b0:425:69b7:3361 with SMTP id 5b1f17b1804b1-42abd21ef23mr587585e9.18.1724174539551; Tue, 20 Aug 2024 10:22:19 -0700 (PDT) Received: from chenghaoyang-germany.c.googlers.com.com (161.126.77.34.bc.googleusercontent.com. [34.77.126.161]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-42ab6e90db5sm30328575e9.0.2024.08.20.10.22.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 20 Aug 2024 10:22:19 -0700 (PDT) From: Harvey Yang X-Google-Original-From: Harvey Yang To: libcamera-devel@lists.libcamera.org Cc: Konami Shu , Harvey Yang , Yunke Cao , Tomasz Figa Subject: [PATCH v9 7/8] libcamera: pipeline: Load images Date: Tue, 20 Aug 2024 16:23:38 +0000 Message-ID: <20240820172202.526547-8-chenghaoyang@google.com> X-Mailer: git-send-email 2.46.0.184.g6999bdac58-goog In-Reply-To: <20240820172202.526547-1-chenghaoyang@google.com> References: <20240820172202.526547-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: Konami Shu - Refactor ImageFrameGenerator and TestPatternGenerator - Extend the config file to have section to choose to use images or test pattern - Extend Parser to parse the configuration - Add ImageFrameGenerator which uses images to provide frames Patchset1->2 - Extend the parser to accept files with ".jpeg" - Heap allocate some buffers in ImageFrameGenerator::generateFrame so the buffers won't cause stack over flow Patchset5->6 - Move static factory function from the interface class to the derived classes Patchset6->8 - create FrameGenerator before starting the camera Signed-off-by: Konami Shu Co-developed-by: Harvey Yang Co-developed-by: Yunke Cao Co-developed-by: Tomasz Figa --- src/libcamera/pipeline/virtual/README.md | 18 +- .../pipeline/virtual/common_functions.cpp | 27 +++ .../pipeline/virtual/common_functions.h | 18 ++ .../pipeline/virtual/frame_generator.h | 2 +- .../virtual/image_frame_generator.cpp | 154 ++++++++++++++++++ .../pipeline/virtual/image_frame_generator.h | 65 ++++++++ src/libcamera/pipeline/virtual/meson.build | 6 +- src/libcamera/pipeline/virtual/parser.cpp | 77 +++++++-- src/libcamera/pipeline/virtual/parser.h | 5 +- .../virtual/test_pattern_generator.cpp | 4 +- .../pipeline/virtual/test_pattern_generator.h | 2 +- src/libcamera/pipeline/virtual/virtual.cpp | 37 +++-- src/libcamera/pipeline/virtual/virtual.h | 20 ++- 13 files changed, 390 insertions(+), 45 deletions(-) create mode 100644 src/libcamera/pipeline/virtual/common_functions.cpp create mode 100644 src/libcamera/pipeline/virtual/common_functions.h create mode 100644 src/libcamera/pipeline/virtual/image_frame_generator.cpp create mode 100644 src/libcamera/pipeline/virtual/image_frame_generator.h diff --git a/src/libcamera/pipeline/virtual/README.md b/src/libcamera/pipeline/virtual/README.md index 27d6283df..5e21ce74a 100644 --- a/src/libcamera/pipeline/virtual/README.md +++ b/src/libcamera/pipeline/virtual/README.md @@ -15,8 +15,13 @@ Each camera block is a dictionary, containing the following keys: - `supported_formats` (list of `VirtualCameraData::Resolution`, optional) : List of supported resolution and frame rates of the emulated camera - `width` (`unsigned int`, default=1920): Width of the window resolution. This needs to be even. - `height` (`unsigned int`, default=1080): Height of the window resolution. - - `frame_rates` (list of `int`, default=`[30,60]` ): Range of the frame rate. The list has to be two values of the lower bound and the upper bound of the frame rate. -- `test_pattern` (`string`, default="bars"): Which test pattern to use as frames. The options are "bars", "lines". + - `frame_rates` (list of `int`, default=`[30,60]` ): Range of the frame rate. The list has to be two values of the lower bound and the upper bound of the frame rate. This does not affect the frame rate for now. +- `frames` (dictionary): + - `path` (`string`, default="bars"): Name of a test pattern, path to an image, or path to a directory of a series of images. + - The test patterns are "bars" which means color bars, and "lines" which means diagonal lines. + - The path to an image has ".jpg" extension. + - The path to a directory ends with "/". The name of the images in the directory are "{n}.jpg" with {n} is the sequence of images starting with 0. + - `scale_mode`(`string`, default="fill"): Scale mode when the frames are images. The scale modes are "fill", "contain", and "cover". This does not matter when frames is a test pattern. This does not affect the scale mode for now. - `location` (`string`, default="front"): The location of the camera. Support "front" and "back". This is displayed in qcam camera selection window but this does not change the output. - `model` (`string`, default="Unknown"): The model name of the camera. This is displayed in qcam camera selection window but this does not change the output. @@ -35,13 +40,16 @@ A sample config file: frame_rates: - 70 - 80 - test_pattern: "bars" + frames: + path: "lines" location: "front" model: "Virtual Video Device" "Virtual1": supported_formats: - width: 800 - test_pattern: "lines" + frames: + path: "path/to/directory_of_images/" + scale_mode: "contain" location: "back" model: "Virtual Video Device1" "Virtual2": @@ -61,7 +69,7 @@ This is the procedure of the Parser class: - If the config file contains invalid configuration, this method returns nullptr. The camera will be skipped. 3. Parse each property and register the data. - `parseSupportedFormats()`: Parses `supported_formats` in the config, which contains resolutions and frame rates. - - `parseTestPattern()`: Parses `test_pattern` in the config. + - `parseFrame()`: Parses `frames` in the config. - `parseLocation()`: Parses `location` in the config. - `parseModel()`: Parses `model` in the config. 4. Back to `parseConfigFile()` and append the camera configuration. diff --git a/src/libcamera/pipeline/virtual/common_functions.cpp b/src/libcamera/pipeline/virtual/common_functions.cpp new file mode 100644 index 000000000..207827ee0 --- /dev/null +++ b/src/libcamera/pipeline/virtual/common_functions.cpp @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Google Inc. + * + * common_functions.cpp - Helper that do not depend on any class + */ + +#include "common_functions.h" + +namespace libcamera { + +std::string getExtension(const std::string &path) +{ + size_t i = path.find("."); + if (i != std::string::npos) { + return path.substr(i); + } + return ""; +} + +std::size_t numberOfFilesInDirectory(std::filesystem::path path) +{ + using std::filesystem::directory_iterator; + return std::distance(directory_iterator(path), directory_iterator{}); +} + +} // namespace libcamera diff --git a/src/libcamera/pipeline/virtual/common_functions.h b/src/libcamera/pipeline/virtual/common_functions.h new file mode 100644 index 000000000..4203f9505 --- /dev/null +++ b/src/libcamera/pipeline/virtual/common_functions.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Google Inc. + * + * common_functions.h - Helper that do not depend on any class + */ + +#pragma once + +#include + +namespace libcamera { + +std::string getExtension(const std::string &path); + +std::size_t numberOfFilesInDirectory(std::filesystem::path path); + +} // namespace libcamera diff --git a/src/libcamera/pipeline/virtual/frame_generator.h b/src/libcamera/pipeline/virtual/frame_generator.h index 9699af7a4..f69576b36 100644 --- a/src/libcamera/pipeline/virtual/frame_generator.h +++ b/src/libcamera/pipeline/virtual/frame_generator.h @@ -23,7 +23,7 @@ public: /** Fill the output frame buffer. * Use the frame at the frameCount of image frames */ - virtual void generateFrame(const Size &size, + virtual void generateFrame(unsigned int &frameCount, const Size &size, const FrameBuffer *buffer) = 0; protected: diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.cpp b/src/libcamera/pipeline/virtual/image_frame_generator.cpp new file mode 100644 index 000000000..d374877ff --- /dev/null +++ b/src/libcamera/pipeline/virtual/image_frame_generator.cpp @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Google Inc. + * + * image_frame_generator.cpp - Derived class of FrameGenerator for + * generating frames from images + */ + +#include "image_frame_generator.h" + +#include +#include +#include + +#include +#include + +#include + +#include "libcamera/internal/mapped_framebuffer.h" + +#include "libyuv/convert.h" +#include "libyuv/scale.h" +namespace libcamera { + +LOG_DECLARE_CATEGORY(Virtual) + +std::unique_ptr ImageFrameGenerator::create( + ImageFrames &imageFrames) +{ + std::unique_ptr imageFrameGenerator = + std::make_unique(); + imageFrameGenerator->imageFrames_ = &imageFrames; + + /** For each file in the directory + * load the image, convert it to NV12, and store the pointer + */ + for (unsigned int i = 0; i < imageFrames.number.value_or(1); i++) { + std::string path; + if (!imageFrames.number.has_value()) { + /* If the path is to an image */ + path = imageFrames.path; + } else { + /* If the path is to a directory */ + path = constructPath(imageFrames.path, i); + } + + File file(path); + bool isOpen = file.open(File::OpenModeFlag::ReadOnly); + if (!isOpen) { + LOG(Virtual, Error) << "Failed to open image file: " << file.fileName(); + return nullptr; + } + + /* Read the image file to data */ + uint8_t buffer[file.size()]; + Span data{ buffer, (unsigned long)file.size() }; + long dataSize = file.read(data); + + /* Get the width and height of the image */ + int width, height; + if (libyuv::MJPGSize(data.data(), dataSize, &width, &height)) { + LOG(Virtual, Error) << "Failed to get the size of the image file: " + << file.fileName(); + return nullptr; + } + + /* Convert to NV12 and write the data to tmpY and tmpUV */ + int halfWidth = (width + 1) / 2; + int halfHeight = (height + 1) / 2; + std::unique_ptr dstY = + std::make_unique(width * height); + std::unique_ptr dstUV = + std::make_unique(halfWidth * halfHeight * 2); + int ret = libyuv::MJPGToNV12(data.data(), dataSize, + dstY.get(), width, dstUV.get(), + width, width, height, width, height); + if (ret != 0) { + LOG(Virtual, Error) << "MJPGToNV12() failed with " << ret; + } + + imageFrameGenerator->imageFrameDatas_.emplace_back( + ImageFrameData{ std::move(dstY), std::move(dstUV), + Size(width, height) }); + } + return imageFrameGenerator; +} + +std::string ImageFrameGenerator::constructPath(std::string &name, unsigned int &i) +{ + return name + std::to_string(i) + ".jpg"; +} + +void ImageFrameGenerator::configure(const Size &size) +{ + for (unsigned int i = 0; i < imageFrames_->number.value_or(1); i++) { + /* Scale the imageFrameDatas_ to scaledY and scaledUV */ + int halfSizeWidth = (size.width + 1) / 2; + int halfSizeHeight = (size.height + 1) / 2; + std::unique_ptr scaledY = + std::make_unique(size.width * size.height); + std::unique_ptr scaledUV = + std::make_unique(halfSizeWidth * halfSizeHeight * 2); + auto &src = imageFrameDatas_[i]; + + /* + * \todo Implement "contain" & "cover", based on + * |imageFrames_[i].scaleMode|. + */ + + /* + * \todo Some platforms might enforce stride due to GPU, like + * ChromeOS ciri (64). The weight needs to be a multiple of + * the stride to work properly for now. + */ + libyuv::NV12Scale(src.Y.get(), src.size.width, + src.UV.get(), src.size.width, + src.size.width, src.size.height, + scaledY.get(), size.width, scaledUV.get(), size.width, + size.width, size.height, libyuv::FilterMode::kFilterBilinear); + + /* Store the pointers to member variable */ + scaledFrameDatas_.emplace_back( + ImageFrameData{ std::move(scaledY), std::move(scaledUV), size }); + } +} + +void ImageFrameGenerator::generateFrame(unsigned int &frameCount, const Size &size, const FrameBuffer *buffer) +{ + /* Don't do anything when the list of buffers is empty*/ + if (scaledFrameDatas_.size() == 0) + return; + + MappedFrameBuffer mappedFrameBuffer(buffer, MappedFrameBuffer::MapFlag::Write); + + auto planes = mappedFrameBuffer.planes(); + + /* Make sure the frameCount does not over the number of images */ + frameCount %= imageFrames_->number.value_or(1); + + /* Write the scaledY and scaledUV to the mapped frame buffer */ + libyuv::NV12Copy(scaledFrameDatas_[frameCount].Y.get(), size.width, + scaledFrameDatas_[frameCount].UV.get(), size.width, planes[0].begin(), + size.width, planes[1].begin(), size.width, + size.width, size.height); + + /* proceed an image every 4 frames */ + /* \todo read the parameter_ from the configuration file? */ + parameter_++; + if (parameter_ % 4 == 0) + frameCount++; +} + +} // namespace libcamera diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.h b/src/libcamera/pipeline/virtual/image_frame_generator.h new file mode 100644 index 000000000..74468e075 --- /dev/null +++ b/src/libcamera/pipeline/virtual/image_frame_generator.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Google Inc. + * + * image_frame_generator.h - Derived class of FrameGenerator for + * generating frames from images + */ + +#pragma once + +#include +#include +#include +#include + +#include "frame_generator.h" + +namespace libcamera { + +enum class ScaleMode : char { + Fill = 0, + Contain = 1, + Cover = 2, +}; + +/* Frame configuration provided by the config file */ +struct ImageFrames { + std::string path; + ScaleMode scaleMode; + std::optional number; +}; + +class ImageFrameGenerator : public FrameGenerator +{ +public: + /** Factory function to create an ImageFrameGenerator object. + * Read the images and convert them to buffers in NV12 format. + * Store the pointers to the buffers to a list (imageFrameDatas) + */ + static std::unique_ptr create(ImageFrames &imageFrames); + +private: + struct ImageFrameData { + std::unique_ptr Y; + std::unique_ptr UV; + Size size; + }; + + /* Scale the buffers for image frames. */ + void configure(const Size &size) override; + void generateFrame(unsigned int &frameCount, const Size &size, const FrameBuffer *buffer) override; + + static std::string constructPath(std::string &name, unsigned int &i); + + /* List of pointers to the not scaled image buffers */ + std::vector imageFrameDatas_; + /* List of pointers to the scaled image buffers */ + std::vector scaledFrameDatas_; + /* Pointer to the imageFrames_ in VirtualCameraData */ + ImageFrames *imageFrames_; + /* Speed parameter. Change to the next image every parameter_ frames. */ + int parameter_; +}; + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/meson.build b/src/libcamera/pipeline/virtual/meson.build index 2e82e64cb..d56aedec4 100644 --- a/src/libcamera/pipeline/virtual/meson.build +++ b/src/libcamera/pipeline/virtual/meson.build @@ -2,11 +2,14 @@ libcamera_sources += files([ 'virtual.cpp', - 'test_pattern_generator.cpp', 'parser.cpp', + 'test_pattern_generator.cpp', + 'image_frame_generator.cpp', + 'common_functions.cpp', ]) libyuv_dep = dependency('libyuv', required : false) +libjpeg = dependency('libjpeg', required : false) # Fallback to a subproject if libyuv isn't found, as it's typically not # provided by distributions. @@ -26,3 +29,4 @@ if not libyuv_dep.found() endif libcamera_deps += [libyuv_dep] +libcamera_deps += [libjpeg] diff --git a/src/libcamera/pipeline/virtual/parser.cpp b/src/libcamera/pipeline/virtual/parser.cpp index 032c0cd9d..46d1ab181 100644 --- a/src/libcamera/pipeline/virtual/parser.cpp +++ b/src/libcamera/pipeline/virtual/parser.cpp @@ -18,6 +18,7 @@ #include "libcamera/internal/pipeline_handler.h" #include "libcamera/internal/yaml_parser.h" +#include "common_functions.h" #include "virtual.h" namespace libcamera { @@ -52,12 +53,12 @@ std::vector> Parser::parseConfigFile( continue; } - data->id_ = cameraId; + data->config_.id = cameraId; ControlInfoMap::Map controls; /* todo: Check which resolution's frame rate to be reported */ controls[&controls::FrameDurationLimits] = - ControlInfo(int64_t(1000 / data->supportedResolutions_[0].frameRates[1]), - int64_t(1000 / data->supportedResolutions_[0].frameRates[0])); + ControlInfo(int64_t(1000 / data->config_.resolutions[0].frameRates[1]), + int64_t(1000 / data->config_.resolutions[0].frameRates[0])); data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls); configurations.push_back(std::move(data)); } @@ -72,7 +73,7 @@ std::unique_ptr Parser::parseCameraConfigData( if (parseSupportedFormats(cameraConfigData, data.get())) return nullptr; - if (parseTestPattern(cameraConfigData, data.get())) + if (parseFrame(cameraConfigData, data.get())) return nullptr; if (parseLocation(cameraConfigData, data.get())) @@ -122,14 +123,14 @@ int Parser::parseSupportedFormats( frameRates.push_back(60); } - data->supportedResolutions_.emplace_back( + data->config_.resolutions.emplace_back( VirtualCameraData::Resolution{ Size{ width, height }, frameRates }); activeResolution = std::max(activeResolution, Size{ width, height }); } } else { - data->supportedResolutions_.emplace_back( + data->config_.resolutions.emplace_back( VirtualCameraData::Resolution{ Size{ 1920, 1080 }, { 30, 60 } }); activeResolution = Size(1920, 1080); @@ -141,21 +142,65 @@ int Parser::parseSupportedFormats( return 0; } -int Parser::parseTestPattern( +int Parser::parseFrame( const YamlObject &cameraConfigData, VirtualCameraData *data) { - std::string testPattern = cameraConfigData["test_pattern"].get().value(); + const YamlObject &frames = cameraConfigData["frames"]; + /* When there is no frames provided in the config file, use color bar test pattern */ + if (frames.size() == 0) { + data->config_.frame = TestPattern::ColorBars; + return 0; + } + + if (!frames.isDictionary()) { + LOG(Virtual, Error) << "'frames' is not a dictionary."; + return -EINVAL; + } + + std::string path = frames["path"].get().value(); + + if (auto ext = getExtension(path); ext == ".jpg" || ext == ".jpeg") { + ScaleMode scaleMode; + if (parseScaleMode(frames, &scaleMode)) + return -EINVAL; + data->config_.frame = ImageFrames{ path, scaleMode, std::nullopt }; + } else if (path.back() == '/') { + ScaleMode scaleMode; + if (parseScaleMode(frames, &scaleMode)) + return -EINVAL; + data->config_.frame = ImageFrames{ path, scaleMode, + numberOfFilesInDirectory(path) }; + } else if (path == "bars" || path == "") { + /* Default value is "bars" */ + data->config_.frame = TestPattern::ColorBars; + } else if (path == "lines") { + data->config_.frame = TestPattern::DiagonalLines; + } else { + LOG(Virtual, Error) << "Frame: " << path + << " is not supported"; + return -EINVAL; + } + return 0; +} - /* Default value is "bars" */ - if (testPattern == "bars" || testPattern == "") { - data->testPattern_ = TestPattern::ColorBars; - } else if (testPattern == "lines") { - data->testPattern_ = TestPattern::DiagonalLines; +int Parser::parseScaleMode( + const YamlObject &framesConfigData, ScaleMode *scaleMode) +{ + std::string mode = framesConfigData["scale_mode"].get().value(); + + /* Default value is fill */ + if (mode == "fill" || mode == "") { + *scaleMode = ScaleMode::Fill; + } else if (mode == "contain") { + *scaleMode = ScaleMode::Contain; + } else if (mode == "cover") { + *scaleMode = ScaleMode::Cover; } else { - LOG(Virtual, Error) << "Test pattern: " << testPattern - << "is not supported"; + LOG(Virtual, Error) << "scaleMode: " << mode + << " is not supported"; return -EINVAL; } + return 0; } @@ -195,4 +240,4 @@ int Parser::parseModel( return 0; } -} /* namespace libcamera */ +} // namespace libcamera diff --git a/src/libcamera/pipeline/virtual/parser.h b/src/libcamera/pipeline/virtual/parser.h index a377d8aa1..38ea460d5 100644 --- a/src/libcamera/pipeline/virtual/parser.h +++ b/src/libcamera/pipeline/virtual/parser.h @@ -34,12 +34,15 @@ private: int parseSupportedFormats( const YamlObject &cameraConfigData, VirtualCameraData *data); - int parseTestPattern( + int parseFrame( const YamlObject &cameraConfigData, VirtualCameraData *data); int parseLocation( const YamlObject &cameraConfigData, VirtualCameraData *data); int parseModel( const YamlObject &cameraConfigData, VirtualCameraData *data); + + int parseScaleMode( + const YamlObject &framesConfigData, ScaleMode *scaleMode); }; } // namespace libcamera diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp index 6df9b31e9..844dffd49 100644 --- a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp @@ -20,7 +20,7 @@ LOG_DECLARE_CATEGORY(Virtual) static const unsigned int kARGBSize = 4; void TestPatternGenerator::generateFrame( - const Size &size, + [[maybe_unused]] unsigned int &frameCount, const Size &size, const FrameBuffer *buffer) { MappedFrameBuffer mappedFrameBuffer(buffer, @@ -29,7 +29,7 @@ void TestPatternGenerator::generateFrame( auto planes = mappedFrameBuffer.planes(); /* TODO: select whether to do shifting or not */ - shiftLeft(size); + // shiftLeft(size); /* Convert the template_ to the frame buffer */ int ret = libyuv::ARGBToNV12( diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.h b/src/libcamera/pipeline/virtual/test_pattern_generator.h index ed8d4e43b..cebdd0141 100644 --- a/src/libcamera/pipeline/virtual/test_pattern_generator.h +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.h @@ -25,7 +25,7 @@ enum class TestPattern : char { class TestPatternGenerator : public FrameGenerator { private: - void generateFrame(const Size &size, + void generateFrame(unsigned int &frameCount, const Size &size, const FrameBuffer *buffer) override; protected: diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp index 0fe471f00..2412af703 100644 --- a/src/libcamera/pipeline/virtual/virtual.cpp +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -20,9 +20,13 @@ #include "libcamera/internal/pipeline_handler.h" #include "libcamera/internal/yaml_parser.h" -#include "frame_generator.h" #include "parser.h" +#define ifTestPattern(v) std::holds_alternative(v) +#define getTestPattern(v) std::get(v) +#define ifImageFrames(v) std::holds_alternative(v) +#define getImageFrames(v) std::get(v) + namespace libcamera { LOG_DEFINE_CATEGORY(Virtual) @@ -63,12 +67,12 @@ CameraConfiguration::Status VirtualCameraConfiguration::validate() } Size maxSize; - for (const auto &resolution : data_->supportedResolutions_) + for (const auto &resolution : data_->config_.resolutions) maxSize = std::max(maxSize, resolution.size); for (StreamConfiguration &cfg : config_) { bool found = false; - for (const auto &resolution : data_->supportedResolutions_) { + for (const auto &resolution : data_->config_.resolutions) { if (resolution.size.width == cfg.size.width && resolution.size.height == cfg.size.height) { found = true; @@ -110,7 +114,7 @@ PipelineHandlerVirtual::generateConfiguration(Camera *camera, return config; Size minSize, sensorResolution; - for (const auto &resolution : data->supportedResolutions_) { + for (const auto &resolution : data->config_.resolutions) { if (minSize.isNull() || minSize > resolution.size) minSize = resolution.size; @@ -199,7 +203,7 @@ int PipelineHandlerVirtual::exportFrameBuffers( int PipelineHandlerVirtual::start(Camera *camera, [[maybe_unused]] const ControlList *controls) { - /* \todo Start reading the virtual video if any. */ + /* Start reading the images/generating test patterns */ VirtualCameraData *data = cameraData(camera); data->frameGenerator_->configure(data->stream_.configuration().size); @@ -219,8 +223,8 @@ int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, /* \todo Read from the virtual video if any. */ for (auto const &[stream, buffer] : request->buffers()) { - /* map buffer and fill test patterns */ - data->frameGenerator_->generateFrame(stream->configuration().size, buffer); + /* Map buffer. Fill test patterns or images */ + data->frameGenerator_->generateFrame(data->frameCount_, stream->configuration().size, buffer); completeBuffer(request, buffer); } @@ -250,9 +254,10 @@ bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator *enumerator /* Configure and register cameras with configData */ for (auto &data : configData) { std::set streams{ &data->stream_ }; - std::string id = data->id_; + std::string id = data->config_.id; std::shared_ptr camera = Camera::create(std::move(data), id, streams); + /* Initialize FrameGenerator*/ initFrameGenerator(camera.get()); registerCamera(std::move(camera)); @@ -264,13 +269,19 @@ bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator *enumerator void PipelineHandlerVirtual::initFrameGenerator(Camera *camera) { auto data = cameraData(camera); - if (data->testPattern_ == TestPattern::DiagonalLines) { - data->frameGenerator_ = DiagonalLinesGenerator::create(); - } else { - data->frameGenerator_ = ColorBarsGenerator::create(); + auto &frame = data->config_.frame; + if (ifTestPattern(frame)) { + TestPattern &testPattern = getTestPattern(frame); + if (testPattern == TestPattern::DiagonalLines) { + data->frameGenerator_ = DiagonalLinesGenerator::create(); + } else { + data->frameGenerator_ = ColorBarsGenerator::create(); + } + } else if (ifImageFrames(frame)) { + data->frameGenerator_ = ImageFrameGenerator::create(getImageFrames(frame)); } } REGISTER_PIPELINE_HANDLER(PipelineHandlerVirtual, "virtual") -} /* namespace libcamera */ +} // namespace libcamera diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h index c1ac4eb90..f41a4a906 100644 --- a/src/libcamera/pipeline/virtual/virtual.h +++ b/src/libcamera/pipeline/virtual/virtual.h @@ -7,16 +7,22 @@ #pragma once +#include + #include #include "libcamera/internal/camera.h" #include "libcamera/internal/dma_buf_allocator.h" #include "libcamera/internal/pipeline_handler.h" +#include "frame_generator.h" +#include "image_frame_generator.h" #include "test_pattern_generator.h" namespace libcamera { +using VirtualFrame = std::variant; + class VirtualCameraData : public Camera::Private { public: @@ -24,6 +30,13 @@ public: Size size; std::vector frameRates; }; + /* The config file is parsed to the Configuration struct */ + struct Configuration { + std::string id; + std::vector resolutions; + VirtualFrame frame; + }; + VirtualCameraData(PipelineHandler *pipe) : Camera::Private(pipe) { @@ -31,12 +44,9 @@ public: ~VirtualCameraData() = default; - std::string id_; - std::vector supportedResolutions_; - TestPattern testPattern_; - + unsigned int frameCount_ = 0; + Configuration config_; Stream stream_; - std::unique_ptr frameGenerator_; }; From patchwork Tue Aug 20 16:23:39 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Harvey Yang X-Patchwork-Id: 20975 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 727D7C323E for ; Tue, 20 Aug 2024 17:22:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C3046633FA; Tue, 20 Aug 2024 19:22:34 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="bHWZZm6o"; dkim-atps=neutral Received: from mail-wm1-x335.google.com (mail-wm1-x335.google.com [IPv6:2a00:1450:4864:20::335]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 809F7633D5 for ; Tue, 20 Aug 2024 19:22:21 +0200 (CEST) Received: by mail-wm1-x335.google.com with SMTP id 5b1f17b1804b1-428243f928cso46685215e9.3 for ; Tue, 20 Aug 2024 10:22:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1724174541; x=1724779341; 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=TsnaMW9RnciKwlBvDg1jm/k8oS0941yZ3v5QaArfrqE=; b=bHWZZm6o/oN6PcmIrTXRZDiB9qwshSK46yPBcW1XqkDwP6mNr2FejLSb56RJkBCU9I NLIS/EqtODWUCpfBeVqE3xX3G6/W88sAwv5h1XQxT17dFrvxBM4Q/5W2keJqhDdzXVeX 6I1pAQ48WfmV/8Gt+hgKHB0QDpm0AqjjA7+rA= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724174541; x=1724779341; 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=TsnaMW9RnciKwlBvDg1jm/k8oS0941yZ3v5QaArfrqE=; b=BUwiYBVWUDpctfmeSoKfuWplOTIe3QMHQLzF/sWUs8nygvjCYshRR7I6F7f8Ca1hYY clG7xESyWCA4zNZPto0+xIg11XER8UMgK5MR1yMzbx+qduT2DQFOWFnotU9p8OFOvq0B 2DB0F3Z7wLwbwUCmN0QX1/qN8ASOhN5DF4VM+vOTGZWPbetKgD5kssgeWEM2wI55vzFm hUKfk3HsaMDbs86UdcgaERRVlCOa+ZaIKEBC2bJ3rUq0jhQoih1r0inquL1rKRKs1P8B UY3tlPCrrC+3aZFvTLJPOSUrfNnu2csv/93o9mILTPcprjXU+IOf1eZDAjP9P/ijojDy G9GQ== X-Gm-Message-State: AOJu0YzutWQgIIyV+0wcBvpVZwEm1ug8fpf2w9KVYlqAabT2YPUd9jbP sZnZNlhqkoRDOpa+Y2EOwjMxBioTLtvCYlBFBCsbwfxKsHmvG/E5RKwooRxtjAZzXaEaDAuzm2Y 1h33ifFI= X-Google-Smtp-Source: AGHT+IGUQ126/tskHXTdmzT8XnEC3ItBHDOwWkqMxcNYRe0QlAQgMnJdRJg2T+Eqq4/g+hJ9JZXxSw== X-Received: by 2002:a05:600c:46c4:b0:428:16a0:1c3d with SMTP id 5b1f17b1804b1-42abd2282c3mr558915e9.19.1724174540789; Tue, 20 Aug 2024 10:22:20 -0700 (PDT) Received: from chenghaoyang-germany.c.googlers.com.com (161.126.77.34.bc.googleusercontent.com. [34.77.126.161]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-42ab6e90db5sm30328575e9.0.2024.08.20.10.22.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 20 Aug 2024 10:22:19 -0700 (PDT) From: Harvey Yang X-Google-Original-From: Harvey Yang To: libcamera-devel@lists.libcamera.org Cc: Harvey Yang , Harvey Yang , Milan Zamazal Subject: [PATCH v9 8/8] libcamera: software_isp: Refactor SoftwareIsp to use DmaBufAllocator::exportBuffers Date: Tue, 20 Aug 2024 16:23:39 +0000 Message-ID: <20240820172202.526547-9-chenghaoyang@google.com> X-Mailer: git-send-email 2.46.0.184.g6999bdac58-goog In-Reply-To: <20240820172202.526547-1-chenghaoyang@google.com> References: <20240820172202.526547-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" As the helper function DmaBufAllocator::exportBuffers is added, we can avoid some code duplication in SoftwareIsp as well. Signed-off-by: Harvey Yang Reviewed-by: Milan Zamazal --- src/libcamera/software_isp/software_isp.cpp | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp index c8748d888..35077feb2 100644 --- a/src/libcamera/software_isp/software_isp.cpp +++ b/src/libcamera/software_isp/software_isp.cpp @@ -256,25 +256,7 @@ int SoftwareIsp::exportBuffers(const Stream *stream, unsigned int count, if (stream == nullptr) return -EINVAL; - for (unsigned int i = 0; i < count; i++) { - const std::string name = "frame-" + std::to_string(i); - const size_t frameSize = debayer_->frameSize(); - - FrameBuffer::Plane outPlane; - outPlane.fd = SharedFD(dmaHeap_.alloc(name.c_str(), frameSize)); - if (!outPlane.fd.isValid()) { - LOG(SoftwareIsp, Error) - << "failed to allocate a dma_buf"; - return -ENOMEM; - } - outPlane.offset = 0; - outPlane.length = frameSize; - - std::vector planes{ outPlane }; - buffers->emplace_back(std::make_unique(std::move(planes))); - } - - return count; + return dmaHeap_.exportBuffers(count, { debayer_->frameSize() }, buffers); } /**