[libcamera-devel] [PATCH 2/2] gstreamer: Implement renegotiation

Jaslo Ziska jaslo at ziska.de
Wed Nov 22 14:43:55 CET 2023


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 the 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. Just
like the case for GST_FLOW_OK, the function will return without an error
because the renegotiation will happen later in the running task.

In gst_libcamera_src_task_run all the source pads are checked for the
reconfigure flag by calling gst_pad_check_reconfigure. It is important
to iterate all source pads and not break after one pad requested a
reconfiguration because gst_pad_check_reconfigure clears the reconfigure
flag and if two pads request a reconfiguration at the same time the
renegotiation would happen twice.
If a source pad requested a reconfiguration it is first checked whether
the old caps are still sufficient. If they are not the renegotiation
will happen.
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 because the completed
   buffers have the wrong configuration (see below).
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.

Clearing the completedRequests queue is archived with a new method in
GstLibcameraSrcState called clearRequests. This function is now also
used in gst_libcamera_src_task_leave as the code there did the same
thing.

In gst_libcamera_src_task_enter, after the initial negotiation, a call
to gst_pad_check_reconfigure was added to clear the reconfigure flag to
avoid triggering a renegotiation when running the task for the first
time.

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

diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
index e7a49fef..868fa20a 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)
@@ -133,6 +132,7 @@ struct GstLibcameraSrcState {
 	int queueRequest();
 	void requestCompleted(Request *request);
 	int processRequest();
+	void clearRequests();
 };
 
 struct _GstLibcameraSrc {
@@ -301,23 +301,39 @@ 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:
+	case GST_FLOW_NOT_NEGOTIATED:
+		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);
 
-		return -EPIPE;
+		err = -EPIPE;
+		break;
 	}
 
 	return err;
 }
 
+void GstLibcameraSrcState::clearRequests()
+{
+	GLibLocker locker(&lock_);
+	completedRequests_ = {};
+}
+
 static bool
 gst_libcamera_src_open(GstLibcameraSrc *self)
 {
@@ -488,6 +504,31 @@ gst_libcamera_src_task_run(gpointer user_data)
 		return;
 	}
 
+	// check if a srcpad requested a renegotiation
+	gboolean 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);
+			g_autoptr(GstCaps) intersection = gst_pad_peer_query_caps(srcpad, caps);
+
+			if (gst_caps_is_empty(intersection))
+				reconfigure = TRUE;
+		}
+	}
+
+	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
@@ -594,6 +635,7 @@ gst_libcamera_src_task_enter(GstTask *task, [[maybe_unused]] GThread *thread,
 		gst_segment_init(&segment, GST_FORMAT_TIME);
 		gst_pad_push_event(srcpad, gst_event_new_segment(&segment));
 
+		gst_pad_check_reconfigure(srcpad); // clear reconfigure flag
 	}
 
 	if (self->auto_focus_mode != controls::AfModeManual) {
@@ -629,11 +671,7 @@ gst_libcamera_src_task_leave([[maybe_unused]] GstTask *task,
 	GST_DEBUG_OBJECT(self, "Streaming thread is about to stop");
 
 	state->cam_->stop();
-
-	{
-		GLibLocker locker(&state->lock_);
-		state->completedRequests_ = {};
-	}
+	state->clearRequests();
 
 	{
 		GLibRecLocker locker(&self->stream_lock);
-- 
2.42.1



More information about the libcamera-devel mailing list