diff --git a/ext/opus/Makefile.am b/ext/opus/Makefile.am index 88845a3cab..cb0a9b338a 100644 --- a/ext/opus/Makefile.am +++ b/ext/opus/Makefile.am @@ -1,6 +1,6 @@ plugin_LTLIBRARIES = libgstopus.la -libgstopus_la_SOURCES = gstopus.c gstopusdec.c gstopusenc.c gstopusparse.c gstopusheader.c +libgstopus_la_SOURCES = gstopus.c gstopusdec.c gstopusenc.c gstopusparse.c gstopusheader.c gstopuscommon.c libgstopus_la_CFLAGS = \ -DGST_USE_UNSTABLE_API \ $(GST_PLUGINS_BASE_CFLAGS) \ @@ -15,4 +15,4 @@ libgstopus_la_LIBADD = \ libgstopus_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) $(LIBM) libgstopus_la_LIBTOOLFLAGS = --tag=disable-static -noinst_HEADERS = gstopusenc.h gstopusdec.h gstopusparse.h gstopusheader.h +noinst_HEADERS = gstopusenc.h gstopusdec.h gstopusparse.h gstopusheader.h gstopuscommon.h diff --git a/ext/opus/gstopuscommon.c b/ext/opus/gstopuscommon.c new file mode 100644 index 0000000000..fc3e0376b9 --- /dev/null +++ b/ext/opus/gstopuscommon.c @@ -0,0 +1,72 @@ +/* GStreamer + * Copyright (C) 2009 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 + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "gstopuscommon.h" + +/* http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-800004.3.9 */ +/* copy of the same structure in the vorbis plugin */ +const GstAudioChannelPosition gst_opus_channel_positions[][8] = { + { /* Mono */ + GST_AUDIO_CHANNEL_POSITION_FRONT_MONO}, + { /* Stereo */ + GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT}, + { /* Stereo + Centre */ + GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, + GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER, + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT}, + { /* Quadraphonic */ + GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT, + GST_AUDIO_CHANNEL_POSITION_REAR_LEFT, + GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT, + }, + { /* Stereo + Centre + rear stereo */ + GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, + GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER, + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT, + GST_AUDIO_CHANNEL_POSITION_REAR_LEFT, + GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT, + }, + { /* Full 5.1 Surround */ + GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, + GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER, + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT, + GST_AUDIO_CHANNEL_POSITION_REAR_LEFT, + GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT, + GST_AUDIO_CHANNEL_POSITION_LFE, + }, + { /* 6.1 Surround, in Vorbis spec since 2010-01-13 */ + GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, + GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER, + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT, + GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT, + GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT, + GST_AUDIO_CHANNEL_POSITION_REAR_CENTER, + GST_AUDIO_CHANNEL_POSITION_LFE}, + { /* 7.1 Surround, in Vorbis spec since 2010-01-13 */ + GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, + GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER, + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT, + GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT, + GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT, + GST_AUDIO_CHANNEL_POSITION_REAR_LEFT, + GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT, + GST_AUDIO_CHANNEL_POSITION_LFE}, +}; diff --git a/ext/opus/gstopuscommon.h b/ext/opus/gstopuscommon.h new file mode 100644 index 0000000000..96a303e303 --- /dev/null +++ b/ext/opus/gstopuscommon.h @@ -0,0 +1,33 @@ +/* GStreamer Opus Encoder + * Copyright (C) 2009 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 + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __GST_OPUS_COMMON_H__ +#define __GST_OPUS_COMMON_H__ + +#include +#include + +G_BEGIN_DECLS + +extern const GstAudioChannelPosition gst_opus_channel_positions[][8]; + +G_END_DECLS + +#endif /* __GST_OPUS_COMMON_H__ */ diff --git a/ext/opus/gstopusdec.c b/ext/opus/gstopusdec.c index f8b39ba08c..58283973a2 100644 --- a/ext/opus/gstopusdec.c +++ b/ext/opus/gstopusdec.c @@ -45,6 +45,7 @@ #include #include #include "gstopusheader.h" +#include "gstopuscommon.h" #include "gstopusdec.h" GST_DEBUG_CATEGORY_STATIC (opusdec_debug); @@ -56,7 +57,7 @@ GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_ALWAYS, GST_STATIC_CAPS ("audio/x-raw-int, " "rate = (int) { 48000, 24000, 16000, 12000, 8000 }, " - "channels = (int) [ 1, 2 ], " + "channels = (int) [ 1, 8 ], " "endianness = (int) BYTE_ORDER, " "signed = (boolean) true, " "width = (int) 16, " "depth = (int) 16") ); @@ -217,26 +218,91 @@ gst_opus_dec_get_r128_volume (gint16 r128_gain) static GstFlowReturn gst_opus_dec_parse_header (GstOpusDec * dec, GstBuffer * buf) { - g_return_val_if_fail (gst_opus_header_is_header (buf, "OpusHead", 8), - GST_FLOW_ERROR); - g_return_val_if_fail (GST_BUFFER_SIZE (buf) >= 19, GST_FLOW_ERROR); + const guint8 *data = GST_BUFFER_DATA (buf); + GstCaps *caps; + GstStructure *s; + const GstAudioChannelPosition *pos = NULL; - dec->pre_skip = GST_READ_UINT16_LE (GST_BUFFER_DATA (buf) + 10); - dec->r128_gain = GST_READ_UINT16_LE (GST_BUFFER_DATA (buf) + 14); + g_return_val_if_fail (gst_opus_header_is_id_header (buf), GST_FLOW_ERROR); + g_return_val_if_fail (dec->n_channels != data[9], GST_FLOW_ERROR); + + dec->n_channels = data[9]; + dec->pre_skip = GST_READ_UINT16_LE (data + 10); + dec->r128_gain = GST_READ_UINT16_LE (data + 14); 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 = GST_BUFFER_DATA (buf)[18]; - if (dec->channel_mapping_family != 0) { - GST_ELEMENT_ERROR (dec, STREAM, DECODE, - ("Decoding error: unsupported channel nmapping family %d", - dec->channel_mapping_family), (NULL)); - return GST_FLOW_ERROR; + 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: + pos = gst_opus_channel_positions[dec->n_channels - 1]; + break; + default:{ + gint i; + GstAudioChannelPosition *posn = + g_new (GstAudioChannelPosition, dec->n_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++) + posn[i] = GST_AUDIO_CHANNEL_POSITION_NONE; + + pos = posn; + } + } + } else { + GST_INFO_OBJECT (dec, "Channel mapping family %d", + dec->channel_mapping_family); + } } - dec->channel_mapping[0] = 0; - dec->channel_mapping[1] = 1; + + /* negotiate width with downstream */ + caps = gst_pad_get_allowed_caps (GST_AUDIO_DECODER_SRC_PAD (dec)); + s = gst_caps_get_structure (caps, 0); + gst_structure_fixate_field_nearest_int (s, "rate", 48000); + gst_structure_get_int (s, "rate", &dec->sample_rate); + gst_structure_fixate_field_nearest_int (s, "channels", dec->n_channels); + gst_structure_get_int (s, "channels", &dec->n_channels); + + GST_INFO_OBJECT (dec, "Negotiated %d channels, %d Hz", dec->n_channels, + dec->sample_rate); + + if (pos) { + gst_audio_set_channel_positions (gst_caps_get_structure (caps, 0), pos); + } + + if (dec->n_channels > 8) { + g_free ((GstAudioChannelPosition *) pos); + } + + GST_INFO_OBJECT (dec, "Setting src caps to %" GST_PTR_FORMAT, caps); + gst_pad_set_caps (GST_AUDIO_DECODER_SRC_PAD (dec), caps); + gst_caps_unref (caps); return GST_FLOW_OK; } @@ -248,48 +314,6 @@ gst_opus_dec_parse_comments (GstOpusDec * dec, GstBuffer * buf) return GST_FLOW_OK; } -static void -gst_opus_dec_setup_from_peer_caps (GstOpusDec * dec) -{ - GstPad *srcpad, *peer; - GstStructure *s; - GstCaps *caps; - const GstCaps *template_caps; - const GstCaps *peer_caps; - - srcpad = GST_AUDIO_DECODER_SRC_PAD (dec); - peer = gst_pad_get_peer (srcpad); - - if (peer) { - template_caps = gst_pad_get_pad_template_caps (srcpad); - peer_caps = gst_pad_get_caps (peer); - GST_DEBUG_OBJECT (dec, "Peer caps: %" GST_PTR_FORMAT, peer_caps); - caps = gst_caps_intersect (template_caps, peer_caps); - gst_pad_fixate_caps (peer, caps); - GST_DEBUG_OBJECT (dec, "Fixated caps: %" GST_PTR_FORMAT, caps); - - s = gst_caps_get_structure (caps, 0); - if (!gst_structure_get_int (s, "channels", &dec->n_channels)) { - dec->n_channels = 2; - GST_WARNING_OBJECT (dec, "Failed to get channels, using default %d", - dec->n_channels); - } else { - GST_DEBUG_OBJECT (dec, "Got channels %d", dec->n_channels); - } - if (!gst_structure_get_int (s, "rate", &dec->sample_rate)) { - dec->sample_rate = 48000; - GST_WARNING_OBJECT (dec, "Failed to get rate, using default %d", - dec->sample_rate); - } else { - GST_DEBUG_OBJECT (dec, "Got sample rate %d", dec->sample_rate); - } - - gst_pad_set_caps (GST_AUDIO_DECODER_SRC_PAD (dec), caps); - } else { - GST_WARNING_OBJECT (dec, "Failed to get src pad peer"); - } -} - static GstFlowReturn opus_dec_chain_parse_data (GstOpusDec * dec, GstBuffer * buffer) { @@ -304,12 +328,11 @@ opus_dec_chain_parse_data (GstOpusDec * dec, GstBuffer * buffer) GstBuffer *buf; if (dec->state == NULL) { - gst_opus_dec_setup_from_peer_caps (dec); - GST_DEBUG_OBJECT (dec, "Creating decoder with %d channels, %d Hz", dec->n_channels, dec->sample_rate); dec->state = opus_multistream_decoder_create (dec->sample_rate, - dec->n_channels, 1, 1, dec->channel_mapping, &err); + dec->n_channels, dec->n_streams, dec->n_stereo_streams, + dec->channel_mapping, &err); if (!dec->state || err != OPUS_OK) goto creation_failed; } diff --git a/ext/opus/gstopusdec.h b/ext/opus/gstopusdec.h index aa04b814d9..3ccfa26969 100644 --- a/ext/opus/gstopusdec.h +++ b/ext/opus/gstopusdec.h @@ -55,6 +55,9 @@ struct _GstOpusDec { int n_channels; guint32 pre_skip; gint16 r128_gain; + + guint8 n_streams; + guint8 n_stereo_streams; guint8 channel_mapping_family; guint8 channel_mapping[256]; diff --git a/ext/opus/gstopusenc.c b/ext/opus/gstopusenc.c index 93e00aa38f..6dad531156 100644 --- a/ext/opus/gstopusenc.c +++ b/ext/opus/gstopusenc.c @@ -49,6 +49,7 @@ #include #include #include "gstopusheader.h" +#include "gstopuscommon.h" #include "gstopusenc.h" GST_DEBUG_CATEGORY_STATIC (opusenc_debug); @@ -116,8 +117,8 @@ static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("audio/x-raw-int, " - "rate = (int) { 8000, 12000, 16000, 24000, 48000 }, " - "channels = (int) [ 1, 2 ], " + "rate = (int) { 48000, 24000, 16000, 12000, 8000 }, " + "channels = (int) [ 1, 8 ], " "endianness = (int) BYTE_ORDER, " "signed = (boolean) TRUE, " "width = (int) 16, " "depth = (int) 16") ); @@ -419,6 +420,82 @@ gst_opus_enc_get_frame_samples (GstOpusEnc * enc) return frame_samples; } +static void +gst_opus_enc_setup_channel_mapping (GstOpusEnc * enc, const GstAudioInfo * info) +{ +#define MAPS(idx,pos) (GST_AUDIO_INFO_POSITION (info, (idx)) == GST_AUDIO_CHANNEL_POSITION_##pos) + + int n; + + GST_DEBUG_OBJECT (enc, "Setting up channel mapping for %d channels", + enc->n_channels); + + /* Start by setting up a default trivial mapping */ + for (n = 0; n < 255; ++n) + enc->channel_mapping[n] = n; + + /* For one channel, use the basic RTP mapping */ + if (enc->n_channels == 1) { + GST_INFO_OBJECT (enc, "Mono, trivial RTP mapping"); + enc->channel_mapping_family = 0; + enc->channel_mapping[0] = 0; + return; + } + + /* 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; + /* 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. */ + } else { + GST_DEBUG_OBJECT (enc, "Stereo, but not canonical mapping, continuing"); + } + } + + /* 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. */ + if (enc->n_channels >= 1 && enc->n_channels <= 8) { + GST_DEBUG_OBJECT (enc, + "In range for the Vorbis mapping, checking channel positions"); + for (n = 0; n < enc->n_channels; ++n) { + GstAudioChannelPosition pos = GST_AUDIO_INFO_POSITION (info, n); + int c; + + GST_DEBUG_OBJECT (enc, "Channel %d has position %d", n, pos); + for (c = 0; c < enc->n_channels; ++c) { + if (gst_opus_channel_positions[enc->n_channels - 1][c] == pos) { + GST_DEBUG_OBJECT (enc, "Found in Vorbis mapping as channel %d", c); + break; + } + } + if (c == enc->n_channels) { + /* We did not find that position, so use undefined */ + GST_WARNING_OBJECT (enc, + "Position %d not found in Vorbis mapping, using unknown mapping", + pos); + enc->channel_mapping_family = 255; + return; + } + GST_DEBUG_OBJECT (enc, "Mapping output channel %d to %d", c, n); + enc->channel_mapping[c] = n; + } + GST_INFO_OBJECT (enc, "Permutation found, using Vorbis mapping"); + enc->channel_mapping_family = 1; + return; + } + + /* For other cases, we use undefined, with the default trivial mapping */ + GST_WARNING_OBJECT (enc, "Unknown mapping"); + enc->channel_mapping_family = 255; + +#undef MAPS +} + static gboolean gst_opus_enc_set_format (GstAudioEncoder * benc, GstAudioInfo * info) { @@ -430,6 +507,7 @@ gst_opus_enc_set_format (GstAudioEncoder * benc, GstAudioInfo * info) enc->n_channels = GST_AUDIO_INFO_CHANNELS (info); enc->sample_rate = GST_AUDIO_INFO_RATE (info); + gst_opus_enc_setup_channel_mapping (enc, info); GST_DEBUG_OBJECT (benc, "Setup with %d channels, %d Hz", enc->n_channels, enc->sample_rate); @@ -455,17 +533,12 @@ static gboolean gst_opus_enc_setup (GstOpusEnc * enc) { int error = OPUS_OK; - unsigned char mapping[256]; - int n; GST_DEBUG_OBJECT (enc, "setup"); - for (n = 0; n < enc->n_channels; ++n) - mapping[n] = n; - enc->state = opus_multistream_encoder_create (enc->sample_rate, enc->n_channels, - (enc->n_channels + 1) / 2, enc->n_channels / 2, mapping, + (enc->n_channels + 1) / 2, enc->n_channels / 2, enc->channel_mapping, enc->audio_or_voip ? OPUS_APPLICATION_AUDIO : OPUS_APPLICATION_VOIP, &error); if (!enc->state || error != OPUS_OK) @@ -557,18 +630,19 @@ gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buf) GstBuffer *outbuf; ret = gst_pad_alloc_buffer_and_set_caps (GST_AUDIO_ENCODER_SRC_PAD (enc), - GST_BUFFER_OFFSET_NONE, enc->max_payload_size, + GST_BUFFER_OFFSET_NONE, enc->max_payload_size * enc->n_channels, GST_PAD_CAPS (GST_AUDIO_ENCODER_SRC_PAD (enc)), &outbuf); if (GST_FLOW_OK != ret) goto done; GST_DEBUG_OBJECT (enc, "encoding %d samples (%d bytes)", - enc->frame_samples); + enc->frame_samples, (int) bytes); outsize = opus_multistream_encode (enc->state, (const gint16 *) data, - enc->frame_samples, GST_BUFFER_DATA (outbuf), enc->max_payload_size); + enc->frame_samples, GST_BUFFER_DATA (outbuf), + enc->max_payload_size * enc->n_channels); if (outsize < 0) { GST_ERROR_OBJECT (enc, "Encoding failed: %d", outsize); @@ -582,6 +656,7 @@ gst_opus_enc_encode (GstOpusEnc * enc, GstBuffer * buf) goto done; } + GST_DEBUG_OBJECT (enc, "Output packet is %u bytes", outsize); GST_BUFFER_SIZE (outbuf) = outsize; ret = @@ -621,7 +696,8 @@ gst_opus_enc_handle_frame (GstAudioEncoder * benc, GstBuffer * buf) enc->headers = NULL; gst_opus_header_create_caps (&caps, &enc->headers, enc->n_channels, - enc->sample_rate, gst_tag_setter_get_tag_list (GST_TAG_SETTER (enc))); + enc->sample_rate, enc->channel_mapping_family, enc->channel_mapping, + gst_tag_setter_get_tag_list (GST_TAG_SETTER (enc))); /* negotiate with these caps */ diff --git a/ext/opus/gstopusenc.h b/ext/opus/gstopusenc.h index 8304e82556..8c2c3c6e82 100644 --- a/ext/opus/gstopusenc.h +++ b/ext/opus/gstopusenc.h @@ -77,6 +77,9 @@ struct _GstOpusEnc { GSList *headers; GstTagList *tags; + + guint8 channel_mapping_family; + guint8 channel_mapping[256]; }; struct _GstOpusEncClass { diff --git a/ext/opus/gstopusheader.c b/ext/opus/gstopusheader.c index 3551055840..0379c909f4 100644 --- a/ext/opus/gstopusheader.c +++ b/ext/opus/gstopusheader.c @@ -27,7 +27,8 @@ #include "gstopusheader.h" static GstBuffer * -gst_opus_enc_create_id_buffer (gint nchannels, gint sample_rate) +gst_opus_enc_create_id_buffer (gint nchannels, gint sample_rate, + guint8 channel_mapping_family, const guint8 * channel_mapping) { GstBuffer *buffer; GstByteWriter bw; @@ -41,7 +42,12 @@ gst_opus_enc_create_id_buffer (gint nchannels, gint sample_rate) gst_byte_writer_put_uint16_le (&bw, 0); /* pre-skip *//* TODO: endianness ? */ gst_byte_writer_put_uint32_le (&bw, sample_rate); gst_byte_writer_put_uint16_le (&bw, 0); /* output gain *//* TODO: endianness ? */ - gst_byte_writer_put_uint8 (&bw, 0); /* channel mapping *//* TODO: what is this ? */ + gst_byte_writer_put_uint8 (&bw, channel_mapping_family); + if (channel_mapping_family > 0) { + gst_byte_writer_put_uint8 (&bw, (nchannels + 1) / 2); + gst_byte_writer_put_uint8 (&bw, nchannels / 2); + gst_byte_writer_put_data (&bw, channel_mapping, nchannels); + } buffer = gst_byte_writer_reset_and_get_buffer (&bw); @@ -136,23 +142,11 @@ _gst_caps_set_buffer_array (GstCaps * caps, const gchar * field, } void -gst_opus_header_create_caps (GstCaps ** caps, GSList ** headers, gint nchannels, - gint sample_rate, const GstTagList * tags) +gst_opus_header_create_caps_from_headers (GstCaps ** caps, GSList ** headers, + GstBuffer * buf1, GstBuffer * buf2) { - GstBuffer *buf1, *buf2; - g_return_if_fail (caps); g_return_if_fail (headers && !*headers); - g_return_if_fail (nchannels > 0); - g_return_if_fail (sample_rate >= 0); /* 0 -> unset */ - - /* 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 (nchannels, sample_rate); - buf2 = gst_opus_enc_create_metadata_buffer (tags); /* mark and put on caps */ *caps = gst_caps_from_string ("audio/x-opus"); @@ -162,9 +156,75 @@ gst_opus_header_create_caps (GstCaps ** caps, GSList ** headers, gint nchannels, *headers = g_slist_prepend (*headers, buf1); } +void +gst_opus_header_create_caps (GstCaps ** caps, GSList ** headers, gint nchannels, + gint sample_rate, 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 (sample_rate >= 0); /* 0 -> unset */ + 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 (nchannels, sample_rate, + channel_mapping_family, channel_mapping); + buf2 = gst_opus_enc_create_metadata_buffer (tags); + + gst_opus_header_create_caps_from_headers (caps, headers, buf1, buf2); +} + gboolean gst_opus_header_is_header (GstBuffer * buf, const char *magic, guint magic_size) { return (GST_BUFFER_SIZE (buf) >= magic_size && !memcmp (magic, GST_BUFFER_DATA (buf), magic_size)); } + +gboolean +gst_opus_header_is_id_header (GstBuffer * buf) +{ + gsize size = GST_BUFFER_SIZE (buf); + const guint8 *data = GST_BUFFER_DATA (buf); + guint8 channels, channel_mapping_family, n_streams, n_stereo_streams; + + if (size < 19) + return FALSE; + if (!gst_opus_header_is_header (buf, "OpusHead", 8)) + return FALSE; + channels = data[9]; + if (channels == 0) + return FALSE; + channel_mapping_family = data[18]; + if (channel_mapping_family == 0) { + if (channels > 2) + return FALSE; + } else { + channels = data[9]; + if (size < 21 + channels) + return FALSE; + n_streams = data[19]; + n_stereo_streams = data[20]; + if (n_streams == 0) + return FALSE; + if (n_stereo_streams > n_streams) + return FALSE; + if (n_streams + n_stereo_streams > 255) + return FALSE; + } + return TRUE; +} + +gboolean +gst_opus_header_is_comment_header (GstBuffer * buf) +{ + return gst_opus_header_is_header (buf, "OpusTags", 8); +} diff --git a/ext/opus/gstopusheader.h b/ext/opus/gstopusheader.h index 4594264ccd..3b2cfc265f 100644 --- a/ext/opus/gstopusheader.h +++ b/ext/opus/gstopusheader.h @@ -25,8 +25,16 @@ G_BEGIN_DECLS -extern void gst_opus_header_create_caps (GstCaps **caps, GSList **headers, gint nchannels, gint sample_rate, const GstTagList *tags); -extern gboolean gst_opus_header_is_header (GstBuffer * buf, const char *magic, guint magic_size); +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, + gint nchannels, gint sample_rate, + 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); +extern gboolean gst_opus_header_is_comment_header (GstBuffer * buf); G_END_DECLS diff --git a/ext/opus/gstopusparse.c b/ext/opus/gstopusparse.c index 278cead432..8627f93028 100644 --- a/ext/opus/gstopusparse.c +++ b/ext/opus/gstopusparse.c @@ -37,6 +37,7 @@ # include "config.h" #endif +#include #include #include "gstopusheader.h" #include "gstopusparse.h" @@ -136,7 +137,6 @@ gst_opus_parse_check_valid_frame (GstBaseParse * base, gsize size; guint32 packet_size; int ret = FALSE; - int channels; const unsigned char *frames[48]; unsigned char toc; short frame_sizes[48]; @@ -152,8 +152,8 @@ gst_opus_parse_check_valid_frame (GstBaseParse * base, GST_DEBUG_OBJECT (parse, "Checking for frame, %u bytes in buffer", size); /* check for headers */ - is_idheader = gst_opus_header_is_header (frame->buffer, "OpusHead", 8); - is_commentheader = gst_opus_header_is_header (frame->buffer, "OpusTags", 8); + is_idheader = gst_opus_header_is_id_header (frame->buffer); + is_commentheader = gst_opus_header_is_comment_header (frame->buffer); is_header = is_idheader || is_commentheader; if (!is_header) { @@ -193,27 +193,6 @@ gst_opus_parse_check_valid_frame (GstBaseParse * base, data += packet_offset; } - if (!parse->header_sent) { - GstCaps *caps; - - /* Opus streams can decode to 1 or 2 channels, so use the header - value if we have one, or 2 otherwise */ - if (is_idheader) { - channels = data[9]; - } else { - channels = 2; - } - - g_slist_foreach (parse->headers, (GFunc) gst_buffer_unref, NULL); - parse->headers = NULL; - - gst_opus_header_create_caps (&caps, &parse->headers, channels, 0, NULL); - - gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (parse), caps); - - parse->header_sent = TRUE; - } - if (is_header) { *skip = 0; *frame_size = size; @@ -291,16 +270,56 @@ gst_opus_parse_parse_frame (GstBaseParse * base, GstBaseParseFrame * frame) { guint64 duration; GstOpusParse *parse; + gboolean is_idheader, is_commentheader; parse = GST_OPUS_PARSE (base); - if (gst_opus_header_is_header (frame->buffer, "OpusHead", 8) - || gst_opus_header_is_header (frame->buffer, "OpusTags", 8)) { - GST_BUFFER_TIMESTAMP (frame->buffer) = 0; - GST_BUFFER_DURATION (frame->buffer) = GST_CLOCK_TIME_NONE; - GST_BUFFER_OFFSET_END (frame->buffer) = GST_CLOCK_TIME_NONE; - GST_BUFFER_OFFSET (frame->buffer) = GST_CLOCK_TIME_NONE; - return GST_FLOW_OK; + is_idheader = gst_opus_header_is_id_header (frame->buffer); + is_commentheader = gst_opus_header_is_comment_header (frame->buffer); + + if (!parse->header_sent) { + GstCaps *caps; + guint8 channels, channel_mapping_family, channel_mapping[256]; + const guint8 *data = GST_BUFFER_DATA (frame->buffer); + + /* Opus streams can decode to 1 or 2 channels, so use the header + value if we have one, or 2 otherwise */ + if (is_idheader) { + channels = data[9]; + channel_mapping_family = data[18]; + /* header probing will already have done the size check */ + memcpy (channel_mapping, GST_BUFFER_DATA (frame->buffer) + 21, channels); + gst_buffer_replace (&parse->id_header, frame->buffer); + GST_DEBUG_OBJECT (parse, "Found ID header, keeping"); + return GST_BASE_PARSE_FLOW_DROPPED; + } else if (is_commentheader) { + gst_buffer_replace (&parse->comment_header, frame->buffer); + GST_DEBUG_OBJECT (parse, "Found comment header, keeping"); + return GST_BASE_PARSE_FLOW_DROPPED; + } + + g_slist_foreach (parse->headers, (GFunc) gst_buffer_unref, NULL); + parse->headers = NULL; + + if (parse->id_header && parse->comment_header) { + gst_opus_header_create_caps_from_headers (&caps, &parse->headers, + parse->id_header, parse->comment_header); + } else { + 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, channels, 0, + channel_mapping_family, channel_mapping, NULL); + } + + gst_buffer_replace (&parse->id_header, NULL); + gst_buffer_replace (&parse->comment_header, NULL); + + gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (parse), caps); + parse->header_sent = TRUE; } GST_BUFFER_TIMESTAMP (frame->buffer) = parse->next_ts; diff --git a/ext/opus/gstopusparse.h b/ext/opus/gstopusparse.h index 60ea5c536b..f0fd24194f 100644 --- a/ext/opus/gstopusparse.h +++ b/ext/opus/gstopusparse.h @@ -46,6 +46,8 @@ struct _GstOpusParse { gboolean header_sent; GSList *headers; GstClockTime next_ts; + GstBuffer *id_header; + GstBuffer *comment_header; }; struct _GstOpusParseClass {