[libcamera-devel] [PATCH 2/2] gstreamer: Implement renegotiation
Jaslo Ziska
jaslo at ziska.de
Thu Nov 23 12:00:15 CET 2023
Hi Nicolas,
thanks for the reviews!
Nicolas Dufresne <nicolas at ndufresne.ca> writes:
> Le mercredi 22 novembre 2023 à 14: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 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.
>
> That's an interesting comment, I don't think we expect
> GST_FLOW_NOT_NEGOTIATED
> in the renegotiation process. Downstream should keep handling
> the current
> format, emit an upstream reconfigure event and wait (may drop)
> but returning
> that flow is not really expected. How did you get that ?
You are right, the downstream element does send the
GST_EVENT_RECONFIGURE event. And I think we could handle this just
like with the EOS event by setting an atomic and handling it in
the running task. The way that I solved it now is similar to how
GstBaseSrc does it, they also check for reconfigure with
gst_pad_check_reconfigure.
As to why we receive GST_FLOW_NOT_NEGOTIATED: I'm not really sure
what is supposed to happen in this situation as the documentation
is a little scarce around this. I test this by dynamically
switching capsfilter elements with different caps so I think it
makes sense that the new capsfilter does not accept the buffers
and instead returns GST_FLOW_NOT_NEGOTIATED. Maybe other elements
can keep processing the buffers with the old caps and only send
the event?
But GstBaseSrc also has a case where they check for
GST_FLOW_NOT_NEGOTIATED (although they then also check if it
really is a reconfiguration with gst_pad_needs_reconfigure, maybe
I should add that) so it might be that this is supposed to happen.
>>
>> 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.
>
> nit: We may want to break on first, and then just systematically
> clear all the
> reconfigure flags in the negotiation() function. This would then
> become handy if
> we need to renegotiation due to some "signal" behind sent by
> libcamera itself.
That's a good idea, I will add that.
>> 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.
>
> Might want to slim down the patch by doing this refactoring in
> its own patch ?
Ok, I will do that.
>>
>> 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);
>
> Just wondering, but maybe gst_pad_peer_query_accept_caps() would
> do the job,
> with a slightly improved performance ? This is only possible
> since
> gst_pad_get_current_caps() is fixed. At this point, it should
> cannot be null
> either.
I will test if this works and if it does add it to v2.
Regards,
Jaslo
>> +
>> + 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);
More information about the libcamera-devel
mailing list