mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-19 14:56:36 +00:00
0619168e2a
And clean up any old pending headers if we receive a new identification header, or if we receive a new set of headers via caps. Otherwise it might happen that we receive one or more header but not all, and then afterwards all headers again, and libvorbis does not like getting headers passed multiple times and would error out. It only makes sense to pass the very latest headers to the decoder at the time we can actually make use of them. https://bugzilla.gnome.org/show_bug.cgi?id=796980
776 lines
21 KiB
C
776 lines
21 KiB
C
/* GStreamer
|
|
* Copyright (C) 2004 Benjamin Otte <in7y118@public.uni-hamburg.de>
|
|
*
|
|
* 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., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-vorbisdec
|
|
* @title: vorbisdec
|
|
* @see_also: vorbisenc, oggdemux
|
|
*
|
|
* This element decodes a Vorbis stream to raw float audio.
|
|
* <ulink url="http://www.vorbis.com/">Vorbis</ulink> is a royalty-free
|
|
* audio codec maintained by the <ulink url="http://www.xiph.org/">Xiph.org
|
|
* Foundation</ulink>. As it outputs raw float audio you will often need to
|
|
* put an audioconvert element after it.
|
|
*
|
|
* ## Example pipelines
|
|
* |[
|
|
* gst-launch-1.0 -v filesrc location=sine.ogg ! oggdemux ! vorbisdec ! audioconvert ! audioresample ! autoaudiosink
|
|
* ]|
|
|
* Decode an Ogg/Vorbis. To create an Ogg/Vorbis file refer to the documentation of vorbisenc.
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include "gstvorbisdec.h"
|
|
#include <string.h>
|
|
#include <gst/audio/audio.h>
|
|
#include <gst/tag/tag.h>
|
|
|
|
#include "gstvorbiscommon.h"
|
|
|
|
#ifndef TREMOR
|
|
GST_DEBUG_CATEGORY_EXTERN (vorbisdec_debug);
|
|
#define GST_CAT_DEFAULT vorbisdec_debug
|
|
#else
|
|
GST_DEBUG_CATEGORY_EXTERN (ivorbisdec_debug);
|
|
#define GST_CAT_DEFAULT ivorbisdec_debug
|
|
#endif
|
|
|
|
static GstStaticPadTemplate vorbis_dec_src_factory =
|
|
GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_VORBIS_DEC_SRC_CAPS);
|
|
|
|
static GstStaticPadTemplate vorbis_dec_sink_factory =
|
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("audio/x-vorbis")
|
|
);
|
|
|
|
#define gst_vorbis_dec_parent_class parent_class
|
|
G_DEFINE_TYPE (GstVorbisDec, gst_vorbis_dec, GST_TYPE_AUDIO_DECODER);
|
|
|
|
static void vorbis_dec_finalize (GObject * object);
|
|
|
|
static gboolean vorbis_dec_start (GstAudioDecoder * dec);
|
|
static gboolean vorbis_dec_stop (GstAudioDecoder * dec);
|
|
static GstFlowReturn vorbis_dec_handle_frame (GstAudioDecoder * dec,
|
|
GstBuffer * buffer);
|
|
static void vorbis_dec_flush (GstAudioDecoder * dec, gboolean hard);
|
|
static gboolean vorbis_dec_set_format (GstAudioDecoder * dec, GstCaps * caps);
|
|
static void vorbis_dec_reset (GstAudioDecoder * dec);
|
|
|
|
static void
|
|
gst_vorbis_dec_class_init (GstVorbisDecClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
|
GstAudioDecoderClass *base_class = GST_AUDIO_DECODER_CLASS (klass);
|
|
|
|
gobject_class->finalize = vorbis_dec_finalize;
|
|
|
|
gst_element_class_add_static_pad_template (element_class,
|
|
&vorbis_dec_src_factory);
|
|
gst_element_class_add_static_pad_template (element_class,
|
|
&vorbis_dec_sink_factory);
|
|
|
|
gst_element_class_set_static_metadata (element_class,
|
|
"Vorbis audio decoder", "Codec/Decoder/Audio",
|
|
GST_VORBIS_DEC_DESCRIPTION,
|
|
"Benjamin Otte <otte@gnome.org>, Chris Lord <chris@openedhand.com>");
|
|
|
|
base_class->start = GST_DEBUG_FUNCPTR (vorbis_dec_start);
|
|
base_class->stop = GST_DEBUG_FUNCPTR (vorbis_dec_stop);
|
|
base_class->set_format = GST_DEBUG_FUNCPTR (vorbis_dec_set_format);
|
|
base_class->handle_frame = GST_DEBUG_FUNCPTR (vorbis_dec_handle_frame);
|
|
base_class->flush = GST_DEBUG_FUNCPTR (vorbis_dec_flush);
|
|
}
|
|
|
|
static void
|
|
gst_vorbis_dec_init (GstVorbisDec * dec)
|
|
{
|
|
gst_audio_decoder_set_use_default_pad_acceptcaps (GST_AUDIO_DECODER_CAST
|
|
(dec), TRUE);
|
|
GST_PAD_SET_ACCEPT_TEMPLATE (GST_AUDIO_DECODER_SINK_PAD (dec));
|
|
}
|
|
|
|
static void
|
|
vorbis_dec_finalize (GObject * object)
|
|
{
|
|
/* Release any possibly allocated libvorbis data.
|
|
* _clear functions can safely be called multiple times
|
|
*/
|
|
GstVorbisDec *vd = GST_VORBIS_DEC (object);
|
|
|
|
#ifndef USE_TREMOLO
|
|
vorbis_block_clear (&vd->vb);
|
|
#endif
|
|
vorbis_dsp_clear (&vd->vd);
|
|
vorbis_comment_clear (&vd->vc);
|
|
vorbis_info_clear (&vd->vi);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static gboolean
|
|
vorbis_dec_start (GstAudioDecoder * dec)
|
|
{
|
|
GstVorbisDec *vd = GST_VORBIS_DEC (dec);
|
|
|
|
GST_DEBUG_OBJECT (dec, "start");
|
|
vorbis_info_init (&vd->vi);
|
|
vorbis_comment_init (&vd->vc);
|
|
vd->initialized = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
vorbis_dec_stop (GstAudioDecoder * dec)
|
|
{
|
|
GstVorbisDec *vd = GST_VORBIS_DEC (dec);
|
|
|
|
GST_DEBUG_OBJECT (dec, "stop");
|
|
vd->initialized = FALSE;
|
|
#ifndef USE_TREMOLO
|
|
vorbis_block_clear (&vd->vb);
|
|
#endif
|
|
vorbis_dsp_clear (&vd->vd);
|
|
vorbis_comment_clear (&vd->vc);
|
|
vorbis_info_clear (&vd->vi);
|
|
if (vd->pending_headers) {
|
|
g_list_free_full (vd->pending_headers, (GDestroyNotify) gst_buffer_unref);
|
|
vd->pending_headers = NULL;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
vorbis_handle_identification_packet (GstVorbisDec * vd)
|
|
{
|
|
GstAudioInfo info;
|
|
|
|
switch (vd->vi.channels) {
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
{
|
|
const GstAudioChannelPosition *pos;
|
|
|
|
pos = gst_vorbis_default_channel_positions[vd->vi.channels - 1];
|
|
gst_audio_info_set_format (&info, GST_VORBIS_AUDIO_FORMAT, vd->vi.rate,
|
|
vd->vi.channels, pos);
|
|
break;
|
|
}
|
|
default:{
|
|
GstAudioChannelPosition position[64];
|
|
gint i, max_pos = MAX (vd->vi.channels, 64);
|
|
|
|
GST_ELEMENT_WARNING (vd, STREAM, DECODE,
|
|
(NULL), ("Using NONE channel layout for more than 8 channels"));
|
|
for (i = 0; i < max_pos; i++)
|
|
position[i] = GST_AUDIO_CHANNEL_POSITION_NONE;
|
|
gst_audio_info_set_format (&info, GST_VORBIS_AUDIO_FORMAT, vd->vi.rate,
|
|
vd->vi.channels, position);
|
|
break;
|
|
}
|
|
}
|
|
|
|
gst_audio_decoder_set_output_format (GST_AUDIO_DECODER (vd), &info);
|
|
|
|
vd->info = info;
|
|
/* select a copy_samples function, this way we can have specialized versions
|
|
* for mono/stereo and avoid the depth switch in tremor case */
|
|
vd->copy_samples = gst_vorbis_get_copy_sample_func (info.channels);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
/* FIXME 0.11: remove tag handling and let container take care of that? */
|
|
static GstFlowReturn
|
|
vorbis_handle_comment_packet (GstVorbisDec * vd, ogg_packet * packet)
|
|
{
|
|
guint bitrate = 0;
|
|
gchar *encoder = NULL;
|
|
GstTagList *list;
|
|
guint8 *data;
|
|
gsize size;
|
|
|
|
GST_DEBUG_OBJECT (vd, "parsing comment packet");
|
|
|
|
data = gst_ogg_packet_data (packet);
|
|
size = gst_ogg_packet_size (packet);
|
|
|
|
list =
|
|
gst_tag_list_from_vorbiscomment (data, size, (guint8 *) "\003vorbis", 7,
|
|
&encoder);
|
|
|
|
if (!list) {
|
|
GST_ERROR_OBJECT (vd, "couldn't decode comments");
|
|
list = gst_tag_list_new_empty ();
|
|
}
|
|
|
|
if (encoder) {
|
|
if (encoder[0])
|
|
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
|
|
GST_TAG_ENCODER, encoder, NULL);
|
|
g_free (encoder);
|
|
}
|
|
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
|
|
GST_TAG_ENCODER_VERSION, vd->vi.version,
|
|
GST_TAG_AUDIO_CODEC, "Vorbis", NULL);
|
|
if (vd->vi.bitrate_nominal > 0 && vd->vi.bitrate_nominal <= 0x7FFFFFFF) {
|
|
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
|
|
GST_TAG_NOMINAL_BITRATE, (guint) vd->vi.bitrate_nominal, NULL);
|
|
bitrate = vd->vi.bitrate_nominal;
|
|
}
|
|
if (vd->vi.bitrate_upper > 0 && vd->vi.bitrate_upper <= 0x7FFFFFFF) {
|
|
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
|
|
GST_TAG_MAXIMUM_BITRATE, (guint) vd->vi.bitrate_upper, NULL);
|
|
if (!bitrate)
|
|
bitrate = vd->vi.bitrate_upper;
|
|
}
|
|
if (vd->vi.bitrate_lower > 0 && vd->vi.bitrate_lower <= 0x7FFFFFFF) {
|
|
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
|
|
GST_TAG_MINIMUM_BITRATE, (guint) vd->vi.bitrate_lower, NULL);
|
|
if (!bitrate)
|
|
bitrate = vd->vi.bitrate_lower;
|
|
}
|
|
if (bitrate) {
|
|
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
|
|
GST_TAG_BITRATE, (guint) bitrate, NULL);
|
|
}
|
|
|
|
gst_audio_decoder_merge_tags (GST_AUDIO_DECODER_CAST (vd), list,
|
|
GST_TAG_MERGE_REPLACE);
|
|
gst_tag_list_unref (list);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
vorbis_handle_type_packet (GstVorbisDec * vd)
|
|
{
|
|
gint res;
|
|
|
|
g_assert (!vd->initialized);
|
|
|
|
#ifdef USE_TREMOLO
|
|
if (G_UNLIKELY ((res = vorbis_dsp_init (&vd->vd, &vd->vi))))
|
|
goto synthesis_init_error;
|
|
#else
|
|
if (G_UNLIKELY ((res = vorbis_synthesis_init (&vd->vd, &vd->vi))))
|
|
goto synthesis_init_error;
|
|
|
|
if (G_UNLIKELY ((res = vorbis_block_init (&vd->vd, &vd->vb))))
|
|
goto block_init_error;
|
|
#endif
|
|
|
|
vd->initialized = TRUE;
|
|
|
|
return GST_FLOW_OK;
|
|
|
|
/* ERRORS */
|
|
synthesis_init_error:
|
|
{
|
|
GST_ELEMENT_ERROR (GST_ELEMENT (vd), STREAM, DECODE,
|
|
(NULL), ("couldn't initialize synthesis (%d)", res));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
block_init_error:
|
|
{
|
|
GST_ELEMENT_ERROR (GST_ELEMENT (vd), STREAM, DECODE,
|
|
(NULL), ("couldn't initialize block (%d)", res));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
vorbis_handle_header_packet (GstVorbisDec * vd, ogg_packet * packet)
|
|
{
|
|
GstFlowReturn res;
|
|
gint ret;
|
|
|
|
GST_DEBUG_OBJECT (vd, "parsing header packet");
|
|
|
|
/* Packetno = 0 if the first byte is exactly 0x01 */
|
|
packet->b_o_s = ((gst_ogg_packet_data (packet))[0] == 0x1) ? 1 : 0;
|
|
|
|
#ifdef USE_TREMOLO
|
|
if ((ret = vorbis_dsp_headerin (&vd->vi, &vd->vc, packet)))
|
|
#else
|
|
if ((ret = vorbis_synthesis_headerin (&vd->vi, &vd->vc, packet)))
|
|
#endif
|
|
goto header_read_error;
|
|
|
|
switch ((gst_ogg_packet_data (packet))[0]) {
|
|
case 0x01:
|
|
res = vorbis_handle_identification_packet (vd);
|
|
break;
|
|
case 0x03:
|
|
res = vorbis_handle_comment_packet (vd, packet);
|
|
break;
|
|
case 0x05:
|
|
res = vorbis_handle_type_packet (vd);
|
|
break;
|
|
default:
|
|
/* ignore */
|
|
g_warning ("unknown vorbis header packet found");
|
|
res = GST_FLOW_OK;
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
|
|
/* ERRORS */
|
|
header_read_error:
|
|
{
|
|
GST_ELEMENT_ERROR (GST_ELEMENT (vd), STREAM, DECODE,
|
|
(NULL), ("couldn't read header packet (%d)", ret));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
vorbis_dec_handle_header_buffer (GstVorbisDec * vd, GstBuffer * buffer)
|
|
{
|
|
ogg_packet *packet;
|
|
ogg_packet_wrapper packet_wrapper;
|
|
GstFlowReturn ret;
|
|
GstMapInfo map;
|
|
|
|
gst_ogg_packet_wrapper_map (&packet_wrapper, buffer, &map);
|
|
packet = gst_ogg_packet_from_wrapper (&packet_wrapper);
|
|
|
|
ret = vorbis_handle_header_packet (vd, packet);
|
|
|
|
gst_ogg_packet_wrapper_unmap (&packet_wrapper, buffer, &map);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define MIN_NUM_HEADERS 3
|
|
static GstFlowReturn
|
|
vorbis_dec_handle_header_caps (GstVorbisDec * vd)
|
|
{
|
|
GstFlowReturn result = GST_FLOW_OK;
|
|
GstCaps *caps;
|
|
GstStructure *s = NULL;
|
|
const GValue *array = NULL;
|
|
|
|
caps = gst_pad_get_current_caps (GST_AUDIO_DECODER_SINK_PAD (vd));
|
|
if (caps)
|
|
s = gst_caps_get_structure (caps, 0);
|
|
if (s)
|
|
array = gst_structure_get_value (s, "streamheader");
|
|
|
|
if (caps)
|
|
gst_caps_unref (caps);
|
|
|
|
if (array && (gst_value_array_get_size (array) >= MIN_NUM_HEADERS)) {
|
|
const GValue *value = NULL;
|
|
GstBuffer *buf = NULL;
|
|
gint i = 0;
|
|
|
|
if (vd->pending_headers) {
|
|
GST_DEBUG_OBJECT (vd,
|
|
"got new headers from caps, discarding old pending headers");
|
|
|
|
g_list_free_full (vd->pending_headers, (GDestroyNotify) gst_buffer_unref);
|
|
vd->pending_headers = NULL;
|
|
}
|
|
|
|
while (result == GST_FLOW_OK && i < gst_value_array_get_size (array)) {
|
|
value = gst_value_array_get_value (array, i);
|
|
buf = gst_value_get_buffer (value);
|
|
if (!buf)
|
|
goto null_buffer;
|
|
result = vorbis_dec_handle_header_buffer (vd, buf);
|
|
i++;
|
|
}
|
|
} else
|
|
goto array_error;
|
|
|
|
done:
|
|
return (result != GST_FLOW_OK ? GST_FLOW_NOT_NEGOTIATED : GST_FLOW_OK);
|
|
|
|
/* ERRORS */
|
|
array_error:
|
|
{
|
|
GST_WARNING_OBJECT (vd, "streamheader array not found");
|
|
result = GST_FLOW_ERROR;
|
|
goto done;
|
|
}
|
|
null_buffer:
|
|
{
|
|
GST_WARNING_OBJECT (vd, "streamheader with null buffer received");
|
|
result = GST_FLOW_ERROR;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
|
|
static GstFlowReturn
|
|
vorbis_handle_data_packet (GstVorbisDec * vd, ogg_packet * packet,
|
|
GstClockTime timestamp, GstClockTime duration)
|
|
{
|
|
#ifdef USE_TREMOLO
|
|
vorbis_sample_t *pcm;
|
|
#else
|
|
vorbis_sample_t **pcm;
|
|
#endif
|
|
guint sample_count;
|
|
GstBuffer *out = NULL;
|
|
GstFlowReturn result;
|
|
GstMapInfo map;
|
|
gsize size;
|
|
|
|
if (G_UNLIKELY (!vd->initialized)) {
|
|
result = vorbis_dec_handle_header_caps (vd);
|
|
if (result != GST_FLOW_OK)
|
|
goto not_initialized;
|
|
}
|
|
|
|
/* normal data packet */
|
|
/* FIXME, we can skip decoding if the packet is outside of the
|
|
* segment, this is however not very trivial as we need a previous
|
|
* packet to decode the current one so we must be careful not to
|
|
* throw away too much. For now we decode everything and clip right
|
|
* before pushing data. */
|
|
|
|
#ifdef USE_TREMOLO
|
|
if (G_UNLIKELY (vorbis_dsp_synthesis (&vd->vd, packet, 1)))
|
|
goto could_not_read;
|
|
#else
|
|
if (G_UNLIKELY (vorbis_synthesis (&vd->vb, packet)))
|
|
goto could_not_read;
|
|
|
|
if (G_UNLIKELY (vorbis_synthesis_blockin (&vd->vd, &vd->vb) < 0))
|
|
goto not_accepted;
|
|
#endif
|
|
|
|
/* assume all goes well here */
|
|
result = GST_FLOW_OK;
|
|
|
|
/* count samples ready for reading */
|
|
#ifdef USE_TREMOLO
|
|
if ((sample_count = vorbis_dsp_pcmout (&vd->vd, NULL, 0)) == 0)
|
|
#else
|
|
if ((sample_count = vorbis_synthesis_pcmout (&vd->vd, NULL)) == 0)
|
|
goto done;
|
|
#endif
|
|
|
|
size = sample_count * vd->info.bpf;
|
|
GST_LOG_OBJECT (vd, "%d samples ready for reading, size %" G_GSIZE_FORMAT,
|
|
sample_count, size);
|
|
|
|
/* alloc buffer for it */
|
|
out = gst_audio_decoder_allocate_output_buffer (GST_AUDIO_DECODER (vd), size);
|
|
|
|
gst_buffer_map (out, &map, GST_MAP_WRITE);
|
|
/* get samples ready for reading now, should be sample_count */
|
|
#ifdef USE_TREMOLO
|
|
if (G_UNLIKELY (vorbis_dsp_pcmout (&vd->vd, map.data, sample_count) !=
|
|
sample_count))
|
|
#else
|
|
if (G_UNLIKELY (vorbis_synthesis_pcmout (&vd->vd, &pcm) != sample_count))
|
|
#endif
|
|
goto wrong_samples;
|
|
|
|
#ifdef USE_TREMOLO
|
|
if (vd->info.channels < 9)
|
|
gst_audio_reorder_channels (map.data, map.size, GST_VORBIS_AUDIO_FORMAT,
|
|
vd->info.channels, gst_vorbis_channel_positions[vd->info.channels - 1],
|
|
gst_vorbis_default_channel_positions[vd->info.channels - 1]);
|
|
#else
|
|
/* copy samples in buffer */
|
|
vd->copy_samples ((vorbis_sample_t *) map.data, pcm,
|
|
sample_count, vd->info.channels);
|
|
#endif
|
|
|
|
GST_LOG_OBJECT (vd, "have output size of %" G_GSIZE_FORMAT, size);
|
|
gst_buffer_unmap (out, &map);
|
|
|
|
done:
|
|
/* whether or not data produced, consume one frame and advance time */
|
|
result = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (vd), out, 1);
|
|
|
|
#ifdef USE_TREMOLO
|
|
vorbis_dsp_read (&vd->vd, sample_count);
|
|
#else
|
|
vorbis_synthesis_read (&vd->vd, sample_count);
|
|
#endif
|
|
|
|
return result;
|
|
|
|
/* ERRORS */
|
|
not_initialized:
|
|
{
|
|
GST_ELEMENT_ERROR (GST_ELEMENT (vd), STREAM, DECODE,
|
|
(NULL), ("no header sent yet"));
|
|
return GST_FLOW_NOT_NEGOTIATED;
|
|
}
|
|
could_not_read:
|
|
{
|
|
GST_ELEMENT_ERROR (GST_ELEMENT (vd), STREAM, DECODE,
|
|
(NULL), ("couldn't read data packet"));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
not_accepted:
|
|
{
|
|
GST_ELEMENT_ERROR (GST_ELEMENT (vd), STREAM, DECODE,
|
|
(NULL), ("vorbis decoder did not accept data packet"));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
wrong_samples:
|
|
{
|
|
gst_buffer_unref (out);
|
|
GST_ELEMENT_ERROR (GST_ELEMENT (vd), STREAM, DECODE,
|
|
(NULL), ("vorbis decoder reported wrong number of samples"));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
check_pending_headers (GstVorbisDec * vd)
|
|
{
|
|
GstBuffer *buffer1, *buffer3, *buffer5;
|
|
GstMapInfo map;
|
|
gboolean isvalid;
|
|
GList *tmp = vd->pending_headers;
|
|
GstFlowReturn result = GST_FLOW_OK;
|
|
|
|
if (g_list_length (vd->pending_headers) < MIN_NUM_HEADERS)
|
|
goto not_enough;
|
|
|
|
buffer1 = (GstBuffer *) tmp->data;
|
|
tmp = tmp->next;
|
|
buffer3 = (GstBuffer *) tmp->data;
|
|
tmp = tmp->next;
|
|
buffer5 = (GstBuffer *) tmp->data;
|
|
|
|
/* Start checking the headers */
|
|
gst_buffer_map (buffer1, &map, GST_MAP_READ);
|
|
isvalid = map.size >= 1 && map.data[0] == 0x01;
|
|
gst_buffer_unmap (buffer1, &map);
|
|
if (!isvalid) {
|
|
GST_WARNING_OBJECT (vd, "Pending first header was invalid");
|
|
goto cleanup;
|
|
}
|
|
|
|
gst_buffer_map (buffer3, &map, GST_MAP_READ);
|
|
isvalid = map.size >= 1 && map.data[0] == 0x03;
|
|
gst_buffer_unmap (buffer3, &map);
|
|
if (!isvalid) {
|
|
GST_WARNING_OBJECT (vd, "Pending second header was invalid");
|
|
goto cleanup;
|
|
}
|
|
|
|
gst_buffer_map (buffer5, &map, GST_MAP_READ);
|
|
isvalid = map.size >= 1 && map.data[0] == 0x05;
|
|
gst_buffer_unmap (buffer5, &map);
|
|
if (!isvalid) {
|
|
GST_WARNING_OBJECT (vd, "Pending third header was invalid");
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Discard any other pending headers */
|
|
if (tmp->next) {
|
|
GST_DEBUG_OBJECT (vd, "Discarding extra headers");
|
|
g_list_free_full (tmp->next, (GDestroyNotify) gst_buffer_unref);
|
|
tmp->next = NULL;
|
|
}
|
|
g_list_free (vd->pending_headers);
|
|
vd->pending_headers = NULL;
|
|
|
|
GST_DEBUG_OBJECT (vd, "Resetting and processing new headers");
|
|
|
|
/* All good, let's reset ourselves and process the headers */
|
|
vorbis_dec_reset ((GstAudioDecoder *) vd);
|
|
result = vorbis_dec_handle_header_buffer (vd, buffer1);
|
|
if (result != GST_FLOW_OK) {
|
|
gst_buffer_unref (buffer3);
|
|
gst_buffer_unref (buffer5);
|
|
return result;
|
|
}
|
|
result = vorbis_dec_handle_header_buffer (vd, buffer3);
|
|
if (result != GST_FLOW_OK) {
|
|
gst_buffer_unref (buffer5);
|
|
return result;
|
|
}
|
|
result = vorbis_dec_handle_header_buffer (vd, buffer5);
|
|
|
|
return result;
|
|
|
|
/* ERRORS */
|
|
cleanup:
|
|
{
|
|
g_list_free_full (vd->pending_headers, (GDestroyNotify) gst_buffer_unref);
|
|
vd->pending_headers = NULL;
|
|
return result;
|
|
}
|
|
not_enough:
|
|
{
|
|
GST_LOG_OBJECT (vd,
|
|
"Not enough pending headers to properly reset, ignoring them");
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
vorbis_dec_handle_frame (GstAudioDecoder * dec, GstBuffer * buffer)
|
|
{
|
|
ogg_packet *packet;
|
|
ogg_packet_wrapper packet_wrapper;
|
|
GstFlowReturn result = GST_FLOW_OK;
|
|
GstMapInfo map;
|
|
GstVorbisDec *vd = GST_VORBIS_DEC (dec);
|
|
|
|
/* no draining etc */
|
|
if (G_UNLIKELY (!buffer))
|
|
return GST_FLOW_OK;
|
|
|
|
GST_LOG_OBJECT (vd, "got buffer %p", buffer);
|
|
/* make ogg_packet out of the buffer */
|
|
gst_ogg_packet_wrapper_map (&packet_wrapper, buffer, &map);
|
|
packet = gst_ogg_packet_from_wrapper (&packet_wrapper);
|
|
/* set some more stuff */
|
|
packet->granulepos = -1;
|
|
packet->packetno = 0; /* we don't care */
|
|
/* EOS does not matter, it is used in vorbis to implement clipping the last
|
|
* block of samples based on the granulepos. We clip based on segments. */
|
|
packet->e_o_s = 0;
|
|
|
|
GST_LOG_OBJECT (vd, "decode buffer of size %ld", packet->bytes);
|
|
|
|
/* error out on empty header packets, but just skip empty data packets */
|
|
if (G_UNLIKELY (packet->bytes == 0)) {
|
|
if (vd->initialized)
|
|
goto empty_buffer;
|
|
else
|
|
goto empty_header;
|
|
}
|
|
|
|
/* switch depending on packet type */
|
|
if ((gst_ogg_packet_data (packet))[0] & 1) {
|
|
GST_LOG_OBJECT (vd, "storing header for later analyzis");
|
|
if (vd->pending_headers && (gst_ogg_packet_data (packet))[0] == 0x01) {
|
|
GST_DEBUG_OBJECT (vd,
|
|
"got new identification header packet, discarding old pending headers");
|
|
|
|
g_list_free_full (vd->pending_headers, (GDestroyNotify) gst_buffer_unref);
|
|
vd->pending_headers = NULL;
|
|
}
|
|
|
|
vd->pending_headers =
|
|
g_list_append (vd->pending_headers, gst_buffer_ref (buffer));
|
|
result = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (vd), NULL, 1);
|
|
} else {
|
|
GstClockTime timestamp, duration;
|
|
|
|
if (vd->pending_headers)
|
|
result = check_pending_headers (vd);
|
|
if (G_UNLIKELY (result != GST_FLOW_OK))
|
|
goto done;
|
|
|
|
timestamp = GST_BUFFER_TIMESTAMP (buffer);
|
|
duration = GST_BUFFER_DURATION (buffer);
|
|
|
|
result = vorbis_handle_data_packet (vd, packet, timestamp, duration);
|
|
}
|
|
|
|
done:
|
|
GST_LOG_OBJECT (vd, "unmap buffer %p", buffer);
|
|
gst_ogg_packet_wrapper_unmap (&packet_wrapper, buffer, &map);
|
|
|
|
return result;
|
|
|
|
empty_buffer:
|
|
{
|
|
/* don't error out here, just ignore the buffer, it's invalid for vorbis
|
|
* but not fatal. */
|
|
GST_WARNING_OBJECT (vd, "empty buffer received, ignoring");
|
|
result = GST_FLOW_OK;
|
|
goto done;
|
|
}
|
|
|
|
/* ERRORS */
|
|
empty_header:
|
|
{
|
|
GST_ELEMENT_ERROR (vd, STREAM, DECODE, (NULL), ("empty header received"));
|
|
result = GST_FLOW_ERROR;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
static void
|
|
vorbis_dec_flush (GstAudioDecoder * dec, gboolean hard)
|
|
{
|
|
#ifdef HAVE_VORBIS_SYNTHESIS_RESTART
|
|
GstVorbisDec *vd = GST_VORBIS_DEC (dec);
|
|
|
|
vorbis_synthesis_restart (&vd->vd);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
vorbis_dec_reset (GstAudioDecoder * dec)
|
|
{
|
|
GstVorbisDec *vd = GST_VORBIS_DEC (dec);
|
|
|
|
vd->initialized = FALSE;
|
|
#ifndef USE_TREMOLO
|
|
vorbis_block_clear (&vd->vb);
|
|
#endif
|
|
vorbis_dsp_clear (&vd->vd);
|
|
|
|
vorbis_comment_clear (&vd->vc);
|
|
vorbis_info_clear (&vd->vi);
|
|
vorbis_info_init (&vd->vi);
|
|
vorbis_comment_init (&vd->vc);
|
|
}
|
|
|
|
static gboolean
|
|
vorbis_dec_set_format (GstAudioDecoder * dec, GstCaps * caps)
|
|
{
|
|
GstVorbisDec *vd = GST_VORBIS_DEC (dec);
|
|
|
|
GST_DEBUG_OBJECT (vd, "New caps %" GST_PTR_FORMAT " - resetting", caps);
|
|
|
|
/* A set_format call implies new data with new header packets */
|
|
if (!vd->initialized)
|
|
return TRUE;
|
|
|
|
/* We need to free and re-init libvorbis,
|
|
* or it chokes */
|
|
vorbis_dec_reset (dec);
|
|
|
|
return TRUE;
|
|
}
|