opus: Add proper support for multichannel audio

https://bugzilla.gnome.org/show_bug.cgi?id=757152
This commit is contained in:
Sebastian Dröge 2015-11-03 14:50:53 +02:00
parent 4c8f76f05b
commit 51edbeb9d9
10 changed files with 214 additions and 401 deletions

View file

@ -10,6 +10,7 @@ libgstopus_la_CFLAGS = \
libgstopus_la_LIBADD = \
$(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_API_VERSION) \
-lgsttag-$(GST_API_VERSION) -lgstrtp-$(GST_API_VERSION) \
-lgstpbutils-$(GST_API_VERSION) \
$(GST_BASE_LIBS) \
$(GST_LIBS) \
$(OPUS_LIBS)

View file

@ -47,6 +47,7 @@
#include "gstopusheader.h"
#include "gstopuscommon.h"
#include "gstopusdec.h"
#include <gst/pbutils/pbutils.h>
GST_DEBUG_CATEGORY_STATIC (opusdec_debug);
#define GST_CAT_DEFAULT opusdec_debug
@ -225,6 +226,8 @@ gst_opus_dec_negotiate (GstOpusDec * dec, const GstAudioChannelPosition * pos)
GstAudioInfo info;
if (caps) {
gint rate, channels;
caps = gst_caps_truncate (caps);
caps = gst_caps_make_writable (caps);
s = gst_caps_get_structure (caps, 0);
@ -233,13 +236,15 @@ gst_opus_dec_negotiate (GstOpusDec * dec, const GstAudioChannelPosition * pos)
gst_structure_fixate_field_nearest_int (s, "rate", dec->sample_rate);
else
gst_structure_set (s, "rate", G_TYPE_INT, dec->sample_rate, NULL);
gst_structure_get_int (s, "rate", &dec->sample_rate);
gst_structure_get_int (s, "rate", &rate);
dec->sample_rate = rate;
if (gst_structure_has_field (s, "channels"))
gst_structure_fixate_field_nearest_int (s, "channels", dec->n_channels);
else
gst_structure_set (s, "channels", G_TYPE_INT, dec->n_channels, NULL);
gst_structure_get_int (s, "channels", &dec->n_channels);
gst_structure_get_int (s, "channels", &channels);
dec->n_channels = channels;
gst_caps_unref (caps);
}
@ -273,7 +278,6 @@ gst_opus_dec_negotiate (GstOpusDec * dec, const GstAudioChannelPosition * pos)
/* but we still need the opus order for later reordering */
if (pos) {
memcpy (dec->opus_pos, pos, sizeof (pos[0]) * dec->n_channels);
gst_audio_channel_positions_to_valid_order (dec->opus_pos, dec->n_channels);
} else {
dec->opus_pos[0] = GST_AUDIO_CHANNEL_POSITION_INVALID;
}
@ -284,79 +288,64 @@ gst_opus_dec_negotiate (GstOpusDec * dec, const GstAudioChannelPosition * pos)
static GstFlowReturn
gst_opus_dec_parse_header (GstOpusDec * dec, GstBuffer * buf)
{
const guint8 *data;
GstAudioChannelPosition pos[64];
const GstAudioChannelPosition *posn = NULL;
GstMapInfo map;
if (!gst_opus_header_is_id_header (buf)) {
GST_ERROR_OBJECT (dec, "Header is not an Opus ID header");
return GST_FLOW_ERROR;
}
gst_buffer_map (buf, &map, GST_MAP_READ);
data = map.data;
dec->n_channels = data[9];
dec->sample_rate = GST_READ_UINT32_LE (data + 12);
if (dec->sample_rate == 0)
dec->sample_rate = 48000;
dec->pre_skip = GST_READ_UINT16_LE (data + 10);
dec->r128_gain = GST_READ_UINT16_LE (data + 16);
if (!gst_codec_utils_opus_parse_header (buf,
&dec->sample_rate,
&dec->n_channels,
&dec->channel_mapping_family,
&dec->n_streams,
&dec->n_stereo_streams,
dec->channel_mapping, &dec->pre_skip, &dec->r128_gain)) {
GST_ERROR_OBJECT (dec, "Failed to parse Opus ID header");
return GST_FLOW_ERROR;
}
dec->r128_gain_volume = gst_opus_dec_get_r128_volume (dec->r128_gain);
GST_INFO_OBJECT (dec,
"Found pre-skip of %u samples, R128 gain %d (volume %f)",
dec->pre_skip, dec->r128_gain, dec->r128_gain_volume);
dec->channel_mapping_family = data[18];
if (dec->channel_mapping_family == 0) {
/* implicit mapping */
GST_INFO_OBJECT (dec, "Channel mapping family 0, implicit mapping");
dec->n_streams = dec->n_stereo_streams = 1;
dec->channel_mapping[0] = 0;
dec->channel_mapping[1] = 1;
} else {
dec->n_streams = data[19];
dec->n_stereo_streams = data[20];
memcpy (dec->channel_mapping, data + 21, dec->n_channels);
if (dec->channel_mapping_family == 1) {
GST_INFO_OBJECT (dec, "Channel mapping family 1, Vorbis mapping");
switch (dec->n_channels) {
case 1:
case 2:
/* nothing */
break;
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
posn = gst_opus_channel_positions[dec->n_channels - 1];
break;
default:{
gint i;
if (dec->channel_mapping_family == 1) {
GST_INFO_OBJECT (dec, "Channel mapping family 1, Vorbis mapping");
switch (dec->n_channels) {
case 1:
case 2:
/* nothing */
break;
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
posn = gst_opus_channel_positions[dec->n_channels - 1];
break;
default:{
gint i;
GST_ELEMENT_WARNING (GST_ELEMENT (dec), STREAM, DECODE,
(NULL), ("Using NONE channel layout for more than 8 channels"));
GST_ELEMENT_WARNING (GST_ELEMENT (dec), STREAM, DECODE,
(NULL), ("Using NONE channel layout for more than 8 channels"));
for (i = 0; i < dec->n_channels; i++)
pos[i] = GST_AUDIO_CHANNEL_POSITION_NONE;
for (i = 0; i < dec->n_channels; i++)
pos[i] = GST_AUDIO_CHANNEL_POSITION_NONE;
posn = pos;
}
posn = pos;
}
} else {
GST_INFO_OBJECT (dec, "Channel mapping family %d",
dec->channel_mapping_family);
}
} else {
GST_INFO_OBJECT (dec, "Channel mapping family %d",
dec->channel_mapping_family);
}
gst_opus_dec_negotiate (dec, posn);
gst_buffer_unmap (buf, &map);
return GST_FLOW_OK;
}
@ -660,8 +649,10 @@ gst_opus_dec_set_format (GstAudioDecoder * bdec, GstCaps * caps)
if (header && G_VALUE_HOLDS (header, GST_TYPE_BUFFER)) {
buf = gst_value_get_buffer (header);
res = gst_opus_dec_parse_header (dec, buf);
if (res != GST_FLOW_OK)
if (res != GST_FLOW_OK) {
ret = FALSE;
goto done;
}
gst_buffer_replace (&dec->streamheader, buf);
}
@ -669,26 +660,26 @@ gst_opus_dec_set_format (GstAudioDecoder * bdec, GstCaps * caps)
if (vorbiscomment && G_VALUE_HOLDS (vorbiscomment, GST_TYPE_BUFFER)) {
buf = gst_value_get_buffer (vorbiscomment);
res = gst_opus_dec_parse_comments (dec, buf);
if (res != GST_FLOW_OK)
if (res != GST_FLOW_OK) {
ret = FALSE;
goto done;
}
gst_buffer_replace (&dec->vorbiscomment, buf);
}
} else {
/* defaults if not in the caps */
dec->n_channels = 2;
dec->sample_rate = 48000;
const GstAudioChannelPosition *posn = NULL;
gst_structure_get_int (s, "channels", &dec->n_channels);
gst_structure_get_int (s, "rate", &dec->sample_rate);
if (!gst_codec_utils_opus_parse_caps (caps, &dec->sample_rate,
&dec->n_channels, &dec->channel_mapping_family, &dec->n_streams,
&dec->n_stereo_streams, dec->channel_mapping)) {
ret = FALSE;
goto done;
}
/* default stereo mapping */
dec->channel_mapping_family = 0;
dec->channel_mapping[0] = 0;
dec->channel_mapping[1] = 1;
dec->n_streams = 1;
dec->n_stereo_streams = 1;
if (dec->channel_mapping_family == 1 && dec->n_channels <= 8)
posn = gst_opus_channel_positions[dec->n_channels - 1];
gst_opus_dec_negotiate (dec, NULL);
gst_opus_dec_negotiate (dec, posn);
}
done:

View file

@ -52,9 +52,9 @@ struct _GstOpusDec {
GstBuffer *streamheader;
GstBuffer *vorbiscomment;
int sample_rate;
int n_channels;
guint32 pre_skip;
guint32 sample_rate;
guint8 n_channels;
guint16 pre_skip;
gint16 r128_gain;
GstAudioChannelPosition opus_pos[64];

View file

@ -48,6 +48,8 @@
#include <gst/gsttagsetter.h>
#include <gst/audio/audio.h>
#include <gst/pbutils/pbutils.h>
#include <gst/tag/tag.h>
#include <gst/glib-compat-private.h>
#include "gstopusheader.h"
#include "gstopuscommon.h"
@ -167,12 +169,12 @@ static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
"format = (string) " FORMAT_STR ", "
"layout = (string) interleaved, "
"rate = (int) 48000, "
"channels = (int) [ 1, 2 ]; "
"channels = (int) [ 1, 8 ]; "
"audio/x-raw, "
"format = (string) " FORMAT_STR ", "
"layout = (string) interleaved, "
"rate = (int) { 8000, 12000, 16000, 24000 }, "
"channels = (int) [ 1, 2 ] ")
"channels = (int) [ 1, 8 ] ")
);
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
@ -545,27 +547,20 @@ gst_opus_enc_setup_channel_mappings (GstOpusEnc * enc,
/* For two channels, use the basic RTP mapping if the channels are
mapped as left/right. */
if (enc->n_channels == 2) {
if (MAPS (0, FRONT_LEFT) && MAPS (1, FRONT_RIGHT)) {
GST_INFO_OBJECT (enc, "Stereo, canonical mapping");
enc->channel_mapping_family = 0;
enc->n_stereo_streams = 1;
/* The channel mapping is implicit for family 0, that's why we do not
attempt to create one for right/left - this will be mapped to the
Vorbis mapping below. */
return;
} else {
GST_DEBUG_OBJECT (enc, "Stereo, but not canonical mapping, continuing");
}
GST_INFO_OBJECT (enc, "Stereo, trivial RTP mapping");
enc->channel_mapping_family = 0;
enc->n_stereo_streams = 1;
/* implicit mapping for family 0 */
return;
}
/* For channels between 1 and 8, we use the Vorbis mapping if we can
find a permutation that matches it. Mono will have been taken care
of earlier, but this code also handles it. Same for left/right stereo.
There are two mappings. One maps the input channels to an ordering
which has the natural pairs first so they can benefit from the Opus
stereo channel coupling, and the other maps this ordering to the
Vorbis ordering. */
if (enc->n_channels >= 1 && enc->n_channels <= 8) {
/* For channels between 3 and 8, we use the Vorbis mapping if we can
find a permutation that matches it. Mono and stereo will have been taken
care of earlier, but this code also handles it. There are two mappings.
One maps the input channels to an ordering which has the natural pairs
first so they can benefit from the Opus stereo channel coupling, and the
other maps this ordering to the Vorbis ordering. */
if (enc->n_channels >= 3 && enc->n_channels <= 8) {
int c0, c1, c0v, c1v;
int mapped;
gboolean positions_done[256];
@ -580,6 +575,8 @@ gst_opus_enc_setup_channel_mappings (GstOpusEnc * enc,
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER},
{GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT},
{GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
GST_AUDIO_CHANNEL_POSITION_REAR_CENTER},
};
size_t pair;
@ -633,13 +630,8 @@ gst_opus_enc_setup_channel_mappings (GstOpusEnc * enc,
GST_DEBUG_OBJECT (enc, "Channel position %s is not mapped yet, adding",
gst_opus_channel_names[position]);
cv = gst_opus_enc_find_channel_position_in_vorbis_order (enc, position);
if (cv < 0) {
GST_WARNING_OBJECT (enc,
"Cannot map channel positions to Vorbis order, using unknown mapping");
enc->channel_mapping_family = 255;
enc->n_stereo_streams = 0;
return;
}
if (cv < 0)
g_assert_not_reached ();
enc->encoding_channel_mapping[mapped] = n;
enc->decoding_channel_mapping[cv] = mapped;
mapped++;
@ -718,6 +710,8 @@ gst_opus_enc_setup (GstOpusEnc * enc)
GstCaps *caps;
gboolean ret;
gint32 lookahead;
const GstTagList *tags;
GstBuffer *header, *comments;
#ifndef GST_DISABLE_GST_DEBUG
GST_DEBUG_OBJECT (enc,
@ -764,10 +758,17 @@ gst_opus_enc_setup (GstOpusEnc * enc)
lookahead = lookahead * 48000 / enc->sample_rate;
enc->lookahead = enc->pending_lookahead = lookahead;
gst_opus_header_create_caps (&caps, NULL, lookahead, enc->sample_rate,
enc->n_channels, enc->n_stereo_streams, enc->channel_mapping_family,
enc->decoding_channel_mapping,
gst_tag_setter_get_tag_list (GST_TAG_SETTER (enc)));
header = gst_codec_utils_opus_create_header (enc->sample_rate,
enc->n_channels, enc->channel_mapping_family,
enc->n_channels - enc->n_stereo_streams, enc->n_stereo_streams,
enc->decoding_channel_mapping, lookahead, 0);
tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (enc));
comments =
gst_tag_list_to_vorbiscomment_buffer (tags, (const guint8 *) "OpusTags",
8, "Encoded with GStreamer opusenc");
caps = gst_codec_utils_opus_create_caps_from_header (header, comments);
gst_buffer_unref (header);
gst_buffer_unref (comments);
/* negotiate with these caps */
GST_DEBUG_OBJECT (enc, "here are the caps: %" GST_PTR_FORMAT, caps);
@ -813,71 +814,96 @@ gst_opus_enc_sink_event (GstAudioEncoder * benc, GstEvent * event)
return GST_AUDIO_ENCODER_CLASS (parent_class)->sink_event (benc, event);
}
static GstCaps *
gst_opus_enc_get_sink_template_caps (void)
{
static volatile gsize init = 0;
static GstCaps *caps = NULL;
if (g_once_init_enter (&init)) {
GValue rate_array = G_VALUE_INIT;
GValue v = G_VALUE_INIT;
GstStructure *s1, *s2, *s;
gint i, c;
caps = gst_caps_new_empty ();
/* Generate our two template structures */
g_value_init (&rate_array, GST_TYPE_LIST);
g_value_init (&v, G_TYPE_INT);
g_value_set_int (&v, 8000);
gst_value_list_append_value (&rate_array, &v);
g_value_set_int (&v, 12000);
gst_value_list_append_value (&rate_array, &v);
g_value_set_int (&v, 16000);
gst_value_list_append_value (&rate_array, &v);
g_value_set_int (&v, 24000);
gst_value_list_append_value (&rate_array, &v);
s1 = gst_structure_new ("audio/x-raw",
"format", G_TYPE_STRING, GST_AUDIO_NE (S16),
"layout", G_TYPE_STRING, "interleaved",
"rate", G_TYPE_INT, 48000, NULL);
s2 = gst_structure_new ("audio/x-raw",
"format", G_TYPE_STRING, GST_AUDIO_NE (S16),
"layout", G_TYPE_STRING, "interleaved", NULL);
gst_structure_set_value (s2, "rate", &rate_array);
g_value_unset (&rate_array);
g_value_unset (&v);
/* Mono */
s = gst_structure_copy (s1);
gst_structure_set (s, "channels", G_TYPE_INT, 1, NULL);
gst_caps_append_structure (caps, s);
s = gst_structure_copy (s2);
gst_structure_set (s, "channels", G_TYPE_INT, 1, NULL);
gst_caps_append_structure (caps, s);
/* Stereo and further */
for (i = 2; i <= 8; i++) {
guint64 channel_mask = 0;
const GstAudioChannelPosition *pos = gst_opus_channel_positions[i - 1];
for (c = 0; c < i; c++) {
channel_mask |= G_GUINT64_CONSTANT (1) << pos[c];
}
s = gst_structure_copy (s1);
gst_structure_set (s, "channels", G_TYPE_INT, i, "channel-mask",
GST_TYPE_BITMASK, channel_mask, NULL);
gst_caps_append_structure (caps, s);
s = gst_structure_copy (s2);
gst_structure_set (s, "channels", G_TYPE_INT, i, "channel-mask",
GST_TYPE_BITMASK, channel_mask, NULL);
gst_caps_append_structure (caps, s);
}
gst_structure_free (s1);
gst_structure_free (s2);
g_once_init_leave (&init, 1);
}
return caps;
}
static GstCaps *
gst_opus_enc_sink_getcaps (GstAudioEncoder * benc, GstCaps * filter)
{
GstOpusEnc *enc;
GstCaps *caps;
GstCaps *tcaps;
GstCaps *peercaps = NULL;
GstCaps *intersect = NULL;
guint i;
gboolean allow_multistream;
enc = GST_OPUS_ENC (benc);
GST_DEBUG_OBJECT (enc, "sink getcaps");
peercaps = gst_pad_peer_query_caps (GST_AUDIO_ENCODER_SRC_PAD (benc), NULL);
if (!peercaps) {
GST_DEBUG_OBJECT (benc, "No peercaps, returning template sink caps");
return gst_pad_get_pad_template_caps (GST_AUDIO_ENCODER_SINK_PAD (benc));
}
tcaps = gst_pad_get_pad_template_caps (GST_AUDIO_ENCODER_SRC_PAD (benc));
intersect = gst_caps_intersect (peercaps, tcaps);
gst_caps_unref (tcaps);
gst_caps_unref (peercaps);
if (gst_caps_is_empty (intersect))
return intersect;
allow_multistream = FALSE;
for (i = 0; i < gst_caps_get_size (intersect); i++) {
GstStructure *s = gst_caps_get_structure (intersect, i);
gboolean multistream;
if (gst_structure_get_boolean (s, "multistream", &multistream)) {
if (multistream) {
allow_multistream = TRUE;
}
} else {
allow_multistream = TRUE;
}
}
gst_caps_unref (intersect);
caps = gst_pad_get_pad_template_caps (GST_AUDIO_ENCODER_SINK_PAD (benc));
caps = gst_caps_make_writable (caps);
if (!allow_multistream) {
GValue range = { 0 };
g_value_init (&range, GST_TYPE_INT_RANGE);
gst_value_set_int_range (&range, 1, 2);
for (i = 0; i < gst_caps_get_size (caps); i++) {
GstStructure *s = gst_caps_get_structure (caps, i);
gst_structure_set_value (s, "channels", &range);
}
g_value_unset (&range);
}
if (filter) {
GstCaps *tmp = gst_caps_intersect_full (caps, filter,
GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (caps);
caps = tmp;
}
caps = gst_opus_enc_get_sink_template_caps ();
caps = gst_audio_encoder_proxy_getcaps (benc, caps, filter);
GST_DEBUG_OBJECT (enc, "Returning caps: %" GST_PTR_FORMAT, caps);
return caps;
}

View file

@ -26,218 +26,6 @@
#include <gst/base/gstbytewriter.h>
#include "gstopusheader.h"
static GstBuffer *
gst_opus_enc_create_id_buffer (guint16 pre_skip, guint sample_rate,
guint8 nchannels, guint8 n_stereo_streams, guint8 channel_mapping_family,
const guint8 * channel_mapping)
{
GstBuffer *buffer;
GstByteWriter bw;
gboolean hdl = TRUE;
g_return_val_if_fail (nchannels > 0, NULL);
g_return_val_if_fail (n_stereo_streams <= nchannels - n_stereo_streams, 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, nchannels);
hdl &= gst_byte_writer_put_uint16_le (&bw, pre_skip);
hdl &= gst_byte_writer_put_uint32_le (&bw, sample_rate);
hdl &= gst_byte_writer_put_uint16_le (&bw, 0); /* output gain */
hdl &= gst_byte_writer_put_uint8 (&bw, channel_mapping_family);
if (channel_mapping_family > 0) {
hdl &= gst_byte_writer_put_uint8 (&bw, nchannels - n_stereo_streams);
hdl &= gst_byte_writer_put_uint8 (&bw, n_stereo_streams);
hdl &= gst_byte_writer_put_data (&bw, channel_mapping, nchannels);
}
if (!hdl)
GST_WARNING ("Error creating header");
buffer = gst_byte_writer_reset_and_get_buffer (&bw);
GST_BUFFER_OFFSET (buffer) = 0;
GST_BUFFER_OFFSET_END (buffer) = 0;
return buffer;
}
static GstBuffer *
gst_opus_enc_create_metadata_buffer (const GstTagList * tags)
{
GstTagList *empty_tags = NULL;
GstBuffer *comments = NULL;
GST_DEBUG ("tags = %" GST_PTR_FORMAT, tags);
if (tags == NULL) {
/* FIXME: better fix chain of callers to not write metadata at all,
* if there is none */
empty_tags = gst_tag_list_new_empty ();
tags = empty_tags;
}
comments =
gst_tag_list_to_vorbiscomment_buffer (tags, (const guint8 *) "OpusTags",
8, "Encoded with GStreamer Opusenc");
GST_BUFFER_OFFSET (comments) = 0;
GST_BUFFER_OFFSET_END (comments) = 0;
if (empty_tags)
gst_tag_list_unref (empty_tags);
return comments;
}
/*
* (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;
}
void
gst_opus_header_create_caps_from_headers (GstCaps ** caps, GSList ** headers,
GstBuffer * buf1, GstBuffer * buf2)
{
int n_streams, family;
gint channels, rate;
gboolean multistream;
GstMapInfo map;
guint8 *data;
g_return_if_fail (caps);
g_return_if_fail (!headers || !*headers);
g_return_if_fail (gst_buffer_get_size (buf1) >= 19);
gst_buffer_map (buf1, &map, GST_MAP_READ);
data = map.data;
channels = data[9];
rate = GST_READ_UINT32_LE (data + 12);
/* work out the number of streams */
family = data[18];
if (family == 0) {
n_streams = 1;
} else {
/* only included in the header for family > 0 */
if (map.size >= 20)
n_streams = data[19];
else {
g_warning ("family > 0 but header buffer size < 20");
gst_buffer_unmap (buf1, &map);
return;
}
}
/* TODO: should probably also put the channel mapping into the caps too once
* we actually support multi channel, and pre-skip and other fields */
gst_buffer_unmap (buf1, &map);
/* mark and put on caps */
multistream = n_streams > 1;
*caps = gst_caps_new_simple ("audio/x-opus",
"multistream", G_TYPE_BOOLEAN, multistream,
"channels", G_TYPE_INT, channels, NULL);
if (rate > 0) {
gst_caps_set_simple (*caps, "rate", G_TYPE_INT, rate, NULL);
}
*caps = _gst_caps_set_buffer_array (*caps, "streamheader", buf1, buf2, NULL);
if (headers) {
*headers = g_slist_prepend (*headers, gst_buffer_ref (buf2));
*headers = g_slist_prepend (*headers, gst_buffer_ref (buf1));
}
}
void
gst_opus_header_create_caps (GstCaps ** caps, GSList ** headers,
guint16 pre_skip, guint sample_rate, guint8 nchannels,
guint8 n_stereo_streams, guint8 channel_mapping_family,
const guint8 * channel_mapping, const GstTagList * tags)
{
GstBuffer *buf1, *buf2;
g_return_if_fail (caps);
g_return_if_fail (!headers || !*headers);
g_return_if_fail (nchannels > 0);
g_return_if_fail (channel_mapping_family == 0 || channel_mapping);
/* Opus streams in Ogg begin with two headers; the initial header (with
most of the codec setup parameters) which is mandated by the Ogg
bitstream spec. The second header holds any comment fields. */
/* create header buffers */
buf1 =
gst_opus_enc_create_id_buffer (pre_skip, sample_rate, nchannels,
n_stereo_streams, channel_mapping_family, channel_mapping);
buf2 = gst_opus_enc_create_metadata_buffer (tags);
gst_opus_header_create_caps_from_headers (caps, headers, buf1, buf2);
gst_buffer_unref (buf2);
gst_buffer_unref (buf1);
}
gboolean
gst_opus_header_is_header (GstBuffer * buf, const char *magic, guint magic_size)
{

View file

@ -26,12 +26,6 @@
G_BEGIN_DECLS
extern void gst_opus_header_create_caps_from_headers (GstCaps **caps, GSList **headers,
GstBuffer *id_header, GstBuffer *comment_header);
extern void gst_opus_header_create_caps (GstCaps **caps, GSList **headers,
guint16 pre_skip, guint sample_rate32, guint8 nchannels, guint8 n_stereo_streams,
guint8 channel_mapping_family, const guint8 *channel_mapping,
const GstTagList *tags);
extern gboolean gst_opus_header_is_header (GstBuffer * buf,
const char *magic, guint magic_size);
extern gboolean gst_opus_header_is_id_header (GstBuffer * buf);

View file

@ -44,6 +44,7 @@
#include "gstopusparse.h"
#include <gst/audio/audio.h>
#include <gst/pbutils/pbutils.h>
GST_DEBUG_CATEGORY_STATIC (opusparse_debug);
#define GST_CAT_DEFAULT opusparse_debug
@ -125,10 +126,6 @@ gst_opus_parse_stop (GstBaseParse * base)
{
GstOpusParse *parse = GST_OPUS_PARSE (base);
g_slist_foreach (parse->headers, (GFunc) gst_buffer_unref, NULL);
g_slist_free (parse->headers);
parse->headers = NULL;
parse->header_sent = FALSE;
parse->got_headers = FALSE;
parse->pre_skip = 0;
@ -342,7 +339,6 @@ gst_opus_parse_parse_frame (GstBaseParse * base, GstBaseParseFrame * frame)
if (!parse->got_headers || !parse->header_sent) {
GstCaps *caps;
guint8 channels;
/* Opus streams can decode to 1 or 2 channels, so use the header
value if we have one, or 2 otherwise */
@ -372,11 +368,7 @@ gst_opus_parse_parse_frame (GstBaseParse * base, GstBaseParseFrame * frame)
}
if (!(frame->flags & GST_BASE_PARSE_FRAME_FLAG_QUEUE)) {
g_slist_foreach (parse->headers, (GFunc) gst_buffer_unref, NULL);
g_slist_free (parse->headers);
parse->headers = NULL;
if (parse->id_header && parse->comment_header) {
if (FALSE && parse->id_header && parse->comment_header) {
guint16 pre_skip;
gst_buffer_map (parse->id_header, &map, GST_MAP_READWRITE);
@ -389,18 +381,39 @@ gst_opus_parse_parse_frame (GstBaseParse * base, GstBaseParseFrame * frame)
}
gst_buffer_unmap (parse->id_header, &map);
gst_opus_header_create_caps_from_headers (&caps, &parse->headers,
parse->id_header, parse->comment_header);
caps =
gst_codec_utils_opus_create_caps_from_header (parse->id_header,
parse->comment_header);
} else {
guint8 channel_mapping_family, channel_mapping[256];
GST_INFO_OBJECT (parse,
"No headers, blindly setting up canonical stereo");
channels = 2;
channel_mapping_family = 0;
channel_mapping[0] = 0;
channel_mapping[1] = 1;
gst_opus_header_create_caps (&caps, &parse->headers, parse->pre_skip,
48000, channels, 1, channel_mapping_family, channel_mapping, NULL);
GstCaps *sink_caps;
guint32 sample_rate;
guint8 n_channels, n_streams, n_stereo_streams, channel_mapping_family;
guint8 channel_mapping[256];
GstBuffer *id_header;
sink_caps = gst_pad_get_current_caps (GST_BASE_PARSE_SINK_PAD (parse));
if (!sink_caps
|| !gst_codec_utils_opus_parse_caps (sink_caps, &sample_rate,
&n_channels, &channel_mapping_family, &n_streams,
&n_stereo_streams, channel_mapping)) {
GST_INFO_OBJECT (parse,
"No headers and no caps, blindly setting up canonical stereo");
n_channels = 2;
n_streams = 1;
n_stereo_streams = 1;
channel_mapping_family = 0;
channel_mapping[0] = 0;
channel_mapping[1] = 1;
}
if (sink_caps)
gst_caps_unref (sink_caps);
id_header =
gst_codec_utils_opus_create_header (sample_rate, n_channels,
channel_mapping_family, n_streams, n_stereo_streams,
channel_mapping, parse->pre_skip, 0);
caps = gst_codec_utils_opus_create_caps_from_header (id_header, NULL);
gst_buffer_unref (id_header);
}
gst_buffer_replace (&parse->id_header, NULL);

View file

@ -46,7 +46,6 @@ struct _GstOpusParse {
gboolean got_headers, header_sent;
guint64 pre_skip;
GSList *headers;
GstClockTime next_ts;
GstBuffer *id_header;
GstBuffer *comment_header;

View file

@ -47,7 +47,7 @@ static GstStaticPadTemplate gst_rtp_opus_depay_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-opus, multistream = (boolean) FALSE")
GST_STATIC_CAPS ("audio/x-opus, channel-mapping-family = (int) 0")
);
static GstBuffer *gst_rtp_opus_depay_process (GstRTPBaseDepayload * depayload,
@ -98,8 +98,8 @@ gst_rtp_opus_depay_setcaps (GstRTPBaseDepayload * depayload, GstCaps * caps)
const gchar *sprop_stereo, *sprop_maxcapturerate;
srccaps =
gst_caps_new_simple ("audio/x-opus", "multistream", G_TYPE_BOOLEAN, FALSE,
NULL);
gst_caps_new_simple ("audio/x-opus", "channel-mapping-family", G_TYPE_INT,
0, NULL);
s = gst_caps_get_structure (caps, 0);
if ((sprop_stereo = gst_structure_get_string (s, "sprop-stereo"))) {

View file

@ -38,7 +38,8 @@ static GstStaticPadTemplate gst_rtp_opus_pay_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-opus, multistream = (boolean) FALSE")
GST_STATIC_CAPS
("audio/x-opus, channels = (int) [1, 2], channel-mapping-family = (int) 0")
);
static GstStaticPadTemplate gst_rtp_opus_pay_src_template =
@ -122,7 +123,7 @@ gst_rtp_opus_pay_setcaps (GstRTPBasePayload * payload, GstCaps * caps)
if (gst_structure_get_int (s, "channels", &channels)) {
if (channels > 2) {
GST_ERROR_OBJECT (payload,
"More than 2 channels with multistream=FALSE is invalid");
"More than 2 channels with channel-mapping-family=0 is invalid");
return FALSE;
} else if (channels == 2) {
sprop_stereo = "1";