mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-02 21:48:55 +00:00
opus: Add proper support for multichannel audio
https://bugzilla.gnome.org/show_bug.cgi?id=757152
This commit is contained in:
parent
4c8f76f05b
commit
51edbeb9d9
10 changed files with 214 additions and 401 deletions
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"))) {
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Reference in a new issue