meta: expose API to register and create custom meta
authorMathieu Duponchelle <mathieu@centricular.com>
Tue, 1 Sep 2020 21:03:18 +0000 (23:03 +0200)
committerGStreamer Merge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Sun, 27 Sep 2020 11:08:03 +0000 (11:08 +0000)
Custom meta is backed by a GstStructure, and does not require
that users of the API expose their GstMeta implementation as
public API for other components to make use of it.

In addition, it provides a simpler interface by ignoring the
impl vs. api distinction that the regular API exposes.

This new API is meant to be the meta counterpart to custom events
and messages, and to be more convenient than the lower-level API
when the absolute best performance isn't a requirement.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/609>

gst/gst.c
gst/gst_private.h
gst/gstbuffer.c
gst/gstbuffer.h
gst/gstmeta.c
gst/gstmeta.h
tests/check/gst/gstmeta.c

index 6e10cab..ebe714d 100644 (file)
--- a/gst/gst.c
+++ b/gst/gst.c
@@ -1129,6 +1129,7 @@ gst_deinit (void)
 
   _priv_gst_caps_features_cleanup ();
   _priv_gst_caps_cleanup ();
+  _priv_gst_meta_cleanup ();
 
   g_type_class_unref (g_type_class_peek (gst_object_get_type ()));
   g_type_class_unref (g_type_class_peek (gst_pad_get_type ()));
index 1b0f02b..3459ecb 100644 (file)
@@ -148,6 +148,7 @@ G_GNUC_INTERNAL  void  _priv_gst_allocator_cleanup (void);
 G_GNUC_INTERNAL  void  _priv_gst_caps_features_cleanup (void);
 G_GNUC_INTERNAL  void  _priv_gst_caps_cleanup (void);
 G_GNUC_INTERNAL  void  _priv_gst_debug_cleanup (void);
+G_GNUC_INTERNAL  void  _priv_gst_meta_cleanup (void);
 
 /* called from gst_task_cleanup_all(). */
 G_GNUC_INTERNAL  void  _priv_gst_element_cleanup (void);
index c22de90..3e78ccd 100644 (file)
@@ -2871,3 +2871,65 @@ gst_reference_timestamp_meta_get_info (void)
 
   return meta_info;
 }
+
+/**
+ * gst_buffer_add_custom_meta:
+ * @buffer: (transfer none): a #GstBuffer
+ * @name: the registered name of the desired custom meta
+ *
+ * Creates and adds a #GstCustomMeta for the desired @name. @name must have
+ * been successfully registered with gst_meta_register_custom().
+ *
+ * Returns: (transfer none) (nullable): The #GstCustomMeta that was added to the buffer
+ *
+ * Since: 1.20
+ */
+GstCustomMeta *
+gst_buffer_add_custom_meta (GstBuffer * buffer, const gchar * name)
+{
+  GstCustomMeta *meta;
+  const GstMetaInfo *info;
+
+  g_return_val_if_fail (name != NULL, NULL);
+  g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL);
+
+  info = gst_meta_get_info (name);
+
+  if (info == NULL || !gst_meta_info_is_custom (info))
+    return NULL;
+
+  meta = (GstCustomMeta *) gst_buffer_add_meta (buffer, info, NULL);
+
+  return meta;
+}
+
+/**
+ * gst_buffer_get_custom_meta:
+ * @buffer: a #GstBuffer
+ * @name: the registered name of the custom meta to retrieve.
+ *
+ * Find the first #GstCustomMeta on @buffer for the desired @name.
+ *
+ * Returns: (transfer none) (nullable): the #GstCustomMeta or %NULL when there
+ * is no such metadata on @buffer.
+ *
+ * Since: 1.20
+ */
+GstCustomMeta *
+gst_buffer_get_custom_meta (GstBuffer * buffer, const gchar * name)
+{
+  const GstMetaInfo *info;
+
+  g_return_val_if_fail (buffer != NULL, NULL);
+  g_return_val_if_fail (name != NULL, NULL);
+
+  info = gst_meta_get_info (name);
+
+  if (!info)
+    return NULL;
+
+  if (!gst_meta_info_is_custom (info))
+    return NULL;
+
+  return (GstCustomMeta *) gst_buffer_get_meta (buffer, info->api);
+}
index 82adce9..682ac96 100644 (file)
@@ -664,6 +664,14 @@ gboolean        gst_buffer_foreach_meta         (GstBuffer *buffer,
                                                  GstBufferForeachMetaFunc func,
                                                  gpointer user_data);
 
+GST_API
+GstCustomMeta * gst_buffer_add_custom_meta      (GstBuffer *buffer,
+                                                 const gchar *name);
+
+GST_API
+GstCustomMeta * gst_buffer_get_custom_meta      (GstBuffer *buffer,
+                                                 const gchar *name);
+
 /**
  * gst_value_set_buffer:
  * @v: a #GValue to receive the data
index ab36cef..fc86956 100644 (file)
@@ -58,16 +58,60 @@ static GRWLock lock;
 GQuark _gst_meta_transform_copy;
 GQuark _gst_meta_tag_memory;
 
+typedef struct
+{
+  GstCustomMeta meta;
+
+  GstStructure *structure;
+} GstCustomMetaImpl;
+
+typedef struct
+{
+  GstMetaInfo info;
+  GstCustomMetaTransformFunction custom_transform_func;
+  gpointer custom_transform_user_data;
+  GDestroyNotify custom_transform_destroy_notify;
+  gboolean is_custom;
+} GstMetaInfoImpl;
+
+static void
+free_info (gpointer data)
+{
+  g_slice_free (GstMetaInfoImpl, data);
+}
+
 void
 _priv_gst_meta_initialize (void)
 {
   g_rw_lock_init (&lock);
-  metainfo = g_hash_table_new (g_str_hash, g_str_equal);
+  metainfo = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, free_info);
 
   _gst_meta_transform_copy = g_quark_from_static_string ("gst-copy");
   _gst_meta_tag_memory = g_quark_from_static_string ("memory");
 }
 
+static gboolean
+notify_custom (gchar * key, GstMetaInfo * info, gpointer unused)
+{
+  GstMetaInfoImpl *impl = (GstMetaInfoImpl *) info;
+
+  if (impl->is_custom) {
+    if (impl->custom_transform_destroy_notify)
+      impl->custom_transform_destroy_notify (impl->custom_transform_user_data);
+  }
+  return TRUE;
+}
+
+void
+_priv_gst_meta_cleanup (void)
+{
+  if (metainfo != NULL) {
+    g_hash_table_foreach_remove (metainfo, (GHRFunc) notify_custom, NULL);
+    g_hash_table_unref (metainfo);
+    metainfo = NULL;
+  }
+}
+
 /**
  * gst_meta_api_type_register:
  * @api: an API to register
@@ -104,6 +148,168 @@ gst_meta_api_type_register (const gchar * api, const gchar ** tags)
   return type;
 }
 
+static gboolean
+custom_init_func (GstMeta * meta, gpointer params, GstBuffer * buffer)
+{
+  GstCustomMetaImpl *cmeta = (GstCustomMetaImpl *) meta;
+
+  cmeta->structure = gst_structure_new_empty (g_type_name (meta->info->type));
+
+  gst_structure_set_parent_refcount (cmeta->structure,
+      &GST_MINI_OBJECT_REFCOUNT (buffer));
+
+  return TRUE;
+}
+
+static void
+custom_free_func (GstMeta * meta, GstBuffer * buffer)
+{
+  GstCustomMetaImpl *cmeta = (GstCustomMetaImpl *) meta;
+
+  gst_structure_set_parent_refcount (cmeta->structure, NULL);
+  gst_structure_free (cmeta->structure);
+}
+
+static gboolean
+custom_transform_func (GstBuffer * transbuf, GstMeta * meta,
+    GstBuffer * buffer, GQuark type, gpointer data)
+{
+  GstCustomMetaImpl *custom, *cmeta = (GstCustomMetaImpl *) meta;
+  GstMetaInfoImpl *info = (GstMetaInfoImpl *) meta->info;
+
+  if (info->custom_transform_func)
+    return info->custom_transform_func (transbuf, (GstCustomMeta *) meta,
+        buffer, type, data, info->custom_transform_user_data);
+
+  if (GST_META_TRANSFORM_IS_COPY (type)) {
+    custom =
+        (GstCustomMetaImpl *) gst_buffer_add_meta (transbuf, meta->info, NULL);
+    gst_structure_set_parent_refcount (custom->structure, NULL);
+    gst_structure_take (&custom->structure,
+        gst_structure_copy (cmeta->structure));
+    gst_structure_set_parent_refcount (custom->structure,
+        &GST_MINI_OBJECT_REFCOUNT (buffer));
+  } else {
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+/**
+ * gst_custom_meta_get_structure:
+ *
+ * Retrieve the #GstStructure backing a custom meta, the structure's mutability
+ * is conditioned to the writability of the #GstBuffer @meta is attached to.
+ *
+ * Returns: (transfer none): the #GstStructure backing @meta
+ * Since: 1.20
+ */
+GstStructure *
+gst_custom_meta_get_structure (GstCustomMeta * meta)
+{
+  g_return_val_if_fail (meta != NULL, NULL);
+  g_return_val_if_fail (gst_meta_info_is_custom (((GstMeta *) meta)->info),
+      NULL);
+
+  return ((GstCustomMetaImpl *) meta)->structure;
+}
+
+/**
+ * gst_custom_meta_has_name:
+ *
+ * Checks whether the name of the custom meta is @name
+ *
+ * Returns: Whether @name is the name of the custom meta
+ * Since: 1.20
+ */
+gboolean
+gst_custom_meta_has_name (GstCustomMeta * meta, const gchar * name)
+{
+  g_return_val_if_fail (meta != NULL, FALSE);
+  g_return_val_if_fail (gst_meta_info_is_custom (((GstMeta *) meta)->info),
+      FALSE);
+
+  return gst_structure_has_name (((GstCustomMetaImpl *) meta)->structure, name);
+}
+
+/**
+ * gst_meta_register_custom:
+ * @name: the name of the #GstMeta implementation
+ * @tags: (array zero-terminated=1): tags for @api
+ * @transform_func: (scope notified) (nullable): a #GstMetaTransformFunction
+ * @user_data: (closure): user data passed to @transform_func
+ * @destroy_data: #GDestroyNotify for user_data
+ *
+ * Register a new custom #GstMeta implementation, backed by an opaque
+ * structure holding a #GstStructure.
+ *
+ * The registered info can be retrieved later with gst_meta_get_info() by using
+ * @name as the key.
+ *
+ * The backing #GstStructure can be retrieved with
+ * gst_custom_meta_get_structure(), its mutability is conditioned by the
+ * writability of the buffer the meta is attached to.
+ *
+ * When @transform_func is %NULL, the meta and its backing #GstStructure
+ * will always be copied when the transform operation is copy, other operations
+ * are discarded, copy regions are ignored.
+ *
+ * Returns: (transfer none): a #GstMetaInfo that can be used to
+ * access metadata.
+ * Since: 1.20
+ */
+const GstMetaInfo *
+gst_meta_register_custom (const gchar * name, const gchar ** tags,
+    GstCustomMetaTransformFunction transform_func,
+    gpointer user_data, GDestroyNotify destroy_data)
+{
+  gchar *api_name = g_strdup_printf ("%s-api", name);
+  GType api;
+  GstMetaInfoImpl *info;
+  GstMetaInfo *ret = NULL;
+
+  g_return_val_if_fail (tags != NULL, NULL);
+  g_return_val_if_fail (name != NULL, NULL);
+
+  api = gst_meta_api_type_register (api_name, tags);
+  g_free (api_name);
+  if (api == G_TYPE_INVALID)
+    goto done;
+
+  info = (GstMetaInfoImpl *) gst_meta_register (api, name,
+      sizeof (GstCustomMetaImpl),
+      custom_init_func, custom_free_func, custom_transform_func);
+
+  if (!info)
+    goto done;
+
+  info->is_custom = TRUE;
+  info->custom_transform_func = transform_func;
+  info->custom_transform_user_data = user_data;
+  info->custom_transform_destroy_notify = destroy_data;
+
+  ret = (GstMetaInfo *) info;
+
+done:
+  return ret;
+}
+
+/**
+ * gst_meta_info_is_custom:
+ *
+ * Returns: whether @info was registered as a #GstCustomMeta with
+ *   gst_meta_register_custom()
+ * Since:1.20
+ */
+gboolean
+gst_meta_info_is_custom (const GstMetaInfo * info)
+{
+  g_return_val_if_fail (info != NULL, FALSE);
+
+  return ((GstMetaInfoImpl *) info)->is_custom;
+}
+
 /**
  * gst_meta_api_type_has_tag:
  * @api: an API
@@ -158,7 +364,7 @@ gst_meta_api_type_get_tags (GType api)
  * The same @info can be retrieved later with gst_meta_get_info() by using
  * @impl as the key.
  *
- * Returns: (transfer none) (nullable): a #GstMetaInfo that can be used to
+ * Returns: (transfer none): a #GstMetaInfo that can be used to
  * access metadata.
  */
 
@@ -185,13 +391,14 @@ gst_meta_register (GType api, const gchar * impl, gsize size,
   if (type == 0)
     return NULL;
 
-  info = g_slice_new (GstMetaInfo);
+  info = (GstMetaInfo *) g_slice_new (GstMetaInfoImpl);
   info->api = api;
   info->type = type;
   info->size = size;
   info->init_func = init_func;
   info->free_func = free_func;
   info->transform_func = transform_func;
+  ((GstMetaInfoImpl *) info)->is_custom = FALSE;
 
   GST_CAT_DEBUG (GST_CAT_META,
       "register \"%s\" implementing \"%s\" of size %" G_GSIZE_FORMAT, impl,
index d617ef8..547e5fc 100644 (file)
@@ -105,6 +105,17 @@ struct _GstMeta {
   const GstMetaInfo *info;
 };
 
+/**
+ * GstCustomMeta:
+ *
+ * Simple typing wrapper around #GstMeta
+ *
+ * Since: 1.20
+ */
+typedef struct {
+  GstMeta meta;
+} GstCustomMeta;
+
 #include <gst/gstbuffer.h>
 
 /**
@@ -179,6 +190,30 @@ typedef gboolean (*GstMetaTransformFunction) (GstBuffer *transbuf,
                                               GQuark type, gpointer data);
 
 /**
+ * GstCustomMetaTransformFunction:
+ * @transbuf: a #GstBuffer
+ * @meta: a #GstCustomMeta
+ * @buffer: a #GstBuffer
+ * @type: the transform type
+ * @data: transform specific data.
+ * @user_data: user data passed when registering the meta
+ *
+ * Function called for each @meta in @buffer as a result of performing a
+ * transformation on @transbuf. Additional @type specific transform data
+ * is passed to the function as @data.
+ *
+ * Implementations should check the @type of the transform and parse
+ * additional type specific fields in @data that should be used to update
+ * the metadata on @transbuf.
+ *
+ * Returns: %TRUE if the transform could be performed
+ * Since: 1.20
+ */
+typedef gboolean (*GstCustomMetaTransformFunction) (GstBuffer *transbuf,
+                                                    GstCustomMeta *meta, GstBuffer *buffer,
+                                                    GQuark type, gpointer data, gpointer user_data);
+
+/**
  * GstMetaInfo:
  * @api: tag identifying the metadata structure and api
  * @type: type identifying the implementor of the api
@@ -216,6 +251,21 @@ const GstMetaInfo *  gst_meta_register          (GType api, const gchar *impl,
                                                  GstMetaInitFunction      init_func,
                                                  GstMetaFreeFunction      free_func,
                                                  GstMetaTransformFunction transform_func);
+
+GST_API
+const GstMetaInfo *  gst_meta_register_custom   (const gchar *name, const gchar **tags,
+                                                 GstCustomMetaTransformFunction transform_func,
+                                                 gpointer user_data, GDestroyNotify destroy_data);
+
+GST_API
+gboolean             gst_meta_info_is_custom    (const GstMetaInfo *info);
+
+GST_API
+GstStructure *       gst_custom_meta_get_structure (GstCustomMeta *meta);
+
+GST_API
+gboolean             gst_custom_meta_has_name (GstCustomMeta *meta, const gchar * name);
+
 GST_API
 const GstMetaInfo *  gst_meta_get_info          (const gchar * impl);
 
index 03af6d7..30a37e7 100644 (file)
@@ -688,6 +688,115 @@ GST_START_TEST (test_meta_seqnum)
 
 GST_END_TEST;
 
+GST_START_TEST (test_meta_custom)
+{
+  GstBuffer *buffer;
+  const GstMetaInfo *info;
+  GstCustomMeta *meta;
+  GstMeta *it;
+  GstStructure *s, *expected;
+  gpointer state = NULL;
+  const gchar *tags[] = { "test-tag", NULL };
+
+  info = gst_meta_register_custom ("test-custom", tags, NULL, NULL, NULL);
+
+  fail_unless (info != NULL);
+
+  buffer = gst_buffer_new_and_alloc (4);
+  fail_if (buffer == NULL);
+
+  /* add some metadata */
+  meta = gst_buffer_add_custom_meta (buffer, "test-custom");
+  fail_if (meta == NULL);
+
+  fail_unless (gst_custom_meta_has_name ((GstCustomMeta *) meta,
+          "test-custom"));
+
+  expected = gst_structure_new_empty ("test-custom");
+  s = gst_custom_meta_get_structure (meta);
+  fail_unless (gst_structure_is_equal (s, expected));
+  gst_structure_free (expected);
+
+  gst_structure_set (s, "test-field", G_TYPE_INT, 42, NULL);
+  gst_buffer_ref (buffer);
+  ASSERT_CRITICAL (gst_structure_set (s, "test-field", G_TYPE_INT, 43, NULL));
+  gst_buffer_unref (buffer);
+  expected = gst_structure_new ("test-custom",
+      "test-field", G_TYPE_INT, 42, NULL);
+  fail_unless (gst_structure_is_equal (s, expected));
+  gst_structure_free (expected);
+
+  it = gst_buffer_iterate_meta (buffer, &state);
+
+  fail_unless ((GstCustomMeta *) it == meta);
+
+  fail_unless (it->info == info);
+
+  /* clean up */
+  gst_buffer_unref (buffer);
+}
+
+GST_END_TEST;
+
+static gboolean
+transform_custom (GstBuffer * transbuf, GstMeta * meta, GstBuffer * buffer,
+    GQuark type, gpointer data, gint * user_data)
+{
+  if (GST_META_TRANSFORM_IS_COPY (type)) {
+    GstStructure *s;
+    GstCustomMeta *custom;
+
+    custom = (GstCustomMeta *) gst_buffer_add_meta (transbuf, meta->info, NULL);
+    s = gst_custom_meta_get_structure (custom);
+    gst_structure_set (s, "test-field", G_TYPE_INT, *user_data, NULL);
+  } else {
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+GST_START_TEST (test_meta_custom_transform)
+{
+  GstBuffer *buffer, *buffer_copy;
+  const GstMetaInfo *info;
+  GstCustomMeta *meta;
+  GstStructure *s, *expected;
+  const gchar *tags[] = { "test-tag", NULL };
+  gint *user_data;
+
+  /* That memory should be deallocated at gst_deinit time */
+  user_data = g_malloc (sizeof (gint));
+  *user_data = 42;
+  info =
+      gst_meta_register_custom ("test-custom", tags,
+      (GstCustomMetaTransformFunction) transform_custom, user_data, g_free);
+
+  fail_unless (info != NULL);
+
+  buffer = gst_buffer_new_and_alloc (4);
+  fail_if (buffer == NULL);
+
+  /* add some metadata */
+  meta = gst_buffer_add_custom_meta (buffer, "test-custom");
+  fail_if (meta == NULL);
+
+  buffer_copy = gst_buffer_copy (buffer);
+  meta = gst_buffer_get_custom_meta (buffer_copy, "test-custom");
+  fail_unless (meta != NULL);
+  expected =
+      gst_structure_new ("test-custom", "test-field", G_TYPE_INT, 42, NULL);
+  s = gst_custom_meta_get_structure (meta);
+  fail_unless (gst_structure_is_equal (s, expected));
+  gst_structure_free (expected);
+
+  /* clean up */
+  gst_buffer_unref (buffer_copy);
+  gst_buffer_unref (buffer);
+}
+
+GST_END_TEST;
+
 static Suite *
 gst_buffermeta_suite (void)
 {
@@ -705,6 +814,8 @@ gst_buffermeta_suite (void)
   tcase_add_test (tc_chain, test_meta_foreach_remove_several);
   tcase_add_test (tc_chain, test_meta_iterate);
   tcase_add_test (tc_chain, test_meta_seqnum);
+  tcase_add_test (tc_chain, test_meta_custom);
+  tcase_add_test (tc_chain, test_meta_custom_transform);
 
   return s;
 }