[libcamera-devel] [PATCH v3] gstreamer: Provide framerate support for libcamerasrc.

Rishikesh Donadkar rishikeshdonadkar at gmail.com
Thu Sep 8 08:06:24 CEST 2022


>
> > +
> > +     gst_structure_set(s,
> > +                       "width", G_TYPE_INT, stream_cfg.size.width,
> > +                       "height", G_TYPE_INT, stream_cfg.size.height,
> > +                       nullptr);
> > +
> > +     if (ret) {
> > +             gint numerator, denominator;
> > +             numerator =
> gst_value_get_fraction_numerator(framerate_container);
> > +             denominator =
> gst_value_get_fraction_denominator(framerate_container);
> > +             gst_structure_set(s, "framerate", GST_TYPE_FRACTION,
> numerator, denominator, nullptr);
> > +     } else {
> > +             gst_structure_set(s, "framerate", GST_TYPE_FRACTION, 0, 1,
> nullptr);
> > +     }
> > +
> > +     if (stream_cfg.colorSpace) {
>
> ?!
>
> I am not getting why you are introducing code related to colorspace
> here? Is it a copy/paste fiasco?
>
As new caps are exposed. Shouldn't we expose everything that has been
decided on the libcamera side that includes colorspace, resolutions,
format and framerate?


On Thu, Sep 8, 2022 at 11:15 AM Umang Jain <umang.jain at ideasonboard.com>
wrote:

> Hi Rishi,
>
> On 9/7/22 4:17 PM, Rishikesh Donadkar wrote:
> > Add a field of the type ControlList to GstLibcameraSrcState.
> >
> > Get the framerate from the caps and convert it to FrameDuration.
> > Set the FrameDurationLimits control in the initControls_
> >
> > Passing initControls_ at camera::start() doesn't tell us if the controls
> > have been applied to the camera successfully or not (or they have
> adjusted in
> > some fashion). Until that is introduced in camera::start() API,
> > we need to carry out such handling on the application side.
> >
> > Check the frame_duration whether it is in-bound to the max / min
> > frame-duration supported by the camera using the function
> > gst_libcamera_bind_framerate().
> >
> > If the frame_duartion is out of bounds, bind the frame_duration
> > between the max and min FrameDuration that is supported by the camera.
> >
> > Pass the initControls_ at the time of starting camera.
> >
> > Solve the complications in exposing the correct framerate due to loss in
> > precision as a result of casting the frameduration to int64_t(which is
> > required in libcamera to set the FrameDurationLimits control).
> >
> > Example -
> >
> > * Suppose the framerate requested is 35/1. The framerate is read form
> >    the caps in the from of fraction that has a numerator and
> >    denominator.
> >
> > * Converting to FrameDuration (in microseconds)
> >      (1 * 10^6) / 35 = 28571.4286
> >      int64_t frame_duration = 28571
> >      (the precision here is lost.)
> > * To expose the framerate in caps, Inverting the frame_duration to get
> >    back the framerate and converting to Seconds.
> >      double framerate = 10^6 / 28571
> >      and
> >      10^6/28571 which is close but not equal to 35/1 will fail the
> negotiation.
> >
> > To solve the above problem, Store the framerate requested through
> > negotiated caps in the GValue of the type GST_TYPE_FRACTION.
> >
> > Try to apply the framerate and check if the floor value of framerate
> that gets applied
> > closely matches with the floor value framerate requested in the
> negotiated caps. If
> > the value matches expose the framerate that is stored in the
> framerate_container, else expose
> > the framerate in the new caps as 0/1.
> >
> > Pass the initControls_ at camera->start().
> >
> > ---
> > Changes from v1 to v2
> > * Use GValue of the type GST_TYPE_FRACTION instead of std::pair to store
> the framerate.
> > * Don't shift the call to camera->configure(), instead bound the
> framerate
> >    after the call to camera->configure().
> > * Reset the FrameDurationLimits control only if it is out of bounds.
> > * Expose the caps containing framerate information with a new CAPS event
> >    after the call to camera->configure().
> > ---
> >
> > Signed-off-by: Umang Jain <umang.jain at ideasonboard.com>
>
> As I haven't contributed to any LOC of this patch, please drop of my
> s-o-b tag.
>
> Non-existent s-o-b on behalf of others shouldn't be applied ideally
> > Signed-off-by: Rishikesh Donadkar <rishikeshdonadkar at gmail.com>
> > ---
> >   src/gstreamer/gstlibcamera-utils.cpp | 116 +++++++++++++++++++++++++++
> >   src/gstreamer/gstlibcamera-utils.h   |   8 ++
> >   src/gstreamer/gstlibcamerasrc.cpp    |  28 ++++++-
> >   3 files changed, 151 insertions(+), 1 deletion(-)
> >
> > diff --git a/src/gstreamer/gstlibcamera-utils.cpp
> b/src/gstreamer/gstlibcamera-utils.cpp
> > index 4df5dd6c..e862f7ea 100644
> > --- a/src/gstreamer/gstlibcamera-utils.cpp
> > +++ b/src/gstreamer/gstlibcamera-utils.cpp
> > @@ -8,6 +8,7 @@
> >
> >   #include "gstlibcamera-utils.h"
> >
> > +#include <libcamera/control_ids.h>
> >   #include <libcamera/formats.h>
> >
> >   using namespace libcamera;
> > @@ -405,6 +406,121 @@
> gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
> >       }
> >   }
> >
> > +void
> > +gst_libcamera_get_init_controls_from_caps(ControlList &controls,
> [[maybe_unused]] GstCaps *caps,
> > +                                       GValue *framerate_container)
> > +{
> > +     GstStructure *s = gst_caps_get_structure(caps, 0);
> > +     gint fps_n = -1, fps_d = -1;
> > +
> > +     if (gst_structure_has_field_typed(s, "framerate",
> GST_TYPE_FRACTION)) {
> > +             if (!gst_structure_get_fraction(s, "framerate", &fps_n,
> &fps_d)) {
> > +                     GST_WARNING("invalid framerate in the caps.");
> > +                     return;
> > +             }
> > +     }
> > +
> > +     if (fps_n < 0 || fps_d < 0)
> > +             return;
> > +
> > +     gst_value_set_fraction(framerate_container, fps_n, fps_d);
> > +     int64_t frame_duration = (fps_d * 1000000.0) / fps_n;
> > +
> > +     controls.set(controls::FrameDurationLimits,
> > +                  Span<const int64_t, 2>({ frame_duration,
> frame_duration }));
>
> setting the frame_duration even before bound-checking is wrong. This
> Function should ideally get the 'framerate' and just save it for later
> use/bound-checking after camera::configure()
>
> Once the bound checking is successfully, only then you are allowed to
> set frame_duration on initControls_
> > +}
> > +
> > +void
> > +gst_libcamera_bind_framerate(const ControlInfoMap &camera_controls,
> ControlList &controls)
>
> maybe
> gst_libcamera_framerate_clamp()
> OR
> gst_libcamera_framerate_bounds_check()
> > +{
> > +     gboolean isBound = true;
> > +     auto iterFrameDuration =
> camera_controls.find(controls::FrameDurationLimits.id());
> > +
> > +     if (!(iterFrameDuration != camera_controls.end() &&
> > +           controls.contains(controls::FRAME_DURATION_LIMITS)))
> > +             return;
> > +
> > +     int64_t frame_duration =
> controls.get(controls::FrameDurationLimits).value()[0];
> > +     int64_t min_frame_duration =
> iterFrameDuration->second.min().get<int64_t>();
> > +     int64_t max_frame_duration =
> iterFrameDuration->second.max().get<int64_t>();
> > +
> > +     if (frame_duration < min_frame_duration) {
> > +             frame_duration = min_frame_duration;
> > +     } else if (frame_duration > max_frame_duration) {
> > +             frame_duration = max_frame_duration;
> > +     } else {
> > +             isBound = false;
> > +     }
> > +
> > +     if (isBound)
> > +             controls.set(controls::FrameDurationLimits,
> > +                          Span<const int64_t, 2>({ frame_duration,
> frame_duration }));
> > +}
> > +
> > +gboolean
> > +gst_libcamera_get_framerate_from_init_controls(const ControlList
> &controls, GValue *framerate,
> > +                                            GValue *framerate_container)
> > +{
> > +     gint fps_caps_n, fps_caps_d, numerator, denominator;
> > +
> > +     fps_caps_n = gst_value_get_fraction_numerator(framerate_container);
> > +     fps_caps_d =
> gst_value_get_fraction_denominator(framerate_container);
> > +
> > +     if (!controls.contains(controls::FRAME_DURATION_LIMITS))
> > +             return false;
> > +
> > +     double framerate_ret = 1000000 /
> static_cast<double>(controls.get(controls::FrameDurationLimits).value()[0]);
> > +     gst_util_double_to_fraction(framerate_ret, &numerator,
> &denominator);
> > +     gst_value_set_fraction(framerate, numerator, denominator);
> > +
> > +     if (fps_caps_n / fps_caps_d == numerator / denominator) {
> > +             return true;
> > +     }
> > +     return false;
> > +}
> > +
> > +GstCaps *
> > +gst_libcamera_init_controls_to_caps(const ControlList &controls, const
> StreamConfiguration &stream_cfg,
> > +                                 GValue *framerate_container)
> > +{
> > +     GstCaps *caps = gst_caps_new_empty();
> > +     GstStructure *s =
> bare_structure_from_format(stream_cfg.pixelFormat);
> > +     gboolean ret;
> > +     GValue *framerate = g_new0(GValue, 1);
>
> Why create a new GValue here?
>
> You have everything you need through framerate_container, a applied and
> bound-checked framerate, you just need to query the fraction from it and
> set the structure?
> > +     g_value_init(framerate, GST_TYPE_FRACTION);
> > +
> > +     ret = gst_libcamera_get_framerate_from_init_controls(controls,
> framerate, framerate_container);
>
> As per the above comment, I am not sure if you really need to do that.
> Seems over-kill
> > +
> > +     gst_structure_set(s,
> > +                       "width", G_TYPE_INT, stream_cfg.size.width,
> > +                       "height", G_TYPE_INT, stream_cfg.size.height,
> > +                       nullptr);
> > +
> > +     if (ret) {
> > +             gint numerator, denominator;
> > +             numerator =
> gst_value_get_fraction_numerator(framerate_container);
> > +             denominator =
> gst_value_get_fraction_denominator(framerate_container);
> > +             gst_structure_set(s, "framerate", GST_TYPE_FRACTION,
> numerator, denominator, nullptr);
> > +     } else {
> > +             gst_structure_set(s, "framerate", GST_TYPE_FRACTION, 0, 1,
> nullptr);
> > +     }
> > +
> > +     if (stream_cfg.colorSpace) {
>
> ?!
>
> I am not getting why you are introducing code related to colorspace
> here? Is it a copy/paste fiasco?
> > +             GstVideoColorimetry colorimetry =
> colorimetry_from_colorspace(stream_cfg.colorSpace.value());
> > +             gchar *colorimetry_str =
> gst_video_colorimetry_to_string(&colorimetry);
> > +
> > +             if (colorimetry_str)
> > +                     gst_structure_set(s, "colorimetry", G_TYPE_STRING,
> colorimetry_str, nullptr);
> > +             else
> > +                     g_error("Got invalid colorimetry from ColorSpace:
> %s",
> > +
>  ColorSpace::toString(stream_cfg.colorSpace).c_str());
> > +     }
> > +
> > +     gst_caps_append_structure(caps, s);
> > +     g_free(framerate);
> > +     return caps;
> > +}
> > +
> >   #if !GST_CHECK_VERSION(1, 17, 1)
> >   gboolean
> >   gst_task_resume(GstTask *task)
> > diff --git a/src/gstreamer/gstlibcamera-utils.h
> b/src/gstreamer/gstlibcamera-utils.h
> > index 164189a2..955a717d 100644
> > --- a/src/gstreamer/gstlibcamera-utils.h
> > +++ b/src/gstreamer/gstlibcamera-utils.h
> > @@ -9,6 +9,7 @@
> >   #pragma once
> >
> >   #include <libcamera/camera_manager.h>
> > +#include <libcamera/controls.h>
> >   #include <libcamera/stream.h>
> >
> >   #include <gst/gst.h>
> > @@ -18,6 +19,13 @@ GstCaps *gst_libcamera_stream_formats_to_caps(const
> libcamera::StreamFormats &fo
> >   GstCaps *gst_libcamera_stream_configuration_to_caps(const
> libcamera::StreamConfiguration &stream_cfg);
> >   void
> gst_libcamera_configure_stream_from_caps(libcamera::StreamConfiguration
> &stream_cfg,
> >                                             GstCaps *caps);
> > +void gst_libcamera_get_init_controls_from_caps(libcamera::ControlList
> &controls,
> > +                                            GstCaps *caps, GValue
> *framerate_container);
> > +void gst_libcamera_bind_framerate(const libcamera::ControlInfoMap
> &camera_controls,
> > +                               libcamera::ControlList &controls);
> > +GstCaps *gst_libcamera_init_controls_to_caps(const
> libcamera::ControlList &controls,
> > +                                          const
> libcamera::StreamConfiguration &streame_cfg, GValue *framerate_container);
> > +
> >   #if !GST_CHECK_VERSION(1, 17, 1)
> >   gboolean gst_task_resume(GstTask *task);
> >   #endif
> > diff --git a/src/gstreamer/gstlibcamerasrc.cpp
> b/src/gstreamer/gstlibcamerasrc.cpp
> > index 16d70fea..0c981357 100644
> > --- a/src/gstreamer/gstlibcamerasrc.cpp
> > +++ b/src/gstreamer/gstlibcamerasrc.cpp
> > @@ -131,6 +131,7 @@ struct GstLibcameraSrcState {
> >       std::queue<std::unique_ptr<RequestWrap>> completedRequests_
> >               LIBCAMERA_TSA_GUARDED_BY(lock_);
> >
> > +     ControlList initControls_;
> >       guint group_id_;
> >
> >       int queueRequest();
> > @@ -461,6 +462,8 @@ gst_libcamera_src_task_enter(GstTask *task,
> [[maybe_unused]] GThread *thread,
> >       GstLibcameraSrcState *state = self->state;
> >       GstFlowReturn flow_ret = GST_FLOW_OK;
> >       gint ret;
> > +     GValue *framerate_container = g_new0(GValue, 1);
> > +     framerate_container = g_value_init(framerate_container,
> GST_TYPE_FRACTION);
>
> Since it's a GValue, I would not make it framerate_* specific, so I
> would just rename and remove all "framerate_" specifics.
>
> In future, when we have to parse more parameters as a part of
> initControls_ setting, the same GValue can be used to temporarily hold
> all other values
> >
> >       GST_DEBUG_OBJECT(self, "Streaming thread has started");
> >
> > @@ -496,6 +499,7 @@ gst_libcamera_src_task_enter(GstTask *task,
> [[maybe_unused]] GThread *thread,
> >               /* Retrieve the supported caps. */
> >               g_autoptr(GstCaps) filter =
> gst_libcamera_stream_formats_to_caps(stream_cfg.formats());
> >               g_autoptr(GstCaps) caps = gst_pad_peer_query_caps(srcpad,
> filter);
> > +
> >               if (gst_caps_is_empty(caps)) {
> >                       flow_ret = GST_FLOW_NOT_NEGOTIATED;
> >                       break;
> > @@ -504,6 +508,8 @@ gst_libcamera_src_task_enter(GstTask *task,
> [[maybe_unused]] GThread *thread,
> >               /* Fixate caps and configure the stream. */
> >               caps = gst_caps_make_writable(caps);
> >               gst_libcamera_configure_stream_from_caps(stream_cfg, caps);
> > +
>  gst_libcamera_get_init_controls_from_caps(state->initControls_, caps,
> > +
>  framerate_container);
>
> passing state->initControls_ is un-necessary here as we don't need to
> set frame_duration on it, just right now (it has to go through bound
> check first which is below...)
> >       }
> >
> >       if (flow_ret != GST_FLOW_OK)
> > @@ -524,6 +530,7 @@ gst_libcamera_src_task_enter(GstTask *task,
> [[maybe_unused]] GThread *thread,
> >               const StreamConfiguration &stream_cfg =
> state->config_->at(i);
> >
> >               g_autoptr(GstCaps) caps =
> gst_libcamera_stream_configuration_to_caps(stream_cfg);
> > +
> >               if (!gst_pad_push_event(srcpad, gst_event_new_caps(caps)))
> {
> >                       flow_ret = GST_FLOW_NOT_NEGOTIATED;
> >                       break;
> > @@ -544,6 +551,23 @@ gst_libcamera_src_task_enter(GstTask *task,
> [[maybe_unused]] GThread *thread,
> >               return;
> >       }
> >
> > +     /* bind the framerate */
>
> s/bind/Clamp OR
> /* Check framerate bounds within  controls::FrameDurationLimits */
> > +     gst_libcamera_bind_framerate(state->cam_->controls(),
> state->initControls_);
>
> I would just pass the controls::FrameDurationLimits and GValue
> (containing the asked framerate) and perform the bound check.
>
> You can update the GValue's framerate in that function after which
> simply, set the initControls' ::FrameDurationLimits right here.
> > +
> > +     /* Expose the controls in initControls_ throught a new caps event.
> */
> > +     for (gsize i = 0; i < state->srcpads_.size(); i++) {
> > +             GstPad *srcpad = state->srcpads_[i];
> > +             const StreamConfiguration &stream_cfg =
> state->config_->at(i);
> > +
> > +             g_autoptr(GstCaps) caps =
> gst_libcamera_init_controls_to_caps(state->initControls_,
> > +
>    stream_cfg, framerate_container);
> > +
> > +             if (!gst_pad_push_event(srcpad, gst_event_new_caps(caps)))
> {
> > +                     flow_ret = GST_FLOW_NOT_NEGOTIATED;
> > +                     break;
> > +             }
> > +     }
> > +
> >       self->allocator = gst_libcamera_allocator_new(state->cam_,
> state->config_.get());
> >       if (!self->allocator) {
> >               GST_ELEMENT_ERROR(self, RESOURCE, NO_SPACE_LEFT,
> > @@ -566,7 +590,7 @@ gst_libcamera_src_task_enter(GstTask *task,
> [[maybe_unused]] GThread *thread,
> >               gst_flow_combiner_add_pad(self->flow_combiner, srcpad);
> >       }
> >
> > -     ret = state->cam_->start();
> > +     ret = state->cam_->start(&state->initControls_);
> >       if (ret) {
> >               GST_ELEMENT_ERROR(self, RESOURCE, SETTINGS,
> >                                 ("Failed to start the camera: %s",
> g_strerror(-ret)),
> > @@ -576,6 +600,8 @@ gst_libcamera_src_task_enter(GstTask *task,
> [[maybe_unused]] GThread *thread,
> >       }
> >
> >   done:
> > +     state->initControls_.clear();
> > +     g_free(framerate_container);
> >       switch (flow_ret) {
> >       case GST_FLOW_NOT_NEGOTIATED:
> >               GST_ELEMENT_FLOW_ERROR(self, flow_ret);
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.libcamera.org/pipermail/libcamera-devel/attachments/20220908/ebd064e9/attachment.htm>


More information about the libcamera-devel mailing list