From patchwork Thu Sep 8 18:48:42 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Xavier Roumegue X-Patchwork-Id: 17348 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 C6CC7C327D for ; Thu, 8 Sep 2022 18:49:26 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3110D620B6; Thu, 8 Sep 2022 20:49:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1662662965; bh=EKlqKSFCmwmnPZXIXUE9x0XE7k7IhRmt+tOnU2YRQIw=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=1e674kEPqK4bZMhnezRZCkYerrNcqCBgHb/OqhEOe+nP20Tll5TzDGwzImjs5sSWu 6iq/ogPXXl7Xwwyc3zVAHh451GjwVLGYSCvyRB/7eDzqN+KA25Bzs8ZEmVAQHYFPGf f7q/RViLsF30qtq3SwvVG49zjo8bxkMhtwVbD+2KAAAo74c3J67MoUb9EIrb9oClXC dU3sOox5haMD28e7goMCT/nN8/fHLGiJ1ZahUZaMdnvE3z7A9o60qTK6ZJWrZN9owc MlQ5K4zHKAOzq/a547OhZigkZrJrZtOXXKbQjYNpj6IIwdQ1EIjkGZCmXIQR1gtEHU ffIrlzCxB055A== Received: from EUR04-DB3-obe.outbound.protection.outlook.com (mail-eopbgr60040.outbound.protection.outlook.com [40.107.6.40]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 22940620AF for ; Thu, 8 Sep 2022 20:49:21 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=NXP1.onmicrosoft.com header.i=@NXP1.onmicrosoft.com header.b="cdjhpENZ"; dkim-atps=neutral ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=kufSDeHXOYZY8yWXt+jqlgG4Wpw1mwta2vYRLgf/OutzaeeMV2nC557OfkniOhFkwkN0ZAzddgb8BMZKiqPUqG99Gu9F05lGMYFzMWLM5P4wkV2wrS72BVuXSO19K5RelHZrgjlDDXYoDAdxkhsIfuh12/LiRpBo+cGKmWVn6CToBOyqARnXyrhkUkwrr7ArZ+fu5bmjprIjbapE1NPhP8kb+QdOSihS65/Vj1gffmIb6mrff2mRIIfBFA7pzWFazeuxiRZ8LtjaOTe6ZJa8A9bFl4Szzq+iZa+olYLCYxKxJ3TXQlhgOBvFL8mX13lHrPtNYRJ4Gv9L0RDmeG8wSg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=TEOyI1lC4MfDpLDLdSI+otafx7tIauV6bnWUh0XG0RE=; b=JM8gieQITMj0Jf7hJVQa5bwFoUdW8bvtFyEDaoP9okKPyK6tSS0V8QjhlfJuyJIeuQmRuK7jp6IzSB0ekGjJGPqsgKFYrnG/30j08sog4xtlnbXEcvjjbdQwgtNDJbdA7aEL2S8O3DPqXNNT2V2SU9GOjyGounoTgO0mMrLBwObaM7BKHzgHWfLnh/tyvh9CMSDgzdO5fDfZiLaA9j3EuNYeBBEMjLzk0DQSxq/DERyuZ2EFodOC9RKpZlBCKXkBOgOFxUadkA7OSPK1dhXGPyzWZPdk/nxx3UxmyaI0jb91LQeVz/Dnz980C63CWOycZrHkdgiEAd0guWY/rwYWdg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=oss.nxp.com; dmarc=pass action=none header.from=oss.nxp.com; dkim=pass header.d=oss.nxp.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=NXP1.onmicrosoft.com; s=selector2-NXP1-onmicrosoft-com; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=TEOyI1lC4MfDpLDLdSI+otafx7tIauV6bnWUh0XG0RE=; b=cdjhpENZpACxanhD0OxtxDCe2geY86CyAUefyjxBkAkvjrTj3q3r9vqJJE0uAY4KSExIveZXZJC99s6RrLvyU674lUuJgcGgNKATPJzKUdjsysdtlYQwu4F6nb0hUd2mCFQ4l+j1SP9iCb+vobOu8ElGsIhEmiO2j6+AsaL8MeY= Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=oss.nxp.com; Received: from PAXPR04MB8703.eurprd04.prod.outlook.com (2603:10a6:102:21e::22) by AS8PR04MB8852.eurprd04.prod.outlook.com (2603:10a6:20b:42f::14) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.5612.19; Thu, 8 Sep 2022 18:49:19 +0000 Received: from PAXPR04MB8703.eurprd04.prod.outlook.com ([fe80::485:adba:7081:715a]) by PAXPR04MB8703.eurprd04.prod.outlook.com ([fe80::485:adba:7081:715a%3]) with mapi id 15.20.5612.019; Thu, 8 Sep 2022 18:49:19 +0000 To: libcamera-devel@lists.libcamera.org Date: Thu, 8 Sep 2022 20:48:42 +0200 Message-Id: <20220908184850.1874303-7-xavier.roumegue@oss.nxp.com> X-Mailer: git-send-email 2.37.3 In-Reply-To: <20220908184850.1874303-1-xavier.roumegue@oss.nxp.com> References: <20220908184850.1874303-1-xavier.roumegue@oss.nxp.com> X-ClientProxiedBy: PR3P250CA0001.EURP250.PROD.OUTLOOK.COM (2603:10a6:102:57::6) To PAXPR04MB8703.eurprd04.prod.outlook.com (2603:10a6:102:21e::22) MIME-Version: 1.0 X-MS-Exchange-MessageSentRepresentingType: 1 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: PAXPR04MB8703:EE_|AS8PR04MB8852:EE_ X-MS-Office365-Filtering-Correlation-Id: a8f7b1cf-9893-453c-2213-08da91cad66d X-MS-Exchange-SharedMailbox-RoutingAgent-Processed: True X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: YWGslAEETi+5z4LFMy3+v3MEOiba567Geob1Qklm8Xi9Rs2PRyfpyxhpiE95+vB9D1VDnesCNQl1LNf18uT0iezc5HVbntHIkCI2oAxPVcSRvH8N4Xvw9FuX3PgcK7MLYynomYSoEwcLp8I0l2wGYGBc4eJXWig8C51WumKxYzylidpnqBndDKgQLLFiRzN/8+CsQMwyfbBSSGXfdf1/bveqdMzvVc0zVdA1ctEC62mBogGZzDtLXz05BAY7TzCRf4gNWZI4Co6B6V44KyRSSUSkhR1AyYCxYKYPkq6frNYsY3DDxQyo7wi9Xhkr71SDTulPPLyZaqr62plmX+17iOoQ9W+HPrJmcCNOmpkA7tzrtE+pAApFgE3ZjndBnonHUNAuwmBzPEp5BAyPJoM37JKv7mPUWX2+RqS24H8t0RlMwYeDL+zAp24x3DDoPdWXUnT1w2yKNzzluUkSGT9GdpyZn5Z53OWZM15Dv/0ozg79lFBaAnu+XG8jYUcztA27SQD0Hg2U8xyo4aGV/nkGvCDpmsT8vY4dr3pXmosb/oJe3Mo9E4lMxi1pZYocUhieechFo+V26fQjqX/88ogcEy6QMD9XDFflPNqQWbcslBndLD9GN4hlleTBj9AN2WfRLP6vNqlkRuLeWGXhz0AemVZ0R3/jnQYiQsDdf4ijjmN8x8Xp6BKACojcljtmw3MZaJyEgajUEGQ+zaEzfDw9DQ== X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:PAXPR04MB8703.eurprd04.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230016)(4636009)(39860400002)(346002)(376002)(396003)(136003)(366004)(38100700002)(66556008)(66476007)(66946007)(8936002)(30864003)(44832011)(5660300002)(4326008)(8676002)(6916009)(316002)(41300700001)(186003)(1076003)(2616005)(2906002)(478600001)(6666004)(86362001)(6512007)(52116002)(6506007)(83380400001)(6486002); DIR:OUT; SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: O4uORQXq0+hJce5oL9f8kcXXbD66plsL/kR4NNduqZmFN/uY/YDygVtizL3vIBHlBx36Zn6r1WvjGI/dzZeFiEEF/94ap2tWWOasP2dYvIeMwSiAtRgR7yzvicZ8nMX3FDRtRQDv4ETxwejAZj6N62KXPaj/rYrd0y0jClw9BSbY5ScSLA/EWGASsmYi0ZpuS0JLBsndTITPvmBrloP0O9C36GXqVG+w4Vn7TfD++/+UDe6l39Hc6o8Q1KqZh6hp5TrEfssd/S+qZ2lBf54KaFHzlVfaXFMwvB0x7QSM0ZDzNL3gSOwlsUcrCAxRQAbH0Ry2yiCybSV6ZFcVC9Zb0KvbG1e28ycYTfYNjTTyoKNrkBEDRb3PxzKVDynpm00Armf+kG50auF04l5z148oiluC4gyira80qrRu74e155PuPbtAk39Km0BOgmOtrjujooCjGycF7QnCMwbh1zBVRqzScBkeumJBfuUAQd+tRgAaNIcRWyAtOruFZ1E7egGhrtx9lkprwXKVEm1qa7OOww8+yY3QbMuvR239IH5552bc6cs+5CRjFPHgS7vSqnLg0RmBQnjBDnLll+x1CjhuktcSFrvCTz2ISYbs4n/91eKiEo79kbcGr+pbr6n1PIdC9TesVM37C5+YLqsErN/RJtw1OLlvC5PJ8CQeWFZDycYAUxR+wqEb/jmh4hgyKhYqfw01Zf4EHEVRInMbmnzBT20xlqon20qkwsSIeTSa9LjKwTEFPk+k9h7vkdGw1BaueKL17cA5ef6+cfmwDlbiPv3ajSBwF2sa9+ayqDNbfSnt/bx7U3H4syBunJ9IV+hnRul4dvUm3v7yvKjkHBYeADotdCvPvaXl5CVKy9nw5JRdOk+gxN9x4M1dsMcAx7t8NXRvsNmcZgtSnGT9sXed3xGg5jkyGLlb32MMNyeBza9GfXyMW+GPiTW+EoejAKKySt88hY1Xpaxm2cglNRKPE4Q3MTvOSDl1LMH2xMuK262kPfh42gukO6mFcdfaeutC0SRJe9PWD1sza1Lw5DpztLHVYxKUc3E9sIHJlAFDavkCXejvHd5YCVzORSFTdmentBegTtBcudukGCVzE7t/PKlQt8HiTOhg2/OpcMZGrvjsm74ZmpPkFXw0HbYbtVUJWRKOCM5D7qrK8ZJsmCLOSWQwhq5PxwyI985hRIB2nH+uYiVeEmRr+xmDL7pZLsvMUKpkNfGy15tlt88jjtjavWOpdmSXbYUAqEZ94ca5BvCH1tVaHZp/c6NK2KEolbsvXeS7y3a4AEwVnr7ZGNboL/2zkkw9minqLAMG2ERDz+iaIiXYWWjzeRchYu+utIZrsqugEonhBZ01FUxdiCLqLilWqAkeWH/dvfJKTWV4QFtrnOOvRCLdJzEwlGhYPCEzMUXMhimxNdYRILvRzu0HZ8JcBcWVwCcomm3lEoKVjf1FLw9wCVvTNThaR36uOhAIQA4MhLbG1rAFtSfETBr5Z91Kt/U/CVLl0UgXo85YXN6n7YggG8eAqTDzFYZrR9M7N5LILdD3KmHl4X36oWCSY+g7J3gyZIMQTpOhAHWGkoETO4SlJFHtrHBWH41YPVGElxMTkI2VlM/Yud5VVQMqdrFRgIf4yt4lmbUYVK2J42hgrf3+ptxiAnNsVub7VbaaHxhEiol9fKaygofmY6jL3Q== X-OriginatorOrg: oss.nxp.com X-MS-Exchange-CrossTenant-Network-Message-Id: a8f7b1cf-9893-453c-2213-08da91cad66d X-MS-Exchange-CrossTenant-AuthSource: PAXPR04MB8703.eurprd04.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 08 Sep 2022 18:49:19.5431 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 686ea1d3-bc2b-4c6f-a92c-d99c5c301635 X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: if0tn6tAML0yFK3d+AXErwAFpxwCmqjjYShIap3RfEXnQ1TtPnmtX1XuL7qYk/PxfWVZqj7mBDkrBoYkXyqtUQ== X-MS-Exchange-Transport-CrossTenantHeadersStamped: AS8PR04MB8852 Subject: [libcamera-devel] [PATCH 06/14] libcamera: converter: Add v4l2 m2m converter implementation 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: , X-Patchwork-Original-From: Xavier Roumegue via libcamera-devel From: Xavier Roumegue Reply-To: Xavier Roumegue Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Introduce a converter implementation relying on a v4l2 m2m device, mostly based on the current simple pipeline converter implementation. The main change is the introduction of Mapping object which can be loaded through a configuration file which define vertices remapping coordinates. Those latters can be applied by any classes derived from this base class which define the apply_mapping() method. Signed-off-by: Xavier Roumegue --- .../libcamera/internal/converter_v4l2_m2m.h | 120 +++++ include/libcamera/internal/meson.build | 1 + src/libcamera/converter_v4l2_m2m.cpp | 504 ++++++++++++++++++ src/libcamera/meson.build | 1 + 4 files changed, 626 insertions(+) create mode 100644 include/libcamera/internal/converter_v4l2_m2m.h create mode 100644 src/libcamera/converter_v4l2_m2m.cpp diff --git a/include/libcamera/internal/converter_v4l2_m2m.h b/include/libcamera/internal/converter_v4l2_m2m.h new file mode 100644 index 00000000..3667b128 --- /dev/null +++ b/include/libcamera/internal/converter_v4l2_m2m.h @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright 2022 NXP + * + * converter_v4l2_m2m.h - V4l2 M2M Format converter interface + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "libcamera/internal/converter.h" + +namespace libcamera { + +class FrameBuffer; +class MediaDevice; +class Size; +class SizeRange; +struct StreamConfiguration; +class V4L2M2MDevice; +class V4L2M2MConverter; +class Converter; + +class V4L2M2MConverter : public Converter +{ +protected: + class Mapping + { + public: + Mapping(const Size &input, const Size &output, const std::vector &map) + : input_(input), output_(output), map_(map) {} + Size getInputSize() const { return input_; } + Size getOutputSize() const { return output_; } + std::size_t getLength() const { return map_.size(); } + const uint32_t *getMapping() const { return map_.data(); } + + private: + Size input_; + Size output_; + std::vector map_; + }; + + class Stream : protected Loggable + { + public: + Stream(V4L2M2MConverter *converter, unsigned int index); + + bool isValid() const { return m2m_ != nullptr; } + + int configure(const StreamConfiguration &inputCfg, + const StreamConfiguration &outputCfg); + int exportBuffers(unsigned int count, + std::vector> *buffers); + + int start(); + void stop(); + + int queueBuffers(FrameBuffer *input, FrameBuffer *output); + std::unique_ptr m2m_; + + protected: + std::string logPrefix() const override; + + private: + void captureBufferReady(FrameBuffer *buffer); + void outputBufferReady(FrameBuffer *buffer); + + V4L2M2MConverter *converter_; + unsigned int index_; + + unsigned int inputBufferCount_; + unsigned int outputBufferCount_; + }; + + std::unique_ptr m2m_; + + std::vector streams_; + std::vector mappings_; + std::map queue_; + +public: + V4L2M2MConverter(MediaDevice *media); + + int loadConfiguration(const std::string &filename) override; + + bool isValid() const { return m2m_ != nullptr; } + + std::vector formats(PixelFormat input); + SizeRange sizes(const Size &input); + + std::tuple + strideAndFrameSize(const PixelFormat &pixelFormat, const Size &size); + + int configure(const StreamConfiguration &inputCfg, + const std::vector> &outputCfg); + int exportBuffers(unsigned int ouput, unsigned int count, + std::vector> *buffers); + + int start(); + void stop(); + + int queueBuffers(FrameBuffer *input, + const std::map &outputs); + + virtual int applyMapping([[maybe_unused]] Stream *stream, [[maybe_unused]] Mapping &mapping) { return 0; }; +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build index 8f50d755..132de5ef 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -20,6 +20,7 @@ libcamera_internal_headers = files([ 'control_serializer.h', 'control_validator.h', 'converter.h', + 'converter_v4l2_m2m.h', 'delayed_controls.h', 'device_enumerator.h', 'device_enumerator_sysfs.h', diff --git a/src/libcamera/converter_v4l2_m2m.cpp b/src/libcamera/converter_v4l2_m2m.cpp new file mode 100644 index 00000000..942e6e6f --- /dev/null +++ b/src/libcamera/converter_v4l2_m2m.cpp @@ -0,0 +1,504 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Laurent Pinchart + * Copyright 2022 NXP + * + * converter_v4l2_m2m.cpp - V4L2 M2M Format converter + */ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "libcamera/internal/converter_v4l2_m2m.h" +#include "libcamera/internal/media_device.h" +#include "libcamera/internal/v4l2_videodevice.h" +#include "libcamera/internal/yaml_parser.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Converter) + +/* ----------------------------------------------------------------------------- + * V4L2M2MConverter::Stream + */ + +V4L2M2MConverter::Stream::Stream(V4L2M2MConverter *converter, unsigned int index) + : converter_(converter), index_(index) +{ + m2m_ = std::make_unique(converter->deviceNode_); + + m2m_->output()->bufferReady.connect(this, &Stream::outputBufferReady); + m2m_->capture()->bufferReady.connect(this, &Stream::captureBufferReady); + + int ret = m2m_->open(); + if (ret < 0) + m2m_.reset(); +} + +int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg, + const StreamConfiguration &outputCfg) +{ + V4L2PixelFormat videoFormat = + m2m_->output()->toV4L2PixelFormat(inputCfg.pixelFormat); + + V4L2DeviceFormat format; + format.fourcc = videoFormat; + format.size = inputCfg.size; + format.planesCount = 1; + format.planes[0].bpl = inputCfg.stride; + + int ret = m2m_->output()->setFormat(&format); + if (ret < 0) { + LOG(Converter, Error) + << "Failed to set input format: " << strerror(-ret); + return ret; + } + + if (format.fourcc != videoFormat || format.size != inputCfg.size || + format.planes[0].bpl != inputCfg.stride) { + LOG(Converter, Error) + << "Input format not supported (requested " + << inputCfg.size << "-" << videoFormat + << ", got " << format << ")"; + return -EINVAL; + } + + /* Set the pixel format and size on the output. */ + videoFormat = m2m_->capture()->toV4L2PixelFormat(outputCfg.pixelFormat); + format = {}; + format.fourcc = videoFormat; + format.size = outputCfg.size; + + ret = m2m_->capture()->setFormat(&format); + if (ret < 0) { + LOG(Converter, Error) + << "Failed to set output format: " << strerror(-ret); + return ret; + } + + if (format.fourcc != videoFormat || format.size != outputCfg.size) { + LOG(Converter, Error) + << "Output format not supported"; + return -EINVAL; + } + + inputBufferCount_ = inputCfg.bufferCount; + outputBufferCount_ = outputCfg.bufferCount; + + for (Mapping &mapping : converter_->mappings_) { + ControlList ctrls; + if (mapping.getInputSize() == inputCfg.size && mapping.getOutputSize() == outputCfg.size) { + LOG(Converter, Debug) + << "Got a configuration match " + << inputCfg.size << " --> " << outputCfg.size; + converter_->applyMapping(this, mapping); + } + } + + return 0; +} + +int V4L2M2MConverter::Stream::exportBuffers(unsigned int count, + std::vector> *buffers) +{ + return m2m_->capture()->exportBuffers(count, buffers); +} + +int V4L2M2MConverter::Stream::start() +{ + int ret = m2m_->output()->importBuffers(inputBufferCount_); + if (ret < 0) + return ret; + + ret = m2m_->capture()->importBuffers(outputBufferCount_); + if (ret < 0) { + stop(); + return ret; + } + + ret = m2m_->output()->streamOn(); + if (ret < 0) { + stop(); + return ret; + } + + ret = m2m_->capture()->streamOn(); + if (ret < 0) { + stop(); + return ret; + } + + return 0; +} + +void V4L2M2MConverter::Stream::stop() +{ + m2m_->capture()->streamOff(); + m2m_->output()->streamOff(); + m2m_->capture()->releaseBuffers(); + m2m_->output()->releaseBuffers(); +} + +int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *output) +{ + int ret = m2m_->output()->queueBuffer(input); + if (ret < 0) + return ret; + + ret = m2m_->capture()->queueBuffer(output); + if (ret < 0) + return ret; + + return 0; +} + +std::string V4L2M2MConverter::Stream::logPrefix() const +{ + return "stream" + std::to_string(index_); +} + +void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer) +{ + auto it = converter_->queue_.find(buffer); + if (it == converter_->queue_.end()) + return; + + if (!--it->second) { + converter_->inputBufferReady.emit(buffer); + converter_->queue_.erase(it); + } +} + +void V4L2M2MConverter::Stream::captureBufferReady(FrameBuffer *buffer) +{ + converter_->outputBufferReady.emit(buffer); +} + +/* ----------------------------------------------------------------------------- + * V4L2M2MConverter + */ + +V4L2M2MConverter::V4L2M2MConverter(MediaDevice *media) + : Converter(media) +{ + if (deviceNode_.empty()) + return; + + m2m_ = std::make_unique(deviceNode_); + int ret = m2m_->open(); + if (ret < 0) { + m2m_.reset(); + return; + } +} + +int V4L2M2MConverter::loadConfiguration(const std::string &filename) +{ + LOG(Converter, Debug) + << "Parsing configuration file " << filename; + + File file(filename); + + if (!file.open(File::OpenModeFlag::ReadOnly)) { + int ret = file.error(); + LOG(Converter, Error) + << "Failed to open configuration file " + << filename << ": " << strerror(-ret); + return ret; + } + + std::unique_ptr data = YamlParser::parse(file); + if (!data) + return -EINVAL; + + if (!data->contains("mappings")) { + LOG(Converter, Error) + << "Vertex mapping key missing"; + return -EINVAL; + } + + const YamlObject &mappings = (*data)["mappings"]; + if (!mappings.isList() || mappings.size() == 0) { + LOG(Converter, Error) + << "Invalid mappings entry"; + return -EINVAL; + } + + LOG(Converter, Debug) + << "Parsing " << mappings.size() << " mappings"; + mappings_.clear(); + mappings_.reserve(mappings.size()); + + for (std::size_t i = 0; i < mappings.size(); i++) { + const YamlObject &mapping = mappings[i]; + if (!mapping.isDictionary()) { + LOG(Converter, Error) + << "Mapping is not a dictionnary"; + return -EINVAL; + } + + if (!mapping.contains("input-resolution")) { + LOG(Converter, Error) + << "Input resolution missing"; + return -EINVAL; + } + + if (!mapping.contains("output-resolution")) { + LOG(Converter, Error) + << "Output resolution missing"; + return -EINVAL; + } + + if (!mapping.contains("mapping")) { + LOG(Converter, Error) + << "Mapping table missing"; + return -EINVAL; + } + + const YamlObject &input_res = mapping["input-resolution"]; + if (!input_res.isList() || input_res.size() != 2) { + LOG(Converter, Error) + << "Incorrect input resolution"; + return -EINVAL; + } + + const YamlObject &output_res = mapping["output-resolution"]; + if (!output_res.isList() || output_res.size() != 2) { + LOG(Converter, Error) + << "Incorrect output resolution"; + return -EINVAL; + } + + const YamlObject &map = mapping["mapping"]; + if (!map.isList() || map.size() == 0) { + LOG(Converter, Error) + << "Incorrect mapping entries"; + return -EINVAL; + } + + Size input(input_res[0].get(0), input_res[1].get(0)); + Size output(output_res[0].get(0), output_res[1].get(0)); + const auto &mapVector = map.getList().value_or(utils::defopt); + + LOG(Converter, Debug) + << "Input/Output mapping resolution " << input << " ---> " << output; + mappings_.emplace_back(Mapping(input, output, mapVector)); + } + + return mappings.size(); +} + +std::vector V4L2M2MConverter::formats(PixelFormat input) +{ + if (!m2m_) + return {}; + + /* + * Set the format on the input side (V4L2 output) of the converter to + * enumerate the conversion capabilities on its output (V4L2 capture). + */ + V4L2DeviceFormat v4l2Format; + v4l2Format.fourcc = m2m_->output()->toV4L2PixelFormat(input); + v4l2Format.size = { 1, 1 }; + + int ret = m2m_->output()->setFormat(&v4l2Format); + if (ret < 0) { + LOG(Converter, Error) + << "Failed to set format: " << strerror(-ret); + return {}; + } + + if (v4l2Format.fourcc != m2m_->output()->toV4L2PixelFormat(input)) { + LOG(Converter, Debug) + << "Input format " << input << " not supported."; + return {}; + } + + std::vector pixelFormats; + + for (const auto &format : m2m_->capture()->formats()) { + PixelFormat pixelFormat = format.first.toPixelFormat(); + if (pixelFormat) + pixelFormats.push_back(pixelFormat); + } + + return pixelFormats; +} + +SizeRange V4L2M2MConverter::sizes(const Size &input) +{ + if (!m2m_) + return {}; + + /* + * Set the size on the input side (V4L2 output) of the converter to + * enumerate the scaling capabilities on its output (V4L2 capture). + */ + V4L2DeviceFormat format; + format.fourcc = V4L2PixelFormat(); + format.size = input; + + int ret = m2m_->output()->setFormat(&format); + if (ret < 0) { + LOG(Converter, Error) + << "Failed to set format: " << strerror(-ret); + return {}; + } + + SizeRange sizes; + + format.size = { 1, 1 }; + ret = m2m_->capture()->setFormat(&format); + if (ret < 0) { + LOG(Converter, Error) + << "Failed to set format: " << strerror(-ret); + return {}; + } + + sizes.min = format.size; + + format.size = { UINT_MAX, UINT_MAX }; + ret = m2m_->capture()->setFormat(&format); + if (ret < 0) { + LOG(Converter, Error) + << "Failed to set format: " << strerror(-ret); + return {}; + } + + sizes.max = format.size; + + return sizes; +} + +std::tuple +V4L2M2MConverter::strideAndFrameSize(const PixelFormat &pixelFormat, + const Size &size) +{ + V4L2DeviceFormat format; + format.fourcc = m2m_->capture()->toV4L2PixelFormat(pixelFormat); + format.size = size; + + int ret = m2m_->capture()->tryFormat(&format); + if (ret < 0) + return std::make_tuple(0, 0); + + return std::make_tuple(format.planes[0].bpl, format.planes[0].size); +} + +int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg, + const std::vector> &outputCfgs) +{ + int ret = 0; + + streams_.clear(); + streams_.reserve(outputCfgs.size()); + + for (unsigned int i = 0; i < outputCfgs.size(); ++i) { + Stream &stream = streams_.emplace_back(this, i); + + if (!stream.isValid()) { + LOG(Converter, Error) + << "Failed to create stream " << i; + ret = -EINVAL; + break; + } + + ret = stream.configure(inputCfg, outputCfgs[i]); + if (ret < 0) + break; + } + + if (ret < 0) { + streams_.clear(); + return ret; + } + + return 0; +} + +int V4L2M2MConverter::exportBuffers(unsigned int output, unsigned int count, + std::vector> *buffers) +{ + if (output >= streams_.size()) + return -EINVAL; + + return streams_[output].exportBuffers(count, buffers); +} + +int V4L2M2MConverter::start() +{ + int ret; + + for (Stream &stream : streams_) { + ret = stream.start(); + if (ret < 0) { + stop(); + return ret; + } + } + + return 0; +} + +void V4L2M2MConverter::stop() +{ + for (Stream &stream : utils::reverse(streams_)) + stream.stop(); +} + +int V4L2M2MConverter::queueBuffers(FrameBuffer *input, + const std::map &outputs) +{ + unsigned int mask = 0; + int ret; + + /* + * Validate the outputs as a sanity check: at least one output is + * required, all outputs must reference a valid stream and no two + * outputs can reference the same stream. + */ + if (outputs.empty()) + return -EINVAL; + + for (auto [index, buffer] : outputs) { + if (!buffer) + return -EINVAL; + if (index >= streams_.size()) + return -EINVAL; + if (mask & (1 << index)) + return -EINVAL; + + mask |= 1 << index; + } + + /* Queue the input and output buffers to all the streams. */ + for (auto [index, buffer] : outputs) { + ret = streams_[index].queueBuffers(input, buffer); + if (ret < 0) + return ret; + } + + /* + * Add the input buffer to the queue, with the number of streams as a + * reference count. Completion of the input buffer will be signalled by + * the stream that releases the last reference. + */ + queue_.emplace(std::piecewise_construct, + std::forward_as_tuple(input), + std::forward_as_tuple(outputs.size())); + + return 0; +} + +REGISTER_CONVERTER("v4l2_m2m", V4L2M2MConverter, "pxp") + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index a261d4b4..b12c8401 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -14,6 +14,7 @@ libcamera_sources = files([ 'control_serializer.cpp', 'control_validator.cpp', 'converter.cpp', + 'converter_v4l2_m2m.cpp', 'delayed_controls.cpp', 'device_enumerator.cpp', 'device_enumerator_sysfs.cpp',