[libcamera-devel,v2,3/3] gstreamer: Implement renegotiation
diff mbox series

Message ID 20231130155323.13259-4-jaslo@ziska.de
State Accepted
Headers show
Series
  • gstreamer: Implement renegotiation
Related show

Commit Message

Jaslo Ziska Nov. 30, 2023, 3:43 p.m. UTC
This commit implements renegotiation of the camera configuration and
source pad caps. A renegotiation can happen when a downstream element
decides to change caps or the pipeline is dynamically changed.

To handle a renegotiation the GST_FLOW_NOT_NEGOTIATED return value has
to be handled in GstLibcameraSrcState::processRequest. Otherwise the
default would be to print an error and stop streaming.
To archive this in a clean way the if statement is altered into a switch
statement which now also has a case for GST_FLOW_NOT_NEGOTIATED.
In the case of GST_FLOW_NOT_NEGOTIATED every source pad is checked for
the reconfiguration flag with gst_pad_needs_reconfigure which does not
clear this flag. If at least one pad requested a reconfiguration the
function returns without an error and the renegotiation will happen
later in the running task. If no pad requested a reconfiguration then
the function will return with an error.

In gst_libcamera_src_task_run the source pads are checked for the
reconfigure flag by calling gst_pad_check_reconfigure and if one pad
returns true and the caps are not sufficient anymore then the
negotiation is triggered. It is fine to trigger the negotiation after
only a single pad returns true for gst_pad_check_reconfigure because the
reconfigure flags are cleared in the gst_libcamera_src_negotiate
function.
If any pad requested a reconfiguration the following will happen:
1. The camera is stopped because changing the configuration may not
   happen while running.
2. The completedRequests queue will be cleared by calling
   GstLibcameraSrcState::clearRequests because the completed buffers
   have the wrong configuration.
3. The new caps are negotiated by calling gst_libcamera_src_negotiate.
   When the negotiation fails streaming will stop.
4. The camera is started again.

Signed-off-by: Jaslo Ziska <jaslo@ziska.de>
---
 src/gstreamer/gstlibcamerasrc.cpp | 72 ++++++++++++++++++++++++++-----
 1 file changed, 61 insertions(+), 11 deletions(-)

Comments

Nicolas Dufresne Dec. 5, 2023, 4:14 p.m. UTC | #1
Le jeudi 30 novembre 2023 à 16:43 +0100, Jaslo Ziska via libcamera-devel a
écrit :
> This commit implements renegotiation of the camera configuration and
> source pad caps. A renegotiation can happen when a downstream element
> decides to change caps or the pipeline is dynamically changed.
> 
> To handle a renegotiation the GST_FLOW_NOT_NEGOTIATED return value has
> to be handled in GstLibcameraSrcState::processRequest. Otherwise the
> default would be to print an error and stop streaming.
> To archive this in a clean way the if statement is altered into a switch
> statement which now also has a case for GST_FLOW_NOT_NEGOTIATED.
> In the case of GST_FLOW_NOT_NEGOTIATED every source pad is checked for
> the reconfiguration flag with gst_pad_needs_reconfigure which does not
> clear this flag. If at least one pad requested a reconfiguration the
> function returns without an error and the renegotiation will happen
> later in the running task. If no pad requested a reconfiguration then
> the function will return with an error.
> 
> In gst_libcamera_src_task_run the source pads are checked for the
> reconfigure flag by calling gst_pad_check_reconfigure and if one pad
> returns true and the caps are not sufficient anymore then the
> negotiation is triggered. It is fine to trigger the negotiation after
> only a single pad returns true for gst_pad_check_reconfigure because the
> reconfigure flags are cleared in the gst_libcamera_src_negotiate
> function.
> If any pad requested a reconfiguration the following will happen:
> 1. The camera is stopped because changing the configuration may not
>    happen while running.
> 2. The completedRequests queue will be cleared by calling
>    GstLibcameraSrcState::clearRequests because the completed buffers
>    have the wrong configuration.
> 3. The new caps are negotiated by calling gst_libcamera_src_negotiate.
>    When the negotiation fails streaming will stop.
> 4. The camera is started again.
> 
> Signed-off-by: Jaslo Ziska <jaslo@ziska.de>

Reviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>

> ---
>  src/gstreamer/gstlibcamerasrc.cpp | 72 ++++++++++++++++++++++++++-----
>  1 file changed, 61 insertions(+), 11 deletions(-)
> 
> diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
> index d448a750..8bd32306 100644
> --- a/src/gstreamer/gstlibcamerasrc.cpp
> +++ b/src/gstreamer/gstlibcamerasrc.cpp
> @@ -11,7 +11,6 @@
>   *  - Implement GstElement::send_event
>   *    + Allowing application to use FLUSH/FLUSH_STOP
>   *    + Prevent the main thread from accessing streaming thread
> - *  - Implement renegotiation (even if slow)
>   *  - Implement GstElement::request-new-pad (multi stream)
>   *    + Evaluate if a single streaming thread is fine
>   *  - Add application driven request (snapshot)
> @@ -302,18 +301,42 @@ int GstLibcameraSrcState::processRequest()
>  							srcpad, ret);
>  	}
>  
> -	if (ret != GST_FLOW_OK) {
> -		if (ret == GST_FLOW_EOS) {
> -			g_autoptr(GstEvent) eos = gst_event_new_eos();
> -			guint32 seqnum = gst_util_seqnum_next();
> -			gst_event_set_seqnum(eos, seqnum);
> -			for (GstPad *srcpad : srcpads_)
> -				gst_pad_push_event(srcpad, gst_event_ref(eos));
> -		} else if (ret != GST_FLOW_FLUSHING) {
> -			GST_ELEMENT_FLOW_ERROR(src_, ret);
> +	switch (ret) {
> +	case GST_FLOW_OK:
> +		break;
> +	case GST_FLOW_NOT_NEGOTIATED: {
> +		bool reconfigure = false;
> +		for (GstPad *srcpad : srcpads_) {
> +			if (gst_pad_needs_reconfigure(srcpad)) {
> +				reconfigure = true;
> +				break;
> +			}
>  		}
>  
> -		return -EPIPE;
> +		// if no pads needs a reconfiguration something went wrong
> +		if (!reconfigure)
> +			err = -EPIPE;
> +
> +		break;
> +	}
> +	case GST_FLOW_EOS: {
> +		g_autoptr(GstEvent) eos = gst_event_new_eos();
> +		guint32 seqnum = gst_util_seqnum_next();
> +		gst_event_set_seqnum(eos, seqnum);
> +		for (GstPad *srcpad : srcpads_)
> +			gst_pad_push_event(srcpad, gst_event_ref(eos));
> +
> +		err = -EPIPE;
> +		break;
> +	}
> +	case GST_FLOW_FLUSHING:
> +		err = -EPIPE;
> +		break;
> +	default:
> +		GST_ELEMENT_FLOW_ERROR(src_, ret);
> +
> +		err = -EPIPE;
> +		break;
>  	}
>  
>  	return err;
> @@ -463,6 +486,8 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self)
>  					 G_CALLBACK(gst_task_resume), self->task);
>  
>  		gst_libcamera_pad_set_pool(srcpad, pool);
> +
> +		gst_pad_check_reconfigure(srcpad); // clear all reconfigure flags
>  	}
>  
>  	return true;
> @@ -496,6 +521,31 @@ gst_libcamera_src_task_run(gpointer user_data)
>  		return;
>  	}
>  
> +	// check if a srcpad requested a renegotiation
> +	bool reconfigure = false;
> +	for (GstPad *srcpad : state->srcpads_) {
> +		if (gst_pad_check_reconfigure(srcpad)) {
> +			// check whether the caps even need changing
> +			g_autoptr(GstCaps) caps = gst_pad_get_current_caps(srcpad);
> +			if (!gst_pad_peer_query_accept_caps(srcpad, caps)) {
> +				reconfigure = true;
> +				break;
> +			}
> +		}
> +	}
> +
> +	if (reconfigure) {
> +		state->cam_->stop();
> +		state->clearRequests();
> +
> +		if (!gst_libcamera_src_negotiate(self)) {
> +			GST_ELEMENT_FLOW_ERROR(self, GST_FLOW_NOT_NEGOTIATED);
> +			gst_task_stop(self->task);
> +		}
> +
> +		state->cam_->start(&state->initControls_);
> +	}
> +
>  	/*
>  	 * Create and queue one request. If no buffers are available the
>  	 * function returns -ENOBUFS, which we ignore here as that's not a
Laurent Pinchart Dec. 6, 2023, 11:39 p.m. UTC | #2
Hi Jaslo,

Thank you for the patch.

On Thu, Nov 30, 2023 at 04:43:10PM +0100, Jaslo Ziska via libcamera-devel wrote:
> This commit implements renegotiation of the camera configuration and
> source pad caps. A renegotiation can happen when a downstream element
> decides to change caps or the pipeline is dynamically changed.
> 
> To handle a renegotiation the GST_FLOW_NOT_NEGOTIATED return value has
> to be handled in GstLibcameraSrcState::processRequest. Otherwise the
> default would be to print an error and stop streaming.
> To archive this in a clean way the if statement is altered into a switch
> statement which now also has a case for GST_FLOW_NOT_NEGOTIATED.
> In the case of GST_FLOW_NOT_NEGOTIATED every source pad is checked for
> the reconfiguration flag with gst_pad_needs_reconfigure which does not
> clear this flag. If at least one pad requested a reconfiguration the
> function returns without an error and the renegotiation will happen
> later in the running task. If no pad requested a reconfiguration then
> the function will return with an error.
> 
> In gst_libcamera_src_task_run the source pads are checked for the
> reconfigure flag by calling gst_pad_check_reconfigure and if one pad
> returns true and the caps are not sufficient anymore then the
> negotiation is triggered. It is fine to trigger the negotiation after
> only a single pad returns true for gst_pad_check_reconfigure because the
> reconfigure flags are cleared in the gst_libcamera_src_negotiate
> function.
> If any pad requested a reconfiguration the following will happen:
> 1. The camera is stopped because changing the configuration may not
>    happen while running.
> 2. The completedRequests queue will be cleared by calling
>    GstLibcameraSrcState::clearRequests because the completed buffers
>    have the wrong configuration.
> 3. The new caps are negotiated by calling gst_libcamera_src_negotiate.
>    When the negotiation fails streaming will stop.
> 4. The camera is started again.
> 
> Signed-off-by: Jaslo Ziska <jaslo@ziska.de>
> ---
>  src/gstreamer/gstlibcamerasrc.cpp | 72 ++++++++++++++++++++++++++-----
>  1 file changed, 61 insertions(+), 11 deletions(-)
> 
> diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
> index d448a750..8bd32306 100644
> --- a/src/gstreamer/gstlibcamerasrc.cpp
> +++ b/src/gstreamer/gstlibcamerasrc.cpp
> @@ -11,7 +11,6 @@
>   *  - Implement GstElement::send_event
>   *    + Allowing application to use FLUSH/FLUSH_STOP
>   *    + Prevent the main thread from accessing streaming thread
> - *  - Implement renegotiation (even if slow)
>   *  - Implement GstElement::request-new-pad (multi stream)
>   *    + Evaluate if a single streaming thread is fine
>   *  - Add application driven request (snapshot)
> @@ -302,18 +301,42 @@ int GstLibcameraSrcState::processRequest()
>  							srcpad, ret);
>  	}
>  
> -	if (ret != GST_FLOW_OK) {
> -		if (ret == GST_FLOW_EOS) {
> -			g_autoptr(GstEvent) eos = gst_event_new_eos();
> -			guint32 seqnum = gst_util_seqnum_next();
> -			gst_event_set_seqnum(eos, seqnum);
> -			for (GstPad *srcpad : srcpads_)
> -				gst_pad_push_event(srcpad, gst_event_ref(eos));
> -		} else if (ret != GST_FLOW_FLUSHING) {
> -			GST_ELEMENT_FLOW_ERROR(src_, ret);
> +	switch (ret) {
> +	case GST_FLOW_OK:
> +		break;

We usually add blank lines between cases.

> +	case GST_FLOW_NOT_NEGOTIATED: {
> +		bool reconfigure = false;
> +		for (GstPad *srcpad : srcpads_) {
> +			if (gst_pad_needs_reconfigure(srcpad)) {
> +				reconfigure = true;
> +				break;
> +			}
>  		}
>  
> -		return -EPIPE;
> +		// if no pads needs a reconfiguration something went wrong

We use C-style comments:

		/* If no pads need a reconfiguration something went wrong. */

Same comment below.

I'll fix these minor issues when applying the series, no need to submit
a new version.

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> +		if (!reconfigure)
> +			err = -EPIPE;
> +
> +		break;
> +	}
> +	case GST_FLOW_EOS: {
> +		g_autoptr(GstEvent) eos = gst_event_new_eos();
> +		guint32 seqnum = gst_util_seqnum_next();
> +		gst_event_set_seqnum(eos, seqnum);
> +		for (GstPad *srcpad : srcpads_)
> +			gst_pad_push_event(srcpad, gst_event_ref(eos));
> +
> +		err = -EPIPE;
> +		break;
> +	}
> +	case GST_FLOW_FLUSHING:
> +		err = -EPIPE;
> +		break;
> +	default:
> +		GST_ELEMENT_FLOW_ERROR(src_, ret);
> +
> +		err = -EPIPE;
> +		break;
>  	}
>  
>  	return err;
> @@ -463,6 +486,8 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self)
>  					 G_CALLBACK(gst_task_resume), self->task);
>  
>  		gst_libcamera_pad_set_pool(srcpad, pool);
> +
> +		gst_pad_check_reconfigure(srcpad); // clear all reconfigure flags
>  	}
>  
>  	return true;
> @@ -496,6 +521,31 @@ gst_libcamera_src_task_run(gpointer user_data)
>  		return;
>  	}
>  
> +	// check if a srcpad requested a renegotiation
> +	bool reconfigure = false;
> +	for (GstPad *srcpad : state->srcpads_) {
> +		if (gst_pad_check_reconfigure(srcpad)) {
> +			// check whether the caps even need changing
> +			g_autoptr(GstCaps) caps = gst_pad_get_current_caps(srcpad);
> +			if (!gst_pad_peer_query_accept_caps(srcpad, caps)) {
> +				reconfigure = true;
> +				break;
> +			}
> +		}
> +	}
> +
> +	if (reconfigure) {
> +		state->cam_->stop();
> +		state->clearRequests();
> +
> +		if (!gst_libcamera_src_negotiate(self)) {
> +			GST_ELEMENT_FLOW_ERROR(self, GST_FLOW_NOT_NEGOTIATED);
> +			gst_task_stop(self->task);
> +		}
> +
> +		state->cam_->start(&state->initControls_);
> +	}
> +
>  	/*
>  	 * Create and queue one request. If no buffers are available the
>  	 * function returns -ENOBUFS, which we ignore here as that's not a

Patch
diff mbox series

diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
index d448a750..8bd32306 100644
--- a/src/gstreamer/gstlibcamerasrc.cpp
+++ b/src/gstreamer/gstlibcamerasrc.cpp
@@ -11,7 +11,6 @@ 
  *  - Implement GstElement::send_event
  *    + Allowing application to use FLUSH/FLUSH_STOP
  *    + Prevent the main thread from accessing streaming thread
- *  - Implement renegotiation (even if slow)
  *  - Implement GstElement::request-new-pad (multi stream)
  *    + Evaluate if a single streaming thread is fine
  *  - Add application driven request (snapshot)
@@ -302,18 +301,42 @@  int GstLibcameraSrcState::processRequest()
 							srcpad, ret);
 	}
 
-	if (ret != GST_FLOW_OK) {
-		if (ret == GST_FLOW_EOS) {
-			g_autoptr(GstEvent) eos = gst_event_new_eos();
-			guint32 seqnum = gst_util_seqnum_next();
-			gst_event_set_seqnum(eos, seqnum);
-			for (GstPad *srcpad : srcpads_)
-				gst_pad_push_event(srcpad, gst_event_ref(eos));
-		} else if (ret != GST_FLOW_FLUSHING) {
-			GST_ELEMENT_FLOW_ERROR(src_, ret);
+	switch (ret) {
+	case GST_FLOW_OK:
+		break;
+	case GST_FLOW_NOT_NEGOTIATED: {
+		bool reconfigure = false;
+		for (GstPad *srcpad : srcpads_) {
+			if (gst_pad_needs_reconfigure(srcpad)) {
+				reconfigure = true;
+				break;
+			}
 		}
 
-		return -EPIPE;
+		// if no pads needs a reconfiguration something went wrong
+		if (!reconfigure)
+			err = -EPIPE;
+
+		break;
+	}
+	case GST_FLOW_EOS: {
+		g_autoptr(GstEvent) eos = gst_event_new_eos();
+		guint32 seqnum = gst_util_seqnum_next();
+		gst_event_set_seqnum(eos, seqnum);
+		for (GstPad *srcpad : srcpads_)
+			gst_pad_push_event(srcpad, gst_event_ref(eos));
+
+		err = -EPIPE;
+		break;
+	}
+	case GST_FLOW_FLUSHING:
+		err = -EPIPE;
+		break;
+	default:
+		GST_ELEMENT_FLOW_ERROR(src_, ret);
+
+		err = -EPIPE;
+		break;
 	}
 
 	return err;
@@ -463,6 +486,8 @@  gst_libcamera_src_negotiate(GstLibcameraSrc *self)
 					 G_CALLBACK(gst_task_resume), self->task);
 
 		gst_libcamera_pad_set_pool(srcpad, pool);
+
+		gst_pad_check_reconfigure(srcpad); // clear all reconfigure flags
 	}
 
 	return true;
@@ -496,6 +521,31 @@  gst_libcamera_src_task_run(gpointer user_data)
 		return;
 	}
 
+	// check if a srcpad requested a renegotiation
+	bool reconfigure = false;
+	for (GstPad *srcpad : state->srcpads_) {
+		if (gst_pad_check_reconfigure(srcpad)) {
+			// check whether the caps even need changing
+			g_autoptr(GstCaps) caps = gst_pad_get_current_caps(srcpad);
+			if (!gst_pad_peer_query_accept_caps(srcpad, caps)) {
+				reconfigure = true;
+				break;
+			}
+		}
+	}
+
+	if (reconfigure) {
+		state->cam_->stop();
+		state->clearRequests();
+
+		if (!gst_libcamera_src_negotiate(self)) {
+			GST_ELEMENT_FLOW_ERROR(self, GST_FLOW_NOT_NEGOTIATED);
+			gst_task_stop(self->task);
+		}
+
+		state->cam_->start(&state->initControls_);
+	}
+
 	/*
 	 * Create and queue one request. If no buffers are available the
 	 * function returns -ENOBUFS, which we ignore here as that's not a