buffer: add gst_buffer_{set,get}_qdata()
authorTim-Philipp Müller <tim.muller@collabora.co.uk>
Wed, 16 Nov 2011 01:04:45 +0000 (01:04 +0000)
committerTim-Philipp Müller <tim.muller@collabora.co.uk>
Fri, 25 Nov 2011 17:42:07 +0000 (17:42 +0000)
Allows people/us to attach arbitrary metadata to buffers.

https://bugzilla.gnome.org/show_bug.cgi?id=664720

API: gst_buffer_set_qdata()
API: get_buffer_get_qdata()

docs/gst/gstreamer-sections.txt
gst/gstbuffer.c
gst/gstbuffer.h
tests/check/gst/gstbuffer.c
win32/common/libgstreamer.def

index b9500b7..c0902e5 100644 (file)
@@ -200,6 +200,9 @@ gst_buffer_is_metadata_writable
 gst_buffer_make_metadata_writable
 gst_buffer_replace
 
+gst_buffer_set_qdata
+gst_buffer_get_qdata
+
 gst_buffer_get_caps
 gst_buffer_set_caps
 
index 5a12663..edc0a6e 100644 (file)
 #include "gstminiobject.h"
 #include "gstversion.h"
 
+struct _GstBufferPrivate
+{
+  GList *qdata;
+  /* think about locking buffer->priv etc. when adding more fields */
+};
+
 static void gst_buffer_finalize (GstBuffer * buffer);
 static GstBuffer *_gst_buffer_copy (GstBuffer * buffer);
 
@@ -185,6 +191,8 @@ gst_buffer_class_init (GstBufferClass * klass)
   klass->mini_object_class.copy = (GstMiniObjectCopyFunction) _gst_buffer_copy;
   klass->mini_object_class.finalize =
       (GstMiniObjectFinalizeFunction) gst_buffer_finalize;
+
+  g_type_class_add_private (klass, sizeof (GstBufferPrivate));
 }
 
 static void
@@ -203,10 +211,61 @@ gst_buffer_finalize (GstBuffer * buffer)
   if (buffer->parent)
     gst_buffer_unref (buffer->parent);
 
+  if (G_UNLIKELY (buffer->priv != NULL)) {
+    GstBufferPrivate *priv = buffer->priv;
+
+    while (priv->qdata != NULL) {
+      GstStructure *s = priv->qdata->data;
+
+      gst_structure_set_parent_refcount (s, NULL);
+      gst_structure_free (s);
+      priv->qdata = g_list_delete_link (priv->qdata, priv->qdata);
+    }
+    priv->qdata = NULL;
+  }
+
 /*   ((GstMiniObjectClass *) */
 /*       gst_buffer_parent_class)->finalize (GST_MINI_OBJECT_CAST (buffer)); */
 }
 
+static inline GstBufferPrivate *
+gst_buffer_ensure_priv (GstBuffer * buf)
+{
+  GstBufferPrivate *priv = buf->priv;
+
+  if (priv != NULL)
+    return priv;
+
+  priv = buf->priv =
+      G_TYPE_INSTANCE_GET_PRIVATE (buf, GST_TYPE_BUFFER, GstBufferPrivate);
+
+  return priv;
+}
+
+static void
+gst_buffer_copy_qdata (GstBuffer * dest, const GstBuffer * src)
+{
+  GstBufferPrivate *priv;
+  GQueue qdata_copy = G_QUEUE_INIT;
+  GList *l;
+
+  if (G_LIKELY (src->priv == NULL))
+    return;
+
+  for (l = src->priv->qdata; l != NULL; l = l->next) {
+    GstStructure *s = gst_structure_copy (l->data);
+
+    gst_structure_set_parent_refcount (s, &dest->mini_object.refcount);
+    g_queue_push_tail (&qdata_copy, s);
+
+    GST_CAT_TRACE (GST_CAT_BUFFER, "copying qdata '%s' from buffer %p to %p",
+        g_quark_to_string (s->name), src, dest);
+  }
+
+  priv = gst_buffer_ensure_priv (dest);
+  priv->qdata = qdata_copy.head;
+}
+
 /**
  * gst_buffer_copy_metadata:
  * @dest: a destination #GstBuffer
@@ -263,6 +322,116 @@ gst_buffer_copy_metadata (GstBuffer * dest, const GstBuffer * src,
   if (flags & GST_BUFFER_COPY_CAPS) {
     gst_caps_replace (&GST_BUFFER_CAPS (dest), GST_BUFFER_CAPS (src));
   }
+
+  if ((flags & GST_BUFFER_COPY_QDATA)) {
+    GST_CAT_TRACE (GST_CAT_BUFFER, "copying qdata from %p to %p", src, dest);
+    gst_buffer_copy_qdata (dest, src);
+  }
+}
+
+/**
+ * gst_buffer_set_qdata:
+ * @buffer: a #GstBuffer
+ * @quark: name quark of data structure to set or replace
+ * @data: (transfer full) (allow-none): a #GstStructure to store with the
+ *    buffer, name must match @quark. Can be NULL to remove an existing
+ *    structure. This function takes ownership of the structure passed.
+ *
+ * Set metadata structure for name quark @quark to @data, or remove the
+ * existing metadata structure by that name in case @data is NULL.
+ *
+ * Takes ownership of @data.
+ *
+ * Since: 0.10.36
+ */
+void
+gst_buffer_set_qdata (GstBuffer * buffer, GQuark quark, GstStructure * data)
+{
+  GstBufferPrivate *priv;
+  GList *l;
+
+  g_return_if_fail (GST_IS_BUFFER (buffer));
+  g_return_if_fail (gst_buffer_is_metadata_writable (buffer));
+  g_return_if_fail (data == NULL || quark == gst_structure_get_name_id (data));
+
+  /* locking should not really be required, since the metadata_writable
+   * check ensures that the caller is the only one holding a ref, so as
+   * as a second ref is added everything turns read-only */
+  priv = gst_buffer_ensure_priv (buffer);
+
+  if (data) {
+    gst_structure_set_parent_refcount (data, &buffer->mini_object.refcount);
+  }
+
+  for (l = priv->qdata; l != NULL; l = l->next) {
+    GstStructure *s = l->data;
+
+    if (s->name == quark) {
+      GST_CAT_LOG (GST_CAT_BUFFER, "Replacing qdata '%s' on buffer %p: "
+          "%" GST_PTR_FORMAT " => %" GST_PTR_FORMAT, g_quark_to_string (quark),
+          buffer, s, data);
+      gst_structure_set_parent_refcount (s, NULL);
+      gst_structure_free (s);
+
+      if (data == NULL)
+        priv->qdata = g_list_delete_link (priv->qdata, l);
+      else
+        l->data = data;
+
+      goto done;
+    }
+  }
+
+  GST_CAT_LOG (GST_CAT_BUFFER, "Set qdata '%s' on buffer %p: %" GST_PTR_FORMAT,
+      g_quark_to_string (quark), buffer, data);
+
+  priv->qdata = g_list_prepend (priv->qdata, data);
+
+done:
+
+  return;
+}
+
+/**
+ * gst_buffer_get_qdata:
+ * @buffer: a #GstBuffer
+ * @quark: name quark of data structure to find
+ *
+ * Get metadata structure for name quark @quark.
+ *
+ * Returns: (transfer none): a #GstStructure, or NULL if not found
+ *
+ * Since: 0.10.36
+ */
+const GstStructure *
+gst_buffer_get_qdata (GstBuffer * buffer, GQuark quark)
+{
+  GstStructure *ret = NULL;
+
+  /* no need for locking: if the caller has the only ref, we're safe, and
+   * if the buffer has multiple refs, it's not metadata-writable any longer
+   * and the data can't change */
+
+  GST_CAT_LOG (GST_CAT_BUFFER, "Looking for qdata '%s' on buffer %p",
+      g_quark_to_string (quark), buffer);
+
+  if (buffer->priv != NULL) {
+    GList *l;
+
+    for (l = buffer->priv->qdata; l != NULL; l = l->next) {
+      GstStructure *s = l->data;
+
+      GST_CAT_LOG (GST_CAT_BUFFER, "checking qdata '%s' on buffer %p",
+          g_quark_to_string (s->name), buffer);
+
+      if (s->name == quark) {
+        ret = s;
+        break;
+      }
+    }
+  }
+
+  return ret;
 }
 
 static GstBuffer *
@@ -632,6 +801,9 @@ gst_buffer_create_sub (GstBuffer * buffer, guint offset, guint size)
     if ((caps = GST_BUFFER_CAPS (buffer)))
       gst_caps_ref (caps);
     GST_BUFFER_CAPS (subbuffer) = caps;
+
+    /* and also the attached qdata */
+    gst_buffer_copy_qdata (subbuffer, buffer);
   } else {
     GST_BUFFER_DURATION (subbuffer) = GST_CLOCK_TIME_NONE;
     GST_BUFFER_OFFSET_END (subbuffer) = GST_BUFFER_OFFSET_NONE;
index d0893a2..3125f7f 100644 (file)
@@ -32,6 +32,7 @@ G_BEGIN_DECLS
 
 typedef struct _GstBuffer GstBuffer;
 typedef struct _GstBufferClass GstBufferClass;
+typedef struct _GstBufferPrivate GstBufferPrivate;
 
 /**
  * GST_BUFFER_TRACE_NAME:
@@ -288,7 +289,8 @@ struct _GstBuffer {
   GstBuffer             *parent;
 
   /*< private >*/
-  gpointer _gst_reserved[GST_PADDING - 2];
+  GstBufferPrivate      *priv;
+  gpointer _gst_reserved[GST_PADDING - 3];
 };
 
 struct _GstBufferClass {
@@ -392,6 +394,8 @@ gst_buffer_copy (const GstBuffer * buf)
  * @GST_BUFFER_COPY_TIMESTAMPS: flag indicating that buffer timestamp, duration,
  * offset and offset_end should be copied
  * @GST_BUFFER_COPY_CAPS: flag indicating that buffer caps should be copied
+ * @GST_BUFFER_COPY_QDATA: flag indicating that buffer qdata should be copied
+ *    (Since 0.10.36)
  *
  * A set of flags that can be provided to the gst_buffer_copy_metadata()
  * function to specify which metadata fields should be copied.
@@ -401,7 +405,8 @@ gst_buffer_copy (const GstBuffer * buf)
 typedef enum {
   GST_BUFFER_COPY_FLAGS      = (1 << 0),
   GST_BUFFER_COPY_TIMESTAMPS = (1 << 1),
-  GST_BUFFER_COPY_CAPS       = (1 << 2)
+  GST_BUFFER_COPY_CAPS       = (1 << 2),
+  GST_BUFFER_COPY_QDATA      = (1 << 3)
 } GstBufferCopyFlags;
 
 /**
@@ -412,7 +417,7 @@ typedef enum {
  *
  * Since: 0.10.13
  */
-#define GST_BUFFER_COPY_ALL ((GstBufferCopyFlags) (GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS | GST_BUFFER_COPY_CAPS))
+#define GST_BUFFER_COPY_ALL ((GstBufferCopyFlags) (GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS | GST_BUFFER_COPY_CAPS | GST_BUFFER_COPY_QDATA))
 
 /* copies metadata into newly allocated buffer */
 void            gst_buffer_copy_metadata        (GstBuffer *dest, const GstBuffer *src,
@@ -446,6 +451,17 @@ void            gst_buffer_copy_metadata        (GstBuffer *dest, const GstBuffe
 gboolean        gst_buffer_is_metadata_writable (GstBuffer *buf);
 GstBuffer*      gst_buffer_make_metadata_writable (GstBuffer *buf);
 
+/* per-buffer user data */
+
+void                  gst_buffer_set_qdata (GstBuffer     * buffer,
+                                            GQuark          quark,
+                                            GstStructure  * data);
+
+const GstStructure *  gst_buffer_get_qdata (GstBuffer     * buffer,
+                                            GQuark          quark);
+
+
+
 /**
  * gst_buffer_replace:
  * @obuf: (inout) (transfer full): pointer to a pointer to a #GstBuffer to be
index 06cac4c..be608ec 100644 (file)
@@ -451,6 +451,59 @@ GST_START_TEST (test_try_new_and_alloc)
 
 GST_END_TEST;
 
+GST_START_TEST (test_qdata)
+{
+  GstStructure *s;
+  GstBuffer *buf, *buf2, *buf3;
+  GQuark q1, q2, q3;
+
+  q1 = g_quark_from_static_string ("GstFooBar");
+  q2 = g_quark_from_static_string ("MyBorkData");
+  q3 = g_quark_from_static_string ("DoNotExist");
+
+  buf = gst_buffer_new ();
+  ASSERT_CRITICAL (gst_buffer_set_qdata (buf, q1, (s =
+              gst_structure_id_empty_new (q2))));
+  gst_structure_free (s);
+
+  gst_buffer_set_qdata (buf, q1, gst_structure_id_empty_new (q1));
+  gst_buffer_set_qdata (buf, q2, gst_structure_id_empty_new (q2));
+  fail_unless (gst_buffer_get_qdata (buf, q3) == NULL);
+  fail_unless (gst_buffer_get_qdata (buf, q1) != NULL);
+  fail_unless (gst_buffer_get_qdata (buf, q2) != NULL);
+
+  /* full copy */
+  buf2 = gst_buffer_copy (buf);
+
+  /* now back to the original buffer... */
+  gst_buffer_set_qdata (buf, q1, NULL);
+  fail_unless (gst_buffer_get_qdata (buf, q1) == NULL);
+
+  /* force creation of sub-buffer with writable metadata */
+  gst_buffer_ref (buf);
+  buf3 = gst_buffer_make_metadata_writable (buf);
+
+  /* and check the copies/subbuffers.. */
+  fail_unless (gst_buffer_get_qdata (buf2, q3) == NULL);
+  fail_unless (gst_buffer_get_qdata (buf2, q1) != NULL);
+  fail_unless (gst_buffer_get_qdata (buf2, q2) != NULL);
+
+  fail_unless (gst_buffer_get_qdata (buf3, q3) == NULL);
+  fail_unless (gst_buffer_get_qdata (buf3, q1) == NULL);
+  fail_unless (gst_buffer_get_qdata (buf3, q2) != NULL);
+  gst_buffer_set_qdata (buf3, q1, gst_structure_id_empty_new (q1));
+  fail_unless (gst_buffer_get_qdata (buf3, q1) != NULL);
+
+  /* original buffer shouldn't have changed */
+  fail_unless (gst_buffer_get_qdata (buf, q1) == NULL);
+
+  gst_buffer_unref (buf);
+  gst_buffer_unref (buf2);
+  gst_buffer_unref (buf3);
+}
+
+GST_END_TEST;
+
 static Suite *
 gst_buffer_suite (void)
 {
@@ -467,6 +520,7 @@ gst_buffer_suite (void)
   tcase_add_test (tc_chain, test_metadata_writable);
   tcase_add_test (tc_chain, test_copy);
   tcase_add_test (tc_chain, test_try_new_and_alloc);
+  tcase_add_test (tc_chain, test_qdata);
 
   return s;
 }
index 5f2847e..f7a7974 100644 (file)
@@ -94,6 +94,7 @@ EXPORTS
        gst_buffer_create_sub
        gst_buffer_flag_get_type
        gst_buffer_get_caps
+       gst_buffer_get_qdata
        gst_buffer_get_type
        gst_buffer_is_metadata_writable
        gst_buffer_is_span_fast
@@ -123,6 +124,7 @@ EXPORTS
        gst_buffer_new
        gst_buffer_new_and_alloc
        gst_buffer_set_caps
+       gst_buffer_set_qdata
        gst_buffer_span
        gst_buffer_stamp
        gst_buffer_try_new_and_alloc