Show a patch.

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

{
    "id": 22967,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/22967/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/22967/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/1.1/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": "<20250314202943.112109-8-mzamazal@redhat.com>",
    "date": "2025-03-14T20:29:35",
    "name": "[RFC,7/7] apps: cam: Add command line options for stream specific outputs",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "cef10ace26d4ec4b4ca2c9198e5b5e9e8b5cc37e",
    "submitter": {
        "id": 177,
        "url": "https://patchwork.libcamera.org/api/1.1/people/177/?format=api",
        "name": "Milan Zamazal",
        "email": "mzamazal@redhat.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/22967/mbox/",
    "series": [
        {
            "id": 5062,
            "url": "https://patchwork.libcamera.org/api/1.1/series/5062/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5062",
            "date": "2025-03-14T20:29:28",
            "name": "Support different outputs for cam streams",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/5062/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/22967/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/22967/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 F39F8C32F7\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 14 Mar 2025 20:30:31 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id B105968952;\n\tFri, 14 Mar 2025 21:30:31 +0100 (CET)",
            "from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.129.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 480C868950\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 14 Mar 2025 21:30:29 +0100 (CET)",
            "from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com\n\t(ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63])\n\tby relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3,\n\tcipher=TLS_AES_256_GCM_SHA384) id us-mta-589-rJ57H-BdNqqmvXaKIukv6w-1;\n\tFri, 14 Mar 2025 16:30:17 -0400",
            "from mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com\n\t(mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com\n\t[10.30.177.40])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\tkey-exchange X25519 server-signature RSA-PSS (2048 bits)\n\tserver-digest SHA256) (No client certificate requested)\n\tby mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix)\n\twith ESMTPS id 8B0D91956080; Fri, 14 Mar 2025 20:30:11 +0000 (UTC)",
            "from mzamazal-thinkpadp1gen7.tpbc.com (unknown [10.44.32.19])\n\tby mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix)\n\twith ESMTP id 137181954B32; Fri, 14 Mar 2025 20:30:09 +0000 (UTC)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"itMoM1Uu\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1741984228;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tcontent-transfer-encoding:content-transfer-encoding:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=cK0ilC9taCY6uIP7kxjRgmSs8FOpljnBJbd+JeDokrs=;\n\tb=itMoM1Uuo17HORFnldkCIppE1fLG8c2Az+rdpQUw1qRMssO9ZmlMsxJNVq56tCOd0D+CL2\n\t2K/7RTHQRAN/4FYGqCT3FfARwMT3dpXOVwR0p+9MRbZA2gbcKk7fQXSKLY+yW5+Ve4AyF0\n\t2i+UklbH9wTFoEEvCapELiq5HUnm/FM=",
        "X-MC-Unique": "rJ57H-BdNqqmvXaKIukv6w-1",
        "X-Mimecast-MFC-AGG-ID": "rJ57H-BdNqqmvXaKIukv6w_1741984211",
        "From": "Milan Zamazal <mzamazal@redhat.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "Milan Zamazal <mzamazal@redhat.com>,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>",
        "Subject": "[RFC PATCH 7/7] apps: cam: Add command line options for stream\n\tspecific outputs",
        "Date": "Fri, 14 Mar 2025 21:29:35 +0100",
        "Message-ID": "<20250314202943.112109-8-mzamazal@redhat.com>",
        "In-Reply-To": "<20250314202943.112109-1-mzamazal@redhat.com>",
        "References": "<20250314202943.112109-1-mzamazal@redhat.com>",
        "MIME-Version": "1.0",
        "X-Scanned-By": "MIMEDefang 3.0 on 10.30.177.40",
        "X-Mimecast-Spam-Score": "0",
        "X-Mimecast-MFC-PROC-ID": "e1MLZ808tfl427y7heYgNb45U6vY8jBB48cc3PBkCRg_1741984211",
        "X-Mimecast-Originator": "redhat.com",
        "Content-Transfer-Encoding": "8bit",
        "content-type": "text/plain; charset=\"US-ASCII\"; x-default=true",
        "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>",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "Now, when the support for multiple outputs is ready, let's add command\nline options to actually use it.\n\nTo remain backward compatible and to keep the things simple for the\nuser, the original output related command line options work as before.\nFor example, simply running\n\n  cam -c1 -C -D\n\nor\n\n  cam ... -s role=viewfinder,... -s role=raw,... -Fimage#\n\nstill works.\n\nBut the user can also specify a different output for each of the given\nstreams now, for example:\n\n  cam ... -s role=viewfinder,... --stream-display \\\n          -s role=raw,... --stream-file=raw#\n\nor\n\n  cam ... -s role=viewfinder,... --stream-file=processed#.ppm \\\n          -s role=raw,... --stream-file=raw#\n\nTo not complicate the implementation more than necessary, it's not\nallowed to combine the old and the new output options.\n\nIt is also checked that --stream-display is not used more than once (the\nKMS output may be used only for a single stream) and that each stream\nhas at most one output assigned.\n\nSigned-off-by: Milan Zamazal <mzamazal@redhat.com>\n---\n src/apps/cam/camera_session.cpp | 67 ++++++++++++++++++++++++++++++++-\n src/apps/cam/main.cpp           | 23 ++++++++---\n src/apps/cam/main.h             |  3 ++\n 3 files changed, 85 insertions(+), 8 deletions(-)",
    "diff": "diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp\nindex 5124029b..5ef0c2fe 100644\n--- a/src/apps/cam/camera_session.cpp\n+++ b/src/apps/cam/camera_session.cpp\n@@ -7,13 +7,16 @@\n \n #include \"camera_session.h\"\n \n+#include <algorithm>\n #include <iomanip>\n #include <iostream>\n #include <limits.h>\n+#include <memory.h>\n #include <sstream>\n \n #include <libcamera/control_ids.h>\n #include <libcamera/property_ids.h>\n+#include <libcamera/stream.h>\n \n #include \"../common/event_loop.h\"\n #include \"../common/stream_options.h\"\n@@ -292,10 +295,70 @@ int CameraSession::start()\n \t\tdefaultSink = std::move(sink);\n \t}\n \n+#ifdef HAVE_KMS\n+\tbool kmsSinkAssigned = false;\n+#endif\n+\tauto &streamOptions = options_[OptStream];\n \tfor (unsigned int i = 0; i < config_->size(); i++) {\n \t\tconst StreamConfiguration &cfg = config_->at(i);\n-\t\tif (defaultSink)\n-\t\t\tdefaultSink->addStream(cfg.stream());\n+\t\tif (streamOptions.empty()) {\n+\t\t\tif (defaultSink)\n+\t\t\t\tdefaultSink->addStream(cfg.stream());\n+\t\t} else {\n+\t\t\tconst OptionsParser::Options &suboptions = streamOptions.toArray()[i].children();\n+\t\t\tif (defaultSink) {\n+\t\t\t\tif (suboptions.isSet(OptStreamDisplay) ||\n+\t\t\t\t    suboptions.isSet(OptStreamSDL) ||\n+\t\t\t\t    suboptions.isSet(OptStreamFile)) {\n+\t\t\t\t\tstd::cerr << \"Combining default and stream specific outputs is unsupported\" << std::endl;\n+\t\t\t\t\treturn -EINVAL;\n+\t\t\t\t}\n+\n+\t\t\t\tdefaultSink->addStream(cfg.stream());\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tstd::unique_ptr<FrameSink> sink;\n+#ifdef HAVE_KMS\n+\t\t\tif (suboptions.isSet(OptStreamDisplay)) {\n+\t\t\t\tif (kmsSinkAssigned) {\n+\t\t\t\t\tstd::cerr << \"Display doesn't support multiple streams\" << std::endl;\n+\t\t\t\t\treturn -EINVAL;\n+\t\t\t\t}\n+\t\t\t\tkmsSinkAssigned = true;\n+\n+\t\t\t\tsink = std::make_unique<KMSSink>(suboptions[OptStreamDisplay].toString());\n+\t\t\t}\n+#endif\n+#ifdef HAVE_SDL\n+\t\t\tif (suboptions.isSet(OptStreamSDL)) {\n+\t\t\t\tif (sink) {\n+\t\t\t\t\tstd::cerr << \"Single stream cannot have multiple outputs\" << std::endl;\n+\t\t\t\t\treturn -EINVAL;\n+\t\t\t\t}\n+\t\t\t\tsink = std::make_unique<SDLSink>();\n+\t\t\t}\n+#endif\n+\t\t\tif (suboptions.isSet(OptStreamFile)) {\n+\t\t\t\tif (sink) {\n+\t\t\t\t\tstd::cerr << \"Single stream cannot have multiple outputs\" << std::endl;\n+\t\t\t\t\treturn -EINVAL;\n+\t\t\t\t}\n+\t\t\t\tstd::unique_ptr<FileSink> fileSink =\n+\t\t\t\t\tstd::make_unique<FileSink>(camera_.get(), streamNames_);\n+\t\t\t\tif (!suboptions[OptStreamFile].toString().empty()) {\n+\t\t\t\t\tret = fileSink->setFilePattern(suboptions[OptStreamFile]);\n+\t\t\t\t\tif (ret)\n+\t\t\t\t\t\treturn ret;\n+\t\t\t\t}\n+\t\t\t\tsink = std::move(fileSink);\n+\t\t\t}\n+\n+\t\t\tif (sink) {\n+\t\t\t\tsink->addStream(cfg.stream());\n+\t\t\t\tsinks_.push_back(std::move(sink));\n+\t\t\t}\n+\t\t}\n \t}\n \n \tif (defaultSink)\ndiff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp\nindex 9d84ef8e..10b7a33a 100644\n--- a/src/apps/cam/main.cpp\n+++ b/src/apps/cam/main.cpp\n@@ -138,14 +138,21 @@ int CamApp::parseOptions(int argc, char *argv[])\n \t\t\t \"Desired image orientation (rot0, rot180, mirror, flip)\",\n \t\t\t \"orientation\", ArgumentRequired, \"orientation\", false,\n \t\t\t OptCamera);\n+\tparser.addOption(OptStream, &streamKeyValue,\n+\t\t\t \"Set configuration of a camera stream\", \"stream\", true,\n+\t\t\t OptCamera);\n #ifdef HAVE_KMS\n \tparser.addOption(OptDisplay, OptionString,\n-\t\t\t \"Display viewfinder through DRM/KMS on specified connector\",\n+\t\t\t \"Display viewfinder by default through DRM/KMS on specified connector\",\n \t\t\t \"display\", ArgumentOptional, \"connector\", false,\n \t\t\t OptCamera);\n+\tparser.addOption(OptStreamDisplay, OptionString,\n+\t\t\t \"Display viewfinder stream through DRM/KMS on specified connector\",\n+\t\t\t \"stream-display\", ArgumentOptional, \"connector\", false,\n+\t\t\t OptStream);\n #endif\n \tparser.addOption(OptFile, OptionString,\n-\t\t\t \"Write captured frames to disk\\n\"\n+\t\t\t \"Write captured frames by default to disk\\n\"\n \t\t\t \"If the file name ends with a '/', it sets the directory in which\\n\"\n \t\t\t \"to write files, using the default file name. Otherwise it sets the\\n\"\n \t\t\t \"full file path and name. The first '#' character in the file name\\n\"\n@@ -159,13 +166,17 @@ int CamApp::parseOptions(int argc, char *argv[])\n \t\t\t \"The default file name is 'frame-#.bin'.\",\n \t\t\t \"file\", ArgumentOptional, \"filename\", false,\n \t\t\t OptCamera);\n+\tparser.addOption(OptStreamFile, OptionString,\n+\t\t\t \"Write frames captured from a stream to disk\\n\"\n+\t\t\t \"The file name is of the same format as in --file.\",\n+\t\t\t \"stream-file\", ArgumentOptional, \"filename\", false,\n+\t\t\t OptStream);\n #ifdef HAVE_SDL\n-\tparser.addOption(OptSDL, OptionNone, \"Display viewfinder through SDL\",\n+\tparser.addOption(OptSDL, OptionNone, \"Display viewfinder by default through SDL\",\n \t\t\t \"sdl\", ArgumentNone, \"\", false, OptCamera);\n+\tparser.addOption(OptStreamSDL, OptionNone, \"Display stream viewfinder through SDL\",\n+\t\t\t \"stream-sdl\", ArgumentNone, \"\", false, OptStream);\n #endif\n-\tparser.addOption(OptStream, &streamKeyValue,\n-\t\t\t \"Set configuration of a camera stream\", \"stream\", true,\n-\t\t\t OptCamera);\n \tparser.addOption(OptStrictFormats, OptionNone,\n \t\t\t \"Do not allow requested stream format(s) to be adjusted\",\n \t\t\t \"strict-formats\", ArgumentNone, nullptr, false,\ndiff --git a/src/apps/cam/main.h b/src/apps/cam/main.h\nindex 64e6a20e..2f630fbe 100644\n--- a/src/apps/cam/main.h\n+++ b/src/apps/cam/main.h\n@@ -20,6 +20,9 @@ enum {\n \tOptOrientation = 'o',\n \tOptSDL = 'S',\n \tOptStream = 's',\n+\tOptStreamDisplay = 'd',\n+\tOptStreamFile = 'f',\n+\tOptStreamSDL = 'w',\n \tOptListControls = 256,\n \tOptStrictFormats = 257,\n \tOptMetadata = 258,\n",
    "prefixes": [
        "RFC",
        "7/7"
    ]
}