/* GStreamer * Copyright (C) <1999> Erik Walthinsen * * 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 #include #include #include #include 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(klass) \ (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 */ static const GstElementDetails ivorbisfile_details = GST_ELEMENT_DETAILS ("Ogg Vorbis audio decoder", "Codec/Decoder/Audio", "Decodes OGG Vorbis audio using the Tremor vorbisfile API", "Monty \n" "Wim Taymans \n" "Amaury Jacquot "); /* 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 GstFlowReturn gst_ivorbisfile_play (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_peek_parent (klass); 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->adapter = NULL; if (ivorbisfile->metadata) { ivorbisfile->metadata = NULL; } if (ivorbisfile->streaminfo) { ivorbisfile->streaminfo = 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_LOG ("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, ogg_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; if (ivorbisfile->adapter) { gst_adapter_clear (ivorbisfile->adapter); g_object_unref (ivorbisfile->adapter); ivorbisfile->adapter = NULL; } 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_ERROR ("pull expected! Chain func should not be called"); return GST_FLOW_UNEXPECTED; } gst_adapter_push (ivorbisfile->adapter, buffer); return gst_ivorbisfile_play (pad); } static void gst_ivorbisfile_loop (GstPad * pad) { gst_ivorbisfile_play (pad); } static GstFlowReturn gst_ivorbisfile_play (GstPad * pad) { Ivorbisfile *ivorbisfile = GST_IVORBISFILE (gst_pad_get_parent (pad)); GstBuffer *outbuf; long ret; GstClockTime time; gint64 samples; gint link; GstFlowReturn res = GST_FLOW_OK; if (ivorbisfile->eos) { goto done; } /* 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) { goto done; } } 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)); goto done; } 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 { GST_WARNING ("raw seek failed"); } } else GST_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)); } goto done; } 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 ()); } goto done; } else if (ret < 0) { switch (ret) { case OV_HOLE: GST_WARNING ("Vorbisfile encoutered missing or corrupt data in the bitstream." " Recovery is normally automatic and" " this return code is for informational purposes only."); break; case OV_EBADLINK: GST_WARNING ("The given link exists in the Vorbis data stream," " but is not decipherable due to garbacge or corruption."); break; default: GST_ERROR ("ivorbisfile: decoding error, unexpected ret = %ld", ret); break; } gst_buffer_unref (outbuf); goto done; } 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); goto done; } 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 != (res = gst_pad_push (ivorbisfile->srcpad, outbuf))) { goto done; } } } while (TRUE); done: gst_object_unref (ivorbisfile); return res; } 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 = TRUE; 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; gst_event_unref (event); goto done; default: break; } ret = gst_pad_event_default (pad, event); done: 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: ivorbisfile->total_bytes = 0; ivorbisfile->offset = 0; ivorbisfile->seek_pending = 0; ivorbisfile->need_discont = FALSE; if (ivorbisfile->metadata) { gst_caps_unref (ivorbisfile->metadata); ivorbisfile->metadata = NULL; } if (ivorbisfile->streaminfo) { gst_caps_unref (ivorbisfile->streaminfo); ivorbisfile->streaminfo = NULL; } ivorbisfile->current_link = -1; ivorbisfile->rate = -1; ivorbisfile->channels = -1; ivorbisfile->width = -1; break; 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: GST_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: GST_WARNING ("Unknown property id\n"); } }