[libcamera-devel] [PATCH v2 1/3] gstreamer: Add sensor mode selection
Robert Mader
robert.mader at posteo.de
Fri Mar 24 19:24:26 CET 2023
Hi,
On 24.03.23 19:12, Nicolas Dufresne via libcamera-devel wrote:
> From: Nicolas Dufresne<nicolas.dufresne at collabora.com>
>
> 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<nicolas.dufresne at collabora.com>
> ---
> 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;
> + }
I gave this a short try on both my USB camera and on a PPP. In both
cases enumeration gets skipped (as intended), but in the later case the
following warning gets printed:
> ERROR RkISP1 rkisp1.cpp:684 Can't capture both raw and processed streams
So I wonder if we can come a better check here.
When allowing the enumeration by only selecting Raw or VideoRecording I
made the following observations:
1. enumeration on the USB camera is very slow, takes several seconds
2. on the PPP / RkISP1, even though there are many more modes, the
enumeration is super fast - probably a fraction of a second. So a
method like this could probably be used for framerate enumeration.
3. the mode selection via sensor-mode=sensor/mode,... usually fails
though (haven't digged deeper why yet)
Regards,
Robert
> +
> + 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<int64_t>());
> + double max_framerate = gst_util_guint64_to_gdouble(1.0e6) /
> + gst_util_guint64_to_gdouble(fd_ctrl->second.min().get<int64_t>());
> + 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);
> }
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.libcamera.org/pipermail/libcamera-devel/attachments/20230324/6b82f1fe/attachment.htm>
More information about the libcamera-devel
mailing list