[4/4] gstreamer: Split metadata controls into new member
diff mbox

Message ID 20250422142010.13064-5-jaslo@ziska.de
State New
Headers show

Commit Message

Jaslo Ziska April 22, 2025, 2:11 p.m. UTC
When the libcamerasrc element is stopped and started again, the
GstCameraControls::setCamera() function is called, but now it is
populated with metadata returned by the previous requests. Because
GstCameraControls::setCamera() filters the controls, this would cause
warnings to be emitted, as the returned metadata are not valid controls
which can be set.

This is fixed by introducing a new member which only holds the metadata
returned by the requests, so that the controls and the metadata are now
completely independent from each other.

Signed-off-by: Jaslo Ziska <jaslo@ziska.de>
---
 src/gstreamer/gstlibcamera-controls.cpp.in | 22 ++++++++++++++++------
 src/gstreamer/gstlibcamera-controls.h      |  4 +++-
 2 files changed, 19 insertions(+), 7 deletions(-)

Comments

Nicolas Dufresne April 22, 2025, 2:42 p.m. UTC | #1
Hi,

Le mardi 22 avril 2025 à 16:11 +0200, Jaslo Ziska a écrit :
> When the libcamerasrc element is stopped and started again, the
> GstCameraControls::setCamera() function is called, but now it is
> populated with metadata returned by the previous requests. Because
> GstCameraControls::setCamera() filters the controls, this would cause
> warnings to be emitted, as the returned metadata are not valid controls
> which can be set.

Does it also fixes on top the issue where controls that get applied
with delays get resets and forgotten ?

> 
> This is fixed by introducing a new member which only holds the metadata
> returned by the requests, so that the controls and the metadata are now
> completely independent from each other.
> 
> Signed-off-by: Jaslo Ziska <jaslo@ziska.de>
> ---
>  src/gstreamer/gstlibcamera-controls.cpp.in | 22 ++++++++++++++++------
>  src/gstreamer/gstlibcamera-controls.h      |  4 +++-
>  2 files changed, 19 insertions(+), 7 deletions(-)
> 
> diff --git a/src/gstreamer/gstlibcamera-controls.cpp.in b/src/gstreamer/gstlibcamera-controls.cpp.in
> index 89c530da..490e835e 100644
> --- a/src/gstreamer/gstlibcamera-controls.cpp.in
> +++ b/src/gstreamer/gstlibcamera-controls.cpp.in
> @@ -153,20 +153,24 @@ g_param_spec_{{ ctrl.gtype }}(
>  bool GstCameraControls::getProperty(guint propId, GValue *value,
>  				    [[maybe_unused]] GParamSpec *pspec)
>  {
> -	if (!controls_acc_.contains(propId)) {
> +	const ControlValue *cv;
> +	if (controls_acc_.contains(propId)) {
> +		cv = &controls_acc_.get(propId);
> +	} else if (metadata_.contains(propId)) {
> +		cv = &metadata_.get(propId);
> +	} else {
>  		GST_WARNING("Control '%s' is not available, default value will "
>  			    "be returned",
>  			    controls::controls.at(propId)->name().c_str());
>  		return true;
>  	}
> -	const ControlValue &cv = controls_acc_.get(propId);
>  
>  	switch (propId) {
>  {%- for vendor, ctrls in controls %}
>  {%- for ctrl in ctrls %}
>  
>  	case controls::{{ ctrl.namespace }}{{ ctrl.name|snake_case|upper }}: {
> -		auto control = cv.get<{{ ctrl.type }}>();
> +		auto control = cv->get<{{ ctrl.type }}>();
>  
>  {%- if ctrl.is_array %}
>  		for (size_t i = 0; i < control.size(); ++i) {
> @@ -309,9 +313,14 @@ void GstCameraControls::setCamera(const std::shared_ptr<libcamera::Camera> &cam)
>  				    "camera and will be ignored",
>  				    cid->name().c_str());
>  	}
> -
>  	controls_acc_ = new_controls;
>  	controls_ = new_controls;
> +
> +	/*
> +	 * Clear metadata as this might already be populated if the element has
> +	 * been running before.
> +	 */
> +	metadata_.clear();
>  }
>  
>  void GstCameraControls::applyControls(std::unique_ptr<libcamera::Request> &request)
> @@ -322,6 +331,7 @@ void GstCameraControls::applyControls(std::unique_ptr<libcamera::Request> &reque
>  
>  void GstCameraControls::readMetadata(libcamera::Request *request)
>  {
> -	controls_acc_.merge(request->metadata(),
> -			    ControlList::MergePolicy::OverwriteExisting);
> +	metadata_.merge(request->metadata(),
> +			ControlList::MergePolicy::OverwriteExisting);
> +
>  }
> diff --git a/src/gstreamer/gstlibcamera-controls.h b/src/gstreamer/gstlibcamera-controls.h
> index 749220b5..ee97e61e 100644
> --- a/src/gstreamer/gstlibcamera-controls.h
> +++ b/src/gstreamer/gstlibcamera-controls.h
> @@ -36,8 +36,10 @@ private:
>  	ControlInfoMap capabilities_;
>  	/* Set of user modified controls. */
>  	ControlList controls_;
> -	/* Accumulator of all controls ever set and metadata returned by camera */
> +	/* Accumulator of all controls ever */
>  	ControlList controls_acc_;
> +	/* Metadata returned by requests */
> +	ControlList metadata_;
>  };
>  
>  } /* namespace libcamera */

Looks good to me, but I'd like someone from the core to review this
please.

Nicolas
Kieran Bingham April 22, 2025, 3:33 p.m. UTC | #2
Quoting Nicolas Dufresne (2025-04-22 15:42:41)
> Hi,
> 
> Le mardi 22 avril 2025 à 16:11 +0200, Jaslo Ziska a écrit :
> > When the libcamerasrc element is stopped and started again, the
> > GstCameraControls::setCamera() function is called, but now it is
> > populated with metadata returned by the previous requests. Because
> > GstCameraControls::setCamera() filters the controls, this would cause
> > warnings to be emitted, as the returned metadata are not valid controls
> > which can be set.
> 
> Does it also fixes on top the issue where controls that get applied
> with delays get resets and forgotten ?
> 
> > 
> > This is fixed by introducing a new member which only holds the metadata
> > returned by the requests, so that the controls and the metadata are now
> > completely independent from each other.
> > 
> > Signed-off-by: Jaslo Ziska <jaslo@ziska.de>
> > ---
> >  src/gstreamer/gstlibcamera-controls.cpp.in | 22 ++++++++++++++++------
> >  src/gstreamer/gstlibcamera-controls.h      |  4 +++-
> >  2 files changed, 19 insertions(+), 7 deletions(-)
> > 
> > diff --git a/src/gstreamer/gstlibcamera-controls.cpp.in b/src/gstreamer/gstlibcamera-controls.cpp.in
> > index 89c530da..490e835e 100644
> > --- a/src/gstreamer/gstlibcamera-controls.cpp.in
> > +++ b/src/gstreamer/gstlibcamera-controls.cpp.in
> > @@ -153,20 +153,24 @@ g_param_spec_{{ ctrl.gtype }}(
> >  bool GstCameraControls::getProperty(guint propId, GValue *value,
> >                                   [[maybe_unused]] GParamSpec *pspec)
> >  {
> > -     if (!controls_acc_.contains(propId)) {
> > +     const ControlValue *cv;
> > +     if (controls_acc_.contains(propId)) {
> > +             cv = &controls_acc_.get(propId);
> > +     } else if (metadata_.contains(propId)) {
> > +             cv = &metadata_.get(propId);
> > +     } else {
> >               GST_WARNING("Control '%s' is not available, default value will "
> >                           "be returned",
> >                           controls::controls.at(propId)->name().c_str());
> >               return true;
> >       }
> > -     const ControlValue &cv = controls_acc_.get(propId);
> >  
> >       switch (propId) {
> >  {%- for vendor, ctrls in controls %}
> >  {%- for ctrl in ctrls %}
> >  
> >       case controls::{{ ctrl.namespace }}{{ ctrl.name|snake_case|upper }}: {
> > -             auto control = cv.get<{{ ctrl.type }}>();
> > +             auto control = cv->get<{{ ctrl.type }}>();
> >  
> >  {%- if ctrl.is_array %}
> >               for (size_t i = 0; i < control.size(); ++i) {
> > @@ -309,9 +313,14 @@ void GstCameraControls::setCamera(const std::shared_ptr<libcamera::Camera> &cam)
> >                                   "camera and will be ignored",
> >                                   cid->name().c_str());
> >       }
> > -
> >       controls_acc_ = new_controls;
> >       controls_ = new_controls;
> > +
> > +     /*
> > +      * Clear metadata as this might already be populated if the element has
> > +      * been running before.
> > +      */
> > +     metadata_.clear();
> >  }
> >  
> >  void GstCameraControls::applyControls(std::unique_ptr<libcamera::Request> &request)
> > @@ -322,6 +331,7 @@ void GstCameraControls::applyControls(std::unique_ptr<libcamera::Request> &reque
> >  
> >  void GstCameraControls::readMetadata(libcamera::Request *request)
> >  {
> > -     controls_acc_.merge(request->metadata(),
> > -                         ControlList::MergePolicy::OverwriteExisting);
> > +     metadata_.merge(request->metadata(),
> > +                     ControlList::MergePolicy::OverwriteExisting);
> > +
> >  }
> > diff --git a/src/gstreamer/gstlibcamera-controls.h b/src/gstreamer/gstlibcamera-controls.h
> > index 749220b5..ee97e61e 100644
> > --- a/src/gstreamer/gstlibcamera-controls.h
> > +++ b/src/gstreamer/gstlibcamera-controls.h
> > @@ -36,8 +36,10 @@ private:
> >       ControlInfoMap capabilities_;
> >       /* Set of user modified controls. */
> >       ControlList controls_;
> > -     /* Accumulator of all controls ever set and metadata returned by camera */
> > +     /* Accumulator of all controls ever */
> >       ControlList controls_acc_;

Do we need a controls_acc_ at all ? What does this do ?

I'm weary because some of the controls are 'triggers' so they should
only be set once - not on every frame ... ?

> > +     /* Metadata returned by requests */
> > +     ControlList metadata_;
> >  };
> >  
> >  } /* namespace libcamera */
> 
> Looks good to me, but I'd like someone from the core to review this
> please.
> 
> Nicolas
Laurent Pinchart April 22, 2025, 11:34 p.m. UTC | #3
On Tue, Apr 22, 2025 at 04:33:02PM +0100, Kieran Bingham wrote:
> Quoting Nicolas Dufresne (2025-04-22 15:42:41)
> > Le mardi 22 avril 2025 à 16:11 +0200, Jaslo Ziska a écrit :
> > > When the libcamerasrc element is stopped and started again, the
> > > GstCameraControls::setCamera() function is called, but now it is
> > > populated with metadata returned by the previous requests. Because
> > > GstCameraControls::setCamera() filters the controls, this would cause
> > > warnings to be emitted, as the returned metadata are not valid controls
> > > which can be set.
> > 
> > Does it also fixes on top the issue where controls that get applied
> > with delays get resets and forgotten ?
> > 
> > > This is fixed by introducing a new member which only holds the metadata
> > > returned by the requests, so that the controls and the metadata are now
> > > completely independent from each other.
> > > 
> > > Signed-off-by: Jaslo Ziska <jaslo@ziska.de>
> > > ---
> > >  src/gstreamer/gstlibcamera-controls.cpp.in | 22 ++++++++++++++++------
> > >  src/gstreamer/gstlibcamera-controls.h      |  4 +++-
> > >  2 files changed, 19 insertions(+), 7 deletions(-)
> > > 
> > > diff --git a/src/gstreamer/gstlibcamera-controls.cpp.in b/src/gstreamer/gstlibcamera-controls.cpp.in
> > > index 89c530da..490e835e 100644
> > > --- a/src/gstreamer/gstlibcamera-controls.cpp.in
> > > +++ b/src/gstreamer/gstlibcamera-controls.cpp.in
> > > @@ -153,20 +153,24 @@ g_param_spec_{{ ctrl.gtype }}(
> > >  bool GstCameraControls::getProperty(guint propId, GValue *value,
> > >                                   [[maybe_unused]] GParamSpec *pspec)
> > >  {
> > > -     if (!controls_acc_.contains(propId)) {
> > > +     const ControlValue *cv;
> > > +     if (controls_acc_.contains(propId)) {
> > > +             cv = &controls_acc_.get(propId);
> > > +     } else if (metadata_.contains(propId)) {
> > > +             cv = &metadata_.get(propId);

Why do controls take precedence here ?

Taking a step back, does GSreamer expect the value of properties to be
changed dynamically by the device ? If, for instance, auto-exposure is
enabled, does GStreamer expect/allow the exposure time and gain
properties to be modified to reflect the AGC calculation ?

> > > +     } else {
> > >               GST_WARNING("Control '%s' is not available, default value will "
> > >                           "be returned",
> > >                           controls::controls.at(propId)->name().c_str());
> > >               return true;
> > >       }
> > > -     const ControlValue &cv = controls_acc_.get(propId);
> > >  
> > >       switch (propId) {
> > >  {%- for vendor, ctrls in controls %}
> > >  {%- for ctrl in ctrls %}
> > >  
> > >       case controls::{{ ctrl.namespace }}{{ ctrl.name|snake_case|upper }}: {
> > > -             auto control = cv.get<{{ ctrl.type }}>();
> > > +             auto control = cv->get<{{ ctrl.type }}>();
> > >  
> > >  {%- if ctrl.is_array %}
> > >               for (size_t i = 0; i < control.size(); ++i) {
> > > @@ -309,9 +313,14 @@ void GstCameraControls::setCamera(const std::shared_ptr<libcamera::Camera> &cam)
> > >                                   "camera and will be ignored",
> > >                                   cid->name().c_str());
> > >       }
> > > -

Please keep the blank line here.

> > >       controls_acc_ = new_controls;
> > >       controls_ = new_controls;
> > > +
> > > +     /*
> > > +      * Clear metadata as this might already be populated if the element has
> > > +      * been running before.
> > > +      */
> > > +     metadata_.clear();
> > >  }
> > >  
> > >  void GstCameraControls::applyControls(std::unique_ptr<libcamera::Request> &request)
> > > @@ -322,6 +331,7 @@ void GstCameraControls::applyControls(std::unique_ptr<libcamera::Request> &reque
> > >  
> > >  void GstCameraControls::readMetadata(libcamera::Request *request)
> > >  {
> > > -     controls_acc_.merge(request->metadata(),
> > > -                         ControlList::MergePolicy::OverwriteExisting);
> > > +     metadata_.merge(request->metadata(),
> > > +                     ControlList::MergePolicy::OverwriteExisting);
> > > +
> > >  }
> > > diff --git a/src/gstreamer/gstlibcamera-controls.h b/src/gstreamer/gstlibcamera-controls.h
> > > index 749220b5..ee97e61e 100644
> > > --- a/src/gstreamer/gstlibcamera-controls.h
> > > +++ b/src/gstreamer/gstlibcamera-controls.h
> > > @@ -36,8 +36,10 @@ private:
> > >       ControlInfoMap capabilities_;
> > >       /* Set of user modified controls. */
> > >       ControlList controls_;
> > > -     /* Accumulator of all controls ever set and metadata returned by camera */
> > > +     /* Accumulator of all controls ever */

s/ever/ever set/

> > >       ControlList controls_acc_;
> 
> Do we need a controls_acc_ at all ? What does this do ?
> 
> I'm weary because some of the controls are 'triggers' so they should
> only be set once - not on every frame ... ?

There are also dependencies between some controls, and I don't think we
guarantee that setting A=a, B=b in the same request will have the same
effect as setting A=a in a first request and B=b in a second request.
Accumulating the controls may not match the actual state of the device.

For instance, look at the documentation of ColourGains and
ColourTemperature. They respectively state

        If ColourGains is set in a request but ColourTemperature is not, the
        implementation shall calculate and set the ColourTemperature based on
        the ColourGains.

and

        If ColourTemperature is set in a request but ColourGains is not, the
        implementation shall calculate and set the ColourGains based on the
        given ColourTemperature.

Setting ColourGains=cg in a first request and and ColourTemperature=ct
in a second request means that ColourGains will end up being calculated
as CG(ct). Setting them both in the same request means they will both
have the values specified by the user. By accumulating controls, we lose
this information.

> > > +     /* Metadata returned by requests */
> > > +     ControlList metadata_;
> > >  };
> > >  
> > >  } /* namespace libcamera */
> > 
> > Looks good to me, but I'd like someone from the core to review this
> > please.
Jaslo Ziska April 23, 2025, 10 a.m. UTC | #4
Hi Nicolas, Kieran, Laurent,

Laurent Pinchart <laurent.pinchart@ideasonboard.com> writes:
> On Tue, Apr 22, 2025 at 04:33:02PM +0100, Kieran Bingham wrote:
>> Quoting Nicolas Dufresne (2025-04-22 15:42:41)
>> > Le mardi 22 avril 2025 à 16:11 +0200, Jaslo Ziska a écrit :
>> > > When the libcamerasrc element is stopped and started again, 
>> > > the
>> > > GstCameraControls::setCamera() function is called, but now 
>> > > it is
>> > > populated with metadata returned by the previous requests. 
>> > > Because
>> > > GstCameraControls::setCamera() filters the controls, this 
>> > > would cause
>> > > warnings to be emitted, as the returned metadata are not 
>> > > valid controls
>> > > which can be set.
>> >
>> > Does it also fixes on top the issue where controls that get 
>> > applied
>> > with delays get resets and forgotten ?

Sorry, I don't know which issue you are referring to.

>> >
>> > > This is fixed by introducing a new member which only holds 
>> > > the metadata
>> > > returned by the requests, so that the controls and the 
>> > > metadata are now
>> > > completely independent from each other.
>> > >
>> > > Signed-off-by: Jaslo Ziska <jaslo@ziska.de>
>> > > ---
>> > >  src/gstreamer/gstlibcamera-controls.cpp.in | 22 
>> > > ++++++++++++++++------
>> > >  src/gstreamer/gstlibcamera-controls.h      |  4 +++-
>> > >  2 files changed, 19 insertions(+), 7 deletions(-)
>> > >
>> > > diff --git a/src/gstreamer/gstlibcamera-controls.cpp.in 
>> > > b/src/gstreamer/gstlibcamera-controls.cpp.in
>> > > index 89c530da..490e835e 100644
>> > > --- a/src/gstreamer/gstlibcamera-controls.cpp.in
>> > > +++ b/src/gstreamer/gstlibcamera-controls.cpp.in
>> > > @@ -153,20 +153,24 @@ g_param_spec_{{ ctrl.gtype }}(
>> > >  bool GstCameraControls::getProperty(guint propId, GValue 
>> > > *value,
>> > >                                   [[maybe_unused]] 
>> > > GParamSpec *pspec)
>> > >  {
>> > > -     if (!controls_acc_.contains(propId)) {
>> > > +     const ControlValue *cv;
>> > > +     if (controls_acc_.contains(propId)) {
>> > > +             cv = &controls_acc_.get(propId);
>> > > +     } else if (metadata_.contains(propId)) {
>> > > +             cv = &metadata_.get(propId);
>
> Why do controls take precedence here ?

I thought it would make sense, but I was wrong, see below.

>
> Taking a step back, does GSreamer expect the value of properties 
> to be
> changed dynamically by the device ? If, for instance, 
> auto-exposure is
> enabled, does GStreamer expect/allow the exposure time and gain
> properties to be modified to reflect the AGC calculation ?

In theory these modified values should be available as properties 
if they are in the metadata returned by the requests. But from the 
rest of your comments I see that there is a mistake in my logic, 
see below.

>
>> > > +     } else {
>> > >               GST_WARNING("Control '%s' is not available, 
>> > > default value will "
>> > >                           "be returned",
>> > >                           
>> > > controls::controls.at(propId)->name().c_str());
>> > >               return true;
>> > >       }
>> > > -     const ControlValue &cv = controls_acc_.get(propId);
>> > >
>> > >       switch (propId) {
>> > >  {%- for vendor, ctrls in controls %}
>> > >  {%- for ctrl in ctrls %}
>> > >
>> > >       case controls::{{ ctrl.namespace }}{{ 
>> > > ctrl.name|snake_case|upper }}: {
>> > > -             auto control = cv.get<{{ ctrl.type }}>();
>> > > +             auto control = cv->get<{{ ctrl.type }}>();
>> > >
>> > >  {%- if ctrl.is_array %}
>> > >               for (size_t i = 0; i < control.size(); ++i) {
>> > > @@ -309,9 +313,14 @@ void 
>> > > GstCameraControls::setCamera(const 
>> > > std::shared_ptr<libcamera::Camera> &cam)
>> > >                                   "camera and will be 
>> > > ignored",
>> > >                                   cid->name().c_str());
>> > >       }
>> > > -
>
> Please keep the blank line here.
>
>> > >       controls_acc_ = new_controls;
>> > >       controls_ = new_controls;
>> > > +
>> > > +     /*
>> > > +      * Clear metadata as this might already be populated 
>> > > if the element has
>> > > +      * been running before.
>> > > +      */
>> > > +     metadata_.clear();
>> > >  }
>> > >
>> > >  void 
>> > > GstCameraControls::applyControls(std::unique_ptr<libcamera::Request> 
>> > > &request)
>> > > @@ -322,6 +331,7 @@ void 
>> > > GstCameraControls::applyControls(std::unique_ptr<libcamera::Request> 
>> > > &reque
>> > >
>> > >  void GstCameraControls::readMetadata(libcamera::Request 
>> > > *request)
>> > >  {
>> > > -     controls_acc_.merge(request->metadata(),
>> > > -                         
>> > > ControlList::MergePolicy::OverwriteExisting);
>> > > +     metadata_.merge(request->metadata(),
>> > > + 
>> > > ControlList::MergePolicy::OverwriteExisting);
>> > > +
>> > >  }
>> > > diff --git a/src/gstreamer/gstlibcamera-controls.h 
>> > > b/src/gstreamer/gstlibcamera-controls.h
>> > > index 749220b5..ee97e61e 100644
>> > > --- a/src/gstreamer/gstlibcamera-controls.h
>> > > +++ b/src/gstreamer/gstlibcamera-controls.h
>> > > @@ -36,8 +36,10 @@ private:
>> > >       ControlInfoMap capabilities_;
>> > >       /* Set of user modified controls. */
>> > >       ControlList controls_;
>> > > -     /* Accumulator of all controls ever set and metadata 
>> > > returned by camera */
>> > > +     /* Accumulator of all controls ever */
>
> s/ever/ever set/
>
>> > >       ControlList controls_acc_;
>>
>> Do we need a controls_acc_ at all ? What does this do ?
>>
>> I'm weary because some of the controls are 'triggers' so they 
>> should
>> only be set once - not on every frame ... ?

Now I'm confused, I thought all controls should only be set once 
when they changed? controls_acc_ was introduced for this. The 
controls_ member holds the controls which will be set on the next 
request and then gets cleared (so that they only get set once). 
But the control_acc_ variable accumulates the controls so that 
they can be returned to GSreamer in getProperty().

>
> There are also dependencies between some controls, and I don't 
> think we
> guarantee that setting A=a, B=b in the same request will have 
> the same
> effect as setting A=a in a first request and B=b in a second 
> request.
> Accumulating the controls may not match the actual state of the 
> device.
>
> For instance, look at the documentation of ColourGains and
> ColourTemperature. They respectively state
>
>         If ColourGains is set in a request but ColourTemperature 
>         is not, the
>         implementation shall calculate and set the 
>         ColourTemperature based on
>         the ColourGains.
>
> and
>
>         If ColourTemperature is set in a request but ColourGains 
>         is not, the
>         implementation shall calculate and set the ColourGains 
>         based on the
>         given ColourTemperature.
>
> Setting ColourGains=cg in a first request and and 
> ColourTemperature=ct
> in a second request means that ColourGains will end up being 
> calculated
> as CG(ct). Setting them both in the same request means they will 
> both
> have the values specified by the user. By accumulating controls, 
> we lose
> this information.

Thanks for the explanation. Then it seems like it was correct the 
way it was before? Because when ColourGains is set in the first 
request controls_acc_ holds that value and if at a later point 
ColourTemperature is set, ColourGains gets overwritten by the 
value returned by the metadata of the request. Or am I mistaken?

Best Regards
Jaslo

>
>> > > +     /* Metadata returned by requests */
>> > > +     ControlList metadata_;
>> > >  };
>> > >
>> > >  } /* namespace libcamera */
>> >
>> > Looks good to me, but I'd like someone from the core to 
>> > review this
>> > please.
Nicolas Dufresne April 23, 2025, 12:56 p.m. UTC | #5
Le mercredi 23 avril 2025 à 02:34 +0300, Laurent Pinchart a écrit :
> On Tue, Apr 22, 2025 at 04:33:02PM +0100, Kieran Bingham wrote:
> > Quoting Nicolas Dufresne (2025-04-22 15:42:41)
> > > Le mardi 22 avril 2025 à 16:11 +0200, Jaslo Ziska a écrit :
> > > > When the libcamerasrc element is stopped and started again, the
> > > > GstCameraControls::setCamera() function is called, but now it is
> > > > populated with metadata returned by the previous requests. Because
> > > > GstCameraControls::setCamera() filters the controls, this would cause
> > > > warnings to be emitted, as the returned metadata are not valid controls
> > > > which can be set.
> > > 
> > > Does it also fixes on top the issue where controls that get applied
> > > with delays get resets and forgotten ?
> > > 
> > > > This is fixed by introducing a new member which only holds the metadata
> > > > returned by the requests, so that the controls and the metadata are now
> > > > completely independent from each other.
> > > > 
> > > > Signed-off-by: Jaslo Ziska <jaslo@ziska.de>
> > > > ---
> > > >  src/gstreamer/gstlibcamera-controls.cpp.in | 22 ++++++++++++++++------
> > > >  src/gstreamer/gstlibcamera-controls.h      |  4 +++-
> > > >  2 files changed, 19 insertions(+), 7 deletions(-)
> > > > 
> > > > diff --git a/src/gstreamer/gstlibcamera-controls.cpp.in b/src/gstreamer/gstlibcamera-controls.cpp.in
> > > > index 89c530da..490e835e 100644
> > > > --- a/src/gstreamer/gstlibcamera-controls.cpp.in
> > > > +++ b/src/gstreamer/gstlibcamera-controls.cpp.in
> > > > @@ -153,20 +153,24 @@ g_param_spec_{{ ctrl.gtype }}(
> > > >  bool GstCameraControls::getProperty(guint propId, GValue *value,
> > > >                                   [[maybe_unused]] GParamSpec *pspec)
> > > >  {
> > > > -     if (!controls_acc_.contains(propId)) {
> > > > +     const ControlValue *cv;
> > > > +     if (controls_acc_.contains(propId)) {
> > > > +             cv = &controls_acc_.get(propId);
> > > > +     } else if (metadata_.contains(propId)) {
> > > > +             cv = &metadata_.get(propId);
> 
> Why do controls take precedence here ?
> 
> Taking a step back, does GSreamer expect the value of properties to be
> changed dynamically by the device ? If, for instance, auto-exposure is
> enabled, does GStreamer expect/allow the exposure time and gain
> properties to be modified to reflect the AGC calculation ?

Properties can be used for that yes. When they change, I'd expect a
call to g_object_notify() to notify users of the change.

I think what we are missing is the clear split between the properties
are are set by the application, vs the one that are set by libcamera.
This is why we excluded the second bread in the initial control
support.

Nicolas

> 
> > > > +     } else {
> > > >               GST_WARNING("Control '%s' is not available, default value will "
> > > >                           "be returned",
> > > >                           controls::controls.at(propId)->name().c_str());
> > > >               return true;
> > > >       }
> > > > -     const ControlValue &cv = controls_acc_.get(propId);
> > > >  
> > > >       switch (propId) {
> > > >  {%- for vendor, ctrls in controls %}
> > > >  {%- for ctrl in ctrls %}
> > > >  
> > > >       case controls::{{ ctrl.namespace }}{{ ctrl.name|snake_case|upper }}: {
> > > > -             auto control = cv.get<{{ ctrl.type }}>();
> > > > +             auto control = cv->get<{{ ctrl.type }}>();
> > > >  
> > > >  {%- if ctrl.is_array %}
> > > >               for (size_t i = 0; i < control.size(); ++i) {
> > > > @@ -309,9 +313,14 @@ void GstCameraControls::setCamera(const std::shared_ptr<libcamera::Camera> &cam)
> > > >                                   "camera and will be ignored",
> > > >                                   cid->name().c_str());
> > > >       }
> > > > -
> 
> Please keep the blank line here.
> 
> > > >       controls_acc_ = new_controls;
> > > >       controls_ = new_controls;
> > > > +
> > > > +     /*
> > > > +      * Clear metadata as this might already be populated if the element has
> > > > +      * been running before.
> > > > +      */
> > > > +     metadata_.clear();
> > > >  }
> > > >  
> > > >  void GstCameraControls::applyControls(std::unique_ptr<libcamera::Request> &request)
> > > > @@ -322,6 +331,7 @@ void GstCameraControls::applyControls(std::unique_ptr<libcamera::Request> &reque
> > > >  
> > > >  void GstCameraControls::readMetadata(libcamera::Request *request)
> > > >  {
> > > > -     controls_acc_.merge(request->metadata(),
> > > > -                         ControlList::MergePolicy::OverwriteExisting);
> > > > +     metadata_.merge(request->metadata(),
> > > > +                     ControlList::MergePolicy::OverwriteExisting);
> > > > +
> > > >  }
> > > > diff --git a/src/gstreamer/gstlibcamera-controls.h b/src/gstreamer/gstlibcamera-controls.h
> > > > index 749220b5..ee97e61e 100644
> > > > --- a/src/gstreamer/gstlibcamera-controls.h
> > > > +++ b/src/gstreamer/gstlibcamera-controls.h
> > > > @@ -36,8 +36,10 @@ private:
> > > >       ControlInfoMap capabilities_;
> > > >       /* Set of user modified controls. */
> > > >       ControlList controls_;
> > > > -     /* Accumulator of all controls ever set and metadata returned by camera */
> > > > +     /* Accumulator of all controls ever */
> 
> s/ever/ever set/
> 
> > > >       ControlList controls_acc_;
> > 
> > Do we need a controls_acc_ at all ? What does this do ?
> > 
> > I'm weary because some of the controls are 'triggers' so they should
> > only be set once - not on every frame ... ?
> 
> There are also dependencies between some controls, and I don't think we
> guarantee that setting A=a, B=b in the same request will have the same
> effect as setting A=a in a first request and B=b in a second request.
> Accumulating the controls may not match the actual state of the device.
> 
> For instance, look at the documentation of ColourGains and
> ColourTemperature. They respectively state
> 
>         If ColourGains is set in a request but ColourTemperature is not, the
>         implementation shall calculate and set the ColourTemperature based on
>         the ColourGains.
> 
> and
> 
>         If ColourTemperature is set in a request but ColourGains is not, the
>         implementation shall calculate and set the ColourGains based on the
>         given ColourTemperature.
> 
> Setting ColourGains=cg in a first request and and ColourTemperature=ct
> in a second request means that ColourGains will end up being calculated
> as CG(ct). Setting them both in the same request means they will both
> have the values specified by the user. By accumulating controls, we lose
> this information.
> 
> > > > +     /* Metadata returned by requests */
> > > > +     ControlList metadata_;
> > > >  };
> > > >  
> > > >  } /* namespace libcamera */
> > > 
> > > Looks good to me, but I'd like someone from the core to review this
> > > please.

Patch
diff mbox

diff --git a/src/gstreamer/gstlibcamera-controls.cpp.in b/src/gstreamer/gstlibcamera-controls.cpp.in
index 89c530da..490e835e 100644
--- a/src/gstreamer/gstlibcamera-controls.cpp.in
+++ b/src/gstreamer/gstlibcamera-controls.cpp.in
@@ -153,20 +153,24 @@  g_param_spec_{{ ctrl.gtype }}(
 bool GstCameraControls::getProperty(guint propId, GValue *value,
 				    [[maybe_unused]] GParamSpec *pspec)
 {
-	if (!controls_acc_.contains(propId)) {
+	const ControlValue *cv;
+	if (controls_acc_.contains(propId)) {
+		cv = &controls_acc_.get(propId);
+	} else if (metadata_.contains(propId)) {
+		cv = &metadata_.get(propId);
+	} else {
 		GST_WARNING("Control '%s' is not available, default value will "
 			    "be returned",
 			    controls::controls.at(propId)->name().c_str());
 		return true;
 	}
-	const ControlValue &cv = controls_acc_.get(propId);
 
 	switch (propId) {
 {%- for vendor, ctrls in controls %}
 {%- for ctrl in ctrls %}
 
 	case controls::{{ ctrl.namespace }}{{ ctrl.name|snake_case|upper }}: {
-		auto control = cv.get<{{ ctrl.type }}>();
+		auto control = cv->get<{{ ctrl.type }}>();
 
 {%- if ctrl.is_array %}
 		for (size_t i = 0; i < control.size(); ++i) {
@@ -309,9 +313,14 @@  void GstCameraControls::setCamera(const std::shared_ptr<libcamera::Camera> &cam)
 				    "camera and will be ignored",
 				    cid->name().c_str());
 	}
-
 	controls_acc_ = new_controls;
 	controls_ = new_controls;
+
+	/*
+	 * Clear metadata as this might already be populated if the element has
+	 * been running before.
+	 */
+	metadata_.clear();
 }
 
 void GstCameraControls::applyControls(std::unique_ptr<libcamera::Request> &request)
@@ -322,6 +331,7 @@  void GstCameraControls::applyControls(std::unique_ptr<libcamera::Request> &reque
 
 void GstCameraControls::readMetadata(libcamera::Request *request)
 {
-	controls_acc_.merge(request->metadata(),
-			    ControlList::MergePolicy::OverwriteExisting);
+	metadata_.merge(request->metadata(),
+			ControlList::MergePolicy::OverwriteExisting);
+
 }
diff --git a/src/gstreamer/gstlibcamera-controls.h b/src/gstreamer/gstlibcamera-controls.h
index 749220b5..ee97e61e 100644
--- a/src/gstreamer/gstlibcamera-controls.h
+++ b/src/gstreamer/gstlibcamera-controls.h
@@ -36,8 +36,10 @@  private:
 	ControlInfoMap capabilities_;
 	/* Set of user modified controls. */
 	ControlList controls_;
-	/* Accumulator of all controls ever set and metadata returned by camera */
+	/* Accumulator of all controls ever */
 	ControlList controls_acc_;
+	/* Metadata returned by requests */
+	ControlList metadata_;
 };
 
 } /* namespace libcamera */