From bcd7b2fff28ba6e99758785c553d0cc29263346c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 3 Nov 2015 18:30:09 +0200 Subject: [PATCH] codec-utils: Add utilities for Opus caps and the OpusHead header https://bugzilla.gnome.org/show_bug.cgi?id=757152 --- docs/libs/gst-plugins-base-libs-sections.txt | 6 + gst-libs/gst/pbutils/Makefile.am | 1 + gst-libs/gst/pbutils/codec-utils.c | 495 +++++++++++++++++++ gst-libs/gst/pbutils/codec-utils.h | 37 ++ win32/common/libgstpbutils.def | 5 + 5 files changed, 544 insertions(+) diff --git a/docs/libs/gst-plugins-base-libs-sections.txt b/docs/libs/gst-plugins-base-libs-sections.txt index 5473ca358c..e437882f03 100644 --- a/docs/libs/gst-plugins-base-libs-sections.txt +++ b/docs/libs/gst-plugins-base-libs-sections.txt @@ -2180,6 +2180,12 @@ gst_codec_utils_h265_caps_set_level_tier_and_profile gst_codec_utils_mpeg4video_get_profile gst_codec_utils_mpeg4video_get_level gst_codec_utils_mpeg4video_caps_set_level_and_profile + +gst_codec_utils_opus_create_caps +gst_codec_utils_opus_create_caps_from_header +gst_codec_utils_opus_parse_caps +gst_codec_utils_opus_create_header +gst_codec_utils_opus_parse_header
diff --git a/gst-libs/gst/pbutils/Makefile.am b/gst-libs/gst/pbutils/Makefile.am index c941195cb0..fc86a5f663 100644 --- a/gst-libs/gst/pbutils/Makefile.am +++ b/gst-libs/gst/pbutils/Makefile.am @@ -48,6 +48,7 @@ noinst_HEADERS = \ libgstpbutils_@GST_API_VERSION@_la_LIBADD = \ $(top_builddir)/gst-libs/gst/video/libgstvideo-@GST_API_VERSION@.la \ $(top_builddir)/gst-libs/gst/audio/libgstaudio-@GST_API_VERSION@.la \ + $(top_builddir)/gst-libs/gst/tag/libgsttag-@GST_API_VERSION@.la \ $(GST_LIBS) libgstpbutils_@GST_API_VERSION@_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) libgstpbutils_@GST_API_VERSION@_la_LDFLAGS = $(GST_LIB_LDFLAGS) $(GST_ALL_LDFLAGS) $(GST_LT_LDFLAGS) diff --git a/gst-libs/gst/pbutils/codec-utils.c b/gst-libs/gst/pbutils/codec-utils.c index 8d9848113c..18aefcc805 100644 --- a/gst-libs/gst/pbutils/codec-utils.c +++ b/gst-libs/gst/pbutils/codec-utils.c @@ -4,6 +4,7 @@ * 2010 Collabora Multimedia * 2010 Nokia Corporation * 2013 Intel Corporation + * 2015 Sebastian Dröge * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -38,6 +39,8 @@ #endif #include "pbutils.h" +#include +#include #include @@ -1100,3 +1103,495 @@ gst_codec_utils_mpeg4video_caps_set_level_and_profile (GstCaps * caps, return (profile != NULL && level != NULL); } + +/** + * gst_codec_utils_opus_parse_caps: + * @caps: the #GstCaps to which the level and profile are to be added + * @rate: the sample rate + * @channels: the number of channels + * @channel_mapping_family: the channel mapping family + * @stream_count: the number of independent streams + * @coupled_count: the number of stereo streams + * @channel_mapping: the mapping between the streams + * + * Parses Opus caps and fills the different fields with defaults if possible. + * + * Returns: %TRUE if parsing was successful, %FALSE otherwise. + * + * Since: 1.8 + */ +gboolean +gst_codec_utils_opus_parse_caps (GstCaps * caps, + guint32 * rate, + guint8 * channels, + guint8 * channel_mapping_family, + guint8 * stream_count, guint8 * coupled_count, guint8 channel_mapping[256]) +{ + GstStructure *s; + gint c, f, sc, cc; + const GValue *va, *v; + + g_return_val_if_fail (caps != NULL, FALSE); + g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE); + g_return_val_if_fail (!gst_caps_is_empty (caps), FALSE); + + s = gst_caps_get_structure (caps, 0); + + g_return_val_if_fail (gst_structure_has_name (s, "audio/x-opus"), FALSE); + g_return_val_if_fail (gst_structure_has_field_typed (s, + "channel-mapping-family", G_TYPE_INT), FALSE); + + if (rate) { + gint r; + + if (gst_structure_get_int (s, "rate", &r)) + *rate = r; + else + *rate = 48000; + } + + gst_structure_get_int (s, "channel-mapping-family", &f); + if (channel_mapping_family) + *channel_mapping_family = f; + + if (!gst_structure_get_int (s, "channels", &c)) { + if (f == 0) + c = 2; + else + return FALSE; + } + + if (channels) + *channels = c; + + /* RTP mapping */ + if (f == 0) { + if (c > 2) + return FALSE; + + if (stream_count) + *stream_count = 1; + if (coupled_count) + *coupled_count = c == 2 ? 1 : 0; + + if (channel_mapping) { + channel_mapping[0] = 0; + channel_mapping[1] = 1; + } + + return TRUE; + } + + if (!gst_structure_get_int (s, "stream-count", &sc)) + return FALSE; + if (stream_count) + *stream_count = sc; + + if (!gst_structure_get_int (s, "coupled-count", &cc)) + return FALSE; + if (coupled_count) + *coupled_count = cc; + + va = gst_structure_get_value (s, "channel-mapping"); + if (!va || !G_VALUE_HOLDS (va, GST_TYPE_ARRAY)) + return FALSE; + + if (gst_value_array_get_size (va) != c) + return FALSE; + + if (channel_mapping) { + gint i; + + for (i = 0; i < c; i++) { + gint cm; + + v = gst_value_array_get_value (va, i); + + if (!G_VALUE_HOLDS (v, G_TYPE_INT)) + return FALSE; + + cm = g_value_get_int (v); + if (cm < 0 || cm > 255) + return FALSE; + + channel_mapping[i] = cm; + } + } + + return TRUE; +} + +/** + * gst_codec_utils_opus_create_caps: + * @rate: the sample rate + * @channels: the number of channels + * @channel_mapping_family: the channel mapping family + * @stream_count: the number of independent streams + * @coupled_count: the number of stereo streams + * @channel_mapping: (allow-none): the mapping between the streams + * + * Creates Opus caps from the given parameters. + * + * Returns: The #GstCaps. + * + * Since: 1.8 + */ +GstCaps * +gst_codec_utils_opus_create_caps (guint32 rate, + guint8 channels, + guint8 channel_mapping_family, + guint8 stream_count, guint8 coupled_count, const guint8 * channel_mapping) +{ + GstCaps *caps; + GValue va = G_VALUE_INIT; + GValue v = G_VALUE_INIT; + gint i; + + if (rate == 0) + rate = 48000; + + if (channel_mapping_family == 0) { + g_return_val_if_fail (channels <= 2, NULL); + if (channels == 0) + channels = 2; + + g_return_val_if_fail (stream_count == 0 || stream_count == 1, NULL); + if (stream_count == 0) + stream_count = 1; + + g_return_val_if_fail (coupled_count == 0 || coupled_count == 1, NULL); + if (coupled_count == 0) + coupled_count = channels == 2 ? 1 : 0; + + return gst_caps_new_simple ("audio/x-opus", + "rate", G_TYPE_INT, rate, + "channels", G_TYPE_INT, channels, + "channel-mapping-family", G_TYPE_INT, channel_mapping_family, + "stream-count", G_TYPE_INT, stream_count, + "coupled-count", G_TYPE_INT, coupled_count, NULL); + } + + g_return_val_if_fail (channels > 0 && channels < 256, NULL); + g_return_val_if_fail (stream_count > 0, NULL); + g_return_val_if_fail (coupled_count <= stream_count, NULL); + g_return_val_if_fail (channel_mapping != NULL, NULL); + + caps = gst_caps_new_simple ("audio/x-opus", + "rate", G_TYPE_INT, rate, + "channels", G_TYPE_INT, channels, + "channel-mapping-family", G_TYPE_INT, channel_mapping_family, + "stream-count", G_TYPE_INT, stream_count, + "coupled-count", G_TYPE_INT, coupled_count, NULL); + + g_value_init (&va, GST_TYPE_ARRAY); + g_value_init (&v, G_TYPE_INT); + for (i = 0; i < channels; i++) { + g_value_set_int (&v, channel_mapping[i]); + gst_value_array_append_value (&va, &v); + } + gst_structure_set_value (gst_caps_get_structure (caps, 0), "channel-mapping", + &va); + g_value_unset (&va); + g_value_unset (&v); + + return caps; +} + +/* + * (really really) FIXME: move into core (dixit tpm) + */ +/* + * _gst_caps_set_buffer_array: + * @caps: (transfer full): a #GstCaps + * @field: field in caps to set + * @buf: header buffers + * + * Adds given buffers to an array of buffers set as the given @field + * on the given @caps. List of buffer arguments must be NULL-terminated. + * + * Returns: (transfer full): input caps with a streamheader field added, or NULL + * if some error occurred + */ +static GstCaps * +_gst_caps_set_buffer_array (GstCaps * caps, const gchar * field, + GstBuffer * buf, ...) +{ + GstStructure *structure = NULL; + va_list va; + GValue array = { 0 }; + GValue value = { 0 }; + + g_return_val_if_fail (caps != NULL, NULL); + g_return_val_if_fail (gst_caps_is_fixed (caps), NULL); + g_return_val_if_fail (field != NULL, NULL); + + caps = gst_caps_make_writable (caps); + structure = gst_caps_get_structure (caps, 0); + + g_value_init (&array, GST_TYPE_ARRAY); + + va_start (va, buf); + /* put buffers in a fixed list */ + while (buf) { + g_assert (gst_buffer_is_writable (buf)); + + /* mark buffer */ + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER); + + g_value_init (&value, GST_TYPE_BUFFER); + buf = gst_buffer_copy (buf); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER); + gst_value_set_buffer (&value, buf); + gst_buffer_unref (buf); + gst_value_array_append_value (&array, &value); + g_value_unset (&value); + + buf = va_arg (va, GstBuffer *); + } + va_end (va); + + gst_structure_set_value (structure, field, &array); + g_value_unset (&array); + + return caps; +} + +/** + * gst_codec_utils_opus_create_caps_from_header: + * @header: OpusHead header + * @comments: (allow-none): Comment header or NULL + * + * Creates Opus caps from the given OpusHead @header and comment header + * @comments. + * + * Returns: The #GstCaps. + * + * Since: 1.8 + */ +GstCaps * +gst_codec_utils_opus_create_caps_from_header (GstBuffer * header, + GstBuffer * comments) +{ + GstCaps *caps; + guint32 rate; + guint8 channels; + guint8 channel_mapping_family; + guint8 stream_count; + guint8 coupled_count; + guint8 channel_mapping[256]; + GstBuffer *dummy_comments = NULL; + + g_return_val_if_fail (GST_IS_BUFFER (header), NULL); + g_return_val_if_fail (comments == NULL || GST_IS_BUFFER (comments), NULL); + + if (!gst_codec_utils_opus_parse_header (header, &rate, &channels, + &channel_mapping_family, &stream_count, &coupled_count, + channel_mapping, NULL, NULL)) + return NULL; + + caps = + gst_codec_utils_opus_create_caps (rate, channels, channel_mapping_family, + stream_count, coupled_count, channel_mapping); + + if (!comments) { + GstTagList *tags = gst_tag_list_new_empty (); + dummy_comments = + gst_tag_list_to_vorbiscomment_buffer (tags, (const guint8 *) "OpusTags", + 8, NULL); + gst_tag_list_unref (tags); + } + _gst_caps_set_buffer_array (caps, "streamheader", header, + comments ? comments : dummy_comments, NULL); + + if (dummy_comments) + gst_buffer_unref (dummy_comments); + + return caps; +} + +/** + * gst_codec_utils_opus_create_header: + * @rate: the sample rate + * @channels: the number of channels + * @channel_mapping_family: the channel mapping family + * @stream_count: the number of independent streams + * @coupled_count: the number of stereo streams + * @channel_mapping: (allow-none): the mapping between the streams + * @pre_skip: Pre-skip in 48kHz samples or 0 + * @output_gain: Output gain or 0 + * + * Creates OpusHead header from the given parameters. + * + * Returns: The #GstBuffer containing the OpusHead. + * + * Since: 1.8 + */ +GstBuffer * +gst_codec_utils_opus_create_header (guint32 rate, + guint8 channels, + guint8 channel_mapping_family, + guint8 stream_count, + guint8 coupled_count, + const guint8 * channel_mapping, guint16 pre_skip, gint16 output_gain) +{ + GstBuffer *buffer; + GstByteWriter bw; + gboolean hdl = TRUE; + + if (rate == 0) + rate = 48000; + + if (channel_mapping_family == 0) { + g_return_val_if_fail (channels <= 2, NULL); + if (channels == 0) + channels = 2; + + g_return_val_if_fail (stream_count == 0 || stream_count == 1, NULL); + if (stream_count == 0) + stream_count = 1; + + g_return_val_if_fail (coupled_count == 0 || coupled_count == 1, NULL); + if (coupled_count == 0) + coupled_count = channels == 2 ? 1 : 0; + + channel_mapping = NULL; + } else { + g_return_val_if_fail (channels > 0 && channels < 256, NULL); + g_return_val_if_fail (stream_count > 0, NULL); + g_return_val_if_fail (coupled_count <= stream_count, NULL); + g_return_val_if_fail (channel_mapping != NULL, NULL); + } + + gst_byte_writer_init (&bw); + /* See http://wiki.xiph.org/OggOpus */ + hdl &= gst_byte_writer_put_data (&bw, (const guint8 *) "OpusHead", 8); + hdl &= gst_byte_writer_put_uint8 (&bw, 0x01); /* version number */ + hdl &= gst_byte_writer_put_uint8 (&bw, channels); + hdl &= gst_byte_writer_put_uint16_le (&bw, pre_skip); + hdl &= gst_byte_writer_put_uint32_le (&bw, rate); + hdl &= gst_byte_writer_put_uint16_le (&bw, output_gain); + hdl &= gst_byte_writer_put_uint8 (&bw, channel_mapping_family); + if (channel_mapping_family > 0) { + hdl &= gst_byte_writer_put_uint8 (&bw, stream_count); + hdl &= gst_byte_writer_put_uint8 (&bw, coupled_count); + hdl &= gst_byte_writer_put_data (&bw, channel_mapping, channels); + } + + if (!hdl) { + GST_WARNING ("Error creating header"); + return NULL; + } + + buffer = gst_byte_writer_reset_and_get_buffer (&bw); + GST_BUFFER_OFFSET (buffer) = 0; + GST_BUFFER_OFFSET_END (buffer) = 0; + + return buffer; +} + +/** + * gst_codec_utils_opus_parse_header: + * @header: the OpusHead #GstBuffer + * @rate: the sample rate + * @channels: the number of channels + * @channel_mapping_family: the channel mapping family + * @stream_count: the number of independent streams + * @coupled_count: the number of stereo streams + * @channel_mapping: the mapping between the streams + * @pre_skip: Pre-skip in 48kHz samples or 0 + * @output_gain: Output gain or 0 + * + * Parses the OpusHead header. + * + * Returns: %TRUE if parsing was successful, %FALSE otherwise. + * + * Since: 1.8 + */ +gboolean +gst_codec_utils_opus_parse_header (GstBuffer * header, + guint32 * rate, + guint8 * channels, + guint8 * channel_mapping_family, + guint8 * stream_count, + guint8 * coupled_count, + guint8 channel_mapping[256], guint16 * pre_skip, gint16 * output_gain) +{ + GstByteReader br; + GstMapInfo map; + gboolean ret = TRUE; + guint8 c, f; + + g_return_val_if_fail (GST_IS_BUFFER (header), FALSE); + g_return_val_if_fail (gst_buffer_get_size (header) >= 19, FALSE); + + if (!gst_buffer_map (header, &map, GST_MAP_READ)) + return FALSE; + gst_byte_reader_init (&br, map.data, map.size); + /* See http://wiki.xiph.org/OggOpus */ + if (memcmp (gst_byte_reader_get_data_unchecked (&br, 8), "OpusHead", 8) != 0) { + ret = FALSE; + goto done; + } + if (gst_byte_reader_get_uint8_unchecked (&br) != 0x01) { + ret = FALSE; + goto done; + } + + c = gst_byte_reader_get_uint8_unchecked (&br); + if (channels) + *channels = c; + + if (pre_skip) + *pre_skip = gst_byte_reader_get_uint16_le_unchecked (&br); + else + gst_byte_reader_skip_unchecked (&br, 2); + + if (rate) + *rate = gst_byte_reader_get_uint32_le_unchecked (&br); + else + gst_byte_reader_skip_unchecked (&br, 4); + + if (output_gain) + *output_gain = gst_byte_reader_get_uint16_le_unchecked (&br); + else + gst_byte_reader_skip_unchecked (&br, 2); + + f = gst_byte_reader_get_uint8_unchecked (&br); + if (channel_mapping_family) + *channel_mapping_family = f; + if (f == 0 && c <= 2) { + if (stream_count) + *stream_count = 1; + if (coupled_count) + *coupled_count = c == 2 ? 1 : 0; + if (channel_mapping) { + channel_mapping[0] = 0; + channel_mapping[1] = 1; + } + + goto done; + } + + if (gst_byte_reader_get_remaining (&br) < 2 + c) { + ret = FALSE; + goto done; + } + + if (stream_count) + *stream_count = gst_byte_reader_get_uint8_unchecked (&br); + else + gst_byte_reader_skip_unchecked (&br, 1); + + if (coupled_count) + *coupled_count = gst_byte_reader_get_uint8_unchecked (&br); + else + gst_byte_reader_skip_unchecked (&br, 1); + + if (channel_mapping) + memcpy (channel_mapping, gst_byte_reader_get_data_unchecked (&br, c), c); + +done: + gst_buffer_unmap (header, &map); + + return ret; +} diff --git a/gst-libs/gst/pbutils/codec-utils.h b/gst-libs/gst/pbutils/codec-utils.h index 0ef5bbdf44..e1def83eb7 100644 --- a/gst-libs/gst/pbutils/codec-utils.h +++ b/gst-libs/gst/pbutils/codec-utils.h @@ -77,6 +77,43 @@ gboolean gst_codec_utils_mpeg4video_caps_set_level_and_profile (GstCaps const guint8 * vis_obj_seq, guint len); +/* Opus */ +gboolean gst_codec_utils_opus_parse_caps (GstCaps * caps, + guint32 * rate, + guint8 * channels, + guint8 * channel_mapping_family, + guint8 * stream_count, + guint8 * coupled_count, + guint8 channel_mapping[256]); + +GstCaps * gst_codec_utils_opus_create_caps (guint32 rate, + guint8 channels, + guint8 channel_mapping_family, + guint8 stream_count, + guint8 coupled_count, + const guint8 * channel_mapping); + +GstCaps * gst_codec_utils_opus_create_caps_from_header (GstBuffer * header, GstBuffer * comments); + +GstBuffer * gst_codec_utils_opus_create_header (guint32 rate, + guint8 channels, + guint8 channel_mapping_family, + guint8 stream_count, + guint8 coupled_count, + const guint8 * channel_mapping, + guint16 pre_skip, + gint16 output_gain); + +gboolean gst_codec_utils_opus_parse_header (GstBuffer * header, + guint32 * rate, + guint8 * channels, + guint8 * channel_mapping_family, + guint8 * stream_count, + guint8 * coupled_count, + guint8 channel_mapping[256], + guint16 * pre_skip, + gint16 * output_gain); + G_END_DECLS #endif /* __GST_PB_UTILS_CODEC_UTILS_H__ */ diff --git a/win32/common/libgstpbutils.def b/win32/common/libgstpbutils.def index 6c88752490..785bfb4d6e 100644 --- a/win32/common/libgstpbutils.def +++ b/win32/common/libgstpbutils.def @@ -18,6 +18,11 @@ EXPORTS gst_codec_utils_mpeg4video_caps_set_level_and_profile gst_codec_utils_mpeg4video_get_level gst_codec_utils_mpeg4video_get_profile + gst_codec_utils_opus_create_caps + gst_codec_utils_opus_create_caps_from_header + gst_codec_utils_opus_create_header + gst_codec_utils_opus_parse_caps + gst_codec_utils_opus_parse_header gst_discoverer_audio_info_get_bitrate gst_discoverer_audio_info_get_channels gst_discoverer_audio_info_get_depth