codec-utils: Add utilities for Opus caps and the OpusHead header

https://bugzilla.gnome.org/show_bug.cgi?id=757152
This commit is contained in:
Sebastian Dröge 2015-11-03 18:30:09 +02:00
parent 0fa8d284c7
commit bcd7b2fff2
5 changed files with 544 additions and 0 deletions

View file

@ -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
<SUBSECTION>
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
</SECTION>
<SECTION>

View file

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

View file

@ -4,6 +4,7 @@
* 2010 Collabora Multimedia
* 2010 Nokia Corporation
* 2013 Intel Corporation
* 2015 Sebastian Dröge <sebastian@centricular.com>
*
* 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 <gst/base/base.h>
#include <gst/tag/tag.h>
#include <string.h>
@ -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;
}

View file

@ -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__ */

View file

@ -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