diff --git a/subprojects/gst-plugins-base/gst-libs/gst/audio/gstaudiometa.c b/subprojects/gst-plugins-base/gst-libs/gst/audio/gstaudiometa.c index 21d1d82db2..647eab446b 100644 --- a/subprojects/gst-plugins-base/gst-libs/gst/audio/gstaudiometa.c +++ b/subprojects/gst-plugins-base/gst-libs/gst/audio/gstaudiometa.c @@ -30,10 +30,11 @@ #include "config.h" #endif -#include - #include "gstaudiometa.h" +#include +#include + static gboolean gst_audio_downmix_meta_init (GstMeta * meta, gpointer params, GstBuffer * buffer) @@ -351,6 +352,111 @@ gst_audio_meta_transform (GstBuffer * dest, GstMeta * meta, 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: * @buffer: a #GstBuffer @@ -482,11 +588,14 @@ gst_audio_meta_get_info (void) static const GstMetaInfo *audio_meta_info = NULL; 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), gst_audio_meta_init, 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, (GstMetaInfo *) meta); } diff --git a/subprojects/gst-plugins-base/gst-libs/gst/audio/gstaudiometa.h b/subprojects/gst-plugins-base/gst-libs/gst/audio/gstaudiometa.h index 94954d1940..5e54793f41 100644 --- a/subprojects/gst-plugins-base/gst-libs/gst/audio/gstaudiometa.h +++ b/subprojects/gst-plugins-base/gst-libs/gst/audio/gstaudiometa.h @@ -170,6 +170,9 @@ typedef struct _GstAudioMeta GstAudioMeta; * case to do so. It is, however, allowed to attach it, for some potential * future use case. * + * Since 1.24 it can be serialized using gst_meta_serialize() and + * gst_meta_deserialize(). + * * Since: 1.16 */ struct _GstAudioMeta { diff --git a/subprojects/gst-plugins-base/gst-libs/gst/video/gstvideometa.c b/subprojects/gst-plugins-base/gst-libs/gst/video/gstvideometa.c index f9f1a156a0..d450c40bbe 100644 --- a/subprojects/gst-plugins-base/gst-libs/gst/video/gstvideometa.c +++ b/subprojects/gst-plugins-base/gst-libs/gst/video/gstvideometa.c @@ -23,6 +23,7 @@ #include "gstvideometa.h" #include +#include /** * 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 #define GST_CAT_DEFAULT ensure_debug_category() static GstDebugCategory * @@ -134,6 +141,110 @@ gst_video_meta_api_get_type (void) 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 */ const GstMetaInfo * 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)) { 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, - (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, (GstMetaInfo *) meta); } @@ -310,8 +422,8 @@ gst_buffer_add_video_meta (GstBuffer * buffer, GstVideoMeta * gst_buffer_add_video_meta_full (GstBuffer * buffer, GstVideoFrameFlags flags, GstVideoFormat format, guint width, - guint height, guint n_planes, gsize offset[GST_VIDEO_MAX_PLANES], - gint stride[GST_VIDEO_MAX_PLANES]) + guint height, guint n_planes, const gsize offset[GST_VIDEO_MAX_PLANES], + const gint stride[GST_VIDEO_MAX_PLANES]) { GstVideoMeta *meta; guint i; diff --git a/subprojects/gst-plugins-base/gst-libs/gst/video/gstvideometa.h b/subprojects/gst-plugins-base/gst-libs/gst/video/gstvideometa.h index 8c8436b68c..da6dc440de 100644 --- a/subprojects/gst-plugins-base/gst-libs/gst/video/gstvideometa.h +++ b/subprojects/gst-plugins-base/gst-libs/gst/video/gstvideometa.h @@ -74,6 +74,9 @@ typedef struct _GstVideoCropMeta GstVideoCropMeta; * - padding-right (uint): extra pixels on the right side * The padding fields have the same semantic as #GstVideoMeta.alignment * 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 { GstMeta meta; @@ -116,8 +119,8 @@ GstVideoMeta * gst_buffer_add_video_meta (GstBuffer *buffer, GstVideoFrame GST_VIDEO_API GstVideoMeta * gst_buffer_add_video_meta_full (GstBuffer *buffer, GstVideoFrameFlags flags, GstVideoFormat format, guint width, guint height, - guint n_planes, gsize offset[GST_VIDEO_MAX_PLANES], - gint stride[GST_VIDEO_MAX_PLANES]); + guint n_planes, const gsize offset[GST_VIDEO_MAX_PLANES], + const gint stride[GST_VIDEO_MAX_PLANES]); GST_VIDEO_API gboolean gst_video_meta_map (GstVideoMeta *meta, guint plane, GstMapInfo *info, diff --git a/subprojects/gst-plugins-base/tests/check/libs/audio.c b/subprojects/gst-plugins-base/tests/check/libs/audio.c index 1d570073f3..9162def7cc 100644 --- a/subprojects/gst-plugins-base/tests/check/libs/audio.c +++ b/subprojects/gst-plugins-base/tests/check/libs/audio.c @@ -1578,6 +1578,106 @@ GST_START_TEST (test_audio_make_raw_caps) 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 * 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_info_from_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; } diff --git a/subprojects/gst-plugins-base/tests/check/libs/video.c b/subprojects/gst-plugins-base/tests/check/libs/video.c index 32306dd5b1..7509e83909 100644 --- a/subprojects/gst-plugins-base/tests/check/libs/video.c +++ b/subprojects/gst-plugins-base/tests/check/libs/video.c @@ -4215,6 +4215,70 @@ GST_START_TEST (test_info_dma_drm) 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 * 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_video_color_primaries_equivalent); tcase_add_test (tc_chain, test_info_dma_drm); + tcase_add_test (tc_chain, test_video_meta_serialize); return s; } diff --git a/subprojects/gstreamer/gst/gst.h b/subprojects/gstreamer/gst/gst.h index 7bd59cd8a4..25010b3631 100644 --- a/subprojects/gstreamer/gst/gst.h +++ b/subprojects/gstreamer/gst/gst.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include diff --git a/subprojects/gstreamer/gst/gstbuffer.c b/subprojects/gstreamer/gst/gstbuffer.c index d4e65066dc..ebe2d9b09c 100644 --- a/subprojects/gstreamer/gst/gstbuffer.c +++ b/subprojects/gstreamer/gst/gstbuffer.c @@ -2860,6 +2860,49 @@ gst_reference_timestamp_meta_api_get_type (void) 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: * @@ -2876,12 +2919,15 @@ gst_reference_timestamp_meta_get_info (void) if (g_once_init_enter ((GstMetaInfo **) & meta_info)) { 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", sizeof (GstReferenceTimestampMeta), (GstMetaInitFunction) _gst_reference_timestamp_meta_init, (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); } diff --git a/subprojects/gstreamer/gst/gstbuffer.h b/subprojects/gstreamer/gst/gstbuffer.h index 6a8b84fc69..a1896fe247 100644 --- a/subprojects/gstreamer/gst/gstbuffer.h +++ b/subprojects/gstreamer/gst/gstbuffer.h @@ -763,6 +763,9 @@ typedef struct _GstReferenceTimestampMeta GstReferenceTimestampMeta; * * `timestamp/x-unix`: for timestamps based on the UNIX epoch according to * the local clock. * + * Since 1.24 it can be serialized using gst_meta_serialize() and + * gst_meta_deserialize(). + * * Since: 1.14 */ struct _GstReferenceTimestampMeta diff --git a/subprojects/gstreamer/gst/gstbytearrayinterface.h b/subprojects/gstreamer/gst/gstbytearrayinterface.h new file mode 100644 index 0000000000..0ffc8ed77e --- /dev/null +++ b/subprojects/gstreamer/gst/gstbytearrayinterface.h @@ -0,0 +1,129 @@ +/* Copyright (C) 2023 Netflix Inc. + * Author: Xavier Claessens + * + * 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 +#include + +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` or Rust's `Vec`. + * + * @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 diff --git a/subprojects/gstreamer/gst/gstmeta.c b/subprojects/gstreamer/gst/gstmeta.c index 1b395c9fee..e02f3a8127 100644 --- a/subprojects/gstreamer/gst/gstmeta.c +++ b/subprojects/gstreamer/gst/gstmeta.c @@ -193,6 +193,46 @@ custom_transform_func (GstBuffer * transbuf, GstMeta * meta, 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: * @@ -274,9 +314,10 @@ gst_meta_register_custom (const gchar * name, const gchar ** tags, if (api == G_TYPE_INVALID) goto done; - info = (GstMetaInfoImpl *) gst_meta_register (api, name, + info = (GstMetaInfoImpl *) gst_meta_register_serializable (api, name, 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) goto done; @@ -364,28 +405,12 @@ gst_meta_api_type_get_tags (GType api) return (const gchar * const *) tags; } -/** - * 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, +static const GstMetaInfo * +gst_meta_register_internal (GType api, const gchar * impl, gsize size, GstMetaInitFunction init_func, GstMetaFreeFunction free_func, - GstMetaTransformFunction transform_func) + GstMetaTransformFunction transform_func, + GstMetaSerializeFunction serialize_func, + GstMetaDeserializeFunction deserialize_func) { GstMetaInfo *info; GType type; @@ -412,6 +437,8 @@ gst_meta_register (GType api, const gchar * impl, gsize size, info->init_func = init_func; info->free_func = free_func; info->transform_func = transform_func; + info->serialize_func = serialize_func; + info->deserialize_func = deserialize_func; ((GstMetaInfoImpl *) info)->is_custom = FALSE; GST_CAT_DEBUG (GST_CAT_META, @@ -426,6 +453,62 @@ gst_meta_register (GType api, const gchar * impl, gsize size, 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: * @impl: the name @@ -497,3 +580,180 @@ gst_meta_compare_seqnum (const GstMeta * meta1, const GstMeta * meta2) 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; +} diff --git a/subprojects/gstreamer/gst/gstmeta.h b/subprojects/gstreamer/gst/gstmeta.h index 708d76b80d..5314995e00 100644 --- a/subprojects/gstreamer/gst/gstmeta.h +++ b/subprojects/gstreamer/gst/gstmeta.h @@ -25,6 +25,9 @@ #include +#include + + G_BEGIN_DECLS typedef struct _GstMeta GstMeta; @@ -132,6 +135,10 @@ struct _GstMeta { * Extra custom metadata. The @structure field is the same as returned by * 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 */ 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) - /** * GstMetaTransformFunction: * @transbuf: a #GstBuffer @@ -258,6 +264,61 @@ typedef gboolean (*GstCustomMetaTransformFunction) (GstBuffer *transbuf, GstCustomMeta *meta, GstBuffer *buffer, 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: * @api: tag identifying the metadata structure and api @@ -266,6 +327,10 @@ typedef gboolean (*GstCustomMetaTransformFunction) (GstBuffer *transbuf, * @init_func: function for initializing the metadata * @free_func: function for freeing 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 * structure. @@ -278,6 +343,8 @@ struct _GstMetaInfo { GstMetaInitFunction init_func; GstMetaFreeFunction free_func; GstMetaTransformFunction transform_func; + GstMetaSerializeFunction serialize_func; + GstMetaDeserializeFunction deserialize_func; /* No padding needed, GstMetaInfo is always allocated by GStreamer and is * 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, 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 const GstMetaInfo * gst_meta_register_custom (const gchar *name, const gchar **tags, GstCustomMetaTransformFunction transform_func, @@ -327,6 +403,18 @@ GST_API gint gst_meta_compare_seqnum (const GstMeta * meta1, 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 */ GST_API GQuark _gst_meta_tag_memory; diff --git a/subprojects/gstreamer/gst/meson.build b/subprojects/gstreamer/gst/meson.build index e2317d8be1..03f4996731 100644 --- a/subprojects/gstreamer/gst/meson.build +++ b/subprojects/gstreamer/gst/meson.build @@ -82,6 +82,7 @@ gst_headers = files( 'gstbufferlist.h', 'gstbufferpool.h', 'gstbus.h', + 'gstbytearrayinterface.h', 'gstcaps.h', 'gstcapsfeatures.h', 'gstchildproxy.h', diff --git a/subprojects/gstreamer/tests/check/gst/gstbuffer.c b/subprojects/gstreamer/tests/check/gst/gstbuffer.c index 647dcb6ddd..dbb8478bfd 100644 --- a/subprojects/gstreamer/tests/check/gst/gstbuffer.c +++ b/subprojects/gstreamer/tests/check/gst/gstbuffer.c @@ -258,7 +258,7 @@ GST_START_TEST (test_metadata_writable) ASSERT_BUFFER_REFCOUNT (buffer, "buffer", 2); 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. */ sub1 = gst_buffer_make_writable (buffer); fail_if (sub1 == buffer); @@ -967,6 +967,37 @@ GST_START_TEST (test_auto_unmap) 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 * 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_new_memdup); tcase_add_test (tc_chain, test_auto_unmap); + tcase_add_test (tc_chain, test_reference_timestamp_meta_serialization); return s; } diff --git a/subprojects/gstreamer/tests/check/gst/gstmeta.c b/subprojects/gstreamer/tests/check/gst/gstmeta.c index b5d0392076..7228c99e45 100644 --- a/subprojects/gstreamer/tests/check/gst/gstmeta.c +++ b/subprojects/gstreamer/tests/check/gst/gstmeta.c @@ -808,6 +808,53 @@ GST_START_TEST (test_meta_custom_transform) 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 * 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_custom); tcase_add_test (tc_chain, test_meta_custom_transform); + tcase_add_test (tc_chain, test_meta_custom_serialize); return s; }