imagefreeze: Add support for replacing the output buffer
authorSebastian Dröge <sebastian@centricular.com>
Fri, 10 Jan 2020 12:54:26 +0000 (14:54 +0200)
committerGStreamer Merge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Sat, 11 Jan 2020 08:04:43 +0000 (08:04 +0000)
By default imagefreeze will still reject new buffers after the first one
and immediately return GST_FLOW_EOS but the new allow-replace property
allows to change this.

Whenever updating the buffer we now also keep track of the configured
caps of the buffer and from the source pad task negotiate correctly
based on the potentially updated caps.

Only the very first time negotiation of a framerate with downstream is
performed, afterwards only the caps themselves apart from the framerate
are updated.

gst/imagefreeze/gstimagefreeze.c
gst/imagefreeze/gstimagefreeze.h

index f2933ce..424ca0c 100644 (file)
 #include "gstimagefreeze.h"
 
 #define DEFAULT_NUM_BUFFERS     -1
+#define DEFAULT_ALLOW_REPLACE   FALSE
 
 enum
 {
   PROP_0,
-  PROP_NUM_BUFFERS
+  PROP_NUM_BUFFERS,
+  PROP_ALLOW_REPLACE,
 };
 
 static void gst_image_freeze_finalize (GObject * object);
@@ -107,11 +109,16 @@ gst_image_freeze_class_init (GstImageFreezeClass * klass)
   gobject_class->get_property = gst_image_freeze_get_property;
 
   g_object_class_install_property (gobject_class, PROP_NUM_BUFFERS,
-      g_param_spec_int ("num-buffers", "num-buffers",
+      g_param_spec_int ("num-buffers", "Number of buffers",
           "Number of buffers to output before sending EOS (-1 = unlimited)",
           -1, G_MAXINT, DEFAULT_NUM_BUFFERS, G_PARAM_READWRITE |
           G_PARAM_STATIC_STRINGS));
 
+  g_object_class_install_property (gobject_class, PROP_ALLOW_REPLACE,
+      g_param_spec_boolean ("allow-replace", "Allow Replace",
+          "Allow replacing the input buffer and always output the latest",
+          DEFAULT_ALLOW_REPLACE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   gstelement_class->change_state =
       GST_DEBUG_FUNCPTR (gst_image_freeze_change_state);
 
@@ -151,6 +158,7 @@ gst_image_freeze_init (GstImageFreeze * self)
   g_mutex_init (&self->lock);
 
   self->num_buffers = DEFAULT_NUM_BUFFERS;
+  self->allow_replace = DEFAULT_ALLOW_REPLACE;
 
   gst_image_freeze_reset (self);
 }
@@ -176,11 +184,15 @@ gst_image_freeze_reset (GstImageFreeze * self)
 
   g_mutex_lock (&self->lock);
   gst_buffer_replace (&self->buffer, NULL);
+  gst_caps_replace (&self->buffer_caps, NULL);
+  gst_caps_replace (&self->current_caps, NULL);
+  self->buffer_caps_updated = FALSE;
   self->num_buffers_left = self->num_buffers;
 
   gst_segment_init (&self->segment, GST_FORMAT_TIME);
   self->need_segment = TRUE;
 
+  self->negotiated_framerate = FALSE;
   self->fps_n = self->fps_d = 0;
   self->offset = 0;
   self->seqnum = 0;
@@ -200,8 +212,22 @@ gst_image_freeze_sink_setcaps (GstImageFreeze * self, GstCaps * caps)
   GstPad *pad;
 
   pad = self->sinkpad;
+
   caps = gst_caps_copy (caps);
 
+  /* If we already negotiated a framerate then only update for the
+   * caps of the new buffer */
+  if (self->negotiated_framerate) {
+    gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION, self->fps_n,
+        self->fps_d, NULL);
+    GST_DEBUG_OBJECT (pad, "Setting caps %" GST_PTR_FORMAT, caps);
+    ret = gst_pad_set_caps (self->srcpad, caps);
+    gst_caps_unref (caps);
+    return ret;
+  }
+
+  /* Else negotiate a framerate with downstream */
+
   GST_DEBUG_OBJECT (pad, "Setting caps: %" GST_PTR_FORMAT, caps);
 
   s = gst_caps_get_structure (caps, 0);
@@ -246,6 +272,7 @@ gst_image_freeze_sink_setcaps (GstImageFreeze * self, GstCaps * caps)
           self->fps_n = fps_n;
           self->fps_d = fps_d;
           g_mutex_unlock (&self->lock);
+          self->negotiated_framerate = TRUE;
           GST_DEBUG_OBJECT (pad, "Setting caps %" GST_PTR_FORMAT, candidate);
           ret = TRUE;
           gst_caps_unref (candidate);
@@ -540,8 +567,10 @@ gst_image_freeze_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
     {
       GstCaps *caps;
 
+      g_mutex_lock (&self->lock);
       gst_event_parse_caps (event, &caps);
-      gst_image_freeze_sink_setcaps (self, caps);
+      gst_caps_replace (&self->current_caps, caps);
+      g_mutex_unlock (&self->lock);
       gst_event_unref (event);
       ret = TRUE;
       break;
@@ -707,6 +736,9 @@ gst_image_freeze_set_property (GObject * object, guint prop_id,
     case PROP_NUM_BUFFERS:
       self->num_buffers = g_value_get_int (value);
       break;
+    case PROP_ALLOW_REPLACE:
+      self->allow_replace = g_value_get_boolean (value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -725,6 +757,9 @@ gst_image_freeze_get_property (GObject * object, guint prop_id, GValue * value,
     case PROP_NUM_BUFFERS:
       g_value_set_int (value, self->num_buffers);
       break;
+    case PROP_ALLOW_REPLACE:
+      g_value_set_boolean (value, self->allow_replace);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -736,21 +771,33 @@ gst_image_freeze_sink_chain (GstPad * pad, GstObject * parent,
     GstBuffer * buffer)
 {
   GstImageFreeze *self = GST_IMAGE_FREEZE (parent);
+  GstFlowReturn flow_ret;
 
   g_mutex_lock (&self->lock);
-  if (self->buffer) {
+  if (self->buffer && !self->allow_replace) {
     GST_DEBUG_OBJECT (pad, "Already have a buffer, dropping");
     gst_buffer_unref (buffer);
     g_mutex_unlock (&self->lock);
     return GST_FLOW_EOS;
   }
 
-  self->buffer = buffer;
+  if (!self->current_caps) {
+    GST_ERROR_OBJECT (pad, "Not negotiated yet");
+    g_mutex_unlock (&self->lock);
+    return GST_FLOW_NOT_NEGOTIATED;
+  }
+
+  gst_buffer_replace (&self->buffer, buffer);
+  self->buffer_caps_updated = !self->buffer_caps
+      || !gst_caps_is_equal (self->buffer_caps, self->current_caps);
+  gst_caps_replace (&self->buffer_caps, self->current_caps);
+  gst_buffer_unref (buffer);
 
   gst_pad_start_task (self->srcpad, (GstTaskFunction) gst_image_freeze_src_loop,
       self->srcpad, NULL);
+  flow_ret = self->allow_replace ? GST_FLOW_OK : GST_FLOW_EOS;
   g_mutex_unlock (&self->lock);
-  return GST_FLOW_EOS;
+  return flow_ret;
 }
 
 static void
@@ -766,13 +813,6 @@ gst_image_freeze_src_loop (GstPad * pad)
   gboolean first = FALSE;
 
   g_mutex_lock (&self->lock);
-  if (!gst_pad_has_current_caps (self->srcpad)) {
-    GST_ERROR_OBJECT (pad, "Not negotiated yet");
-    flow_ret = GST_FLOW_NOT_NEGOTIATED;
-    g_mutex_unlock (&self->lock);
-    goto pause_task;
-  }
-
   if (!self->buffer) {
     GST_ERROR_OBJECT (pad, "Have no buffer yet");
     flow_ret = GST_FLOW_ERROR;
@@ -793,7 +833,15 @@ gst_image_freeze_src_loop (GstPad * pad)
   }
   buffer = gst_buffer_copy (self->buffer);
 
-  g_mutex_unlock (&self->lock);
+  if (self->buffer_caps_updated) {
+    GstCaps *buffer_caps = gst_caps_ref (self->buffer_caps);
+    self->buffer_caps_updated = FALSE;
+    g_mutex_unlock (&self->lock);
+    gst_image_freeze_sink_setcaps (self, buffer_caps);
+    gst_caps_unref (buffer_caps);
+  } else {
+    g_mutex_unlock (&self->lock);
+  }
 
   if (self->need_segment) {
     GstEvent *e;
index 4d03118..779c676 100644 (file)
@@ -49,6 +49,10 @@ struct _GstImageFreeze
 
   GMutex lock;
   GstBuffer *buffer;
+  GstCaps *buffer_caps, *current_caps;
+  gboolean buffer_caps_updated;
+
+  gboolean negotiated_framerate;
   gint fps_n, fps_d;
 
   GstSegment segment;
@@ -58,6 +62,8 @@ struct _GstImageFreeze
   gint num_buffers;
   gint num_buffers_left;
 
+  gboolean allow_replace;
+
   guint64 offset;
 
   /* TRUE if currently doing a flushing seek, protected