meta: expose API to register and create custom meta

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>
This commit is contained in:
Mathieu Duponchelle 2020-09-01 23:03:18 +02:00 committed by GStreamer Merge Bot
parent e9c99c05ae
commit bbca6b1ddf
7 changed files with 443 additions and 3 deletions

View file

@ -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 ()));

View 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);

View 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);
}

View 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

View 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,

View 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>
/**
@ -178,6 +189,30 @@ typedef gboolean (*GstMetaTransformFunction) (GstBuffer *transbuf,
GstMeta *meta, GstBuffer *buffer,
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
@ -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);

View 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;
}