From patchwork Fri Mar 24 18:12:45 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas Dufresne X-Patchwork-Id: 18459 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 916EAC3260 for ; Fri, 24 Mar 2023 18:12:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 382296271E; Fri, 24 Mar 2023 19:12:59 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1679681579; bh=OD0ahQzIbwPZKN0O8W+b6n38WzK5KqZ4lBNlRtpsKmA=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=Ii7ikJpmjdJXNnTSpAxYbJBcqVPkGzwSZ7p4G0vs/fglfInyqe2uQ3feb1jyLHrrH ElB8gOiUoe0wxJw+fZ3vE9dJq/ZcIWWvuef4kGjXKfjYjfeRGPwRc6x1hbVC4xhQc8 3SOifsuQhdqpHXPvi3uHzrMgJJ4an0QY4DVHCcS8RJT5RP7748a+x1oaRbCylOv9Ng g+4xvpNVuCv+KtleqhQVIPkfjF1JZd28z+qIE+psm3hiU88GZXxo4Cn3HdHIKBgpUq sV8vf1jJGIVo9kx5QwkHR2TBZHlhOeUaKQfcpJYGoWGVyP4gbklCkpTqduen/8+YCl hk7acnsN/Vecw== Received: from madras.collabora.co.uk (madras.collabora.co.uk [IPv6:2a00:1098:0:82:1000:25:2eeb:e5ab]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 505E962715 for ; Fri, 24 Mar 2023 19:12:57 +0100 (CET) Received: from nicolas-tpx395.lan (192-222-136-102.qc.cable.ebox.net [192.222.136.102]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: nicolas) by madras.collabora.co.uk (Postfix) with ESMTPSA id C8751660312C; Fri, 24 Mar 2023 18:12:56 +0000 (GMT) To: libcamera-devel@lists.libcamera.org Date: Fri, 24 Mar 2023 14:12:45 -0400 Message-Id: <20230324181247.302586-2-nicolas@ndufresne.ca> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20230324181247.302586-1-nicolas@ndufresne.ca> References: <20230324181247.302586-1-nicolas@ndufresne.ca> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 1/3] gstreamer: Add sensor mode selection 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: Nicolas Dufresne via libcamera-devel From: Nicolas Dufresne Reply-To: Nicolas Dufresne Cc: Nicolas Dufresne Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: Nicolas Dufresne This add support for selecting the sensor mode. A new read-only property called sensor-modes is filled when the element reaches READY state. It contains the list of all available sensor modes, including the supported framerate range. This is exposed as GstCaps in the form of sensor/mode,width=X,height=Y,format=Y,framerate=[...]. The format string matches the libcamera format string representation. The application can then select a mode using the read/write sensor-mode control. The selected mode is also a caps, it will be intersected with the supported mode and the "best" match will be picked. This allows application to use simple filter when they want to pick a mode for lets say a specific framerate (e.g. sensor/mode,framerate=60/1). Signed-off-by: Nicolas Dufresne --- src/gstreamer/gstlibcamera-utils.cpp | 4 + src/gstreamer/gstlibcamerasrc.cpp | 110 ++++++++++++++++++++++++++- 2 files changed, 112 insertions(+), 2 deletions(-) diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp index 750ec351..c8a8df09 100644 --- a/src/gstreamer/gstlibcamera-utils.cpp +++ b/src/gstreamer/gstlibcamera-utils.cpp @@ -416,6 +416,10 @@ gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg, stream_cfg.pixelFormat = gst_format_to_pixel_format(gst_format); } else if (gst_structure_has_name(s, "image/jpeg")) { stream_cfg.pixelFormat = formats::MJPEG; + } else if (gst_structure_has_name(s, "sensor/mode")) { + gst_structure_fixate_field(s, "format"); + const gchar *format = gst_structure_get_string(s, "format"); + stream_cfg.pixelFormat = PixelFormat::fromString(format); } else { g_critical("Unsupported media type: %s", gst_structure_get_name(s)); } diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp index a10cbd4f..2f05a03f 100644 --- a/src/gstreamer/gstlibcamerasrc.cpp +++ b/src/gstreamer/gstlibcamerasrc.cpp @@ -147,6 +147,9 @@ struct _GstLibcameraSrc { gchar *camera_name; + GstCaps *sensor_modes; + GstCaps *sensor_mode; + GstLibcameraSrcState *state; GstLibcameraAllocator *allocator; GstFlowCombiner *flow_combiner; @@ -154,7 +157,10 @@ struct _GstLibcameraSrc { enum { PROP_0, - PROP_CAMERA_NAME + PROP_CAMERA_NAME, + PROP_SENSOR_MODES, + PROP_SENSOR_MODE, + PROP_LAST }; G_DEFINE_TYPE_WITH_CODE(GstLibcameraSrc, gst_libcamera_src, GST_TYPE_ELEMENT, @@ -318,6 +324,61 @@ int GstLibcameraSrcState::processRequest() return err; } +static GstCaps * +gst_libcamera_src_enumerate_sensor_modes(GstLibcameraSrc *self) +{ + GstCaps *modes = gst_caps_new_empty(); + GstLibcameraSrcState *state = self->state; + auto config = state->cam_->generateConfiguration({ libcamera::StreamRole::Raw, + libcamera::StreamRole::VideoRecording }); + if (config == nullptr) { + GST_DEBUG_OBJECT(self, "Sensor mode selection is not supported, skipping enumeration."); + return modes; + } + + const libcamera::StreamFormats &formats = config->at(0).formats(); + + for (const auto &pixfmt : formats.pixelformats()) { + for (const auto &size : formats.sizes(pixfmt)) { + config->at(0).size = size; + config->at(0).pixelFormat = pixfmt; + + if (config->validate() == CameraConfiguration::Invalid) + continue; + + if (state->cam_->configure(config.get())) + continue; + + auto fd_ctrl = state->cam_->controls().find(&controls::FrameDurationLimits); + if (fd_ctrl == state->cam_->controls().end()) + continue; + + int minrate_num, minrate_denom; + int maxrate_num, maxrate_denom; + double min_framerate = gst_util_guint64_to_gdouble(1.0e6) / + gst_util_guint64_to_gdouble(fd_ctrl->second.max().get()); + double max_framerate = gst_util_guint64_to_gdouble(1.0e6) / + gst_util_guint64_to_gdouble(fd_ctrl->second.min().get()); + gst_util_double_to_fraction(min_framerate, &minrate_num, &minrate_denom); + gst_util_double_to_fraction(max_framerate, &maxrate_num, &maxrate_denom); + + GstStructure *s = gst_structure_new("sensor/mode", + "format", G_TYPE_STRING, pixfmt.toString().c_str(), + "width", G_TYPE_INT, size.width, + "height", G_TYPE_INT, size.height, + "framerate", GST_TYPE_FRACTION_RANGE, + minrate_num, minrate_denom, + maxrate_num, maxrate_denom, + nullptr); + gst_caps_append_structure(modes, s); + } + } + + GST_DEBUG_OBJECT(self, "Camera sensor modes: %" GST_PTR_FORMAT, modes); + + return modes; +} + static bool gst_libcamera_src_open(GstLibcameraSrc *self) { @@ -375,6 +436,7 @@ gst_libcamera_src_open(GstLibcameraSrc *self) /* No need to lock here, we didn't start our threads yet. */ self->state->cm_ = cm; self->state->cam_ = cam; + self->sensor_modes = gst_libcamera_src_enumerate_sensor_modes(self); return true; } @@ -462,6 +524,7 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread, GstLibcameraSrcState *state = self->state; GstFlowReturn flow_ret = GST_FLOW_OK; gint ret; + g_autoptr(GstCaps) sensor_mode = nullptr; g_autoptr(GstStructure) element_caps = gst_structure_new_empty("caps"); @@ -481,6 +544,16 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread, roles.push_back(gst_libcamera_pad_get_role(srcpad)); } + if (!gst_caps_is_any(self->sensor_mode)) { + sensor_mode = gst_caps_intersect(self->sensor_mode, self->sensor_modes); + if (!gst_caps_is_empty(sensor_mode)) { + roles.push_back(libcamera::StreamRole::Raw); + } else { + GST_WARNING_OBJECT(self, "No sensor mode matching the selection, ignoring."); + gst_clear_caps(&sensor_mode); + } + } + /* Generate the stream configurations, there should be one per pad. */ state->config_ = state->cam_->generateConfiguration(roles); if (state->config_ == nullptr) { @@ -490,7 +563,7 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread, gst_task_stop(task); return; } - g_assert(state->config_->size() == state->srcpads_.size()); + g_assert(state->config_->size() == state->srcpads_.size() + (!!sensor_mode)); for (gsize i = 0; i < state->srcpads_.size(); i++) { GstPad *srcpad = state->srcpads_[i]; @@ -510,6 +583,12 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread, gst_libcamera_get_framerate_from_caps(caps, element_caps); } + if (sensor_mode) { + StreamConfiguration &stream_cfg = state->config_->at(state->srcpads_.size()); + g_assert(gst_caps_is_writable(sensor_mode)); + gst_libcamera_configure_stream_from_caps(stream_cfg, sensor_mode); + } + if (flow_ret != GST_FLOW_OK) goto done; @@ -624,6 +703,7 @@ gst_libcamera_src_task_leave([[maybe_unused]] GstTask *task, g_clear_object(&self->allocator); g_clear_pointer(&self->flow_combiner, (GDestroyNotify)gst_flow_combiner_free); + gst_clear_caps(&self->sensor_modes); } static void @@ -659,6 +739,10 @@ gst_libcamera_src_set_property(GObject *object, guint prop_id, g_free(self->camera_name); self->camera_name = g_value_dup_string(value); break; + case PROP_SENSOR_MODE: + gst_clear_caps(&self->sensor_mode); + self->sensor_mode = GST_CAPS(g_value_dup_boxed(value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; @@ -676,6 +760,12 @@ gst_libcamera_src_get_property(GObject *object, guint prop_id, GValue *value, case PROP_CAMERA_NAME: g_value_set_string(value, self->camera_name); break; + case PROP_SENSOR_MODES: + g_value_set_boxed(value, self->sensor_modes); + break; + case PROP_SENSOR_MODE: + g_value_set_boxed(value, self->sensor_mode); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; @@ -763,6 +853,7 @@ gst_libcamera_src_init(GstLibcameraSrc *self) /* C-style friend. */ state->src_ = self; self->state = state; + self->sensor_mode = gst_caps_new_any(); } static GstPad * @@ -844,4 +935,19 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass) | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property(object_class, PROP_CAMERA_NAME, spec); + + spec = g_param_spec_boxed("sensor-modes", "Sensor Modes", + "GstCaps representing available sensor modes.", + GST_TYPE_CAPS, + (GParamFlags)(G_PARAM_READABLE + | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property(object_class, PROP_SENSOR_MODES, spec); + + spec = g_param_spec_boxed("sensor-mode", "Sensor Mode", + "GstCaps representing selected sensor mode.", + GST_TYPE_CAPS, + (GParamFlags)(GST_PARAM_MUTABLE_READY + | G_PARAM_READWRITE + | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property(object_class, PROP_SENSOR_MODE, spec); }