mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-06-06 07:28:53 +00:00
opus: Add proper support for multichannel audio
https://bugzilla.gnome.org/show_bug.cgi?id=757152
This commit is contained in:
parent
fc475ce01a
commit
4ca84a9b1a
6 changed files with 174 additions and 374 deletions
|
@ -10,6 +10,7 @@ libgstopus_la_CFLAGS = \
|
||||||
libgstopus_la_LIBADD = \
|
libgstopus_la_LIBADD = \
|
||||||
$(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_API_VERSION) \
|
$(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_API_VERSION) \
|
||||||
-lgsttag-$(GST_API_VERSION) -lgstrtp-$(GST_API_VERSION) \
|
-lgsttag-$(GST_API_VERSION) -lgstrtp-$(GST_API_VERSION) \
|
||||||
|
-lgstpbutils-$(GST_API_VERSION) \
|
||||||
$(GST_BASE_LIBS) \
|
$(GST_BASE_LIBS) \
|
||||||
$(GST_LIBS) \
|
$(GST_LIBS) \
|
||||||
$(OPUS_LIBS)
|
$(OPUS_LIBS)
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
#include "gstopusheader.h"
|
#include "gstopusheader.h"
|
||||||
#include "gstopuscommon.h"
|
#include "gstopuscommon.h"
|
||||||
#include "gstopusdec.h"
|
#include "gstopusdec.h"
|
||||||
|
#include <gst/pbutils/pbutils.h>
|
||||||
|
|
||||||
GST_DEBUG_CATEGORY_STATIC (opusdec_debug);
|
GST_DEBUG_CATEGORY_STATIC (opusdec_debug);
|
||||||
#define GST_CAT_DEFAULT opusdec_debug
|
#define GST_CAT_DEFAULT opusdec_debug
|
||||||
|
@ -225,6 +226,8 @@ gst_opus_dec_negotiate (GstOpusDec * dec, const GstAudioChannelPosition * pos)
|
||||||
GstAudioInfo info;
|
GstAudioInfo info;
|
||||||
|
|
||||||
if (caps) {
|
if (caps) {
|
||||||
|
gint rate, channels;
|
||||||
|
|
||||||
caps = gst_caps_truncate (caps);
|
caps = gst_caps_truncate (caps);
|
||||||
caps = gst_caps_make_writable (caps);
|
caps = gst_caps_make_writable (caps);
|
||||||
s = gst_caps_get_structure (caps, 0);
|
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);
|
gst_structure_fixate_field_nearest_int (s, "rate", dec->sample_rate);
|
||||||
else
|
else
|
||||||
gst_structure_set (s, "rate", G_TYPE_INT, dec->sample_rate, NULL);
|
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"))
|
if (gst_structure_has_field (s, "channels"))
|
||||||
gst_structure_fixate_field_nearest_int (s, "channels", dec->n_channels);
|
gst_structure_fixate_field_nearest_int (s, "channels", dec->n_channels);
|
||||||
else
|
else
|
||||||
gst_structure_set (s, "channels", G_TYPE_INT, dec->n_channels, NULL);
|
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);
|
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 */
|
/* but we still need the opus order for later reordering */
|
||||||
if (pos) {
|
if (pos) {
|
||||||
memcpy (dec->opus_pos, pos, sizeof (pos[0]) * dec->n_channels);
|
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 {
|
} else {
|
||||||
dec->opus_pos[0] = GST_AUDIO_CHANNEL_POSITION_INVALID;
|
dec->opus_pos[0] = GST_AUDIO_CHANNEL_POSITION_INVALID;
|
||||||
}
|
}
|
||||||
|
@ -284,79 +288,64 @@ gst_opus_dec_negotiate (GstOpusDec * dec, const GstAudioChannelPosition * pos)
|
||||||
static GstFlowReturn
|
static GstFlowReturn
|
||||||
gst_opus_dec_parse_header (GstOpusDec * dec, GstBuffer * buf)
|
gst_opus_dec_parse_header (GstOpusDec * dec, GstBuffer * buf)
|
||||||
{
|
{
|
||||||
const guint8 *data;
|
|
||||||
GstAudioChannelPosition pos[64];
|
GstAudioChannelPosition pos[64];
|
||||||
const GstAudioChannelPosition *posn = NULL;
|
const GstAudioChannelPosition *posn = NULL;
|
||||||
GstMapInfo map;
|
|
||||||
|
|
||||||
if (!gst_opus_header_is_id_header (buf)) {
|
if (!gst_opus_header_is_id_header (buf)) {
|
||||||
GST_ERROR_OBJECT (dec, "Header is not an Opus ID header");
|
GST_ERROR_OBJECT (dec, "Header is not an Opus ID header");
|
||||||
return GST_FLOW_ERROR;
|
return GST_FLOW_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
gst_buffer_map (buf, &map, GST_MAP_READ);
|
if (!gst_codec_utils_opus_parse_header (buf,
|
||||||
data = map.data;
|
&dec->sample_rate,
|
||||||
|
&dec->n_channels,
|
||||||
dec->n_channels = data[9];
|
&dec->channel_mapping_family,
|
||||||
dec->sample_rate = GST_READ_UINT32_LE (data + 12);
|
&dec->n_streams,
|
||||||
if (dec->sample_rate == 0)
|
&dec->n_stereo_streams,
|
||||||
dec->sample_rate = 48000;
|
dec->channel_mapping, &dec->pre_skip, &dec->r128_gain)) {
|
||||||
dec->pre_skip = GST_READ_UINT16_LE (data + 10);
|
GST_ERROR_OBJECT (dec, "Failed to parse Opus ID header");
|
||||||
dec->r128_gain = GST_READ_UINT16_LE (data + 16);
|
return GST_FLOW_ERROR;
|
||||||
|
}
|
||||||
dec->r128_gain_volume = gst_opus_dec_get_r128_volume (dec->r128_gain);
|
dec->r128_gain_volume = gst_opus_dec_get_r128_volume (dec->r128_gain);
|
||||||
|
|
||||||
GST_INFO_OBJECT (dec,
|
GST_INFO_OBJECT (dec,
|
||||||
"Found pre-skip of %u samples, R128 gain %d (volume %f)",
|
"Found pre-skip of %u samples, R128 gain %d (volume %f)",
|
||||||
dec->pre_skip, dec->r128_gain, dec->r128_gain_volume);
|
dec->pre_skip, dec->r128_gain, dec->r128_gain_volume);
|
||||||
|
|
||||||
dec->channel_mapping_family = data[18];
|
if (dec->channel_mapping_family == 1) {
|
||||||
if (dec->channel_mapping_family == 0) {
|
GST_INFO_OBJECT (dec, "Channel mapping family 1, Vorbis mapping");
|
||||||
/* implicit mapping */
|
switch (dec->n_channels) {
|
||||||
GST_INFO_OBJECT (dec, "Channel mapping family 0, implicit mapping");
|
case 1:
|
||||||
dec->n_streams = dec->n_stereo_streams = 1;
|
case 2:
|
||||||
dec->channel_mapping[0] = 0;
|
/* nothing */
|
||||||
dec->channel_mapping[1] = 1;
|
break;
|
||||||
} else {
|
case 3:
|
||||||
dec->n_streams = data[19];
|
case 4:
|
||||||
dec->n_stereo_streams = data[20];
|
case 5:
|
||||||
memcpy (dec->channel_mapping, data + 21, dec->n_channels);
|
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_ELEMENT_WARNING (GST_ELEMENT (dec), STREAM, DECODE,
|
||||||
GST_INFO_OBJECT (dec, "Channel mapping family 1, Vorbis mapping");
|
(NULL), ("Using NONE channel layout for more than 8 channels"));
|
||||||
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,
|
for (i = 0; i < dec->n_channels; i++)
|
||||||
(NULL), ("Using NONE channel layout for more than 8 channels"));
|
pos[i] = GST_AUDIO_CHANNEL_POSITION_NONE;
|
||||||
|
|
||||||
for (i = 0; i < dec->n_channels; i++)
|
posn = pos;
|
||||||
pos[i] = GST_AUDIO_CHANNEL_POSITION_NONE;
|
|
||||||
|
|
||||||
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_opus_dec_negotiate (dec, posn);
|
||||||
|
|
||||||
gst_buffer_unmap (buf, &map);
|
|
||||||
|
|
||||||
return GST_FLOW_OK;
|
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)) {
|
if (header && G_VALUE_HOLDS (header, GST_TYPE_BUFFER)) {
|
||||||
buf = gst_value_get_buffer (header);
|
buf = gst_value_get_buffer (header);
|
||||||
res = gst_opus_dec_parse_header (dec, buf);
|
res = gst_opus_dec_parse_header (dec, buf);
|
||||||
if (res != GST_FLOW_OK)
|
if (res != GST_FLOW_OK) {
|
||||||
|
ret = FALSE;
|
||||||
goto done;
|
goto done;
|
||||||
|
}
|
||||||
gst_buffer_replace (&dec->streamheader, buf);
|
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)) {
|
if (vorbiscomment && G_VALUE_HOLDS (vorbiscomment, GST_TYPE_BUFFER)) {
|
||||||
buf = gst_value_get_buffer (vorbiscomment);
|
buf = gst_value_get_buffer (vorbiscomment);
|
||||||
res = gst_opus_dec_parse_comments (dec, buf);
|
res = gst_opus_dec_parse_comments (dec, buf);
|
||||||
if (res != GST_FLOW_OK)
|
if (res != GST_FLOW_OK) {
|
||||||
|
ret = FALSE;
|
||||||
goto done;
|
goto done;
|
||||||
|
}
|
||||||
gst_buffer_replace (&dec->vorbiscomment, buf);
|
gst_buffer_replace (&dec->vorbiscomment, buf);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* defaults if not in the caps */
|
const GstAudioChannelPosition *posn = NULL;
|
||||||
dec->n_channels = 2;
|
|
||||||
dec->sample_rate = 48000;
|
|
||||||
|
|
||||||
gst_structure_get_int (s, "channels", &dec->n_channels);
|
if (!gst_codec_utils_opus_parse_caps (caps, &dec->sample_rate,
|
||||||
gst_structure_get_int (s, "rate", &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 */
|
if (dec->channel_mapping_family == 1 && dec->n_channels <= 8)
|
||||||
dec->channel_mapping_family = 0;
|
posn = gst_opus_channel_positions[dec->n_channels - 1];
|
||||||
dec->channel_mapping[0] = 0;
|
|
||||||
dec->channel_mapping[1] = 1;
|
|
||||||
dec->n_streams = 1;
|
|
||||||
dec->n_stereo_streams = 1;
|
|
||||||
|
|
||||||
gst_opus_dec_negotiate (dec, NULL);
|
gst_opus_dec_negotiate (dec, posn);
|
||||||
}
|
}
|
||||||
|
|
||||||
done:
|
done:
|
||||||
|
|
|
@ -52,9 +52,9 @@ struct _GstOpusDec {
|
||||||
GstBuffer *streamheader;
|
GstBuffer *streamheader;
|
||||||
GstBuffer *vorbiscomment;
|
GstBuffer *vorbiscomment;
|
||||||
|
|
||||||
int sample_rate;
|
guint32 sample_rate;
|
||||||
int n_channels;
|
guint8 n_channels;
|
||||||
guint32 pre_skip;
|
guint16 pre_skip;
|
||||||
gint16 r128_gain;
|
gint16 r128_gain;
|
||||||
|
|
||||||
GstAudioChannelPosition opus_pos[64];
|
GstAudioChannelPosition opus_pos[64];
|
||||||
|
|
|
@ -48,6 +48,8 @@
|
||||||
|
|
||||||
#include <gst/gsttagsetter.h>
|
#include <gst/gsttagsetter.h>
|
||||||
#include <gst/audio/audio.h>
|
#include <gst/audio/audio.h>
|
||||||
|
#include <gst/pbutils/pbutils.h>
|
||||||
|
#include <gst/tag/tag.h>
|
||||||
#include <gst/glib-compat-private.h>
|
#include <gst/glib-compat-private.h>
|
||||||
#include "gstopusheader.h"
|
#include "gstopusheader.h"
|
||||||
#include "gstopuscommon.h"
|
#include "gstopuscommon.h"
|
||||||
|
@ -167,12 +169,12 @@ static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
|
||||||
"format = (string) " FORMAT_STR ", "
|
"format = (string) " FORMAT_STR ", "
|
||||||
"layout = (string) interleaved, "
|
"layout = (string) interleaved, "
|
||||||
"rate = (int) 48000, "
|
"rate = (int) 48000, "
|
||||||
"channels = (int) [ 1, 2 ]; "
|
"channels = (int) [ 1, 8 ]; "
|
||||||
"audio/x-raw, "
|
"audio/x-raw, "
|
||||||
"format = (string) " FORMAT_STR ", "
|
"format = (string) " FORMAT_STR ", "
|
||||||
"layout = (string) interleaved, "
|
"layout = (string) interleaved, "
|
||||||
"rate = (int) { 8000, 12000, 16000, 24000 }, "
|
"rate = (int) { 8000, 12000, 16000, 24000 }, "
|
||||||
"channels = (int) [ 1, 2 ] ")
|
"channels = (int) [ 1, 8 ] ")
|
||||||
);
|
);
|
||||||
|
|
||||||
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
|
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
|
/* For two channels, use the basic RTP mapping if the channels are
|
||||||
mapped as left/right. */
|
mapped as left/right. */
|
||||||
if (enc->n_channels == 2) {
|
if (enc->n_channels == 2) {
|
||||||
if (MAPS (0, FRONT_LEFT) && MAPS (1, FRONT_RIGHT)) {
|
GST_INFO_OBJECT (enc, "Stereo, trivial RTP mapping");
|
||||||
GST_INFO_OBJECT (enc, "Stereo, canonical mapping");
|
enc->channel_mapping_family = 0;
|
||||||
enc->channel_mapping_family = 0;
|
enc->n_stereo_streams = 1;
|
||||||
enc->n_stereo_streams = 1;
|
/* implicit mapping for family 0 */
|
||||||
/* The channel mapping is implicit for family 0, that's why we do not
|
return;
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For channels between 1 and 8, we use the Vorbis mapping if we can
|
/* For channels between 3 and 8, we use the Vorbis mapping if we can
|
||||||
find a permutation that matches it. Mono will have been taken care
|
find a permutation that matches it. Mono and stereo will have been taken
|
||||||
of earlier, but this code also handles it. Same for left/right stereo.
|
care of earlier, but this code also handles it. There are two mappings.
|
||||||
There are two mappings. One maps the input channels to an ordering
|
One maps the input channels to an ordering which has the natural pairs
|
||||||
which has the natural pairs first so they can benefit from the Opus
|
first so they can benefit from the Opus stereo channel coupling, and the
|
||||||
stereo channel coupling, and the other maps this ordering to the
|
other maps this ordering to the Vorbis ordering. */
|
||||||
Vorbis ordering. */
|
if (enc->n_channels >= 3 && enc->n_channels <= 8) {
|
||||||
if (enc->n_channels >= 1 && enc->n_channels <= 8) {
|
|
||||||
int c0, c1, c0v, c1v;
|
int c0, c1, c0v, c1v;
|
||||||
int mapped;
|
int mapped;
|
||||||
gboolean positions_done[256];
|
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_FRONT_RIGHT_OF_CENTER},
|
||||||
{GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
|
{GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
|
||||||
GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT},
|
GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT},
|
||||||
|
{GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
||||||
|
GST_AUDIO_CHANNEL_POSITION_REAR_CENTER},
|
||||||
};
|
};
|
||||||
size_t pair;
|
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_DEBUG_OBJECT (enc, "Channel position %s is not mapped yet, adding",
|
||||||
gst_opus_channel_names[position]);
|
gst_opus_channel_names[position]);
|
||||||
cv = gst_opus_enc_find_channel_position_in_vorbis_order (enc, position);
|
cv = gst_opus_enc_find_channel_position_in_vorbis_order (enc, position);
|
||||||
if (cv < 0) {
|
if (cv < 0)
|
||||||
GST_WARNING_OBJECT (enc,
|
g_assert_not_reached ();
|
||||||
"Cannot map channel positions to Vorbis order, using unknown mapping");
|
|
||||||
enc->channel_mapping_family = 255;
|
|
||||||
enc->n_stereo_streams = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
enc->encoding_channel_mapping[mapped] = n;
|
enc->encoding_channel_mapping[mapped] = n;
|
||||||
enc->decoding_channel_mapping[cv] = mapped;
|
enc->decoding_channel_mapping[cv] = mapped;
|
||||||
mapped++;
|
mapped++;
|
||||||
|
@ -718,6 +710,8 @@ gst_opus_enc_setup (GstOpusEnc * enc)
|
||||||
GstCaps *caps;
|
GstCaps *caps;
|
||||||
gboolean ret;
|
gboolean ret;
|
||||||
gint32 lookahead;
|
gint32 lookahead;
|
||||||
|
const GstTagList *tags;
|
||||||
|
GstBuffer *header, *comments;
|
||||||
|
|
||||||
#ifndef GST_DISABLE_GST_DEBUG
|
#ifndef GST_DISABLE_GST_DEBUG
|
||||||
GST_DEBUG_OBJECT (enc,
|
GST_DEBUG_OBJECT (enc,
|
||||||
|
@ -764,10 +758,17 @@ gst_opus_enc_setup (GstOpusEnc * enc)
|
||||||
lookahead = lookahead * 48000 / enc->sample_rate;
|
lookahead = lookahead * 48000 / enc->sample_rate;
|
||||||
enc->lookahead = enc->pending_lookahead = lookahead;
|
enc->lookahead = enc->pending_lookahead = lookahead;
|
||||||
|
|
||||||
gst_opus_header_create_caps (&caps, NULL, lookahead, enc->sample_rate,
|
header = gst_codec_utils_opus_create_header (enc->sample_rate,
|
||||||
enc->n_channels, enc->n_stereo_streams, enc->channel_mapping_family,
|
enc->n_channels, enc->channel_mapping_family,
|
||||||
enc->decoding_channel_mapping,
|
enc->n_channels - enc->n_stereo_streams, enc->n_stereo_streams,
|
||||||
gst_tag_setter_get_tag_list (GST_TAG_SETTER (enc)));
|
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 */
|
/* negotiate with these caps */
|
||||||
GST_DEBUG_OBJECT (enc, "here are the caps: %" GST_PTR_FORMAT, 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);
|
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 *
|
static GstCaps *
|
||||||
gst_opus_enc_sink_getcaps (GstAudioEncoder * benc, GstCaps * filter)
|
gst_opus_enc_sink_getcaps (GstAudioEncoder * benc, GstCaps * filter)
|
||||||
{
|
{
|
||||||
GstOpusEnc *enc;
|
GstOpusEnc *enc;
|
||||||
GstCaps *caps;
|
GstCaps *caps;
|
||||||
GstCaps *tcaps;
|
|
||||||
GstCaps *peercaps = NULL;
|
|
||||||
GstCaps *intersect = NULL;
|
|
||||||
guint i;
|
|
||||||
gboolean allow_multistream;
|
|
||||||
|
|
||||||
enc = GST_OPUS_ENC (benc);
|
enc = GST_OPUS_ENC (benc);
|
||||||
|
|
||||||
GST_DEBUG_OBJECT (enc, "sink getcaps");
|
GST_DEBUG_OBJECT (enc, "sink getcaps");
|
||||||
|
|
||||||
peercaps = gst_pad_peer_query_caps (GST_AUDIO_ENCODER_SRC_PAD (benc), NULL);
|
caps = gst_opus_enc_get_sink_template_caps ();
|
||||||
if (!peercaps) {
|
caps = gst_audio_encoder_proxy_getcaps (benc, caps, filter);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
GST_DEBUG_OBJECT (enc, "Returning caps: %" GST_PTR_FORMAT, caps);
|
GST_DEBUG_OBJECT (enc, "Returning caps: %" GST_PTR_FORMAT, caps);
|
||||||
|
|
||||||
return caps;
|
return caps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,218 +26,6 @@
|
||||||
#include <gst/base/gstbytewriter.h>
|
#include <gst/base/gstbytewriter.h>
|
||||||
#include "gstopusheader.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
|
gboolean
|
||||||
gst_opus_header_is_header (GstBuffer * buf, const char *magic, guint magic_size)
|
gst_opus_header_is_header (GstBuffer * buf, const char *magic, guint magic_size)
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,12 +26,6 @@
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
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,
|
extern gboolean gst_opus_header_is_header (GstBuffer * buf,
|
||||||
const char *magic, guint magic_size);
|
const char *magic, guint magic_size);
|
||||||
extern gboolean gst_opus_header_is_id_header (GstBuffer * buf);
|
extern gboolean gst_opus_header_is_id_header (GstBuffer * buf);
|
||||||
|
|
Loading…
Reference in a new issue