meta: Add serialize/deserialize API

This allows metas to be serialized to be transmitted or stored. This is
intended to be used for example by gdppay or unixfdsink.

Implemented on GstCustomMeta, GstVideoMeta, GstReferenceTimestampMeta,
and GstAudioMeta.

Sponsored-by: Netflix Inc.
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5355>
This commit is contained in:
Xavier Claessens 2023-09-18 19:24:35 -04:00 committed by GStreamer Marge Bot
parent 9501d64ccd
commit 06d9d934f9
15 changed files with 1039 additions and 37 deletions

View file

@ -30,10 +30,11 @@
#include "config.h" #include "config.h"
#endif #endif
#include <string.h>
#include "gstaudiometa.h" #include "gstaudiometa.h"
#include <string.h>
#include <gst/base/base.h>
static gboolean static gboolean
gst_audio_downmix_meta_init (GstMeta * meta, gpointer params, gst_audio_downmix_meta_init (GstMeta * meta, gpointer params,
GstBuffer * buffer) GstBuffer * buffer)
@ -351,6 +352,111 @@ gst_audio_meta_transform (GstBuffer * dest, GstMeta * meta,
return TRUE; return TRUE;
} }
static gboolean
gst_audio_meta_serialize (const GstMeta * meta, GstByteArrayInterface * data,
guint8 * version)
{
GstAudioMeta *ameta = (GstAudioMeta *) meta;
/* Position is limited to 64 */
gint n_position = ameta->info.channels > 64 ? 0 : ameta->info.channels;
gsize size = 28 + n_position * 4 + ameta->info.channels * 8;
guint8 *ptr = gst_byte_array_interface_append (data, size);
if (ptr == NULL)
return FALSE;
GstByteWriter bw;
gboolean success = TRUE;
gst_byte_writer_init_with_data (&bw, ptr, size, FALSE);
success &= gst_byte_writer_put_int32_le (&bw, ameta->info.finfo->format);
success &= gst_byte_writer_put_int32_le (&bw, ameta->info.flags);
success &= gst_byte_writer_put_int32_le (&bw, ameta->info.layout);
success &= gst_byte_writer_put_int32_le (&bw, ameta->info.rate);
success &= gst_byte_writer_put_int32_le (&bw, ameta->info.channels);
for (int i = 0; i < n_position; i++)
success &= gst_byte_writer_put_int32_le (&bw, ameta->info.position[i]);
success &= gst_byte_writer_put_uint64_le (&bw, ameta->samples);
for (int i = 0; i < ameta->info.channels; i++)
success &= gst_byte_writer_put_uint64_le (&bw, ameta->offsets[i]);
g_assert (success);
return TRUE;
}
static GstMeta *
gst_audio_meta_deserialize (const GstMetaInfo * info, GstBuffer * buffer,
const guint8 * data, gsize size, guint8 version)
{
GstAudioMeta *ameta = NULL;
gint32 format;
gint32 flags;
gint32 layout;
gint32 rate;
gint32 channels;
if (version != 0)
return NULL;
GstByteReader br;
gboolean success = TRUE;
gst_byte_reader_init (&br, data, size);
success &= gst_byte_reader_get_int32_le (&br, &format);
success &= gst_byte_reader_get_int32_le (&br, &flags);
success &= gst_byte_reader_get_int32_le (&br, &layout);
success &= gst_byte_reader_get_int32_le (&br, &rate);
success &= gst_byte_reader_get_int32_le (&br, &channels);
if (!success)
return NULL;
/* Position is limited to 64 */
gint n_position = channels > 64 ? 0 : channels;
gint32 *position = g_new (gint32, n_position);
guint64 *offsets64 = g_new (guint64, channels);
guint64 samples = 0;
for (int i = 0; i < n_position; i++)
success &= gst_byte_reader_get_int32_le (&br, &position[i]);
success &= gst_byte_reader_get_uint64_le (&br, &samples);
for (int i = 0; i < channels; i++)
success &= gst_byte_reader_get_uint64_le (&br, &offsets64[i]);
if (!success) {
g_free (position);
g_free (offsets64);
return NULL;
}
#if GLIB_SIZEOF_SIZE_T != 8
gsize *offsets = g_new (gsize, channels);
for (int i = 0; i < channels; i++) {
if (offsets64[i] > G_MAXSIZE) {
g_free (offsets64);
g_free (offsets);
g_free (position);
return NULL;
}
offsets[i] = offsets64[i];
}
g_free (offsets64);
#else
gsize *offsets = (gsize *) offsets64;
#endif
GstAudioInfo audio_info;
gst_audio_info_set_format (&audio_info, format, rate, channels,
(channels > 64) ? NULL : position);
audio_info.flags = flags;
audio_info.layout = layout;
ameta = gst_buffer_add_audio_meta (buffer, &audio_info, samples, offsets);
g_free (offsets);
g_free (position);
return (GstMeta *) ameta;
}
/** /**
* gst_buffer_add_audio_meta: * gst_buffer_add_audio_meta:
* @buffer: a #GstBuffer * @buffer: a #GstBuffer
@ -482,11 +588,14 @@ gst_audio_meta_get_info (void)
static const GstMetaInfo *audio_meta_info = NULL; static const GstMetaInfo *audio_meta_info = NULL;
if (g_once_init_enter ((GstMetaInfo **) & audio_meta_info)) { if (g_once_init_enter ((GstMetaInfo **) & audio_meta_info)) {
const GstMetaInfo *meta = gst_meta_register (GST_AUDIO_META_API_TYPE, const GstMetaInfo *meta =
gst_meta_register_serializable (GST_AUDIO_META_API_TYPE,
"GstAudioMeta", sizeof (GstAudioMeta), "GstAudioMeta", sizeof (GstAudioMeta),
gst_audio_meta_init, gst_audio_meta_init,
gst_audio_meta_free, gst_audio_meta_free,
gst_audio_meta_transform); gst_audio_meta_transform,
gst_audio_meta_serialize,
gst_audio_meta_deserialize);
g_once_init_leave ((GstMetaInfo **) & audio_meta_info, g_once_init_leave ((GstMetaInfo **) & audio_meta_info,
(GstMetaInfo *) meta); (GstMetaInfo *) meta);
} }

View file

@ -170,6 +170,9 @@ typedef struct _GstAudioMeta GstAudioMeta;
* case to do so. It is, however, allowed to attach it, for some potential * case to do so. It is, however, allowed to attach it, for some potential
* future use case. * future use case.
* *
* Since 1.24 it can be serialized using gst_meta_serialize() and
* gst_meta_deserialize().
*
* Since: 1.16 * Since: 1.16
*/ */
struct _GstAudioMeta { struct _GstAudioMeta {

View file

@ -23,6 +23,7 @@
#include "gstvideometa.h" #include "gstvideometa.h"
#include <string.h> #include <string.h>
#include <gst/base/base.h>
/** /**
* SECTION:gstvideometa * SECTION:gstvideometa
@ -31,6 +32,12 @@
* *
*/ */
static gboolean
default_map (GstVideoMeta * meta, guint plane, GstMapInfo * info,
gpointer * data, gint * stride, GstMapFlags flags);
static gboolean
default_unmap (GstVideoMeta * meta, guint plane, GstMapInfo * info);
#ifndef GST_DISABLE_GST_DEBUG #ifndef GST_DISABLE_GST_DEBUG
#define GST_CAT_DEFAULT ensure_debug_category() #define GST_CAT_DEFAULT ensure_debug_category()
static GstDebugCategory * static GstDebugCategory *
@ -134,6 +141,110 @@ gst_video_meta_api_get_type (void)
return type; return type;
} }
static gboolean
video_meta_serialize (const GstMeta * meta, GstByteArrayInterface * data,
guint8 * version)
{
GstVideoMeta *vmeta = (GstVideoMeta *) meta;
if (vmeta->map != default_map || vmeta->unmap != default_unmap) {
GST_WARNING ("Cannot serialize video meta with custom map/unmap functions");
return FALSE;
}
gsize size = 36 + vmeta->n_planes * 16;
guint8 *ptr = gst_byte_array_interface_append (data, size);
if (ptr == NULL)
return FALSE;
GstByteWriter bw;
gboolean success = TRUE;
gst_byte_writer_init_with_data (&bw, ptr, size, FALSE);
success &= gst_byte_writer_put_int32_le (&bw, vmeta->flags);
success &= gst_byte_writer_put_int32_le (&bw, vmeta->format);
success &= gst_byte_writer_put_uint32_le (&bw, vmeta->width);
success &= gst_byte_writer_put_uint32_le (&bw, vmeta->height);
success &= gst_byte_writer_put_uint32_le (&bw, vmeta->n_planes);
for (int n = 0; n < vmeta->n_planes; n++)
success &= gst_byte_writer_put_uint64_le (&bw, vmeta->offset[n]);
for (int n = 0; n < vmeta->n_planes; n++)
success &= gst_byte_writer_put_int32_le (&bw, vmeta->stride[n]);
success &= gst_byte_writer_put_uint32_le (&bw, vmeta->alignment.padding_top);
success &=
gst_byte_writer_put_uint32_le (&bw, vmeta->alignment.padding_bottom);
success &= gst_byte_writer_put_uint32_le (&bw, vmeta->alignment.padding_left);
success &=
gst_byte_writer_put_uint32_le (&bw, vmeta->alignment.padding_right);
for (int n = 0; n < vmeta->n_planes; n++)
success &=
gst_byte_writer_put_uint32_le (&bw, vmeta->alignment.stride_align[n]);
g_assert (success);
return TRUE;
}
static GstMeta *
video_meta_deserialize (const GstMetaInfo * info, GstBuffer * buffer,
const guint8 * data, gsize size, guint8 version)
{
GstVideoMeta *vmeta = NULL;
gint32 flags;
gint32 format;
guint width;
guint height;
guint n_planes;
GstVideoAlignment align;
guint64 offset64[GST_VIDEO_MAX_PLANES];
gint32 stride[GST_VIDEO_MAX_PLANES];
if (version != 0)
return NULL;
GstByteReader br;
gboolean success = TRUE;
gst_byte_reader_init (&br, data, size);
success &= gst_byte_reader_get_int32_le (&br, &flags);
success &= gst_byte_reader_get_int32_le (&br, &format);
success &= gst_byte_reader_get_uint32_le (&br, &width);
success &= gst_byte_reader_get_uint32_le (&br, &height);
success &= gst_byte_reader_get_uint32_le (&br, &n_planes);
if (!success || n_planes > GST_VIDEO_MAX_PLANES)
return NULL;
for (int n = 0; n < n_planes; n++)
success &= gst_byte_reader_get_uint64_le (&br, &offset64[n]);
for (int n = 0; n < n_planes; n++)
success &= gst_byte_reader_get_int32_le (&br, &stride[n]);
success &= gst_byte_reader_get_uint32_le (&br, &align.padding_top);
success &= gst_byte_reader_get_uint32_le (&br, &align.padding_bottom);
success &= gst_byte_reader_get_uint32_le (&br, &align.padding_left);
success &= gst_byte_reader_get_uint32_le (&br, &align.padding_right);
for (int n = 0; n < n_planes; n++)
success &= gst_byte_reader_get_uint32_le (&br, &align.stride_align[n]);
if (!success)
return NULL;
#if GLIB_SIZEOF_SIZE_T != 8
gsize offset[GST_VIDEO_MAX_PLANES];
for (int i = 0; i < n_planes; i++) {
if (offset64[i] > G_MAXSIZE)
return NULL;
offset[i] = offset64[i];
}
#else
gsize *offset = (gsize *) offset64;
#endif
vmeta =
gst_buffer_add_video_meta_full (buffer, flags, format, width, height,
n_planes, offset, stride);
gst_video_meta_set_alignment (vmeta, align);
return (GstMeta *) vmeta;
}
/* video metadata */ /* video metadata */
const GstMetaInfo * const GstMetaInfo *
gst_video_meta_get_info (void) gst_video_meta_get_info (void)
@ -142,9 +253,10 @@ gst_video_meta_get_info (void)
if (g_once_init_enter ((GstMetaInfo **) & video_meta_info)) { if (g_once_init_enter ((GstMetaInfo **) & video_meta_info)) {
const GstMetaInfo *meta = const GstMetaInfo *meta =
gst_meta_register (GST_VIDEO_META_API_TYPE, "GstVideoMeta", gst_meta_register_serializable (GST_VIDEO_META_API_TYPE, "GstVideoMeta",
sizeof (GstVideoMeta), (GstMetaInitFunction) gst_video_meta_init, sizeof (GstVideoMeta), (GstMetaInitFunction) gst_video_meta_init,
(GstMetaFreeFunction) NULL, gst_video_meta_transform); (GstMetaFreeFunction) NULL, gst_video_meta_transform,
video_meta_serialize, video_meta_deserialize);
g_once_init_leave ((GstMetaInfo **) & video_meta_info, g_once_init_leave ((GstMetaInfo **) & video_meta_info,
(GstMetaInfo *) meta); (GstMetaInfo *) meta);
} }
@ -310,8 +422,8 @@ gst_buffer_add_video_meta (GstBuffer * buffer,
GstVideoMeta * GstVideoMeta *
gst_buffer_add_video_meta_full (GstBuffer * buffer, gst_buffer_add_video_meta_full (GstBuffer * buffer,
GstVideoFrameFlags flags, GstVideoFormat format, guint width, GstVideoFrameFlags flags, GstVideoFormat format, guint width,
guint height, guint n_planes, gsize offset[GST_VIDEO_MAX_PLANES], guint height, guint n_planes, const gsize offset[GST_VIDEO_MAX_PLANES],
gint stride[GST_VIDEO_MAX_PLANES]) const gint stride[GST_VIDEO_MAX_PLANES])
{ {
GstVideoMeta *meta; GstVideoMeta *meta;
guint i; guint i;

View file

@ -74,6 +74,9 @@ typedef struct _GstVideoCropMeta GstVideoCropMeta;
* - padding-right (uint): extra pixels on the right side * - padding-right (uint): extra pixels on the right side
* The padding fields have the same semantic as #GstVideoMeta.alignment * The padding fields have the same semantic as #GstVideoMeta.alignment
* and so represent the paddings requested on produced video buffers. * and so represent the paddings requested on produced video buffers.
*
* Since 1.24 it can be serialized using gst_meta_serialize() and
* gst_meta_deserialize().
*/ */
struct _GstVideoMeta { struct _GstVideoMeta {
GstMeta meta; GstMeta meta;
@ -116,8 +119,8 @@ GstVideoMeta * gst_buffer_add_video_meta (GstBuffer *buffer, GstVideoFrame
GST_VIDEO_API GST_VIDEO_API
GstVideoMeta * gst_buffer_add_video_meta_full (GstBuffer *buffer, GstVideoFrameFlags flags, GstVideoMeta * gst_buffer_add_video_meta_full (GstBuffer *buffer, GstVideoFrameFlags flags,
GstVideoFormat format, guint width, guint height, GstVideoFormat format, guint width, guint height,
guint n_planes, gsize offset[GST_VIDEO_MAX_PLANES], guint n_planes, const gsize offset[GST_VIDEO_MAX_PLANES],
gint stride[GST_VIDEO_MAX_PLANES]); const gint stride[GST_VIDEO_MAX_PLANES]);
GST_VIDEO_API GST_VIDEO_API
gboolean gst_video_meta_map (GstVideoMeta *meta, guint plane, GstMapInfo *info, gboolean gst_video_meta_map (GstVideoMeta *meta, guint plane, GstMapInfo *info,

View file

@ -1578,6 +1578,106 @@ GST_START_TEST (test_audio_make_raw_caps)
GST_END_TEST; GST_END_TEST;
GST_START_TEST (test_audio_meta_serialize)
{
GstBuffer *buf;
GstAudioInfo info;
GstAudioMeta *meta;
GstAudioChannelPosition position[] = { GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT
};
gst_audio_info_init (&info);
gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S32, 44100, 2, position);
info.layout = GST_AUDIO_LAYOUT_NON_INTERLEAVED;
/* Add audio meta */
gsize samples = 1;
gsize buf_size = 4 * 2 * samples;
buf = gst_buffer_new_and_alloc (buf_size);
gsize offsets[] = { 0, buf_size / 2 };
meta = gst_buffer_add_audio_meta (buf, &info, samples, offsets);
/* Serialize */
GByteArray *data = g_byte_array_new ();
fail_unless (gst_meta_serialize_simple ((GstMeta *) meta, data));
gst_buffer_unref (buf);
/* Create a new buffer */
buf = gst_buffer_new_and_alloc (buf_size);
guint32 consumed;
meta = (GstAudioMeta *) gst_meta_deserialize (buf, data->data, data->len,
&consumed);
fail_unless (meta);
fail_unless (consumed == data->len);
g_byte_array_unref (data);
/* Check meta's content */
g_assert_cmpint (meta->info.finfo->format, ==, GST_AUDIO_FORMAT_S32);
g_assert_cmpint (meta->info.layout, ==, GST_AUDIO_LAYOUT_NON_INTERLEAVED);
g_assert_cmpint (meta->info.rate, ==, 44100);
g_assert_cmpint (meta->info.channels, ==, 2);
g_assert_cmpint (meta->info.position[0], ==,
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT);
g_assert_cmpint (meta->info.position[1], ==,
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT);
g_assert_cmpuint (meta->samples, ==, samples);
g_assert_cmpuint (meta->offsets[0], ==, 0);
g_assert_cmpuint (meta->offsets[1], ==, buf_size / 2);
gst_buffer_unref (buf);
}
GST_END_TEST;
GST_START_TEST (test_audio_meta_serialize_65_chans)
{
GstBuffer *buf;
GstAudioInfo info;
GstAudioMeta *meta;
/* With more than 64 channels we cannot have positions */
gst_audio_info_init (&info);
gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S32, 44100, 65, NULL);
info.layout = GST_AUDIO_LAYOUT_NON_INTERLEAVED;
/* Add audio meta */
gsize samples = 1;
gsize buf_size = 4 * 65 * samples;
buf = gst_buffer_new_and_alloc (buf_size);
gsize offsets[65];
for (int i = 0; i < 65; i++)
offsets[i] = i * samples * 4;
meta = gst_buffer_add_audio_meta (buf, &info, samples, offsets);
/* Serialize */
GByteArray *data = g_byte_array_new ();
fail_unless (gst_meta_serialize_simple ((GstMeta *) meta, data));
gst_buffer_unref (buf);
/* Create a new buffer */
buf = gst_buffer_new_and_alloc (buf_size);
guint32 consumed;
meta = (GstAudioMeta *) gst_meta_deserialize (buf, data->data, data->len,
&consumed);
fail_unless (meta);
fail_unless (consumed == data->len);
g_byte_array_unref (data);
/* Check meta's content */
g_assert_cmpint (meta->info.finfo->format, ==, GST_AUDIO_FORMAT_S32);
g_assert_cmpint (meta->info.layout, ==, GST_AUDIO_LAYOUT_NON_INTERLEAVED);
g_assert_cmpint (meta->info.rate, ==, 44100);
g_assert_cmpint (meta->info.channels, ==, 65);
g_assert_cmpuint (meta->samples, ==, samples);
for (int i = 0; i < 65; i++)
g_assert_cmpuint (meta->offsets[i], ==, i * samples * 4);
gst_buffer_unref (buf);
}
GST_END_TEST;
static Suite * static Suite *
audio_suite (void) audio_suite (void)
{ {
@ -1616,6 +1716,8 @@ audio_suite (void)
tcase_add_test (tc_chain, test_audio_buffer_and_audio_meta); tcase_add_test (tc_chain, test_audio_buffer_and_audio_meta);
tcase_add_test (tc_chain, test_audio_info_from_caps); tcase_add_test (tc_chain, test_audio_info_from_caps);
tcase_add_test (tc_chain, test_audio_make_raw_caps); tcase_add_test (tc_chain, test_audio_make_raw_caps);
tcase_add_test (tc_chain, test_audio_meta_serialize);
tcase_add_test (tc_chain, test_audio_meta_serialize_65_chans);
return s; return s;
} }

View file

@ -4215,6 +4215,70 @@ GST_START_TEST (test_info_dma_drm)
GST_END_TEST; GST_END_TEST;
GST_START_TEST (test_video_meta_serialize)
{
GstBuffer *buf;
GstVideoInfo info;
GstVideoMeta *meta;
GstVideoAlignment alig;
gst_video_alignment_reset (&alig);
alig.padding_left = 2;
alig.padding_right = 6;
alig.padding_top = 2;
alig.padding_bottom = 6;
gst_video_info_init (&info);
gst_video_info_set_format (&info, GST_VIDEO_FORMAT_NV12, 1920, 1080);
g_assert (gst_video_info_align (&info, &alig));
/* Add video meta */
buf = gst_buffer_new ();
meta = gst_buffer_add_video_meta_full (buf, GST_VIDEO_FRAME_FLAG_NONE,
GST_VIDEO_INFO_FORMAT (&info), GST_VIDEO_INFO_WIDTH (&info),
GST_VIDEO_INFO_HEIGHT (&info), GST_VIDEO_INFO_N_PLANES (&info),
info.offset, info.stride);
g_assert (gst_video_meta_set_alignment (meta, alig));
/* Serialize */
GByteArray *data = g_byte_array_new ();
fail_unless (gst_meta_serialize_simple ((GstMeta *) meta, data));
gst_buffer_unref (buf);
/* Create a new buffer */
buf = gst_buffer_new ();
guint32 consumed;
meta = (GstVideoMeta *) gst_meta_deserialize (buf, data->data, data->len,
&consumed);
fail_unless (meta);
fail_unless (consumed == data->len);
g_byte_array_unref (data);
/* Check meta's content */
g_assert_cmpuint (meta->flags, ==, GST_VIDEO_FRAME_FLAG_NONE);
g_assert_cmpuint (meta->format, ==, GST_VIDEO_FORMAT_NV12);
g_assert_cmpuint (meta->id, ==, 0);
g_assert_cmpuint (meta->width, ==, 1920);
g_assert_cmpuint (meta->height, ==, 1080);
g_assert_cmpuint (meta->alignment.padding_top, ==, 2);
g_assert_cmpuint (meta->alignment.padding_bottom, ==, 6);
g_assert_cmpuint (meta->alignment.padding_left, ==, 2);
g_assert_cmpuint (meta->alignment.padding_right, ==, 6);
g_assert_cmpuint (meta->n_planes, ==, GST_VIDEO_INFO_N_PLANES (&info));
for (int i = 0; i < meta->n_planes; i++) {
g_assert_cmpuint (meta->stride[i], ==, GST_VIDEO_INFO_PLANE_STRIDE (&info,
i));
g_assert_cmpuint (meta->offset[i], ==, GST_VIDEO_INFO_PLANE_OFFSET (&info,
i));
g_assert_cmpuint (meta->alignment.stride_align[i], ==, 0);
}
gst_buffer_unref (buf);
}
GST_END_TEST;
static Suite * static Suite *
video_suite (void) video_suite (void)
{ {
@ -4273,6 +4337,7 @@ video_suite (void)
tcase_add_test (tc_chain, test_auto_video_frame_unmap); tcase_add_test (tc_chain, test_auto_video_frame_unmap);
tcase_add_test (tc_chain, test_video_color_primaries_equivalent); tcase_add_test (tc_chain, test_video_color_primaries_equivalent);
tcase_add_test (tc_chain, test_info_dma_drm); tcase_add_test (tc_chain, test_info_dma_drm);
tcase_add_test (tc_chain, test_video_meta_serialize);
return s; return s;
} }

View file

@ -36,6 +36,7 @@
#include <gst/gstbuffer.h> #include <gst/gstbuffer.h>
#include <gst/gstbufferlist.h> #include <gst/gstbufferlist.h>
#include <gst/gstbufferpool.h> #include <gst/gstbufferpool.h>
#include <gst/gstbytearrayinterface.h>
#include <gst/gstcaps.h> #include <gst/gstcaps.h>
#include <gst/gstcapsfeatures.h> #include <gst/gstcapsfeatures.h>
#include <gst/gstchildproxy.h> #include <gst/gstchildproxy.h>

View file

@ -2860,6 +2860,49 @@ gst_reference_timestamp_meta_api_get_type (void)
return type; return type;
} }
static gboolean
timestamp_meta_serialize (const GstMeta * meta, GstByteArrayInterface * data,
guint8 * version)
{
const GstReferenceTimestampMeta *rtmeta =
(const GstReferenceTimestampMeta *) meta;
gchar *caps_str = gst_caps_to_string (rtmeta->reference);
gsize caps_str_len = strlen (caps_str);
gsize size = 16 + caps_str_len + 1;
guint8 *ptr = gst_byte_array_interface_append (data, size);
if (ptr == NULL) {
g_free (caps_str);
return FALSE;
}
GST_WRITE_UINT64_LE (ptr, rtmeta->timestamp);
GST_WRITE_UINT64_LE (ptr + 8, rtmeta->duration);
memcpy (ptr + 16, caps_str, caps_str_len + 1);
g_free (caps_str);
return TRUE;
}
static GstMeta *
timestamp_meta_deserialize (const GstMetaInfo * info, GstBuffer * buffer,
const guint8 * data, gsize size, guint8 version)
{
/* Sanity check: caps_str must be 0-terminated. */
if (version != 0 || size < 2 * sizeof (guint64) + 1 || data[size - 1] != '\0')
return NULL;
guint64 timestamp = GST_READ_UINT64_LE (data);
guint64 duration = GST_READ_UINT64_LE (data + 8);
const gchar *caps_str = (const gchar *) data + 16;
GstCaps *reference = gst_caps_from_string (caps_str);
GstMeta *meta = (GstMeta *) gst_buffer_add_reference_timestamp_meta (buffer,
reference, timestamp, duration);
gst_caps_unref (reference);
return meta;
}
/** /**
* gst_reference_timestamp_meta_get_info: * gst_reference_timestamp_meta_get_info:
* *
@ -2876,12 +2919,15 @@ gst_reference_timestamp_meta_get_info (void)
if (g_once_init_enter ((GstMetaInfo **) & meta_info)) { if (g_once_init_enter ((GstMetaInfo **) & meta_info)) {
const GstMetaInfo *meta = const GstMetaInfo *meta =
gst_meta_register (gst_reference_timestamp_meta_api_get_type (), gst_meta_register_serializable
(gst_reference_timestamp_meta_api_get_type (),
"GstReferenceTimestampMeta", "GstReferenceTimestampMeta",
sizeof (GstReferenceTimestampMeta), sizeof (GstReferenceTimestampMeta),
(GstMetaInitFunction) _gst_reference_timestamp_meta_init, (GstMetaInitFunction) _gst_reference_timestamp_meta_init,
(GstMetaFreeFunction) _gst_reference_timestamp_meta_free, (GstMetaFreeFunction) _gst_reference_timestamp_meta_free,
_gst_reference_timestamp_meta_transform); _gst_reference_timestamp_meta_transform,
timestamp_meta_serialize,
timestamp_meta_deserialize);
g_once_init_leave ((GstMetaInfo **) & meta_info, (GstMetaInfo *) meta); g_once_init_leave ((GstMetaInfo **) & meta_info, (GstMetaInfo *) meta);
} }

View file

@ -763,6 +763,9 @@ typedef struct _GstReferenceTimestampMeta GstReferenceTimestampMeta;
* * `timestamp/x-unix`: for timestamps based on the UNIX epoch according to * * `timestamp/x-unix`: for timestamps based on the UNIX epoch according to
* the local clock. * the local clock.
* *
* Since 1.24 it can be serialized using gst_meta_serialize() and
* gst_meta_deserialize().
*
* Since: 1.14 * Since: 1.14
*/ */
struct _GstReferenceTimestampMeta struct _GstReferenceTimestampMeta

View file

@ -0,0 +1,129 @@
/* Copyright (C) 2023 Netflix Inc.
* Author: Xavier Claessens <xavier.claessens@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <gst/gstconfig.h>
G_BEGIN_DECLS
/**
* GstByteArrayInterface:
* @data: A pointer to an array of bytes.
* @len: Number of bytes in @data.
* @resize: Reallocate @data.
*
* Interface for an array of bytes. It is expected to be subclassed to implement
* @resize virtual method using language native array implementation, such as
* GLib's #GByteArray, C++'s `std::vector<uint8_t>` or Rust's `Vec<u8>`.
*
* @resize implementation could allocate more than requested to avoid repeated
* reallocations. It can return %FALSE, or be set to %NULL, in the case the
* array cannot grow.
*
* Since: 1.24
*/
typedef struct _GstByteArrayInterface GstByteArrayInterface;
struct _GstByteArrayInterface
{
guint8 *data;
gsize len;
gboolean (*resize) (GstByteArrayInterface *self, gsize length);
/* < private > */
gpointer _gst_reserved[GST_PADDING];
};
/**
* gst_byte_array_interface_init:
* @self: A #GstByteArrayInterface.
* @length: New size.
*
* Initialize #GstByteArrayInterface structure.
*
* Since: 1.24
*/
static inline void
gst_byte_array_interface_init (GstByteArrayInterface *self)
{
memset (self, 0, sizeof (GstByteArrayInterface));
}
/**
* gst_byte_array_interface_set_size:
* @self: A #GstByteArrayInterface.
* @length: New size.
*
* Reallocate data pointer to fit at least @length bytes. @self->len is updated
* to @length.
*
* Returns: %TRUE on success, %FALSE otherwise.
* Since: 1.24
*/
static inline gboolean
gst_byte_array_interface_set_size (GstByteArrayInterface *self, gsize length)
{
if (self->resize == NULL || !self->resize (self, length))
return FALSE;
self->len = length;
return TRUE;
}
/**
* gst_byte_array_interface_append:
* @self: A #GstByteArrayInterface.
* @size: Number of bytes to append to the array.
*
* Grow the array by @size bytes and return a pointer to the newly added memory.
*
* Returns: Pointer to added memory, or %NULL if reallocation failed.
* Since: 1.24
*/
static inline guint8 *
gst_byte_array_interface_append (GstByteArrayInterface *self, gsize size)
{
gsize orig = self->len;
if (!gst_byte_array_interface_set_size (self, self->len + size))
return NULL;
return self->data + orig;
}
/**
* gst_byte_array_interface_append_data:
* @self: A #GstByteArrayInterface.
* @data: Source data.
* @size: Size of @data.
*
* Append @size bytes from @data, reallocating @self->data pointer if necessary.
*
* Returns: %TRUE on success, %FALSE otherwise.
* Since: 1.24
*/
static inline gboolean
gst_byte_array_interface_append_data (GstByteArrayInterface *self, const guint8 *data, gsize size)
{
guint8 *ptr = gst_byte_array_interface_append (self, size);
if (ptr == NULL)
return FALSE;
memcpy (ptr, data, size);
return TRUE;
}
G_END_DECLS

View file

@ -193,6 +193,46 @@ custom_transform_func (GstBuffer * transbuf, GstMeta * meta,
return TRUE; return TRUE;
} }
static gboolean
custom_serialize_func (const GstMeta * meta, GstByteArrayInterface * data,
guint8 * version)
{
const GstCustomMeta *cmeta = (const GstCustomMeta *) meta;
gchar *str =
gst_structure_serialize (cmeta->structure, GST_SERIALIZE_FLAG_STRICT);
if (str == NULL)
return FALSE;
gboolean ret = gst_byte_array_interface_append_data (data, (guint8 *) str,
strlen (str) + 1);
g_free (str);
return ret;
}
static GstMeta *
custom_deserialize_func (const GstMetaInfo * info, GstBuffer * buffer,
const guint8 * data, gsize size, guint8 version)
{
if (version != 0 || size < 1 || data[size - 1] != '\0')
return NULL;
GstStructure *structure =
gst_structure_new_from_string ((const gchar *) data);
if (structure == NULL)
return NULL;
GstMeta *meta = gst_buffer_add_meta (buffer, info, NULL);
GstCustomMeta *cmeta = (GstCustomMeta *) meta;
gst_structure_set_parent_refcount (cmeta->structure, NULL);
gst_structure_take (&cmeta->structure, structure);
gst_structure_set_parent_refcount (cmeta->structure,
&GST_MINI_OBJECT_REFCOUNT (buffer));
return meta;
}
/** /**
* gst_custom_meta_get_structure: * gst_custom_meta_get_structure:
* *
@ -274,9 +314,10 @@ gst_meta_register_custom (const gchar * name, const gchar ** tags,
if (api == G_TYPE_INVALID) if (api == G_TYPE_INVALID)
goto done; goto done;
info = (GstMetaInfoImpl *) gst_meta_register (api, name, info = (GstMetaInfoImpl *) gst_meta_register_serializable (api, name,
sizeof (GstCustomMeta), sizeof (GstCustomMeta),
custom_init_func, custom_free_func, custom_transform_func); custom_init_func, custom_free_func, custom_transform_func,
custom_serialize_func, custom_deserialize_func);
if (!info) if (!info)
goto done; goto done;
@ -364,28 +405,12 @@ gst_meta_api_type_get_tags (GType api)
return (const gchar * const *) tags; return (const gchar * const *) tags;
} }
/** static const GstMetaInfo *
* gst_meta_register: gst_meta_register_internal (GType api, const gchar * impl, gsize size,
* @api: the type of the #GstMeta API
* @impl: the name of the #GstMeta implementation
* @size: the size of the #GstMeta structure
* @init_func: (scope async): a #GstMetaInitFunction
* @free_func: (scope async): a #GstMetaFreeFunction
* @transform_func: (scope async): a #GstMetaTransformFunction
*
* Register a new #GstMeta implementation.
*
* The same @info can be retrieved later with gst_meta_get_info() by using
* @impl as the key.
*
* Returns: (transfer none): a #GstMetaInfo that can be used to
* access metadata.
*/
const GstMetaInfo *
gst_meta_register (GType api, const gchar * impl, gsize size,
GstMetaInitFunction init_func, GstMetaFreeFunction free_func, GstMetaInitFunction init_func, GstMetaFreeFunction free_func,
GstMetaTransformFunction transform_func) GstMetaTransformFunction transform_func,
GstMetaSerializeFunction serialize_func,
GstMetaDeserializeFunction deserialize_func)
{ {
GstMetaInfo *info; GstMetaInfo *info;
GType type; GType type;
@ -412,6 +437,8 @@ gst_meta_register (GType api, const gchar * impl, gsize size,
info->init_func = init_func; info->init_func = init_func;
info->free_func = free_func; info->free_func = free_func;
info->transform_func = transform_func; info->transform_func = transform_func;
info->serialize_func = serialize_func;
info->deserialize_func = deserialize_func;
((GstMetaInfoImpl *) info)->is_custom = FALSE; ((GstMetaInfoImpl *) info)->is_custom = FALSE;
GST_CAT_DEBUG (GST_CAT_META, GST_CAT_DEBUG (GST_CAT_META,
@ -426,6 +453,62 @@ gst_meta_register (GType api, const gchar * impl, gsize size,
return info; return info;
} }
/**
* gst_meta_register:
* @api: the type of the #GstMeta API
* @impl: the name of the #GstMeta implementation
* @size: the size of the #GstMeta structure
* @init_func: (scope async): a #GstMetaInitFunction
* @free_func: (scope async): a #GstMetaFreeFunction
* @transform_func: (scope async): a #GstMetaTransformFunction
*
* Register a new #GstMeta implementation.
*
* The same @info can be retrieved later with gst_meta_get_info() by using
* @impl as the key.
*
* Returns: (transfer none): a #GstMetaInfo that can be used to
* access metadata.
*/
const GstMetaInfo *
gst_meta_register (GType api, const gchar * impl, gsize size,
GstMetaInitFunction init_func, GstMetaFreeFunction free_func,
GstMetaTransformFunction transform_func)
{
return gst_meta_register_internal (api, impl, size, init_func, free_func,
transform_func, NULL, NULL);
}
/**
* gst_meta_register_serializable:
* @api: the type of the #GstMeta API
* @impl: the name of the #GstMeta implementation
* @size: the size of the #GstMeta structure
* @init_func: (scope async): a #GstMetaInitFunction
* @free_func: (scope async): a #GstMetaFreeFunction
* @transform_func: (scope async): a #GstMetaTransformFunction
* @serialize_func: (scope async): a #GstMetaSerializeFunction
* @deserialize_func: (scope async): a #GstMetaDeserializeFunction
*
* Same as gst_meta_register() but also set serialize/deserialize functions.
*
* Returns: (transfer none): a #GstMetaInfo that can be used to access metadata.
*
* Since: 1.24
*/
const GstMetaInfo *
gst_meta_register_serializable (GType api, const gchar * impl, gsize size,
GstMetaInitFunction init_func, GstMetaFreeFunction free_func,
GstMetaTransformFunction transform_func,
GstMetaSerializeFunction serialize_func,
GstMetaDeserializeFunction deserialize_func)
{
g_return_val_if_fail (serialize_func != NULL, NULL);
g_return_val_if_fail (deserialize_func != NULL, NULL);
return gst_meta_register_internal (api, impl, size, init_func, free_func,
transform_func, serialize_func, deserialize_func);
}
/** /**
* gst_meta_get_info: * gst_meta_get_info:
* @impl: the name * @impl: the name
@ -497,3 +580,180 @@ gst_meta_compare_seqnum (const GstMeta * meta1, const GstMeta * meta2)
return (seqnum1 < seqnum2) ? -1 : 1; return (seqnum1 < seqnum2) ? -1 : 1;
} }
/**
* gst_meta_serialize:
* @meta: a #GstMeta
* @data: #GstByteArrayInterface to append serialization data
*
* Serialize @meta into a format that can be stored or transmitted and later
* deserialized by gst_meta_deserialize().
*
* This is only supported for meta that implements #GstMetaInfo.serialize_func,
* %FALSE is returned otherwise.
*
* Upon failure, @data->data pointer could have been reallocated, but @data->len
* won't be modified. This is intended to be able to append multiple metas
* into the same #GByteArray.
*
* Since serialization size is often the same for every buffer, caller may want
* to remember the size of previous data to preallocate the next.
*
* Returns: %TRUE on success, %FALSE otherwise.
*
* Since: 1.24
*/
gboolean
gst_meta_serialize (const GstMeta * meta, GstByteArrayInterface * data)
{
g_return_val_if_fail (meta != NULL, FALSE);
g_return_val_if_fail (data != NULL, FALSE);
if (meta->info->serialize_func != NULL) {
const gchar *name = g_type_name (meta->info->type);
guint32 name_len = strlen (name);
guint32 orig_len = data->len;
guint8 version = 0;
/* Format: [total size][name_len][name][\0][version][payload]
* Preallocate space for header but only write it on success because we
* don't have every info yet.
*/
guint8 header_size = 2 * sizeof (guint32) + name_len + 2;
if (!gst_byte_array_interface_set_size (data, data->len + header_size))
return FALSE;
if (meta->info->serialize_func (meta, data, &version)) {
guint8 *header = data->data + orig_len;
GST_WRITE_UINT32_LE (header + 0, data->len - orig_len);
GST_WRITE_UINT32_LE (header + 4, name_len);
memcpy (header + 8, name, name_len + 1);
header[header_size - 1] = version;
return TRUE;
}
// Serialization failed, rollback.
gst_byte_array_interface_set_size (data, orig_len);
}
return FALSE;
}
typedef struct
{
GstByteArrayInterface parent;
GByteArray *data;
} ByteArrayImpl;
static gboolean
byte_array_impl_resize (GstByteArrayInterface * parent, gsize length)
{
ByteArrayImpl *self = (ByteArrayImpl *) parent;
g_byte_array_set_size (self->data, length);
parent->data = self->data->data;
return TRUE;
}
/**
* gst_meta_serialize_simple:
* @meta: a #GstMeta
* @data: #GByteArray to append serialization data
*
* Same as gst_meta_serialize() but with a #GByteArray instead of
* #GstByteArrayInterface.
*
* Returns: %TRUE on success, %FALSE otherwise.
*
* Since: 1.24
*/
gboolean
gst_meta_serialize_simple (const GstMeta * meta, GByteArray * data)
{
ByteArrayImpl impl;
gst_byte_array_interface_init (&impl.parent);
impl.parent.data = data->data;
impl.parent.len = data->len;
impl.parent.resize = byte_array_impl_resize;
impl.data = data;
return gst_meta_serialize (meta, (GstByteArrayInterface *) & impl);
}
/**
* gst_meta_deserialize:
* @buffer: a #GstBuffer
* @data: serialization data obtained from gst_meta_serialize()
* @size: size of @data
* @consumed: (out): total size used by this meta, could be less than @size
*
* Recreate a #GstMeta from serialized data returned by
* gst_meta_serialize() and add it to @buffer.
*
* Note that the meta must have been previously registered by calling one of
* `gst_*_meta_get_info ()` functions.
*
* @consumed is set to the number of bytes that can be skipped from @data to
* find the next meta serialization, if any. In case of parsing error that does
* not allow to determine that size, @consumed is set to 0.
*
* Returns: (transfer none) (nullable): the metadata owned by @buffer, or %NULL.
*
* Since: 1.24
*/
GstMeta *
gst_meta_deserialize (GstBuffer * buffer, const guint8 * data, gsize size,
guint32 * consumed)
{
g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL);
g_return_val_if_fail (data != NULL, NULL);
g_return_val_if_fail (consumed != NULL, NULL);
*consumed = 0;
/* Format: [total size][name_len][name][\0][version][payload] */
if (size < 2 * sizeof (guint32))
goto bad_header;
guint32 total_size = GST_READ_UINT32_LE (data + 0);
guint32 name_len = GST_READ_UINT32_LE (data + 4);
guint32 header_size = 2 * sizeof (guint32) + name_len + 2;
if (size < total_size || total_size < header_size)
goto bad_header;
guint8 version = data[header_size - 1];
const gchar *name = (const gchar *) (data + 2 * sizeof (guint32));
if (name[name_len] != '\0')
goto bad_header;
*consumed = total_size;
const GstMetaInfo *info = gst_meta_get_info (name);
if (info == NULL) {
GST_CAT_WARNING (GST_CAT_META,
"%s does not correspond to a registered meta", name);
return NULL;
}
if (info->deserialize_func == NULL) {
GST_CAT_WARNING (GST_CAT_META, "Meta %s does not support deserialization",
name);
return NULL;
}
const guint8 *payload = data + header_size;
guint32 payload_size = total_size - header_size;
GstMeta *meta =
info->deserialize_func (info, buffer, payload, payload_size, version);
if (meta == NULL) {
GST_CAT_WARNING (GST_CAT_META, "Failed to deserialize %s payload", name);
GST_CAT_MEMDUMP (GST_CAT_META, "Meta serialization payload", payload,
payload_size);
return NULL;
}
return meta;
bad_header:
GST_CAT_WARNING (GST_CAT_META, "Could not parse meta serialization header");
GST_CAT_MEMDUMP (GST_CAT_META, "Meta serialization data", data, size);
return NULL;
}

View file

@ -25,6 +25,9 @@
#include <glib.h> #include <glib.h>
#include <gst/gstbytearrayinterface.h>
G_BEGIN_DECLS G_BEGIN_DECLS
typedef struct _GstMeta GstMeta; typedef struct _GstMeta GstMeta;
@ -132,6 +135,10 @@ struct _GstMeta {
* Extra custom metadata. The @structure field is the same as returned by * Extra custom metadata. The @structure field is the same as returned by
* gst_custom_meta_get_structure(). * gst_custom_meta_get_structure().
* *
* Since 1.24 it can be serialized using gst_meta_serialize() and
* gst_meta_deserialize(), but only if the #GstStructure does not contain any
* fields that cannot be serialized, see %GST_SERIALIZE_FLAG_STRICT.
*
* Since: 1.20 * Since: 1.20
*/ */
typedef struct { typedef struct {
@ -211,7 +218,6 @@ GST_API GQuark _gst_meta_transform_clear;
*/ */
#define GST_META_TRANSFORM_IS_CLEAR(type) ((type) == _gst_meta_transform_clear) #define GST_META_TRANSFORM_IS_CLEAR(type) ((type) == _gst_meta_transform_clear)
/** /**
* GstMetaTransformFunction: * GstMetaTransformFunction:
* @transbuf: a #GstBuffer * @transbuf: a #GstBuffer
@ -258,6 +264,61 @@ typedef gboolean (*GstCustomMetaTransformFunction) (GstBuffer *transbuf,
GstCustomMeta *meta, GstBuffer *buffer, GstCustomMeta *meta, GstBuffer *buffer,
GQuark type, gpointer data, gpointer user_data); GQuark type, gpointer data, gpointer user_data);
/**
* GstMetaSerializeFunction:
* @meta: a #GstMeta
* @data: #GstByteArrayInterface to append serialization data
* @version: (out): version of the serialization format
*
* Serialize @meta into a format that can be stored or transmitted and later
* deserialized by #GstMetaDeserializeFunction.
*
* By default version is set to 0, it should be bumped if incompatible changes
* are made to the format so %GstMetaDeserializeFunction can deserialize each
* version.
*
* Returns: %TRUE on success, %FALSE otherwise.
*
* Since: 1.24
*/
typedef gboolean (*GstMetaSerializeFunction) (const GstMeta *meta,
GstByteArrayInterface *data, guint8 *version);
/**
* GstMetaDeserializeFunction:
* @info: #GstMetaInfo of the meta
* @buffer: a #GstBuffer
* @data: data obtained from #GstMetaSerializeFunction
* @size: size of data to avoid buffer overflow
*
* Recreate a #GstMeta from serialized data returned by
* #GstMetaSerializeFunction and add it to @buffer.
*
* Returns: (transfer none) (nullable): the metadata owned by @buffer, or %NULL.
*
* Since: 1.24
*/
typedef GstMeta *(*GstMetaDeserializeFunction) (const GstMetaInfo *info,
GstBuffer *buffer, const guint8 *data, gsize size, guint8 version);
/**
* GstMetaInfo.serialize_func:
*
* Function for serializing the metadata, or %NULL if not supported by this
* meta.
*
* Since: 1.24
*/
/**
* GstMetaInfo.deserialize_func:
*
* Function for deserializing the metadata, or %NULL if not supported by this
* meta.
*
* Since: 1.24
*/
/** /**
* GstMetaInfo: * GstMetaInfo:
* @api: tag identifying the metadata structure and api * @api: tag identifying the metadata structure and api
@ -266,6 +327,10 @@ typedef gboolean (*GstCustomMetaTransformFunction) (GstBuffer *transbuf,
* @init_func: function for initializing the metadata * @init_func: function for initializing the metadata
* @free_func: function for freeing the metadata * @free_func: function for freeing the metadata
* @transform_func: function for transforming the metadata * @transform_func: function for transforming the metadata
* @serialize_func: function for serializing the metadata into a #GstStructure,
* or %NULL if not supported by this meta. (Since 1.24)
* @deserialize_func: function for deserializing the metadata from a
* #GstStructure, or %NULL if not supported by this meta. (Since 1.24)
* *
* The #GstMetaInfo provides information about a specific metadata * The #GstMetaInfo provides information about a specific metadata
* structure. * structure.
@ -278,6 +343,8 @@ struct _GstMetaInfo {
GstMetaInitFunction init_func; GstMetaInitFunction init_func;
GstMetaFreeFunction free_func; GstMetaFreeFunction free_func;
GstMetaTransformFunction transform_func; GstMetaTransformFunction transform_func;
GstMetaSerializeFunction serialize_func;
GstMetaDeserializeFunction deserialize_func;
/* No padding needed, GstMetaInfo is always allocated by GStreamer and is /* No padding needed, GstMetaInfo is always allocated by GStreamer and is
* not subclassable or stack-allocatable, so we can extend it as we please * not subclassable or stack-allocatable, so we can extend it as we please
@ -297,6 +364,15 @@ const GstMetaInfo * gst_meta_register (GType api, const gchar *impl,
GstMetaFreeFunction free_func, GstMetaFreeFunction free_func,
GstMetaTransformFunction transform_func); GstMetaTransformFunction transform_func);
GST_API
const GstMetaInfo * gst_meta_register_serializable (GType api, const gchar *impl,
gsize size,
GstMetaInitFunction init_func,
GstMetaFreeFunction free_func,
GstMetaTransformFunction transform_func,
GstMetaSerializeFunction serialize_func,
GstMetaDeserializeFunction deserialize_func);
GST_API GST_API
const GstMetaInfo * gst_meta_register_custom (const gchar *name, const gchar **tags, const GstMetaInfo * gst_meta_register_custom (const gchar *name, const gchar **tags,
GstCustomMetaTransformFunction transform_func, GstCustomMetaTransformFunction transform_func,
@ -327,6 +403,18 @@ GST_API
gint gst_meta_compare_seqnum (const GstMeta * meta1, gint gst_meta_compare_seqnum (const GstMeta * meta1,
const GstMeta * meta2); const GstMeta * meta2);
GST_API
gboolean gst_meta_serialize (const GstMeta *meta,
GstByteArrayInterface *data);
GST_API
gboolean gst_meta_serialize_simple (const GstMeta *meta,
GByteArray *data);
GST_API
GstMeta * gst_meta_deserialize (GstBuffer *buffer,
const guint8 *data,
gsize size,
guint32 *consumed);
/* some default tags */ /* some default tags */
GST_API GQuark _gst_meta_tag_memory; GST_API GQuark _gst_meta_tag_memory;

View file

@ -82,6 +82,7 @@ gst_headers = files(
'gstbufferlist.h', 'gstbufferlist.h',
'gstbufferpool.h', 'gstbufferpool.h',
'gstbus.h', 'gstbus.h',
'gstbytearrayinterface.h',
'gstcaps.h', 'gstcaps.h',
'gstcapsfeatures.h', 'gstcapsfeatures.h',
'gstchildproxy.h', 'gstchildproxy.h',

View file

@ -258,7 +258,7 @@ GST_START_TEST (test_metadata_writable)
ASSERT_BUFFER_REFCOUNT (buffer, "buffer", 2); ASSERT_BUFFER_REFCOUNT (buffer, "buffer", 2);
fail_unless (gst_buffer_is_writable (buffer) == FALSE); fail_unless (gst_buffer_is_writable (buffer) == FALSE);
/* Check that make_metadata_writable produces a new sub-buffer with /* Check that make_metadata_writable produces a new sub-buffer with
* writable metadata. */ * writable metadata. */
sub1 = gst_buffer_make_writable (buffer); sub1 = gst_buffer_make_writable (buffer);
fail_if (sub1 == buffer); fail_if (sub1 == buffer);
@ -967,6 +967,37 @@ GST_START_TEST (test_auto_unmap)
GST_END_TEST; GST_END_TEST;
GST_START_TEST (test_reference_timestamp_meta_serialization)
{
GstCaps *reference =
gst_caps_new_simple ("timestamp/x-unix", NULL, NULL, NULL);
/* Serialize */
GstBuffer *buffer = gst_buffer_new ();
GstReferenceTimestampMeta *meta =
gst_buffer_add_reference_timestamp_meta (buffer, reference, 1, 2);
GByteArray *data = g_byte_array_new ();
fail_unless (gst_meta_serialize_simple ((GstMeta *) meta, data));
gst_buffer_unref (buffer);
/* Deserialize */
buffer = gst_buffer_new ();
guint32 consumed;
meta = (GstReferenceTimestampMeta *) gst_meta_deserialize (buffer, data->data,
data->len, &consumed);
fail_unless (meta);
fail_unless (consumed == data->len);
fail_unless (gst_caps_is_equal (meta->reference, reference));
fail_unless_equals_uint64 (meta->timestamp, 1);
fail_unless_equals_uint64 (meta->duration, 2);
gst_buffer_unref (buffer);
gst_caps_unref (reference);
g_byte_array_unref (data);
}
GST_END_TEST;
static Suite * static Suite *
gst_buffer_suite (void) gst_buffer_suite (void)
{ {
@ -994,6 +1025,7 @@ gst_buffer_suite (void)
tcase_add_test (tc_chain, test_wrapped_bytes); tcase_add_test (tc_chain, test_wrapped_bytes);
tcase_add_test (tc_chain, test_new_memdup); tcase_add_test (tc_chain, test_new_memdup);
tcase_add_test (tc_chain, test_auto_unmap); tcase_add_test (tc_chain, test_auto_unmap);
tcase_add_test (tc_chain, test_reference_timestamp_meta_serialization);
return s; return s;
} }

View file

@ -808,6 +808,53 @@ GST_START_TEST (test_meta_custom_transform)
GST_END_TEST; GST_END_TEST;
GST_START_TEST (test_meta_custom_serialize)
{
const GstMetaInfo *info;
GstCustomMeta *meta;
GstBuffer *buffer;
info = gst_meta_register_custom_simple ("test-custom-serialize");
fail_unless (info != NULL);
/* add some metadata */
buffer = gst_buffer_new ();
meta = gst_buffer_add_custom_meta (buffer, "test-custom-serialize");
gst_structure_set (meta->structure, "test-field", G_TYPE_INT, 42, NULL);
/* Serialize */
GByteArray *data = g_byte_array_new ();
fail_unless (gst_meta_serialize_simple ((GstMeta *) meta, data));
gst_buffer_unref (buffer);
/* Create a new buffer */
buffer = gst_buffer_new ();
guint32 consumed;
meta = (GstCustomMeta *) gst_meta_deserialize (buffer, data->data, data->len,
&consumed);
fail_unless (meta);
fail_unless (consumed == data->len);
/* Check meta's content */
fail_unless (gst_custom_meta_has_name (meta, "test-custom-serialize"));
gint val;
fail_unless (gst_structure_get_int (meta->structure, "test-field", &val));
fail_unless_equals_int (val, 42);
/* Add field that cannot be serialized */
GstElement *bin = gst_bin_new ("mybin");
gst_structure_set (meta->structure, "test-field-obj", GST_TYPE_BIN, bin,
NULL);
g_byte_array_set_size (data, 0);
fail_if (gst_meta_serialize_simple ((GstMeta *) meta, data));
fail_if (data->len != 0);
gst_object_unref (bin);
gst_buffer_unref (buffer);
g_byte_array_unref (data);
}
GST_END_TEST;
static Suite * static Suite *
gst_buffermeta_suite (void) gst_buffermeta_suite (void)
{ {
@ -827,6 +874,7 @@ gst_buffermeta_suite (void)
tcase_add_test (tc_chain, test_meta_seqnum); tcase_add_test (tc_chain, test_meta_seqnum);
tcase_add_test (tc_chain, test_meta_custom); tcase_add_test (tc_chain, test_meta_custom);
tcase_add_test (tc_chain, test_meta_custom_transform); tcase_add_test (tc_chain, test_meta_custom_transform);
tcase_add_test (tc_chain, test_meta_custom_serialize);
return s; return s;
} }