Show a patch.

GET /api/patches/17348/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 17348,
    "url": "https://patchwork.libcamera.org/api/patches/17348/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/17348/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20220908184850.1874303-7-xavier.roumegue@oss.nxp.com>",
    "date": "2022-09-08T18:48:42",
    "name": "[libcamera-devel,06/14] libcamera: converter: Add v4l2 m2m converter implementation",
    "commit_ref": null,
    "pull_url": null,
    "state": "changes-requested",
    "archived": false,
    "hash": "3b57fa191c3bd53e81e877b20e5fe1456f295778",
    "submitter": {
        "id": 107,
        "url": "https://patchwork.libcamera.org/api/people/107/?format=api",
        "name": "Xavier Roumegue",
        "email": "xavier.roumegue@oss.nxp.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/17348/mbox/",
    "series": [
        {
            "id": 3477,
            "url": "https://patchwork.libcamera.org/api/series/3477/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=3477",
            "date": "2022-09-08T18:48:36",
            "name": "Add dw100 dewarper support to simple/rkisp1 pipeline",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/3477/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/17348/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/17348/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>",
        "X-Original-To": "parsemail@patchwork.libcamera.org",
        "Delivered-To": "parsemail@patchwork.libcamera.org",
        "Received": [
            "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id C6CC7C327D\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  8 Sep 2022 18:49:26 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 3110D620B6;\n\tThu,  8 Sep 2022 20:49:25 +0200 (CEST)",
            "from EUR04-DB3-obe.outbound.protection.outlook.com\n\t(mail-eopbgr60040.outbound.protection.outlook.com [40.107.6.40])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 22940620AF\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  8 Sep 2022 20:49:21 +0200 (CEST)",
            "from PAXPR04MB8703.eurprd04.prod.outlook.com\n\t(2603:10a6:102:21e::22)\n\tby AS8PR04MB8852.eurprd04.prod.outlook.com (2603:10a6:20b:42f::14)\n\twith Microsoft SMTP Server (version=TLS1_2,\n\tcipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.5612.19;\n\tThu, 8 Sep 2022 18:49:19 +0000",
            "from PAXPR04MB8703.eurprd04.prod.outlook.com\n\t([fe80::485:adba:7081:715a]) by\n\tPAXPR04MB8703.eurprd04.prod.outlook.com\n\t([fe80::485:adba:7081:715a%3]) with mapi id 15.20.5612.019;\n\tThu, 8 Sep 2022 18:49:19 +0000"
        ],
        "DKIM-Signature": [
            "v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1662662965;\n\tbh=EKlqKSFCmwmnPZXIXUE9x0XE7k7IhRmt+tOnU2YRQIw=;\n\th=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=1e674kEPqK4bZMhnezRZCkYerrNcqCBgHb/OqhEOe+nP20Tll5TzDGwzImjs5sSWu\n\t6iq/ogPXXl7Xwwyc3zVAHh451GjwVLGYSCvyRB/7eDzqN+KA25Bzs8ZEmVAQHYFPGf\n\tf7q/RViLsF30qtq3SwvVG49zjo8bxkMhtwVbD+2KAAAo74c3J67MoUb9EIrb9oClXC\n\tdU3sOox5haMD28e7goMCT/nN8/fHLGiJ1ZahUZaMdnvE3z7A9o60qTK6ZJWrZN9owc\n\tMlQ5K4zHKAOzq/a547OhZigkZrJrZtOXXKbQjYNpj6IIwdQ1EIjkGZCmXIQR1gtEHU\n\tffIrlzCxB055A==",
            "v=1; a=rsa-sha256; c=relaxed/relaxed; d=NXP1.onmicrosoft.com;\n\ts=selector2-NXP1-onmicrosoft-com;\n\th=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck;\n\tbh=TEOyI1lC4MfDpLDLdSI+otafx7tIauV6bnWUh0XG0RE=;\n\tb=cdjhpENZpACxanhD0OxtxDCe2geY86CyAUefyjxBkAkvjrTj3q3r9vqJJE0uAY4KSExIveZXZJC99s6RrLvyU674lUuJgcGgNKATPJzKUdjsysdtlYQwu4F6nb0hUd2mCFQ4l+j1SP9iCb+vobOu8ElGsIhEmiO2j6+AsaL8MeY="
        ],
        "Authentication-Results": [
            "lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=NXP1.onmicrosoft.com\n\theader.i=@NXP1.onmicrosoft.com\n\theader.b=\"cdjhpENZ\"; dkim-atps=neutral",
            "dkim=none (message not signed)\n\theader.d=none;dmarc=none action=none header.from=oss.nxp.com;"
        ],
        "ARC-Seal": "i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none;\n\tb=kufSDeHXOYZY8yWXt+jqlgG4Wpw1mwta2vYRLgf/OutzaeeMV2nC557OfkniOhFkwkN0ZAzddgb8BMZKiqPUqG99Gu9F05lGMYFzMWLM5P4wkV2wrS72BVuXSO19K5RelHZrgjlDDXYoDAdxkhsIfuh12/LiRpBo+cGKmWVn6CToBOyqARnXyrhkUkwrr7ArZ+fu5bmjprIjbapE1NPhP8kb+QdOSihS65/Vj1gffmIb6mrff2mRIIfBFA7pzWFazeuxiRZ8LtjaOTe6ZJa8A9bFl4Szzq+iZa+olYLCYxKxJ3TXQlhgOBvFL8mX13lHrPtNYRJ4Gv9L0RDmeG8wSg==",
        "ARC-Message-Signature": "i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com;\n\ts=arcselector9901;\n\th=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;\n\tbh=TEOyI1lC4MfDpLDLdSI+otafx7tIauV6bnWUh0XG0RE=;\n\tb=JM8gieQITMj0Jf7hJVQa5bwFoUdW8bvtFyEDaoP9okKPyK6tSS0V8QjhlfJuyJIeuQmRuK7jp6IzSB0ekGjJGPqsgKFYrnG/30j08sog4xtlnbXEcvjjbdQwgtNDJbdA7aEL2S8O3DPqXNNT2V2SU9GOjyGounoTgO0mMrLBwObaM7BKHzgHWfLnh/tyvh9CMSDgzdO5fDfZiLaA9j3EuNYeBBEMjLzk0DQSxq/DERyuZ2EFodOC9RKpZlBCKXkBOgOFxUadkA7OSPK1dhXGPyzWZPdk/nxx3UxmyaI0jb91LQeVz/Dnz980C63CWOycZrHkdgiEAd0guWY/rwYWdg==",
        "ARC-Authentication-Results": "i=1; mx.microsoft.com 1; spf=pass\n\tsmtp.mailfrom=oss.nxp.com;\n\tdmarc=pass action=none header.from=oss.nxp.com; \n\tdkim=pass header.d=oss.nxp.com; arc=none",
        "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>",
        "Content-Transfer-Encoding": "8bit",
        "Content-Type": "text/plain",
        "X-ClientProxiedBy": "PR3P250CA0001.EURP250.PROD.OUTLOOK.COM\n\t(2603:10a6:102:57::6) To PAXPR04MB8703.eurprd04.prod.outlook.com\n\t(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:;\n\tIPV:NLI; SFV:NSPM; H:PAXPR04MB8703.eurprd04.prod.outlook.com; PTR:;\n\tCAT:NONE; \n\tSFS:(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);\n\tDIR: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\n\t(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\n\tconverter implementation",
        "X-BeenThere": "libcamera-devel@lists.libcamera.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "<libcamera-devel.lists.libcamera.org>",
        "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>",
        "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>",
        "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>",
        "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>",
        "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>",
        "From": "Xavier Roumegue via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>",
        "Reply-To": "Xavier Roumegue <xavier.roumegue@oss.nxp.com>",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "Introduce a converter implementation relying on a v4l2 m2m device, mostly\nbased on the current simple pipeline converter implementation.\n\nThe main change is the introduction of Mapping object which can be\nloaded through a configuration file which define vertices remapping\ncoordinates. Those latters can be applied by any classes derived from\nthis base class which define the apply_mapping() method.\n\nSigned-off-by: Xavier Roumegue <xavier.roumegue@oss.nxp.com>\n---\n .../libcamera/internal/converter_v4l2_m2m.h   | 120 +++++\n include/libcamera/internal/meson.build        |   1 +\n src/libcamera/converter_v4l2_m2m.cpp          | 504 ++++++++++++++++++\n src/libcamera/meson.build                     |   1 +\n 4 files changed, 626 insertions(+)\n create mode 100644 include/libcamera/internal/converter_v4l2_m2m.h\n create mode 100644 src/libcamera/converter_v4l2_m2m.cpp",
    "diff": "diff --git a/include/libcamera/internal/converter_v4l2_m2m.h b/include/libcamera/internal/converter_v4l2_m2m.h\nnew file mode 100644\nindex 00000000..3667b128\n--- /dev/null\n+++ b/include/libcamera/internal/converter_v4l2_m2m.h\n@@ -0,0 +1,120 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright 2022 NXP\n+ *\n+ * converter_v4l2_m2m.h - V4l2 M2M Format converter interface\n+ */\n+\n+#pragma once\n+\n+#include <functional>\n+#include <map>\n+#include <memory>\n+#include <string>\n+#include <tuple>\n+#include <vector>\n+\n+#include <libcamera/base/log.h>\n+#include <libcamera/base/signal.h>\n+\n+#include <libcamera/geometry.h>\n+#include <libcamera/pixel_format.h>\n+\n+#include \"libcamera/internal/converter.h\"\n+\n+namespace libcamera {\n+\n+class FrameBuffer;\n+class MediaDevice;\n+class Size;\n+class SizeRange;\n+struct StreamConfiguration;\n+class V4L2M2MDevice;\n+class V4L2M2MConverter;\n+class Converter;\n+\n+class V4L2M2MConverter : public Converter\n+{\n+protected:\n+\tclass Mapping\n+\t{\n+\tpublic:\n+\t\tMapping(const Size &input, const Size &output, const std::vector<uint32_t> &map)\n+\t\t\t: input_(input), output_(output), map_(map) {}\n+\t\tSize getInputSize() const { return input_; }\n+\t\tSize getOutputSize() const { return output_; }\n+\t\tstd::size_t getLength() const { return map_.size(); }\n+\t\tconst uint32_t *getMapping() const { return map_.data(); }\n+\n+\tprivate:\n+\t\tSize input_;\n+\t\tSize output_;\n+\t\tstd::vector<uint32_t> map_;\n+\t};\n+\n+\tclass Stream : protected Loggable\n+\t{\n+\tpublic:\n+\t\tStream(V4L2M2MConverter *converter, unsigned int index);\n+\n+\t\tbool isValid() const { return m2m_ != nullptr; }\n+\n+\t\tint configure(const StreamConfiguration &inputCfg,\n+\t\t\t      const StreamConfiguration &outputCfg);\n+\t\tint exportBuffers(unsigned int count,\n+\t\t\t\t  std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n+\n+\t\tint start();\n+\t\tvoid stop();\n+\n+\t\tint queueBuffers(FrameBuffer *input, FrameBuffer *output);\n+\t\tstd::unique_ptr<V4L2M2MDevice> m2m_;\n+\n+\tprotected:\n+\t\tstd::string logPrefix() const override;\n+\n+\tprivate:\n+\t\tvoid captureBufferReady(FrameBuffer *buffer);\n+\t\tvoid outputBufferReady(FrameBuffer *buffer);\n+\n+\t\tV4L2M2MConverter *converter_;\n+\t\tunsigned int index_;\n+\n+\t\tunsigned int inputBufferCount_;\n+\t\tunsigned int outputBufferCount_;\n+\t};\n+\n+\tstd::unique_ptr<V4L2M2MDevice> m2m_;\n+\n+\tstd::vector<Stream> streams_;\n+\tstd::vector<Mapping> mappings_;\n+\tstd::map<FrameBuffer *, unsigned int> queue_;\n+\n+public:\n+\tV4L2M2MConverter(MediaDevice *media);\n+\n+\tint loadConfiguration(const std::string &filename) override;\n+\n+\tbool isValid() const { return m2m_ != nullptr; }\n+\n+\tstd::vector<PixelFormat> formats(PixelFormat input);\n+\tSizeRange sizes(const Size &input);\n+\n+\tstd::tuple<unsigned int, unsigned int>\n+\tstrideAndFrameSize(const PixelFormat &pixelFormat, const Size &size);\n+\n+\tint configure(const StreamConfiguration &inputCfg,\n+\t\t      const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfg);\n+\tint exportBuffers(unsigned int ouput, unsigned int count,\n+\t\t\t  std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n+\n+\tint start();\n+\tvoid stop();\n+\n+\tint queueBuffers(FrameBuffer *input,\n+\t\t\t const std::map<unsigned int, FrameBuffer *> &outputs);\n+\n+\tvirtual int applyMapping([[maybe_unused]] Stream *stream, [[maybe_unused]] Mapping &mapping) { return 0; };\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build\nindex 8f50d755..132de5ef 100644\n--- a/include/libcamera/internal/meson.build\n+++ b/include/libcamera/internal/meson.build\n@@ -20,6 +20,7 @@ libcamera_internal_headers = files([\n     'control_serializer.h',\n     'control_validator.h',\n     'converter.h',\n+    'converter_v4l2_m2m.h',\n     'delayed_controls.h',\n     'device_enumerator.h',\n     'device_enumerator_sysfs.h',\ndiff --git a/src/libcamera/converter_v4l2_m2m.cpp b/src/libcamera/converter_v4l2_m2m.cpp\nnew file mode 100644\nindex 00000000..942e6e6f\n--- /dev/null\n+++ b/src/libcamera/converter_v4l2_m2m.cpp\n@@ -0,0 +1,504 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2020, Laurent Pinchart\n+ * Copyright 2022 NXP\n+ *\n+ * converter_v4l2_m2m.cpp - V4L2 M2M Format converter\n+ */\n+\n+#include <algorithm>\n+#include <limits.h>\n+\n+#include <libcamera/base/file.h>\n+#include <libcamera/base/log.h>\n+#include <libcamera/base/signal.h>\n+#include <libcamera/base/utils.h>\n+\n+#include <libcamera/framebuffer.h>\n+#include <libcamera/geometry.h>\n+#include <libcamera/stream.h>\n+\n+#include \"libcamera/internal/converter_v4l2_m2m.h\"\n+#include \"libcamera/internal/media_device.h\"\n+#include \"libcamera/internal/v4l2_videodevice.h\"\n+#include \"libcamera/internal/yaml_parser.h\"\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(Converter)\n+\n+/* -----------------------------------------------------------------------------\n+ * V4L2M2MConverter::Stream\n+ */\n+\n+V4L2M2MConverter::Stream::Stream(V4L2M2MConverter *converter, unsigned int index)\n+\t: converter_(converter), index_(index)\n+{\n+\tm2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode_);\n+\n+\tm2m_->output()->bufferReady.connect(this, &Stream::outputBufferReady);\n+\tm2m_->capture()->bufferReady.connect(this, &Stream::captureBufferReady);\n+\n+\tint ret = m2m_->open();\n+\tif (ret < 0)\n+\t\tm2m_.reset();\n+}\n+\n+int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg,\n+\t\t\t\t\tconst StreamConfiguration &outputCfg)\n+{\n+\tV4L2PixelFormat videoFormat =\n+\t\tm2m_->output()->toV4L2PixelFormat(inputCfg.pixelFormat);\n+\n+\tV4L2DeviceFormat format;\n+\tformat.fourcc = videoFormat;\n+\tformat.size = inputCfg.size;\n+\tformat.planesCount = 1;\n+\tformat.planes[0].bpl = inputCfg.stride;\n+\n+\tint ret = m2m_->output()->setFormat(&format);\n+\tif (ret < 0) {\n+\t\tLOG(Converter, Error)\n+\t\t\t<< \"Failed to set input format: \" << strerror(-ret);\n+\t\treturn ret;\n+\t}\n+\n+\tif (format.fourcc != videoFormat || format.size != inputCfg.size ||\n+\t    format.planes[0].bpl != inputCfg.stride) {\n+\t\tLOG(Converter, Error)\n+\t\t\t<< \"Input format not supported (requested \"\n+\t\t\t<< inputCfg.size << \"-\" << videoFormat\n+\t\t\t<< \", got \" << format << \")\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* Set the pixel format and size on the output. */\n+\tvideoFormat = m2m_->capture()->toV4L2PixelFormat(outputCfg.pixelFormat);\n+\tformat = {};\n+\tformat.fourcc = videoFormat;\n+\tformat.size = outputCfg.size;\n+\n+\tret = m2m_->capture()->setFormat(&format);\n+\tif (ret < 0) {\n+\t\tLOG(Converter, Error)\n+\t\t\t<< \"Failed to set output format: \" << strerror(-ret);\n+\t\treturn ret;\n+\t}\n+\n+\tif (format.fourcc != videoFormat || format.size != outputCfg.size) {\n+\t\tLOG(Converter, Error)\n+\t\t\t<< \"Output format not supported\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tinputBufferCount_ = inputCfg.bufferCount;\n+\toutputBufferCount_ = outputCfg.bufferCount;\n+\n+\tfor (Mapping &mapping : converter_->mappings_) {\n+\t\tControlList ctrls;\n+\t\tif (mapping.getInputSize() == inputCfg.size && mapping.getOutputSize() == outputCfg.size) {\n+\t\t\tLOG(Converter, Debug)\n+\t\t\t\t<< \"Got a configuration match \"\n+\t\t\t\t<< inputCfg.size << \" --> \" << outputCfg.size;\n+\t\t\tconverter_->applyMapping(this, mapping);\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int V4L2M2MConverter::Stream::exportBuffers(unsigned int count,\n+\t\t\t\t\t    std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n+{\n+\treturn m2m_->capture()->exportBuffers(count, buffers);\n+}\n+\n+int V4L2M2MConverter::Stream::start()\n+{\n+\tint ret = m2m_->output()->importBuffers(inputBufferCount_);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tret = m2m_->capture()->importBuffers(outputBufferCount_);\n+\tif (ret < 0) {\n+\t\tstop();\n+\t\treturn ret;\n+\t}\n+\n+\tret = m2m_->output()->streamOn();\n+\tif (ret < 0) {\n+\t\tstop();\n+\t\treturn ret;\n+\t}\n+\n+\tret = m2m_->capture()->streamOn();\n+\tif (ret < 0) {\n+\t\tstop();\n+\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void V4L2M2MConverter::Stream::stop()\n+{\n+\tm2m_->capture()->streamOff();\n+\tm2m_->output()->streamOff();\n+\tm2m_->capture()->releaseBuffers();\n+\tm2m_->output()->releaseBuffers();\n+}\n+\n+int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *output)\n+{\n+\tint ret = m2m_->output()->queueBuffer(input);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tret = m2m_->capture()->queueBuffer(output);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\treturn 0;\n+}\n+\n+std::string V4L2M2MConverter::Stream::logPrefix() const\n+{\n+\treturn \"stream\" + std::to_string(index_);\n+}\n+\n+void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer)\n+{\n+\tauto it = converter_->queue_.find(buffer);\n+\tif (it == converter_->queue_.end())\n+\t\treturn;\n+\n+\tif (!--it->second) {\n+\t\tconverter_->inputBufferReady.emit(buffer);\n+\t\tconverter_->queue_.erase(it);\n+\t}\n+}\n+\n+void V4L2M2MConverter::Stream::captureBufferReady(FrameBuffer *buffer)\n+{\n+\tconverter_->outputBufferReady.emit(buffer);\n+}\n+\n+/* -----------------------------------------------------------------------------\n+ * V4L2M2MConverter\n+ */\n+\n+V4L2M2MConverter::V4L2M2MConverter(MediaDevice *media)\n+\t: Converter(media)\n+{\n+\tif (deviceNode_.empty())\n+\t\treturn;\n+\n+\tm2m_ = std::make_unique<V4L2M2MDevice>(deviceNode_);\n+\tint ret = m2m_->open();\n+\tif (ret < 0) {\n+\t\tm2m_.reset();\n+\t\treturn;\n+\t}\n+}\n+\n+int V4L2M2MConverter::loadConfiguration(const std::string &filename)\n+{\n+\tLOG(Converter, Debug)\n+\t\t<< \"Parsing configuration file \" << filename;\n+\n+\tFile file(filename);\n+\n+\tif (!file.open(File::OpenModeFlag::ReadOnly)) {\n+\t\tint ret = file.error();\n+\t\tLOG(Converter, Error)\n+\t\t\t<< \"Failed to open configuration file \"\n+\t\t\t<< filename << \": \" << strerror(-ret);\n+\t\treturn ret;\n+\t}\n+\n+\tstd::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);\n+\tif (!data)\n+\t\treturn -EINVAL;\n+\n+\tif (!data->contains(\"mappings\")) {\n+\t\tLOG(Converter, Error)\n+\t\t\t<< \"Vertex mapping key missing\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tconst YamlObject &mappings = (*data)[\"mappings\"];\n+\tif (!mappings.isList() || mappings.size() == 0) {\n+\t\tLOG(Converter, Error)\n+\t\t\t<< \"Invalid mappings entry\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tLOG(Converter, Debug)\n+\t\t<< \"Parsing \" << mappings.size() << \" mappings\";\n+\tmappings_.clear();\n+\tmappings_.reserve(mappings.size());\n+\n+\tfor (std::size_t i = 0; i < mappings.size(); i++) {\n+\t\tconst YamlObject &mapping = mappings[i];\n+\t\tif (!mapping.isDictionary()) {\n+\t\t\tLOG(Converter, Error)\n+\t\t\t\t<< \"Mapping is not a dictionnary\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\tif (!mapping.contains(\"input-resolution\")) {\n+\t\t\tLOG(Converter, Error)\n+\t\t\t\t<< \"Input resolution missing\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\tif (!mapping.contains(\"output-resolution\")) {\n+\t\t\tLOG(Converter, Error)\n+\t\t\t\t<< \"Output resolution missing\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\tif (!mapping.contains(\"mapping\")) {\n+\t\t\tLOG(Converter, Error)\n+\t\t\t\t<< \"Mapping table missing\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\tconst YamlObject &input_res = mapping[\"input-resolution\"];\n+\t\tif (!input_res.isList() || input_res.size() != 2) {\n+\t\t\tLOG(Converter, Error)\n+\t\t\t\t<< \"Incorrect input resolution\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\tconst YamlObject &output_res = mapping[\"output-resolution\"];\n+\t\tif (!output_res.isList() || output_res.size() != 2) {\n+\t\t\tLOG(Converter, Error)\n+\t\t\t\t<< \"Incorrect output resolution\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\tconst YamlObject &map = mapping[\"mapping\"];\n+\t\tif (!map.isList() || map.size() == 0) {\n+\t\t\tLOG(Converter, Error)\n+\t\t\t\t<< \"Incorrect mapping entries\";\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\tSize input(input_res[0].get<uint32_t>(0), input_res[1].get<uint32_t>(0));\n+\t\tSize output(output_res[0].get<uint32_t>(0), output_res[1].get<uint32_t>(0));\n+\t\tconst auto &mapVector = map.getList<uint32_t>().value_or(utils::defopt);\n+\n+\t\tLOG(Converter, Debug)\n+\t\t\t<< \"Input/Output mapping resolution \" << input << \" ---> \" << output;\n+\t\tmappings_.emplace_back(Mapping(input, output, mapVector));\n+\t}\n+\n+\treturn mappings.size();\n+}\n+\n+std::vector<PixelFormat> V4L2M2MConverter::formats(PixelFormat input)\n+{\n+\tif (!m2m_)\n+\t\treturn {};\n+\n+\t/*\n+\t * Set the format on the input side (V4L2 output) of the converter to\n+\t * enumerate the conversion capabilities on its output (V4L2 capture).\n+\t */\n+\tV4L2DeviceFormat v4l2Format;\n+\tv4l2Format.fourcc = m2m_->output()->toV4L2PixelFormat(input);\n+\tv4l2Format.size = { 1, 1 };\n+\n+\tint ret = m2m_->output()->setFormat(&v4l2Format);\n+\tif (ret < 0) {\n+\t\tLOG(Converter, Error)\n+\t\t\t<< \"Failed to set format: \" << strerror(-ret);\n+\t\treturn {};\n+\t}\n+\n+\tif (v4l2Format.fourcc != m2m_->output()->toV4L2PixelFormat(input)) {\n+\t\tLOG(Converter, Debug)\n+\t\t\t<< \"Input format \" << input << \" not supported.\";\n+\t\treturn {};\n+\t}\n+\n+\tstd::vector<PixelFormat> pixelFormats;\n+\n+\tfor (const auto &format : m2m_->capture()->formats()) {\n+\t\tPixelFormat pixelFormat = format.first.toPixelFormat();\n+\t\tif (pixelFormat)\n+\t\t\tpixelFormats.push_back(pixelFormat);\n+\t}\n+\n+\treturn pixelFormats;\n+}\n+\n+SizeRange V4L2M2MConverter::sizes(const Size &input)\n+{\n+\tif (!m2m_)\n+\t\treturn {};\n+\n+\t/*\n+\t * Set the size on the input side (V4L2 output) of the converter to\n+\t * enumerate the scaling capabilities on its output (V4L2 capture).\n+\t */\n+\tV4L2DeviceFormat format;\n+\tformat.fourcc = V4L2PixelFormat();\n+\tformat.size = input;\n+\n+\tint ret = m2m_->output()->setFormat(&format);\n+\tif (ret < 0) {\n+\t\tLOG(Converter, Error)\n+\t\t\t<< \"Failed to set format: \" << strerror(-ret);\n+\t\treturn {};\n+\t}\n+\n+\tSizeRange sizes;\n+\n+\tformat.size = { 1, 1 };\n+\tret = m2m_->capture()->setFormat(&format);\n+\tif (ret < 0) {\n+\t\tLOG(Converter, Error)\n+\t\t\t<< \"Failed to set format: \" << strerror(-ret);\n+\t\treturn {};\n+\t}\n+\n+\tsizes.min = format.size;\n+\n+\tformat.size = { UINT_MAX, UINT_MAX };\n+\tret = m2m_->capture()->setFormat(&format);\n+\tif (ret < 0) {\n+\t\tLOG(Converter, Error)\n+\t\t\t<< \"Failed to set format: \" << strerror(-ret);\n+\t\treturn {};\n+\t}\n+\n+\tsizes.max = format.size;\n+\n+\treturn sizes;\n+}\n+\n+std::tuple<unsigned int, unsigned int>\n+V4L2M2MConverter::strideAndFrameSize(const PixelFormat &pixelFormat,\n+\t\t\t\t     const Size &size)\n+{\n+\tV4L2DeviceFormat format;\n+\tformat.fourcc = m2m_->capture()->toV4L2PixelFormat(pixelFormat);\n+\tformat.size = size;\n+\n+\tint ret = m2m_->capture()->tryFormat(&format);\n+\tif (ret < 0)\n+\t\treturn std::make_tuple(0, 0);\n+\n+\treturn std::make_tuple(format.planes[0].bpl, format.planes[0].size);\n+}\n+\n+int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg,\n+\t\t\t\tconst std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)\n+{\n+\tint ret = 0;\n+\n+\tstreams_.clear();\n+\tstreams_.reserve(outputCfgs.size());\n+\n+\tfor (unsigned int i = 0; i < outputCfgs.size(); ++i) {\n+\t\tStream &stream = streams_.emplace_back(this, i);\n+\n+\t\tif (!stream.isValid()) {\n+\t\t\tLOG(Converter, Error)\n+\t\t\t\t<< \"Failed to create stream \" << i;\n+\t\t\tret = -EINVAL;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tret = stream.configure(inputCfg, outputCfgs[i]);\n+\t\tif (ret < 0)\n+\t\t\tbreak;\n+\t}\n+\n+\tif (ret < 0) {\n+\t\tstreams_.clear();\n+\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int V4L2M2MConverter::exportBuffers(unsigned int output, unsigned int count,\n+\t\t\t\t    std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n+{\n+\tif (output >= streams_.size())\n+\t\treturn -EINVAL;\n+\n+\treturn streams_[output].exportBuffers(count, buffers);\n+}\n+\n+int V4L2M2MConverter::start()\n+{\n+\tint ret;\n+\n+\tfor (Stream &stream : streams_) {\n+\t\tret = stream.start();\n+\t\tif (ret < 0) {\n+\t\t\tstop();\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void V4L2M2MConverter::stop()\n+{\n+\tfor (Stream &stream : utils::reverse(streams_))\n+\t\tstream.stop();\n+}\n+\n+int V4L2M2MConverter::queueBuffers(FrameBuffer *input,\n+\t\t\t\t   const std::map<unsigned int, FrameBuffer *> &outputs)\n+{\n+\tunsigned int mask = 0;\n+\tint ret;\n+\n+\t/*\n+\t * Validate the outputs as a sanity check: at least one output is\n+\t * required, all outputs must reference a valid stream and no two\n+\t * outputs can reference the same stream.\n+\t */\n+\tif (outputs.empty())\n+\t\treturn -EINVAL;\n+\n+\tfor (auto [index, buffer] : outputs) {\n+\t\tif (!buffer)\n+\t\t\treturn -EINVAL;\n+\t\tif (index >= streams_.size())\n+\t\t\treturn -EINVAL;\n+\t\tif (mask & (1 << index))\n+\t\t\treturn -EINVAL;\n+\n+\t\tmask |= 1 << index;\n+\t}\n+\n+\t/* Queue the input and output buffers to all the streams. */\n+\tfor (auto [index, buffer] : outputs) {\n+\t\tret = streams_[index].queueBuffers(input, buffer);\n+\t\tif (ret < 0)\n+\t\t\treturn ret;\n+\t}\n+\n+\t/*\n+\t * Add the input buffer to the queue, with the number of streams as a\n+\t * reference count. Completion of the input buffer will be signalled by\n+\t * the stream that releases the last reference.\n+\t */\n+\tqueue_.emplace(std::piecewise_construct,\n+\t\t       std::forward_as_tuple(input),\n+\t\t       std::forward_as_tuple(outputs.size()));\n+\n+\treturn 0;\n+}\n+\n+REGISTER_CONVERTER(\"v4l2_m2m\", V4L2M2MConverter, \"pxp\")\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\nindex a261d4b4..b12c8401 100644\n--- a/src/libcamera/meson.build\n+++ b/src/libcamera/meson.build\n@@ -14,6 +14,7 @@ libcamera_sources = files([\n     'control_serializer.cpp',\n     'control_validator.cpp',\n     'converter.cpp',\n+    'converter_v4l2_m2m.cpp',\n     'delayed_controls.cpp',\n     'device_enumerator.cpp',\n     'device_enumerator_sysfs.cpp',\n",
    "prefixes": [
        "libcamera-devel",
        "06/14"
    ]
}