From patchwork Fri Mar 14 20:29:35 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Milan Zamazal X-Patchwork-Id: 22967 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 F39F8C32F7 for ; Fri, 14 Mar 2025 20:30:31 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B105968952; Fri, 14 Mar 2025 21:30:31 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="itMoM1Uu"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 480C868950 for ; Fri, 14 Mar 2025 21:30:29 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1741984228; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=cK0ilC9taCY6uIP7kxjRgmSs8FOpljnBJbd+JeDokrs=; b=itMoM1Uuo17HORFnldkCIppE1fLG8c2Az+rdpQUw1qRMssO9ZmlMsxJNVq56tCOd0D+CL2 2K/7RTHQRAN/4FYGqCT3FfARwMT3dpXOVwR0p+9MRbZA2gbcKk7fQXSKLY+yW5+Ve4AyF0 2i+UklbH9wTFoEEvCapELiq5HUnm/FM= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-589-rJ57H-BdNqqmvXaKIukv6w-1; Fri, 14 Mar 2025 16:30:17 -0400 X-MC-Unique: rJ57H-BdNqqmvXaKIukv6w-1 X-Mimecast-MFC-AGG-ID: rJ57H-BdNqqmvXaKIukv6w_1741984211 Received: from mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.40]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 8B0D91956080; Fri, 14 Mar 2025 20:30:11 +0000 (UTC) Received: from mzamazal-thinkpadp1gen7.tpbc.com (unknown [10.44.32.19]) by mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 137181954B32; Fri, 14 Mar 2025 20:30:09 +0000 (UTC) From: Milan Zamazal To: libcamera-devel@lists.libcamera.org Cc: Milan Zamazal , Laurent Pinchart Subject: [RFC PATCH 7/7] apps: cam: Add command line options for stream specific 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-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: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Now, when the support for multiple outputs is ready, let's add command line options to actually use it. To remain backward compatible and to keep the things simple for the user, the original output related command line options work as before. For example, simply running cam -c1 -C -D or cam ... -s role=viewfinder,... -s role=raw,... -Fimage# still works. But the user can also specify a different output for each of the given streams now, for example: cam ... -s role=viewfinder,... --stream-display \ -s role=raw,... --stream-file=raw# or cam ... -s role=viewfinder,... --stream-file=processed#.ppm \ -s role=raw,... --stream-file=raw# To not complicate the implementation more than necessary, it's not allowed to combine the old and the new output options. It is also checked that --stream-display is not used more than once (the KMS output may be used only for a single stream) and that each stream has at most one output assigned. Signed-off-by: Milan Zamazal --- src/apps/cam/camera_session.cpp | 67 ++++++++++++++++++++++++++++++++- src/apps/cam/main.cpp | 23 ++++++++--- src/apps/cam/main.h | 3 ++ 3 files changed, 85 insertions(+), 8 deletions(-) diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp index 5124029b..5ef0c2fe 100644 --- a/src/apps/cam/camera_session.cpp +++ b/src/apps/cam/camera_session.cpp @@ -7,13 +7,16 @@ #include "camera_session.h" +#include #include #include #include +#include #include #include #include +#include #include "../common/event_loop.h" #include "../common/stream_options.h" @@ -292,10 +295,70 @@ int CameraSession::start() defaultSink = std::move(sink); } +#ifdef HAVE_KMS + bool kmsSinkAssigned = false; +#endif + auto &streamOptions = options_[OptStream]; for (unsigned int i = 0; i < config_->size(); i++) { const StreamConfiguration &cfg = config_->at(i); - if (defaultSink) - defaultSink->addStream(cfg.stream()); + if (streamOptions.empty()) { + if (defaultSink) + defaultSink->addStream(cfg.stream()); + } else { + const OptionsParser::Options &suboptions = streamOptions.toArray()[i].children(); + if (defaultSink) { + if (suboptions.isSet(OptStreamDisplay) || + suboptions.isSet(OptStreamSDL) || + suboptions.isSet(OptStreamFile)) { + std::cerr << "Combining default and stream specific outputs is unsupported" << std::endl; + return -EINVAL; + } + + defaultSink->addStream(cfg.stream()); + continue; + } + + std::unique_ptr sink; +#ifdef HAVE_KMS + if (suboptions.isSet(OptStreamDisplay)) { + if (kmsSinkAssigned) { + std::cerr << "Display doesn't support multiple streams" << std::endl; + return -EINVAL; + } + kmsSinkAssigned = true; + + sink = std::make_unique(suboptions[OptStreamDisplay].toString()); + } +#endif +#ifdef HAVE_SDL + if (suboptions.isSet(OptStreamSDL)) { + if (sink) { + std::cerr << "Single stream cannot have multiple outputs" << std::endl; + return -EINVAL; + } + sink = std::make_unique(); + } +#endif + if (suboptions.isSet(OptStreamFile)) { + if (sink) { + std::cerr << "Single stream cannot have multiple outputs" << std::endl; + return -EINVAL; + } + std::unique_ptr fileSink = + std::make_unique(camera_.get(), streamNames_); + if (!suboptions[OptStreamFile].toString().empty()) { + ret = fileSink->setFilePattern(suboptions[OptStreamFile]); + if (ret) + return ret; + } + sink = std::move(fileSink); + } + + if (sink) { + sink->addStream(cfg.stream()); + sinks_.push_back(std::move(sink)); + } + } } if (defaultSink) diff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp index 9d84ef8e..10b7a33a 100644 --- a/src/apps/cam/main.cpp +++ b/src/apps/cam/main.cpp @@ -138,14 +138,21 @@ int CamApp::parseOptions(int argc, char *argv[]) "Desired image orientation (rot0, rot180, mirror, flip)", "orientation", ArgumentRequired, "orientation", false, OptCamera); + parser.addOption(OptStream, &streamKeyValue, + "Set configuration of a camera stream", "stream", true, + OptCamera); #ifdef HAVE_KMS parser.addOption(OptDisplay, OptionString, - "Display viewfinder through DRM/KMS on specified connector", + "Display viewfinder by default through DRM/KMS on specified connector", "display", ArgumentOptional, "connector", false, OptCamera); + parser.addOption(OptStreamDisplay, OptionString, + "Display viewfinder stream through DRM/KMS on specified connector", + "stream-display", ArgumentOptional, "connector", false, + OptStream); #endif parser.addOption(OptFile, OptionString, - "Write captured frames to disk\n" + "Write captured frames by default to disk\n" "If the file name ends with a '/', it sets the directory in which\n" "to write files, using the default file name. Otherwise it sets the\n" "full file path and name. The first '#' character in the file name\n" @@ -159,13 +166,17 @@ int CamApp::parseOptions(int argc, char *argv[]) "The default file name is 'frame-#.bin'.", "file", ArgumentOptional, "filename", false, OptCamera); + parser.addOption(OptStreamFile, OptionString, + "Write frames captured from a stream to disk\n" + "The file name is of the same format as in --file.", + "stream-file", ArgumentOptional, "filename", false, + OptStream); #ifdef HAVE_SDL - parser.addOption(OptSDL, OptionNone, "Display viewfinder through SDL", + parser.addOption(OptSDL, OptionNone, "Display viewfinder by default through SDL", "sdl", ArgumentNone, "", false, OptCamera); + parser.addOption(OptStreamSDL, OptionNone, "Display stream viewfinder through SDL", + "stream-sdl", ArgumentNone, "", false, OptStream); #endif - parser.addOption(OptStream, &streamKeyValue, - "Set configuration of a camera stream", "stream", true, - OptCamera); parser.addOption(OptStrictFormats, OptionNone, "Do not allow requested stream format(s) to be adjusted", "strict-formats", ArgumentNone, nullptr, false, diff --git a/src/apps/cam/main.h b/src/apps/cam/main.h index 64e6a20e..2f630fbe 100644 --- a/src/apps/cam/main.h +++ b/src/apps/cam/main.h @@ -20,6 +20,9 @@ enum { OptOrientation = 'o', OptSDL = 'S', OptStream = 's', + OptStreamDisplay = 'd', + OptStreamFile = 'f', + OptStreamSDL = 'w', OptListControls = 256, OptStrictFormats = 257, OptMetadata = 258,