gstreamer/ext/ivorbis/vorbisfile.c

1380 lines
36 KiB
C
Raw Normal View History

/* GStreamer
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
*
* 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <gst/gst.h>
#include <tremor/ivorbiscodec.h>
#include <tremor/ivorbisfile.h>
#include <gst/base/gstadapter.h>
GST_DEBUG_CATEGORY_STATIC (ivorbisfile_debug);
#define GST_CAT_DEFAULT ivorbisfile_debug
#define GST_TYPE_IVORBISFILE \
(ivorbisfile_get_type())
#define GST_IVORBISFILE(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_IVORBISFILE,Ivorbisfile))
#define GST_IVORBISFILE_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_IVORBISFILE,IvorbisfileClass))
#define GST_IS_IVORBISFILE(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_IVORBISFILE))
#define GST_IS_IVORBISFILE_CLASS(obj) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_IVORBISFILE))
typedef struct _Ivorbisfile Ivorbisfile;
typedef struct _IvorbisfileClass IvorbisfileClass;
struct _Ivorbisfile
{
GstElement element;
GstPad *sinkpad, *srcpad;
GstAdapter *adapter;
guint64 adapterOffset;
OggVorbis_File vf;
gint current_link;
gboolean restart;
gboolean need_discont;
gboolean eos;
gboolean seek_pending;
gint64 seek_value;
GstFormat seek_format;
gboolean seek_accurate;
gboolean may_eos;
guint64 total_bytes;
guint64 offset;
gint rate;
gint channels;
gint width;
GstCaps *metadata;
GstCaps *streaminfo;
};
struct _IvorbisfileClass
{
GstElementClass parent_class;
};
GType ivorbisfile_get_type (void);
static GstPadTemplate *gst_vorbisdec_src_template, *gst_vorbisdec_sink_template;
/* elementfactory information */
GstElementDetails ivorbisfile_details = {
"Ogg Vorbis decoder",
"Codec/Decoder/Audio",
"Decodes OGG Vorbis audio using the Tremor vorbisfile API",
"Monty <monty@xiph.org>\n"
"Wim Taymans <wim.taymans@chello.be>\n"
"Amaury Jacquot <sxpert@esitcom.org>",
};
/* Ivorbisfile signals and args */
enum
{
LAST_SIGNAL
};
enum
{
ARG_0,
ARG_METADATA,
ARG_STREAMINFO
};
static void gst_ivorbisfile_base_init (gpointer g_class);
static void gst_ivorbisfile_class_init (IvorbisfileClass * klass);
static void gst_ivorbisfile_init (Ivorbisfile * ivorbisfile);
static void gst_ivorbisfile_finalize (GObject * object);
static GstStateChangeReturn
gst_ivorbisfile_change_state (GstElement * element, GstStateChange transition);
static gboolean gst_ivorbisfile_src_convert (GstPad * pad,
GstFormat src_format,
gint64 src_value, GstFormat * dest_format, gint64 * dest_value);
static gboolean gst_ivorbisfile_sink_convert (GstPad * pad,
GstFormat src_format,
gint64 src_value, GstFormat * dest_format, gint64 * dest_value);
static const GstQueryType *gst_ivorbisfile_get_src_query_types (GstPad * pad);
static gboolean gst_ivorbisfile_src_query (GstPad * pad, GstQuery * query);
static gboolean gst_ivorbisfile_src_event (GstPad * pad, GstEvent * event);
static gboolean gst_ivorbisfile_sink_event (GstPad * pad, GstEvent * event);
static const GstQueryType *gst_ivorbisfile_get_sink_query_types (GstPad * pad);
static gboolean gst_ivorbisfile_sink_query (GstPad * pad, GstQuery * query);
static void gst_ivorbisfile_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
static void gst_ivorbisfile_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static GstFlowReturn gst_ivorbisfile_chain (GstPad * pad, GstBuffer * buffer);
static gboolean gst_ivorbisfile_sink_activate (GstPad * sinkpad);
static gboolean
gst_ivorbisfile_sink_activate_pull (GstPad * sinkpad, gboolean active);
static void gst_ivorbisfile_loop (GstPad * pad);
static GstElementClass *parent_class = NULL;
static GstFormat logical_stream_format;
GType
ivorbisfile_get_type (void)
{
static GType ivorbisfile_type = 0;
if (!ivorbisfile_type) {
static const GTypeInfo ivorbisfile_info = {
sizeof (IvorbisfileClass),
gst_ivorbisfile_base_init,
NULL,
(GClassInitFunc) gst_ivorbisfile_class_init, NULL, NULL,
sizeof (Ivorbisfile), 0,
(GInstanceInitFunc) gst_ivorbisfile_init,
};
ivorbisfile_type = g_type_register_static (GST_TYPE_ELEMENT, "Ivorbisfile",
&ivorbisfile_info, 0);
logical_stream_format =
gst_format_register ("logical_stream", "The logical stream");
GST_DEBUG_CATEGORY_INIT (ivorbisfile_debug, "ivorbisfile", 0,
"vorbis in ogg decoding element (integer arithmetic)");
}
return ivorbisfile_type;
}
static GstCaps *
vorbis_caps_factory (void)
{
return gst_caps_new_simple ("application/ogg", NULL);
}
static GstCaps *
raw_caps_factory (void)
{
return
gst_caps_new_simple ("audio/x-raw-int",
"endianness", G_TYPE_INT, G_BYTE_ORDER,
"signed", G_TYPE_BOOLEAN, TRUE,
"width", G_TYPE_INT, 16,
"depth", G_TYPE_INT, 16,
"rate", GST_TYPE_INT_RANGE, 11025, 48000,
"channels", GST_TYPE_INT_RANGE, 1, 2, NULL);
}
static GstCaps *
raw_caps2_factory (void)
{
return
gst_caps_new_simple ("audio/x-raw-float",
"depth", G_TYPE_INT, 32,
"endianness", G_TYPE_INT, G_BYTE_ORDER,
"rate", GST_TYPE_INT_RANGE, 11025, 48000,
"channels", G_TYPE_INT, 2, NULL);
}
static void
gst_ivorbisfile_base_init (gpointer g_class)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
GstCaps *raw_caps, *vorbis_caps, *raw_caps2;
raw_caps = raw_caps_factory ();
raw_caps2 = raw_caps2_factory ();
vorbis_caps = vorbis_caps_factory ();
/* register sink pads */
gst_vorbisdec_sink_template = gst_pad_template_new ("sink", GST_PAD_SINK,
GST_PAD_ALWAYS, vorbis_caps);
gst_caps_append (raw_caps2, raw_caps);
/* register src pads */
gst_vorbisdec_src_template = gst_pad_template_new ("src", GST_PAD_SRC,
GST_PAD_ALWAYS, raw_caps2);
gst_element_class_add_pad_template (element_class,
gst_vorbisdec_sink_template);
gst_element_class_add_pad_template (element_class,
gst_vorbisdec_src_template);
gst_element_class_set_details (element_class, &ivorbisfile_details);
}
static void
gst_ivorbisfile_class_init (IvorbisfileClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
gobject_class->get_property = gst_ivorbisfile_get_property;
gobject_class->set_property = gst_ivorbisfile_set_property;
g_object_class_install_property (gobject_class, ARG_METADATA,
g_param_spec_boxed ("metadata", "Metadata", "(logical) Stream metadata",
GST_TYPE_CAPS, G_PARAM_READABLE));
g_object_class_install_property (gobject_class, ARG_STREAMINFO,
g_param_spec_boxed ("streaminfo", "stream",
"(logical) Stream information", GST_TYPE_CAPS, G_PARAM_READABLE));
gobject_class->finalize = gst_ivorbisfile_finalize;
gstelement_class->change_state = gst_ivorbisfile_change_state;
}
static void
gst_ivorbisfile_init (Ivorbisfile * ivorbisfile)
{
ivorbisfile->sinkpad = gst_pad_new_from_template (gst_vorbisdec_sink_template,
"sink");
gst_element_add_pad (GST_ELEMENT (ivorbisfile), ivorbisfile->sinkpad);
gst_pad_set_query_type_function (ivorbisfile->sinkpad,
gst_ivorbisfile_get_sink_query_types);
gst_pad_set_query_function (ivorbisfile->sinkpad, gst_ivorbisfile_sink_query);
gst_pad_set_activate_function (ivorbisfile->sinkpad,
gst_ivorbisfile_sink_activate);
gst_pad_set_activatepull_function (ivorbisfile->sinkpad,
gst_ivorbisfile_sink_activate_pull);
gst_pad_set_chain_function (ivorbisfile->sinkpad, gst_ivorbisfile_chain);
gst_pad_set_event_function (ivorbisfile->sinkpad, gst_ivorbisfile_sink_event);
ivorbisfile->srcpad =
gst_pad_new_from_template (gst_vorbisdec_src_template, "src");
gst_element_add_pad (GST_ELEMENT (ivorbisfile), ivorbisfile->srcpad);
gst_pad_set_query_type_function (ivorbisfile->srcpad,
gst_ivorbisfile_get_src_query_types);
gst_pad_set_query_function (ivorbisfile->srcpad, gst_ivorbisfile_src_query);
gst_pad_set_event_function (ivorbisfile->srcpad, gst_ivorbisfile_src_event);
ivorbisfile->total_bytes = 0;
ivorbisfile->offset = 0;
ivorbisfile->seek_pending = 0;
ivorbisfile->need_discont = FALSE;
ivorbisfile->metadata = NULL;
ivorbisfile->streaminfo = NULL;
ivorbisfile->current_link = -1;
ivorbisfile->rate = -1;
ivorbisfile->channels = -1;
ivorbisfile->width = -1;
ivorbisfile->adapter = NULL;
}
static void
gst_ivorbisfile_finalize (GObject * object)
{
Ivorbisfile *ivorbisfile;
ivorbisfile = GST_IVORBISFILE (object);
if (ivorbisfile->adapter) {
g_object_unref (ivorbisfile->adapter);
ivorbisfile->adapter = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
/* the next four functions are the ov callbacks we provide to ivorbisfile
* which interface between GStreamer's handling of the data flow and
* vorbis's needs */
static size_t
gst_ivorbisfile_read (void *ptr, size_t size, size_t nmemb, void *datasource)
{
size_t read_size = size * nmemb;
guint buf_size = 0;
Ivorbisfile *ivorbisfile = GST_IVORBISFILE (datasource);
size_t ret;
GST_DEBUG ("read %d", read_size);
/* make sure we don't go to EOS */
if (!ivorbisfile->may_eos && ivorbisfile->total_bytes &&
ivorbisfile->offset + read_size > ivorbisfile->total_bytes) {
read_size = ivorbisfile->total_bytes - ivorbisfile->offset;
}
if (read_size == 0 || ivorbisfile->eos) {
return 0;
}
if (ivorbisfile->adapter) {
const guint8 *buf = NULL;
buf_size = gst_adapter_available (ivorbisfile->adapter);
if (buf_size < read_size) {
return 0;
}
if (buf_size > read_size) {
buf_size = read_size;
} else if (buf_size == 0) {
return 0;
}
buf = gst_adapter_peek (ivorbisfile->adapter, buf_size);
memcpy (ptr, buf, buf_size);
gst_adapter_flush (ivorbisfile->adapter, buf_size);
} else {
GstBuffer *buf = NULL;
if (GST_FLOW_OK != gst_pad_pull_range (ivorbisfile->sinkpad,
ivorbisfile->offset, read_size, &buf)) {
return 0;
}
buf_size = GST_BUFFER_SIZE (buf);
memcpy (ptr, GST_BUFFER_DATA (buf), buf_size);
gst_buffer_unref (buf);
}
ivorbisfile->offset += buf_size;
ret = buf_size / size;
return ret;
}
static int
gst_ivorbisfile_seek (void *datasource, int64_t offset, int whence)
{
Ivorbisfile *ivorbisfile = GST_IVORBISFILE (datasource);
guint64 pending_offset = ivorbisfile->offset;
gboolean need_total = FALSE;
if (!ivorbisfile->vf.seekable) {
return -1;
}
GST_DEBUG ("seek %" G_GINT64_FORMAT " %d", offset, whence);
if (whence == SEEK_SET) {
pending_offset = offset;
ivorbisfile->adapterOffset = offset;
} else if (whence == SEEK_CUR) {
pending_offset += offset;
ivorbisfile->adapterOffset += offset;
} else if (whence == SEEK_END) {
need_total = TRUE;
pending_offset = ivorbisfile->total_bytes - offset;
ivorbisfile->adapterOffset = ivorbisfile->total_bytes - offset;
} else
return -1;
ivorbisfile->offset = pending_offset;
if (need_total)
ivorbisfile->total_bytes = ivorbisfile->adapterOffset + offset;
return 0;
}
static int
gst_ivorbisfile_close (void *datasource)
{
GST_DEBUG ("close");
return 0;
}
static long
gst_ivorbisfile_tell (void *datasource)
{
Ivorbisfile *ivorbisfile = GST_IVORBISFILE (datasource);
long result;
result = ivorbisfile->adapterOffset;
GST_DEBUG ("tell %ld", result);
return result;
}
ov_callbacks ivorbisfile_ov_callbacks = {
gst_ivorbisfile_read,
gst_ivorbisfile_seek,
gst_ivorbisfile_close,
gst_ivorbisfile_tell,
};
#if 0
/* retrieve the comment field (or tags) and put in metadata GstCaps
* returns TRUE if caps could be set,
* FALSE if they couldn't be read somehow */
static gboolean
gst_ivorbisfile_update_metadata (Ivorbisfile * ivorbisfile, gint link)
{
OggVorbis_File *vf = &ivorbisfile->vf;
gchar **ptr;
vorbis_comment *vc;
GstProps *props = NULL;
GstPropsEntry *entry;
gchar *name, *value;
/* clear old one */
if (ivorbisfile->metadata) {
gst_caps_unref (ivorbisfile->metadata);
ivorbisfile->metadata = NULL;
}
/* create props to hold the key/value pairs */
props = gst_props_empty_new ();
vc = ov_comment (vf, link);
ptr = vc->user_comments;
while (*ptr) {
value = strstr (*ptr, "=");
if (value) {
name = g_strndup (*ptr, value - *ptr);
entry = gst_props_entry_new (name, GST_PROPS_STRING_TYPE, value + 1);
gst_props_add_entry (props, (GstPropsEntry *) entry);
}
ptr++;
}
ivorbisfile->metadata = gst_caps_new ("ivorbisfile_metadata",
"application/x-gst-metadata", props);
g_object_notify (G_OBJECT (ivorbisfile), "metadata");
return TRUE;
}
/* retrieve logical stream properties and put them in streaminfo GstCaps
* returns TRUE if caps could be set,
* FALSE if they couldn't be read somehow */
static gboolean
gst_ivorbisfile_update_streaminfo (Ivorbisfile * ivorbisfile, gint link)
{
OggVorbis_File *vf = &ivorbisfile->vf;
vorbis_info *vi;
GstProps *props = NULL;
GstPropsEntry *entry;
/* clear old one */
if (ivorbisfile->streaminfo) {
gst_caps_unref (ivorbisfile->streaminfo);
ivorbisfile->streaminfo = NULL;
}
/* create props to hold the key/value pairs */
props = gst_props_empty_new ();
vi = ov_info (vf, link);
entry = gst_props_entry_new ("version", GST_PROPS_INT_TYPE, vi->version);
gst_props_add_entry (props, (GstPropsEntry *) entry);
entry = gst_props_entry_new ("bitrate_upper", GST_PROPS_INT_TYPE,
vi->bitrate_upper);
gst_props_add_entry (props, (GstPropsEntry *) entry);
entry = gst_props_entry_new ("bitrate_nominal", GST_PROPS_INT_TYPE,
vi->bitrate_nominal);
gst_props_add_entry (props, (GstPropsEntry *) entry);
entry = gst_props_entry_new ("bitrate_lower", GST_PROPS_INT_TYPE,
vi->bitrate_lower);
gst_props_add_entry (props, (GstPropsEntry *) entry);
entry = gst_props_entry_new ("serial", GST_PROPS_INT_TYPE,
ov_serialnumber (vf, link));
gst_props_add_entry (props, (GstPropsEntry *) entry);
entry = gst_props_entry_new ("bitrate", GST_PROPS_INT_TYPE,
ov_bitrate (vf, link));
gst_props_add_entry (props, (GstPropsEntry *) entry);
ivorbisfile->streaminfo = gst_caps_new ("ivorbisfile_streaminfo",
"application/x-gst-streaminfo", props);
g_object_notify (G_OBJECT (ivorbisfile), "streaminfo");
return TRUE;
}
#endif
static gboolean
gst_ivorbisfile_new_link (Ivorbisfile * ivorbisfile, gint link)
{
vorbis_info *vi = ov_info (&ivorbisfile->vf, link);
GstCaps *caps;
gboolean res = TRUE;
/* new logical bitstream */
ivorbisfile->current_link = link;
caps = gst_caps_new_simple ("audio/x-raw-int",
"endianness", G_TYPE_INT, G_BYTE_ORDER,
"signed", G_TYPE_BOOLEAN, TRUE,
"width", G_TYPE_INT, 16,
"depth", G_TYPE_INT, 16,
"rate", G_TYPE_INT, vi->rate, "channels", G_TYPE_INT, vi->channels, NULL);
ivorbisfile->rate = vi->rate;
ivorbisfile->channels = vi->channels;
ivorbisfile->width = 16;
if (gst_pad_set_caps (ivorbisfile->srcpad, caps) <= 0) {
res = FALSE;
}
gst_caps_unref (caps);
return TRUE;
}
static gboolean
gst_ivorbisfile_sink_activate (GstPad * sinkpad)
{
Ivorbisfile *ivorbisfile;
ivorbisfile = GST_IVORBISFILE (GST_PAD_PARENT (sinkpad));
if (gst_pad_check_pull_range (sinkpad)) {
/* FIX ME */
/* ivorbisfile->vf.seekable = TRUE; */
ivorbisfile->vf.seekable = FALSE;
return gst_pad_activate_pull (sinkpad, TRUE);
} else {
if (ivorbisfile->adapter) {
gst_adapter_clear (ivorbisfile->adapter);
} else {
ivorbisfile->adapter = gst_adapter_new ();
}
ivorbisfile->vf.seekable = FALSE;
return gst_pad_activate_push (sinkpad, TRUE);
}
}
static gboolean
gst_ivorbisfile_sink_activate_pull (GstPad * sinkpad, gboolean active)
{
gboolean result;
if (active) {
/* if we have a scheduler we can start the task */
result = gst_pad_start_task (sinkpad,
(GstTaskFunction) gst_ivorbisfile_loop, sinkpad);
} else {
result = gst_pad_stop_task (sinkpad);
}
return result;
}
static GstFlowReturn
gst_ivorbisfile_chain (GstPad * pad, GstBuffer * buffer)
{
Ivorbisfile *ivorbisfile = GST_IVORBISFILE (GST_PAD_PARENT (pad));
if (NULL == ivorbisfile->adapter) {
GST_DEBUG ("pull expected! Chain func should not be called");
return GST_FLOW_UNEXPECTED;
}
gst_adapter_push (ivorbisfile->adapter, buffer);
gst_ivorbisfile_loop (pad);
return GST_FLOW_OK;
}
static void
gst_ivorbisfile_loop (GstPad * pad)
{
Ivorbisfile *ivorbisfile = GST_IVORBISFILE (gst_pad_get_parent (pad));
GstBuffer *outbuf;
long ret;
GstClockTime time;
gint64 samples;
gint link;
if (ivorbisfile->eos) {
gst_object_unref (ivorbisfile);
return;
}
/* this function needs to go first since you don't want to be messing
* with an unset vf ;) */
if (ivorbisfile->restart) {
gint err;
if (ivorbisfile->adapter) {
if (gst_adapter_available (ivorbisfile->adapter) < 40960) {
gst_object_unref (ivorbisfile);
return;
}
}
ivorbisfile->offset = 0;
ivorbisfile->total_bytes = 0;
ivorbisfile->may_eos = FALSE;
ivorbisfile->adapterOffset = 0;
GST_DEBUG ("ivorbisfile: seekable: %s\n",
ivorbisfile->vf.seekable ? "yes" : "no");
/* open our custom ivorbisfile data object with the callbacks we provide */
if ((err = ov_open_callbacks (ivorbisfile, &ivorbisfile->vf, NULL, 0,
ivorbisfile_ov_callbacks)) < 0) {
GST_ELEMENT_ERROR (ivorbisfile, STREAM, DECODE, (NULL), (NULL));
gst_object_unref (ivorbisfile);
return;
}
ivorbisfile->need_discont = TRUE;
ivorbisfile->restart = FALSE;
ivorbisfile->current_link = -1;
}
if (ivorbisfile->seek_pending) {
/* get time to seek to in seconds */
switch (ivorbisfile->seek_format) {
case GST_FORMAT_TIME:
{
gdouble seek_to = (gdouble) ivorbisfile->seek_value / GST_SECOND;
if (ivorbisfile->seek_accurate) {
if (ov_time_seek (&ivorbisfile->vf, seek_to) == 0) {
ivorbisfile->need_discont = TRUE;
}
} else {
if (ov_time_seek_page (&ivorbisfile->vf, seek_to) == 0) {
ivorbisfile->need_discont = TRUE;
}
}
break;
}
case GST_FORMAT_DEFAULT:
if (ivorbisfile->seek_accurate) {
if (ov_pcm_seek (&ivorbisfile->vf, ivorbisfile->seek_value) == 0) {
ivorbisfile->need_discont = TRUE;
}
} else {
if (ov_pcm_seek_page (&ivorbisfile->vf, ivorbisfile->seek_value) == 0) {
ivorbisfile->need_discont = TRUE;
}
}
break;
default:
if (ivorbisfile->seek_format == logical_stream_format) {
gint64 seek_to;
seek_to = ivorbisfile->vf.offsets[ivorbisfile->seek_value];
if (ov_raw_seek (&ivorbisfile->vf, seek_to) == 0) {
ivorbisfile->need_discont = TRUE;
ivorbisfile->current_link = -1;
} else {
g_warning ("raw seek failed");
}
} else
g_warning ("unknown seek method, implement me !");
break;
}
ivorbisfile->seek_pending = FALSE;
}
/* we update the caps for each logical stream */
if (ivorbisfile->vf.current_link != ivorbisfile->current_link) {
if (!gst_ivorbisfile_new_link (ivorbisfile, ivorbisfile->vf.current_link)) {
GST_ELEMENT_ERROR (ivorbisfile, CORE, NEGOTIATION, (NULL), (NULL));
}
gst_object_unref (ivorbisfile);
return;
}
do {
outbuf = gst_buffer_new_and_alloc (4096);
ret = ov_read (&ivorbisfile->vf,
(char *) GST_BUFFER_DATA (outbuf), GST_BUFFER_SIZE (outbuf), &link);
/* get current time for discont and buffer timestamp */
time = (GstClockTime) (ov_time_tell (&ivorbisfile->vf) * GST_SECOND);
if (ret == 0) {
gst_buffer_unref (outbuf);
if (ivorbisfile->adapter == NULL) {
ivorbisfile->eos = TRUE;
ivorbisfile->restart = TRUE;
gst_pad_push_event (ivorbisfile->srcpad, gst_event_new_eos ());
}
gst_object_unref (ivorbisfile);
return;
} else if (ret < 0) {
g_warning ("ivorbisfile: decoding error");
gst_buffer_unref (outbuf);
gst_object_unref (ivorbisfile);
return;
} else {
if (ivorbisfile->need_discont) {
GstEvent *event;
ivorbisfile->need_discont = FALSE;
/* get stream stats */
samples = (gint64) (ov_pcm_tell (&ivorbisfile->vf));
event =
gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, time,
GST_CLOCK_TIME_NONE, 0);
gst_pad_push_event (ivorbisfile->srcpad, event);
}
if (NULL == GST_PAD_CAPS (ivorbisfile->srcpad)) {
gst_buffer_unref (outbuf);
gst_object_unref (ivorbisfile);
return;
}
gst_buffer_set_caps (outbuf, GST_PAD_CAPS (ivorbisfile->srcpad));
GST_BUFFER_SIZE (outbuf) = ret;
/* FIX ME TO SET RIGHT TIMESTAMP
gint bufsize = ret / (ivorbisfile->width / 8);
GST_BUFFER_TIMESTAMP (outbuf) = time;
GST_BUFFER_DURATION (outbuf) = GST_SECOND * bufsize / (ivorbisfile->rate * ivorbisfile->channels);
*/
ivorbisfile->may_eos = TRUE;
if (!ivorbisfile->vf.seekable) {
ivorbisfile->total_bytes += GST_BUFFER_SIZE (outbuf);
}
if (GST_FLOW_OK != gst_pad_push (ivorbisfile->srcpad, outbuf)) {
gst_buffer_unref (outbuf);
return;
}
}
} while (TRUE);
}
static gboolean
gst_ivorbisfile_src_convert (GstPad * pad,
GstFormat src_format, gint64 src_value,
GstFormat * dest_format, gint64 * dest_value)
{
gboolean res = TRUE;
guint scale = 1;
gint bytes_per_sample;
Ivorbisfile *ivorbisfile;
vorbis_info *vi;
ivorbisfile = GST_IVORBISFILE (GST_PAD_PARENT (pad));
vi = ov_info (&ivorbisfile->vf, -1);
bytes_per_sample = vi->channels * 2;
switch (src_format) {
case GST_FORMAT_BYTES:
switch (*dest_format) {
case GST_FORMAT_DEFAULT:
*dest_value = src_value / (vi->channels * 2);
break;
case GST_FORMAT_TIME:
{
gint byterate = bytes_per_sample * vi->rate;
if (byterate == 0)
return FALSE;
*dest_value = src_value * GST_SECOND / byterate;
break;
}
default:
res = FALSE;
}
case GST_FORMAT_DEFAULT:
switch (*dest_format) {
case GST_FORMAT_BYTES:
*dest_value = src_value * bytes_per_sample;
break;
case GST_FORMAT_TIME:
if (vi->rate == 0)
return FALSE;
*dest_value = src_value * GST_SECOND / vi->rate;
break;
default:
res = FALSE;
}
break;
case GST_FORMAT_TIME:
switch (*dest_format) {
case GST_FORMAT_BYTES:
scale = bytes_per_sample;
case GST_FORMAT_DEFAULT:
*dest_value = src_value * scale * vi->rate / GST_SECOND;
break;
default:
res = FALSE;
}
break;
default:
if (src_format == logical_stream_format) {
/* because we need to convert relative from 0, we have to add
* all pcm totals */
gint i;
gint64 count = 0;
switch (*dest_format) {
case GST_FORMAT_BYTES:
res = FALSE;
break;
case GST_FORMAT_DEFAULT:
if (src_value > ivorbisfile->vf.links) {
src_value = ivorbisfile->vf.links;
}
for (i = 0; i < src_value; i++) {
vi = ov_info (&ivorbisfile->vf, i);
count += ov_pcm_total (&ivorbisfile->vf, i);
}
*dest_value = count;
break;
case GST_FORMAT_TIME:
{
if (src_value > ivorbisfile->vf.links) {
src_value = ivorbisfile->vf.links;
}
for (i = 0; i < src_value; i++) {
vi = ov_info (&ivorbisfile->vf, i);
if (vi->rate)
count +=
ov_pcm_total (&ivorbisfile->vf, i) * GST_SECOND / vi->rate;
else
count += ov_time_total (&ivorbisfile->vf, i) * GST_SECOND;
}
/* we use the pcm totals to get the total time, it's more accurate */
*dest_value = count;
break;
}
default:
res = FALSE;
}
} else
res = FALSE;
break;
}
return res;
}
static gboolean
gst_ivorbisfile_sink_query (GstPad * pad, GstQuery * query)
{
gboolean res;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CONVERT:
{
GstFormat src_fmt, dest_fmt;
gint64 src_val, dest_val;
/* peel off input */
gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val);
if ((res = gst_ivorbisfile_sink_convert (pad, src_fmt, src_val,
&dest_fmt, &dest_val))) {
gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
}
break;
}
default:
res = FALSE;
break;
}
return res;
}
static gboolean
gst_ivorbisfile_sink_convert (GstPad * pad,
GstFormat src_format, gint64 src_value,
GstFormat * dest_format, gint64 * dest_value)
{
gboolean res = TRUE;
Ivorbisfile *ivorbisfile;
ivorbisfile = GST_IVORBISFILE (GST_PAD_PARENT (pad));
switch (src_format) {
case GST_FORMAT_BYTES:
switch (*dest_format) {
case GST_FORMAT_TIME:
break;
default:
if (*dest_format == logical_stream_format) {
} else
res = FALSE;
}
case GST_FORMAT_TIME:
switch (*dest_format) {
case GST_FORMAT_BYTES:
break;
default:
if (*dest_format == logical_stream_format) {
} else
res = FALSE;
}
default:
if (src_format == logical_stream_format) {
switch (*dest_format) {
case GST_FORMAT_TIME:
break;
case GST_FORMAT_BYTES:
break;
default:
res = FALSE;
}
} else
res = FALSE;
break;
}
return res;
}
static const GstQueryType *
gst_ivorbisfile_get_sink_query_types (GstPad * pad)
{
static const GstQueryType types[] = {
GST_QUERY_CONVERT,
0
};
return types;
}
static const GstQueryType *
gst_ivorbisfile_get_src_query_types (GstPad * pad)
{
static const GstQueryType types[] = {
GST_QUERY_POSITION,
GST_QUERY_DURATION,
GST_QUERY_CONVERT,
0
};
return types;
}
/* handles queries for location in the stream in the requested format */
static gboolean
gst_ivorbisfile_src_query (GstPad * pad, GstQuery * query)
{
gboolean res = TRUE;
Ivorbisfile *ivorbisfile;
vorbis_info *vi;
ivorbisfile = GST_IVORBISFILE (GST_PAD_PARENT (pad));
vi = ov_info (&ivorbisfile->vf, -1);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_POSITION:
{
GstFormat format;
GstFormat rformat = GST_FORMAT_TIME;
gint64 cur;
GstPad *peer;
/* save requested format */
gst_query_parse_position (query, &format, NULL);
/* query peer for current position in time */
gst_query_set_position (query, GST_FORMAT_TIME, -1);
if ((peer = gst_pad_get_peer (ivorbisfile->sinkpad)) == NULL)
goto error;
if (!gst_pad_query_position (peer, &rformat, &cur)) {
GST_LOG_OBJECT (ivorbisfile, "query on peer pad failed");
gst_object_unref (peer);
goto error;
}
gst_object_unref (peer);
if (format != rformat) {
gst_ivorbisfile_src_convert (pad, rformat, cur, &format, &cur);
}
switch (format) {
case GST_FORMAT_DEFAULT:
if (ivorbisfile->vf.seekable)
cur = ov_pcm_tell (&ivorbisfile->vf);
else
cur = ivorbisfile->total_bytes / (vi->channels * 2);
break;
case GST_FORMAT_TIME:
if (ivorbisfile->vf.seekable)
cur = (gint64) (ov_time_tell (&ivorbisfile->vf) * GST_SECOND);
else
cur = ivorbisfile->total_bytes * GST_SECOND
/ (vi->rate * vi->channels * 2);
break;
case GST_FORMAT_BYTES:
if (ivorbisfile->vf.seekable)
cur = ov_pcm_tell (&ivorbisfile->vf) * vi->channels * 2;
else
cur = ivorbisfile->total_bytes;
break;
default:
if (format == logical_stream_format) {
if (ivorbisfile->vf.seekable)
cur = ivorbisfile->current_link;
else
return FALSE;
} else
res = FALSE;
break;
}
gst_query_set_position (query, format, cur);
break;
}
case GST_QUERY_DURATION:
{
GstFormat format;
GstFormat rformat = GST_FORMAT_TIME;
gint64 cur;
GstPad *peer;
/* save requested format */
gst_query_parse_position (query, &format, NULL);
/* query peer for current position in time */
gst_query_set_position (query, GST_FORMAT_TIME, -1);
if ((peer = gst_pad_get_peer (ivorbisfile->sinkpad)) == NULL)
goto error;
if (!gst_pad_query_position (peer, &rformat, &cur)) {
GST_LOG_OBJECT (ivorbisfile, "query on peer pad failed");
gst_object_unref (peer);
goto error;
}
gst_object_unref (peer);
if (format != rformat) {
gst_ivorbisfile_src_convert (pad, rformat, cur, &format, &cur);
}
switch (format) {
case GST_FORMAT_DEFAULT:
if (ivorbisfile->vf.seekable)
cur = ov_pcm_total (&ivorbisfile->vf, -1);
else
return FALSE;
break;
case GST_FORMAT_BYTES:
if (ivorbisfile->vf.seekable)
cur = ov_pcm_total (&ivorbisfile->vf, -1) * vi->channels * 2;
else
return FALSE;
break;
case GST_FORMAT_TIME:
if (ivorbisfile->vf.seekable)
cur = (gint64) (ov_time_total (&ivorbisfile->vf, -1) * GST_SECOND);
else
return FALSE;
break;
default:
if (format == logical_stream_format) {
if (ivorbisfile->vf.seekable)
cur = ivorbisfile->vf.links;
else
return FALSE;
} else
res = FALSE;
break;
}
gst_query_set_position (query, format, cur);
break;
}
case GST_QUERY_CONVERT:
{
GstFormat src_fmt, dest_fmt;
gint64 src_val, dest_val;
/* peel off input */
gst_query_parse_convert (query, &src_fmt, &src_val, NULL, NULL);
if ((res = gst_ivorbisfile_src_convert (pad, src_fmt, src_val,
&dest_fmt, &dest_val))) {
gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
}
break;
}
default:
res = FALSE;
break;
}
return res;
error:
return FALSE;
}
static gboolean
gst_ivorbisfile_sink_event (GstPad * pad, GstEvent * event)
{
Ivorbisfile *ivorbisfile;
gboolean ret;
ivorbisfile = GST_IVORBISFILE (gst_pad_get_parent (pad));
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_EOS:
GST_DEBUG ("eos");
ivorbisfile->eos = TRUE;
ivorbisfile->restart = TRUE;
break;
case GST_EVENT_NEWSEGMENT:
GST_DEBUG ("discont");
ivorbisfile->need_discont = TRUE;
default:
break;
}
ret = gst_pad_event_default (pad, event);
gst_object_unref (ivorbisfile);
return ret;
}
/* handle events on src pad */
static gboolean
gst_ivorbisfile_src_event (GstPad * pad, GstEvent * event)
{
gboolean res = TRUE;
Ivorbisfile *ivorbisfile;
ivorbisfile = GST_IVORBISFILE (gst_pad_get_parent (pad));
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEEK:
{
gint64 offset;
vorbis_info *vi;
GstFormat format;
GstSeekFlags flags;
GST_DEBUG ("ivorbisfile: handling seek event on pad %s:%s",
GST_DEBUG_PAD_NAME (pad));
if (!ivorbisfile->vf.seekable) {
gst_event_unref (event);
GST_DEBUG ("vorbis stream is not seekable");
gst_object_unref (ivorbisfile);
return FALSE;
}
gst_event_parse_seek (event, NULL, &format, &flags, NULL, &offset, NULL,
NULL);
switch (format) {
case GST_FORMAT_TIME:
ivorbisfile->seek_pending = TRUE;
ivorbisfile->seek_value = offset;
ivorbisfile->seek_format = format;
ivorbisfile->seek_accurate = flags & GST_SEEK_FLAG_ACCURATE;
break;
case GST_FORMAT_BYTES:
vi = ov_info (&ivorbisfile->vf, -1);
if (vi->channels == 0) {
GST_DEBUG ("vorbis stream has 0 channels ?");
res = FALSE;
goto done;
}
offset /= vi->channels * 2;
/* fallthrough */
case GST_FORMAT_DEFAULT:
ivorbisfile->seek_pending = TRUE;
ivorbisfile->seek_value = offset;
ivorbisfile->seek_format = format;
ivorbisfile->seek_accurate = flags & GST_SEEK_FLAG_ACCURATE;
break;
default:
if (format == logical_stream_format) {
ivorbisfile->seek_pending = TRUE;
ivorbisfile->seek_value = offset;
ivorbisfile->seek_format = format;
ivorbisfile->seek_accurate = flags & GST_SEEK_FLAG_ACCURATE;
} else {
GST_DEBUG ("unhandled seek format");
res = FALSE;
}
break;
}
break;
}
default:
res = FALSE;
break;
}
done:
gst_event_unref (event);
gst_object_unref (ivorbisfile);
return res;
}
static GstStateChangeReturn
gst_ivorbisfile_change_state (GstElement * element, GstStateChange transition)
{
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
Ivorbisfile *ivorbisfile = GST_IVORBISFILE (element);
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
/* fall through */
case GST_STATE_CHANGE_READY_TO_PAUSED:
if (ivorbisfile->adapter) {
gst_adapter_clear (ivorbisfile->adapter);
}
ivorbisfile->restart = TRUE;
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
ivorbisfile->eos = FALSE;
break;
default:
break;
}
if (GST_ELEMENT_CLASS (parent_class)->change_state)
ret = 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:
ov_clear (&ivorbisfile->vf);
break;
case GST_STATE_CHANGE_READY_TO_NULL:
default:
break;
}
return ret;
}
static void
gst_ivorbisfile_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
Ivorbisfile *ivorbisfile;
g_return_if_fail (GST_IS_IVORBISFILE (object));
ivorbisfile = GST_IVORBISFILE (object);
switch (prop_id) {
default:
g_warning ("Unknown property id\n");
}
}
static void
gst_ivorbisfile_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
Ivorbisfile *ivorbisfile;
g_return_if_fail (GST_IS_IVORBISFILE (object));
ivorbisfile = GST_IVORBISFILE (object);
switch (prop_id) {
case ARG_METADATA:
g_value_set_boxed (value, ivorbisfile->metadata);
break;
case ARG_STREAMINFO:
g_value_set_boxed (value, ivorbisfile->streaminfo);
break;
default:
g_warning ("Unknown property id\n");
}
}