gstreamer/ext/vorbis/gstvorbisdec.c
Wim Taymans dae848818d audio: rework audio caps.
Rework the audio caps similar to the video caps. Remove
width/depth/endianness/signed fields and replace with a simple string
format and media type audio/x-raw.
Create a GstAudioInfo and some helper methods to parse caps.
Remove duplicate code from the ringbuffer and replace with audio info.
Use AudioInfo in the base audio filter class.
Port elements to new API.
2011-08-18 19:15:03 +02:00

1358 lines
36 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:element-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>.
*
* <refsect2>
* <title>Example pipelines</title>
* |[
* gst-launch -v filesrc location=sine.ogg ! oggdemux ! vorbisdec ! audioconvert ! alsasink
* ]| Decode an Ogg/Vorbis. To create an Ogg/Vorbis file refer to the documentation of vorbisenc.
* </refsect2>
*
* Last reviewed on 2006-03-01 (0.10.4)
*/
#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 <gst/audio/multichannel.h>
#include "gstvorbiscommon.h"
GST_DEBUG_CATEGORY_EXTERN (vorbisdec_debug);
#define GST_CAT_DEFAULT vorbisdec_debug
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 (GST_VORBIS_DEC_GLIB_TYPE_NAME, gst_vorbis_dec, GST_TYPE_ELEMENT);
static void vorbis_dec_finalize (GObject * object);
static gboolean vorbis_dec_sink_event (GstPad * pad, GstEvent * event);
static GstFlowReturn vorbis_dec_chain (GstPad * pad, GstBuffer * buffer);
static GstFlowReturn vorbis_dec_chain_forward (GstVorbisDec * vd,
gboolean discont, GstBuffer * buffer);
static GstFlowReturn vorbis_dec_chain_reverse (GstVorbisDec * vd,
gboolean discont, GstBuffer * buf);
static GstStateChangeReturn vorbis_dec_change_state (GstElement * element,
GstStateChange transition);
static gboolean vorbis_dec_src_event (GstPad * pad, GstEvent * event);
static gboolean vorbis_dec_src_query (GstPad * pad, GstQuery * query);
static gboolean vorbis_dec_convert (GstPad * pad,
GstFormat src_format, gint64 src_value,
GstFormat * dest_format, gint64 * dest_value);
static gboolean vorbis_dec_sink_query (GstPad * pad, GstQuery * query);
static void
gst_vorbis_dec_class_init (GstVorbisDecClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
gobject_class->finalize = vorbis_dec_finalize;
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&vorbis_dec_src_factory));
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&vorbis_dec_sink_factory));
gst_element_class_set_details_simple (gstelement_class,
"Vorbis audio decoder", "Codec/Decoder/Audio",
GST_VORBIS_DEC_DESCRIPTION,
"Benjamin Otte <otte@gnome.org>, Chris Lord <chris@openedhand.com>");
gstelement_class->change_state = GST_DEBUG_FUNCPTR (vorbis_dec_change_state);
}
static const GstQueryType *
vorbis_get_query_types (GstPad * pad)
{
static const GstQueryType vorbis_dec_src_query_types[] = {
GST_QUERY_POSITION,
GST_QUERY_DURATION,
GST_QUERY_CONVERT,
0
};
return vorbis_dec_src_query_types;
}
static void
gst_vorbis_dec_init (GstVorbisDec * dec)
{
dec->sinkpad = gst_pad_new_from_static_template (&vorbis_dec_sink_factory,
"sink");
gst_pad_set_event_function (dec->sinkpad,
GST_DEBUG_FUNCPTR (vorbis_dec_sink_event));
gst_pad_set_chain_function (dec->sinkpad,
GST_DEBUG_FUNCPTR (vorbis_dec_chain));
gst_pad_set_query_function (dec->sinkpad,
GST_DEBUG_FUNCPTR (vorbis_dec_sink_query));
gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad);
dec->srcpad = gst_pad_new_from_static_template (&vorbis_dec_src_factory,
"src");
gst_pad_set_event_function (dec->srcpad,
GST_DEBUG_FUNCPTR (vorbis_dec_src_event));
gst_pad_set_query_type_function (dec->srcpad,
GST_DEBUG_FUNCPTR (vorbis_get_query_types));
gst_pad_set_query_function (dec->srcpad,
GST_DEBUG_FUNCPTR (vorbis_dec_src_query));
gst_pad_use_fixed_caps (dec->srcpad);
gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad);
dec->queued = NULL;
dec->pendingevents = NULL;
dec->taglist = NULL;
}
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 void
gst_vorbis_dec_reset (GstVorbisDec * dec)
{
dec->last_timestamp = GST_CLOCK_TIME_NONE;
dec->discont = TRUE;
dec->seqnum = gst_util_seqnum_next ();
gst_segment_init (&dec->segment, GST_FORMAT_TIME);
g_list_foreach (dec->queued, (GFunc) gst_mini_object_unref, NULL);
g_list_free (dec->queued);
dec->queued = NULL;
g_list_foreach (dec->gather, (GFunc) gst_mini_object_unref, NULL);
g_list_free (dec->gather);
dec->gather = NULL;
g_list_foreach (dec->decode, (GFunc) gst_mini_object_unref, NULL);
g_list_free (dec->decode);
dec->decode = NULL;
g_list_foreach (dec->pendingevents, (GFunc) gst_mini_object_unref, NULL);
g_list_free (dec->pendingevents);
dec->pendingevents = NULL;
if (dec->taglist)
gst_tag_list_free (dec->taglist);
dec->taglist = NULL;
}
static gboolean
vorbis_dec_convert (GstPad * pad,
GstFormat src_format, gint64 src_value,
GstFormat * dest_format, gint64 * dest_value)
{
gboolean res = TRUE;
GstVorbisDec *dec;
guint64 scale = 1;
if (src_format == *dest_format) {
*dest_value = src_value;
return TRUE;
}
dec = GST_VORBIS_DEC (gst_pad_get_parent (pad));
if (!dec->initialized)
goto no_header;
if (dec->sinkpad == pad &&
(src_format == GST_FORMAT_BYTES || *dest_format == GST_FORMAT_BYTES))
goto no_format;
switch (src_format) {
case GST_FORMAT_TIME:
switch (*dest_format) {
case GST_FORMAT_BYTES:
scale = dec->info.bpf;
case GST_FORMAT_DEFAULT:
*dest_value =
scale * gst_util_uint64_scale_int (src_value, dec->vi.rate,
GST_SECOND);
break;
default:
res = FALSE;
}
break;
case GST_FORMAT_DEFAULT:
switch (*dest_format) {
case GST_FORMAT_BYTES:
*dest_value = src_value * dec->info.bpf;
break;
case GST_FORMAT_TIME:
*dest_value =
gst_util_uint64_scale_int (src_value, GST_SECOND, dec->vi.rate);
break;
default:
res = FALSE;
}
break;
case GST_FORMAT_BYTES:
switch (*dest_format) {
case GST_FORMAT_DEFAULT:
*dest_value = src_value / dec->info.bpf;
break;
case GST_FORMAT_TIME:
*dest_value = gst_util_uint64_scale_int (src_value, GST_SECOND,
dec->vi.rate * dec->info.bpf);
break;
default:
res = FALSE;
}
break;
default:
res = FALSE;
}
done:
gst_object_unref (dec);
return res;
/* ERRORS */
no_header:
{
GST_DEBUG_OBJECT (dec, "no header packets received");
res = FALSE;
goto done;
}
no_format:
{
GST_DEBUG_OBJECT (dec, "formats unsupported");
res = FALSE;
goto done;
}
}
static gboolean
vorbis_dec_src_query (GstPad * pad, GstQuery * query)
{
GstVorbisDec *dec;
gboolean res = FALSE;
dec = GST_VORBIS_DEC (gst_pad_get_parent (pad));
if (G_UNLIKELY (dec == NULL))
return FALSE;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_POSITION:
{
gint64 value;
GstFormat format;
gint64 time;
gst_query_parse_position (query, &format, NULL);
/* we start from the last seen time */
time = dec->last_timestamp;
/* correct for the segment values */
time = gst_segment_to_stream_time (&dec->segment, GST_FORMAT_TIME, time);
GST_LOG_OBJECT (dec,
"query %p: our time: %" GST_TIME_FORMAT, query, GST_TIME_ARGS (time));
/* and convert to the final format */
if (!(res =
vorbis_dec_convert (pad, GST_FORMAT_TIME, time, &format, &value)))
goto error;
gst_query_set_position (query, format, value);
GST_LOG_OBJECT (dec,
"query %p: we return %" G_GINT64_FORMAT " (format %u)", query, value,
format);
break;
}
case GST_QUERY_DURATION:
{
res = gst_pad_peer_query (dec->sinkpad, query);
if (!res)
goto error;
break;
}
case GST_QUERY_CONVERT:
{
GstFormat src_fmt, dest_fmt;
gint64 src_val, dest_val;
gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val);
if (!(res =
vorbis_dec_convert (pad, src_fmt, src_val, &dest_fmt, &dest_val)))
goto error;
gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
break;
}
default:
res = gst_pad_query_default (pad, query);
break;
}
done:
gst_object_unref (dec);
return res;
/* ERRORS */
error:
{
GST_WARNING_OBJECT (dec, "error handling query");
goto done;
}
}
static gboolean
vorbis_dec_sink_query (GstPad * pad, GstQuery * query)
{
GstVorbisDec *dec;
gboolean res;
dec = GST_VORBIS_DEC (gst_pad_get_parent (pad));
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CONVERT:
{
GstFormat src_fmt, dest_fmt;
gint64 src_val, dest_val;
gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val);
if (!(res =
vorbis_dec_convert (pad, src_fmt, src_val, &dest_fmt, &dest_val)))
goto error;
gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
break;
}
default:
res = gst_pad_query_default (pad, query);
break;
}
done:
gst_object_unref (dec);
return res;
/* ERRORS */
error:
{
GST_DEBUG_OBJECT (dec, "error converting value");
goto done;
}
}
static gboolean
vorbis_dec_src_event (GstPad * pad, GstEvent * event)
{
gboolean res = TRUE;
GstVorbisDec *dec;
dec = GST_VORBIS_DEC (gst_pad_get_parent (pad));
if (G_UNLIKELY (dec == NULL)) {
gst_event_unref (event);
return FALSE;
}
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEEK:
{
GstFormat format, tformat;
gdouble rate;
GstEvent *real_seek;
GstSeekFlags flags;
GstSeekType cur_type, stop_type;
gint64 cur, stop;
gint64 tcur, tstop;
guint32 seqnum;
gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur,
&stop_type, &stop);
seqnum = gst_event_get_seqnum (event);
gst_event_unref (event);
/* First bring the requested format to time */
tformat = GST_FORMAT_TIME;
if (!(res = vorbis_dec_convert (pad, format, cur, &tformat, &tcur)))
goto convert_error;
if (!(res = vorbis_dec_convert (pad, format, stop, &tformat, &tstop)))
goto convert_error;
/* then seek with time on the peer */
real_seek = gst_event_new_seek (rate, GST_FORMAT_TIME,
flags, cur_type, tcur, stop_type, tstop);
gst_event_set_seqnum (real_seek, seqnum);
res = gst_pad_push_event (dec->sinkpad, real_seek);
break;
}
default:
res = gst_pad_push_event (dec->sinkpad, event);
break;
}
done:
gst_object_unref (dec);
return res;
/* ERRORS */
convert_error:
{
GST_DEBUG_OBJECT (dec, "cannot convert start/stop for seek");
goto done;
}
}
static gboolean
vorbis_dec_sink_event (GstPad * pad, GstEvent * event)
{
gboolean ret = FALSE;
GstVorbisDec *dec;
dec = GST_VORBIS_DEC (gst_pad_get_parent (pad));
GST_LOG_OBJECT (dec, "handling event");
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_EOS:
if (dec->segment.rate < 0.0)
vorbis_dec_chain_reverse (dec, TRUE, NULL);
ret = gst_pad_push_event (dec->srcpad, event);
break;
case GST_EVENT_FLUSH_START:
ret = gst_pad_push_event (dec->srcpad, event);
break;
case GST_EVENT_FLUSH_STOP:
/* here we must clean any state in the decoder */
#ifdef HAVE_VORBIS_SYNTHESIS_RESTART
vorbis_synthesis_restart (&dec->vd);
#endif
gst_vorbis_dec_reset (dec);
ret = gst_pad_push_event (dec->srcpad, event);
break;
case GST_EVENT_SEGMENT:
{
const GstSegment *segment;
gst_event_parse_segment (event, &segment);
/* we need time for now */
if (segment->format != GST_FORMAT_TIME)
goto newseg_wrong_format;
GST_DEBUG_OBJECT (dec, "segment: %" GST_SEGMENT_FORMAT, segment);
/* now configure the values */
gst_segment_copy_into (segment, &dec->segment);
dec->seqnum = gst_event_get_seqnum (event);
if (dec->initialized)
/* and forward */
ret = gst_pad_push_event (dec->srcpad, event);
else {
/* store it to send once we're initialized */
dec->pendingevents = g_list_append (dec->pendingevents, event);
ret = TRUE;
}
break;
}
case GST_EVENT_TAG:
{
if (dec->initialized)
/* and forward */
ret = gst_pad_push_event (dec->srcpad, event);
else {
/* store it to send once we're initialized */
dec->pendingevents = g_list_append (dec->pendingevents, event);
ret = TRUE;
}
break;
}
default:
ret = gst_pad_event_default (pad, event);
break;
}
done:
gst_object_unref (dec);
return ret;
/* ERRORS */
newseg_wrong_format:
{
GST_DEBUG_OBJECT (dec, "received non TIME newsegment");
goto done;
}
}
static GstFlowReturn
vorbis_handle_identification_packet (GstVorbisDec * vd)
{
GstCaps *caps;
GstAudioInfo info;
const GstAudioChannelPosition *pos = NULL;
gst_audio_info_set_format (&info, GST_VORBIS_AUDIO_FORMAT, vd->vi.rate,
vd->vi.channels);
switch (info.channels) {
case 1:
case 2:
/* nothing */
break;
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
pos = gst_vorbis_channel_positions[info.channels - 1];
break;
default:
{
gint i, max_pos = MAX (info.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++)
info.position[i] = GST_AUDIO_CHANNEL_POSITION_NONE;
}
}
caps = gst_audio_info_to_caps (&info);
gst_pad_set_caps (vd->srcpad, caps);
gst_caps_unref (caps);
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 = get_copy_sample_func (info.channels);
return GST_FLOW_OK;
}
static GstFlowReturn
vorbis_handle_comment_packet (GstVorbisDec * vd, ogg_packet * packet)
{
guint bitrate = 0;
gchar *encoder = NULL;
GstTagList *list, *old_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);
old_list = vd->taglist;
vd->taglist = gst_tag_list_merge (vd->taglist, list, GST_TAG_MERGE_REPLACE);
if (old_list)
gst_tag_list_free (old_list);
gst_tag_list_free (list);
if (!vd->taglist) {
GST_ERROR_OBJECT (vd, "couldn't decode comments");
vd->taglist = gst_tag_list_new ();
}
if (encoder) {
if (encoder[0])
gst_tag_list_add (vd->taglist, GST_TAG_MERGE_REPLACE,
GST_TAG_ENCODER, encoder, NULL);
g_free (encoder);
}
gst_tag_list_add (vd->taglist, 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 (vd->taglist, 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 (vd->taglist, 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 (vd->taglist, 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 (vd->taglist, GST_TAG_MERGE_REPLACE,
GST_TAG_BITRATE, (guint) bitrate, NULL);
}
if (vd->initialized) {
gst_element_found_tags_for_pad (GST_ELEMENT_CAST (vd), vd->srcpad,
vd->taglist);
vd->taglist = NULL;
} else {
/* Only post them as messages for the time being. *
* They will be pushed on the pad once the decoder is initialized */
gst_element_post_message (GST_ELEMENT_CAST (vd),
gst_message_new_tag (GST_OBJECT (vd), gst_tag_list_copy (vd->taglist)));
}
return GST_FLOW_OK;
}
static GstFlowReturn
vorbis_handle_type_packet (GstVorbisDec * vd)
{
GList *walk;
gint res;
g_assert (vd->initialized == FALSE);
#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;
if (vd->pendingevents) {
for (walk = vd->pendingevents; walk; walk = g_list_next (walk))
gst_pad_push_event (vd->srcpad, GST_EVENT_CAST (walk->data));
g_list_free (vd->pendingevents);
vd->pendingevents = NULL;
}
if (vd->taglist) {
/* The tags have already been sent on the bus as messages. */
gst_pad_push_event (vd->srcpad, gst_event_new_tag (vd->taglist));
vd->taglist = NULL;
}
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_push_forward (GstVorbisDec * dec, GstBuffer * buf)
{
GstFlowReturn result;
/* clip */
if (!(buf = gst_audio_buffer_clip (buf, &dec->segment, dec->vi.rate,
dec->info.bpf))) {
GST_LOG_OBJECT (dec, "clipped buffer");
return GST_FLOW_OK;
}
if (dec->discont) {
GST_LOG_OBJECT (dec, "setting DISCONT");
GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
dec->discont = FALSE;
}
GST_DEBUG_OBJECT (dec,
"pushing time %" GST_TIME_FORMAT ", dur %" GST_TIME_FORMAT,
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
GST_TIME_ARGS (GST_BUFFER_DURATION (buf)));
result = gst_pad_push (dec->srcpad, buf);
return result;
}
static GstFlowReturn
vorbis_dec_push_reverse (GstVorbisDec * dec, GstBuffer * buf)
{
GstFlowReturn result = GST_FLOW_OK;
dec->queued = g_list_prepend (dec->queued, buf);
return result;
}
static void
vorbis_do_timestamps (GstVorbisDec * vd, GstBuffer * buf, gboolean reverse,
GstClockTime timestamp, GstClockTime duration)
{
/* interpolate reverse */
if (vd->last_timestamp != -1 && duration != -1 && reverse)
vd->last_timestamp -= duration;
/* take buffer timestamp, use interpolated timestamp otherwise */
if (timestamp != -1)
vd->last_timestamp = timestamp;
else
timestamp = vd->last_timestamp;
/* interpolate forwards */
if (vd->last_timestamp != -1 && duration != -1 && !reverse)
vd->last_timestamp += duration;
GST_LOG_OBJECT (vd,
"keeping timestamp %" GST_TIME_FORMAT " ts %" GST_TIME_FORMAT " dur %"
GST_TIME_FORMAT, GST_TIME_ARGS (vd->last_timestamp),
GST_TIME_ARGS (timestamp), GST_TIME_ARGS (duration));
if (buf) {
GST_BUFFER_TIMESTAMP (buf) = timestamp;
GST_BUFFER_DURATION (buf) = duration;
}
}
static GstFlowReturn
vorbis_handle_data_packet (GstVorbisDec * vd, ogg_packet * packet,
GstClockTime timestamp, GstClockTime duration)
{
#ifndef USE_TREMOLO
vorbis_sample_t **pcm;
#endif
guint sample_count;
GstBuffer *out = NULL;
GstFlowReturn result;
guint8 *data;
gsize size;
if (G_UNLIKELY (!vd->initialized))
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 carefull 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)
#endif
goto done;
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_buffer_new_and_alloc (size);
data = gst_buffer_map (out, NULL, NULL, GST_MAP_WRITE);
/* get samples ready for reading now, should be sample_count */
#ifdef USE_TREMOLO
if (G_UNLIKELY ((vorbis_dsp_pcmout (&vd->vd, data,
sample_count)) != sample_count))
#else
if (G_UNLIKELY ((vorbis_synthesis_pcmout (&vd->vd, &pcm)) != sample_count))
#endif
goto wrong_samples;
#ifndef USE_TREMOLO
/* copy samples in buffer */
vd->copy_samples ((vorbis_sample_t *) data, pcm,
sample_count, vd->info.channels);
#endif
GST_LOG_OBJECT (vd, "setting output size to %" G_GSIZE_FORMAT, size);
gst_buffer_unmap (out, data, size);
/* this should not overflow */
if (duration == -1)
duration = sample_count * GST_SECOND / vd->vi.rate;
vorbis_do_timestamps (vd, out, FALSE, timestamp, duration);
if (vd->segment.rate >= 0.0)
result = vorbis_dec_push_forward (vd, out);
else
result = vorbis_dec_push_reverse (vd, out);
done:
if (out == NULL) {
/* no output, still keep track of timestamps */
vorbis_do_timestamps (vd, NULL, FALSE, timestamp, duration);
}
#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_ERROR;
}
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
vorbis_dec_handle_header_buffer (GstVorbisDec * vd, GstBuffer * buffer)
{
ogg_packet *packet;
ogg_packet_wrapper packet_wrapper;
GstFlowReturn ret;
gst_ogg_packet_wrapper_map (&packet_wrapper, buffer);
packet = gst_ogg_packet_from_wrapper (&packet_wrapper);
ret = vorbis_handle_header_packet (vd, packet);
gst_ogg_packet_wrapper_unmap (&packet_wrapper, buffer);
return ret;
}
#define MIN_NUM_HEADERS 3
static GstFlowReturn
vorbis_dec_handle_header_caps (GstVorbisDec * vd, GstBuffer * buffer)
{
GstFlowReturn result = GST_FLOW_OK;
GstCaps *caps;
GstStructure *s;
const GValue *array;
const GValue *value = NULL;
GstBuffer *buf = NULL;
if ((caps = gst_pad_get_current_caps (vd->sinkpad)) == NULL)
goto no_caps;
if ((s = gst_caps_get_structure (caps, 0)) == NULL)
goto no_caps;
array = gst_structure_get_value (s, "streamheader");
if (array == NULL || (gst_value_array_get_size (array) < MIN_NUM_HEADERS))
goto array_error;
/* initial header */
value = gst_value_array_get_value (array, 0);
buf = gst_value_get_buffer (value);
if (!buf)
goto null_buffer;
result = vorbis_dec_handle_header_buffer (vd, buf);
if (result != GST_FLOW_OK)
goto buffer_error;
/* comment header */
value = gst_value_array_get_value (array, 1);
buf = gst_value_get_buffer (value);
if (!buf)
goto null_buffer;
result = vorbis_dec_handle_header_buffer (vd, buf);
if (result != GST_FLOW_OK)
goto buffer_error;
/* bitstream codebook header */
value = gst_value_array_get_value (array, 2);
buf = gst_value_get_buffer (value);
if (!buf)
goto null_buffer;
result = vorbis_dec_handle_header_buffer (vd, buf);
if (result != GST_FLOW_OK)
goto buffer_error;
return result;
no_caps:
{
GST_WARNING_OBJECT (vd, "no caps negotiated");
return GST_FLOW_NOT_NEGOTIATED;
}
array_error:
{
GST_WARNING_OBJECT (vd, "streamheader array not found");
return GST_FLOW_NOT_NEGOTIATED;
}
null_buffer:
{
GST_WARNING_OBJECT (vd, "streamheader with null buffer received");
return GST_FLOW_NOT_NEGOTIATED;
}
buffer_error:
{
GST_WARNING_OBJECT (vd, "error handling buffer");
return GST_FLOW_NOT_NEGOTIATED;
}
}
static GstFlowReturn
vorbis_dec_decode_buffer (GstVorbisDec * vd, GstBuffer * buffer)
{
ogg_packet *packet;
ogg_packet_wrapper packet_wrapper;
GstFlowReturn result = GST_FLOW_OK;
/* make ogg_packet out of the buffer */
gst_ogg_packet_wrapper_map (&packet_wrapper, buffer);
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) {
if (vd->initialized) {
GST_WARNING_OBJECT (vd, "Already initialized, so ignoring header packet");
goto done;
}
result = vorbis_handle_header_packet (vd, packet);
} else {
GstClockTime timestamp, duration;
/* try to find header in caps so we can initialize the decoder */
if (!vd->initialized) {
result = vorbis_dec_handle_header_caps (vd, buffer);
if (result != GST_FLOW_OK)
goto invalid_caps_header;
}
timestamp = GST_BUFFER_TIMESTAMP (buffer);
duration = GST_BUFFER_DURATION (buffer);
result = vorbis_handle_data_packet (vd, packet, timestamp, duration);
}
done:
gst_ogg_packet_wrapper_unmap (&packet_wrapper, buffer);
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;
vd->discont = TRUE;
goto done;
}
invalid_caps_header:
{
GST_ELEMENT_ERROR (vd, STREAM, DECODE, (NULL),
("invalid streamheader in caps"));
goto done;
}
}
/*
* Input:
* Buffer decoding order: 7 8 9 4 5 6 3 1 2 EOS
* Discont flag: D D D D
*
* - Each Discont marks a discont in the decoding order.
*
* for vorbis, each buffer is a keyframe when we have the previous
* buffer. This means that to decode buffer 7, we need buffer 6, which
* arrives out of order.
*
* we first gather buffers in the gather queue until we get a DISCONT. We
* prepend each incomming buffer so that they are in reversed order.
*
* gather queue: 9 8 7
* decode queue:
* output queue:
*
* When a DISCONT is received (buffer 4), we move the gather queue to the
* decode queue. This is simply done be taking the head of the gather queue
* and prepending it to the decode queue. This yields:
*
* gather queue:
* decode queue: 7 8 9
* output queue:
*
* Then we decode each buffer in the decode queue in order and put the output
* buffer in the output queue. The first buffer (7) will not produce any output
* because it needs the previous buffer (6) which did not arrive yet. This
* yields:
*
* gather queue:
* decode queue: 7 8 9
* output queue: 9 8
*
* Then we remove the consumed buffers from the decode queue. Buffer 7 is not
* completely consumed, we need to keep it around for when we receive buffer
* 6. This yields:
*
* gather queue:
* decode queue: 7
* output queue: 9 8
*
* Then we accumulate more buffers:
*
* gather queue: 6 5 4
* decode queue: 7
* output queue:
*
* prepending to the decode queue on DISCONT yields:
*
* gather queue:
* decode queue: 4 5 6 7
* output queue:
*
* after decoding and keeping buffer 4:
*
* gather queue:
* decode queue: 4
* output queue: 7 6 5
*
* Etc..
*/
static GstFlowReturn
vorbis_dec_flush_decode (GstVorbisDec * dec)
{
GstFlowReturn res = GST_FLOW_OK;
GList *walk;
walk = dec->decode;
GST_DEBUG_OBJECT (dec, "flushing buffers to decoder");
while (walk) {
GList *next;
GstBuffer *buf = GST_BUFFER_CAST (walk->data);
GST_DEBUG_OBJECT (dec, "decoding buffer %p, ts %" GST_TIME_FORMAT,
buf, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
next = g_list_next (walk);
/* decode buffer, prepend to output queue */
res = vorbis_dec_decode_buffer (dec, buf);
/* if we generated output, we can discard the buffer, else we
* keep it in the queue */
if (dec->queued) {
GST_DEBUG_OBJECT (dec, "decoded buffer to %p", dec->queued->data);
dec->decode = g_list_delete_link (dec->decode, walk);
gst_buffer_unref (buf);
} else {
GST_DEBUG_OBJECT (dec, "buffer did not decode, keeping");
}
walk = next;
}
while (dec->queued) {
GstBuffer *buf = GST_BUFFER_CAST (dec->queued->data);
GstClockTime timestamp, duration;
timestamp = GST_BUFFER_TIMESTAMP (buf);
duration = GST_BUFFER_DURATION (buf);
vorbis_do_timestamps (dec, buf, TRUE, timestamp, duration);
res = vorbis_dec_push_forward (dec, buf);
dec->queued = g_list_delete_link (dec->queued, dec->queued);
}
return res;
}
static GstFlowReturn
vorbis_dec_chain_reverse (GstVorbisDec * vd, gboolean discont, GstBuffer * buf)
{
GstFlowReturn result = GST_FLOW_OK;
/* if we have a discont, move buffers to the decode list */
if (G_UNLIKELY (discont)) {
GST_DEBUG_OBJECT (vd, "received discont");
while (vd->gather) {
GstBuffer *gbuf;
gbuf = GST_BUFFER_CAST (vd->gather->data);
/* remove from the gather list */
vd->gather = g_list_delete_link (vd->gather, vd->gather);
/* copy to decode queue */
vd->decode = g_list_prepend (vd->decode, gbuf);
}
/* flush and decode the decode queue */
result = vorbis_dec_flush_decode (vd);
}
if (G_LIKELY (buf)) {
GST_DEBUG_OBJECT (vd,
"gathering buffer %p of size %" G_GSIZE_FORMAT
", time %" GST_TIME_FORMAT ", dur %" GST_TIME_FORMAT,
buf, gst_buffer_get_size (buf),
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
GST_TIME_ARGS (GST_BUFFER_DURATION (buf)));
/* add buffer to gather queue */
vd->gather = g_list_prepend (vd->gather, buf);
}
return result;
}
static GstFlowReturn
vorbis_dec_chain_forward (GstVorbisDec * vd, gboolean discont,
GstBuffer * buffer)
{
GstFlowReturn result;
result = vorbis_dec_decode_buffer (vd, buffer);
gst_buffer_unref (buffer);
return result;
}
static GstFlowReturn
vorbis_dec_chain (GstPad * pad, GstBuffer * buffer)
{
GstVorbisDec *vd;
GstFlowReturn result = GST_FLOW_OK;
gboolean discont;
vd = GST_VORBIS_DEC (gst_pad_get_parent (pad));
discont = GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT);
/* resync on DISCONT */
if (G_UNLIKELY (discont)) {
GST_DEBUG_OBJECT (vd, "received DISCONT buffer");
vd->last_timestamp = GST_CLOCK_TIME_NONE;
#ifdef HAVE_VORBIS_SYNTHESIS_RESTART
vorbis_synthesis_restart (&vd->vd);
#endif
vd->discont = TRUE;
}
if (vd->segment.rate >= 0.0)
result = vorbis_dec_chain_forward (vd, discont, buffer);
else
result = vorbis_dec_chain_reverse (vd, discont, buffer);
gst_object_unref (vd);
return result;
}
static GstStateChangeReturn
vorbis_dec_change_state (GstElement * element, GstStateChange transition)
{
GstVorbisDec *vd = GST_VORBIS_DEC (element);
GstStateChangeReturn res;
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
vorbis_info_init (&vd->vi);
vorbis_comment_init (&vd->vc);
vd->initialized = FALSE;
gst_vorbis_dec_reset (vd);
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
break;
default:
break;
}
res = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
switch (transition) {
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
GST_DEBUG_OBJECT (vd, "PAUSED -> READY, clearing vorbis structures");
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);
gst_vorbis_dec_reset (vd);
break;
case GST_STATE_CHANGE_READY_TO_NULL:
break;
default:
break;
}
return res;
}