/* 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 #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; GstByteStream *bs; 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; GstCaps *metadata; GstCaps *streaminfo; }; struct _IvorbisfileClass { GstElementClass parent_class; }; GType ivorbisfile_get_type (void); extern GstPadTemplate *gst_vorbisdec_src_template, *gst_vorbisdec_sink_template; /* elementfactory information */ GstElementDetails ivorbisfile_details = { "Ogg Vorbis decoder", "Codec/Audio/Decoder", "LGPL", "Decodes OGG Vorbis audio using the Tremor vorbisfile API", VERSION, "Monty \n" "Wim Taymans ", "(C) 2000", }; /* Ivorbisfile signals and args */ enum { LAST_SIGNAL }; enum { ARG_0, ARG_METADATA, ARG_STREAMINFO }; static void gst_ivorbisfile_class_init (IvorbisfileClass *klass); static void gst_ivorbisfile_init (Ivorbisfile *ivorbisfile); static GstElementStateReturn gst_ivorbisfile_change_state (GstElement *element); static const GstFormat* gst_ivorbisfile_get_formats (GstPad *pad); 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_query_types (GstPad *pad); static gboolean gst_ivorbisfile_src_query (GstPad *pad, GstQueryType type, GstFormat *format, gint64 *value); static const GstEventMask* gst_ivorbisfile_get_event_masks (GstPad *pad); static gboolean gst_ivorbisfile_src_event (GstPad *pad, GstEvent *event); 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 void gst_ivorbisfile_loop (GstElement *element); static GstElementClass *parent_class = NULL; //static guint gst_ivorbisfile_signals[LAST_SIGNAL] = { 0 }; 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), NULL, 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"); } return ivorbisfile_type; } 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); 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->get_property = gst_ivorbisfile_get_property; gobject_class->set_property = gst_ivorbisfile_set_property; 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_formats_function (ivorbisfile->sinkpad, gst_ivorbisfile_get_formats); gst_pad_set_convert_function (ivorbisfile->sinkpad, gst_ivorbisfile_sink_convert); gst_element_set_loop_function (GST_ELEMENT (ivorbisfile), gst_ivorbisfile_loop); ivorbisfile->srcpad = gst_pad_new_from_template (gst_vorbisdec_src_template, "src"); gst_element_add_pad (GST_ELEMENT (ivorbisfile), ivorbisfile->srcpad); gst_pad_set_formats_function (ivorbisfile->srcpad, gst_ivorbisfile_get_formats); gst_pad_set_query_type_function (ivorbisfile->srcpad, gst_ivorbisfile_get_query_types); gst_pad_set_query_function (ivorbisfile->srcpad, gst_ivorbisfile_src_query); gst_pad_set_event_mask_function (ivorbisfile->srcpad, gst_ivorbisfile_get_event_masks); gst_pad_set_event_function (ivorbisfile->srcpad, gst_ivorbisfile_src_event); gst_pad_set_convert_function (ivorbisfile->srcpad, gst_ivorbisfile_src_convert); 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; } /* 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) { guint32 got_bytes = 0; guint8 *data; size_t read_size = size * nmemb; Ivorbisfile *ivorbisfile = GST_IVORBISFILE (datasource); 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; while (got_bytes == 0) { got_bytes = gst_bytestream_peek_bytes (ivorbisfile->bs, &data, read_size); if (got_bytes < read_size) { GstEvent *event; guint32 avail; gst_bytestream_get_status (ivorbisfile->bs, &avail, &event); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_EOS: GST_DEBUG ("eos"); ivorbisfile->eos = TRUE; if (avail == 0) { gst_event_unref (event); return 0; } break; case GST_EVENT_DISCONTINUOUS: GST_DEBUG ("discont"); ivorbisfile->need_discont = TRUE; default: break; } gst_event_unref (event); if (avail > 0) got_bytes = gst_bytestream_peek_bytes (ivorbisfile->bs, &data, avail); else got_bytes = 0; } } memcpy (ptr, data, got_bytes); gst_bytestream_flush_fast (ivorbisfile->bs, got_bytes); ivorbisfile->offset += got_bytes; return got_bytes / size; } static int gst_ivorbisfile_seek (void *datasource, int64_t offset, int whence) { Ivorbisfile *ivorbisfile = GST_IVORBISFILE (datasource); GstSeekType method; guint64 pending_offset = ivorbisfile->offset; gboolean need_total = FALSE; if (!ivorbisfile->vf.seekable) { return -1; } GST_DEBUG ("seek %lld %d", offset, whence); if (whence == SEEK_SET) { method = GST_SEEK_METHOD_SET; pending_offset = offset; } else if (whence == SEEK_CUR) { method = GST_SEEK_METHOD_CUR; pending_offset += offset; } else if (whence == SEEK_END) { method = GST_SEEK_METHOD_END; need_total = TRUE; pending_offset = ivorbisfile->total_bytes - offset; } else return -1; if (!gst_bytestream_seek (ivorbisfile->bs, offset, method)) return -1; ivorbisfile->offset = pending_offset; if (need_total) ivorbisfile->total_bytes = gst_bytestream_tell (ivorbisfile->bs) + 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 = gst_bytestream_tell (ivorbisfile->bs); GST_DEBUG ("tell %ld", result); return result; } ov_callbacks ivorbisfile_ov_callbacks = { gst_ivorbisfile_read, gst_ivorbisfile_seek, gst_ivorbisfile_close, gst_ivorbisfile_tell, }; /* 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; } static gboolean gst_ivorbisfile_new_link (Ivorbisfile *ivorbisfile, gint link) { vorbis_info *vi = ov_info (&ivorbisfile->vf, link); GstCaps *newcaps; /* new logical bitstream */ ivorbisfile->current_link = link; gst_ivorbisfile_update_metadata (ivorbisfile, link); gst_ivorbisfile_update_streaminfo (ivorbisfile, link); newcaps = GST_CAPS_NEW ("vorbisdec_src", "audio/x-raw-int", "endianness", GST_PROPS_INT (G_BYTE_ORDER), "signed", GST_PROPS_BOOLEAN (TRUE), "width", GST_PROPS_INT (16), "depth", GST_PROPS_INT (16), "rate", GST_PROPS_INT (vi->rate), "channels", GST_PROPS_INT (vi->channels) ); if (gst_pad_try_set_caps (ivorbisfile->srcpad, newcaps) <= 0) { return FALSE; } return TRUE; } static void gst_ivorbisfile_loop (GstElement *element) { Ivorbisfile *ivorbisfile = GST_IVORBISFILE (element); GstBuffer *outbuf; long ret; GstClockTime time; gint64 samples; gint link; /* this function needs to go first since you don't want to be messing * with an unset vf ;) */ if (ivorbisfile->restart) { ivorbisfile->offset = 0; ivorbisfile->total_bytes = 0; ivorbisfile->may_eos = FALSE; ivorbisfile->vf.seekable = gst_bytestream_seek (ivorbisfile->bs, 0, GST_SEEK_METHOD_SET); GST_DEBUG ("ivorbisfile: seekable: %s\n", ivorbisfile->vf.seekable ? "yes" : "no"); /* open our custom ivorbisfile data object with the callbacks we provide */ if (ov_open_callbacks (ivorbisfile, &ivorbisfile->vf, NULL, 0, ivorbisfile_ov_callbacks) < 0) { gst_element_gerror(element, GST_ERROR_UNKNOWN, g_strdup ("unconverted error, file a bug"), g_strdup_printf("this is not a vorbis file")); 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_gerror(GST_ELEMENT (ivorbisfile), GST_ERROR_UNKNOWN, g_strdup ("unconverted error, file a bug"), g_strdup_printf("could not negotiate format")); } return; } outbuf = gst_buffer_new (); GST_BUFFER_DATA (outbuf) = g_malloc (4096); GST_BUFFER_SIZE (outbuf) = 4096; /* get current time for discont and buffer timestamp */ time = (GstClockTime) (ov_time_tell (&ivorbisfile->vf) * GST_SECOND); ret = ov_read (&ivorbisfile->vf, GST_BUFFER_DATA (outbuf), GST_BUFFER_SIZE (outbuf), &link); if (ret == 0) { GST_DEBUG ("eos"); /* send EOS event */ /*ov_clear (&ivorbisfile->vf);*/ ivorbisfile->restart = TRUE; gst_buffer_unref (outbuf); /* if the pad is not usable, don't push it out */ if (GST_PAD_IS_USABLE (ivorbisfile->srcpad)) { gst_pad_push (ivorbisfile->srcpad, GST_BUFFER (gst_event_new (GST_EVENT_EOS))); } gst_element_set_eos (element); return; } else if (ret < 0) { g_warning ("ivorbisfile: decoding error"); gst_buffer_unref (outbuf); return; } else { if (ivorbisfile->need_discont) { GstEvent *discont; ivorbisfile->need_discont = FALSE; /* if the pad is not usable, don't push it out */ if (GST_PAD_IS_USABLE (ivorbisfile->srcpad)) { /* get stream stats */ samples = (gint64) (ov_pcm_tell (&ivorbisfile->vf)); discont = gst_event_new_discontinuous (FALSE, GST_FORMAT_TIME, time, GST_FORMAT_DEFAULT, samples, NULL); gst_pad_push (ivorbisfile->srcpad, GST_BUFFER (discont)); } } GST_BUFFER_SIZE (outbuf) = ret; GST_BUFFER_TIMESTAMP (outbuf) = time; ivorbisfile->may_eos = TRUE; if (!ivorbisfile->vf.seekable) { ivorbisfile->total_bytes += GST_BUFFER_SIZE (outbuf); } if (GST_PAD_IS_USABLE (ivorbisfile->srcpad)) gst_pad_push (ivorbisfile->srcpad, outbuf); else gst_buffer_unref (outbuf); } } static const GstFormat* gst_ivorbisfile_get_formats (GstPad *pad) { static GstFormat src_formats[] = { GST_FORMAT_TIME, GST_FORMAT_BYTES, GST_FORMAT_DEFAULT, 0, 0 }; static GstFormat sink_formats[] = { GST_FORMAT_TIME, GST_FORMAT_BYTES, 0, 0 }; src_formats[3] = logical_stream_format; sink_formats[2] = logical_stream_format; return (GST_PAD_IS_SRC (pad) ? src_formats : sink_formats); } 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_get_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_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_get_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_query_types (GstPad *pad) { static const GstQueryType types[] = { GST_QUERY_TOTAL, GST_QUERY_POSITION, 0 }; return types; } /* handles queries for location in the stream in the requested format */ static gboolean gst_ivorbisfile_src_query (GstPad *pad, GstQueryType type, GstFormat *format, gint64 *value) { gboolean res = TRUE; Ivorbisfile *ivorbisfile; vorbis_info *vi; ivorbisfile = GST_IVORBISFILE (gst_pad_get_parent (pad)); vi = ov_info (&ivorbisfile->vf, -1); switch (type) { case GST_QUERY_TOTAL: { switch (*format) { case GST_FORMAT_DEFAULT: if (ivorbisfile->vf.seekable) *value = ov_pcm_total (&ivorbisfile->vf, -1); else return FALSE; break; case GST_FORMAT_BYTES: if (ivorbisfile->vf.seekable) *value = ov_pcm_total (&ivorbisfile->vf, -1) * vi->channels * 2; else return FALSE; break; case GST_FORMAT_TIME: if (ivorbisfile->vf.seekable) *value = (gint64) (ov_time_total (&ivorbisfile->vf, -1) * GST_SECOND); else return FALSE; break; default: if (*format == logical_stream_format) { if (ivorbisfile->vf.seekable) *value = ivorbisfile->vf.links; else return FALSE; } else res = FALSE; break; } break; } case GST_QUERY_POSITION: switch (*format) { case GST_FORMAT_TIME: if (ivorbisfile->vf.seekable) *value = (gint64) (ov_time_tell (&ivorbisfile->vf) * GST_SECOND); else *value = ivorbisfile->total_bytes * GST_SECOND / (vi->rate * vi->channels * 2); break; case GST_FORMAT_BYTES: if (ivorbisfile->vf.seekable) *value = ov_pcm_tell (&ivorbisfile->vf) * vi->channels * 2; else *value = ivorbisfile->total_bytes; break; case GST_FORMAT_DEFAULT: if (ivorbisfile->vf.seekable) *value = ov_pcm_tell (&ivorbisfile->vf); else *value = ivorbisfile->total_bytes / (vi->channels * 2); break; default: if (*format == logical_stream_format) { if (ivorbisfile->vf.seekable) *value = ivorbisfile->current_link; else return FALSE; } else res = FALSE; break; } break; default: res = FALSE; break; } return res; } static const GstEventMask* gst_ivorbisfile_get_event_masks (GstPad *pad) { static const GstEventMask masks[] = { { GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_ACCURATE }, { 0, } }; return masks; } /* 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; 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"); return FALSE; } offset = GST_EVENT_SEEK_OFFSET (event); format = GST_EVENT_SEEK_FORMAT (event); switch (format) { case GST_FORMAT_TIME: ivorbisfile->seek_pending = TRUE; ivorbisfile->seek_value = offset; ivorbisfile->seek_format = format; ivorbisfile->seek_accurate = GST_EVENT_SEEK_FLAGS (event) & 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 = GST_EVENT_SEEK_FLAGS (event) & 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 = GST_EVENT_SEEK_FLAGS (event) & GST_SEEK_FLAG_ACCURATE; } else { GST_DEBUG ("unhandled seek format"); res = FALSE; } break; } break; } default: res = FALSE; break; } done: gst_event_unref (event); return res; } static GstElementStateReturn gst_ivorbisfile_change_state (GstElement *element) { Ivorbisfile *ivorbisfile = GST_IVORBISFILE (element); switch (GST_STATE_TRANSITION (element)) { case GST_STATE_NULL_TO_READY: case GST_STATE_READY_TO_PAUSED: ivorbisfile->restart = TRUE; ivorbisfile->bs = gst_bytestream_new (ivorbisfile->sinkpad); break; case GST_STATE_PAUSED_TO_PLAYING: ivorbisfile->eos = FALSE; case GST_STATE_PLAYING_TO_PAUSED: break; case GST_STATE_PAUSED_TO_READY: ov_clear (&ivorbisfile->vf); gst_bytestream_destroy (ivorbisfile->bs); break; case GST_STATE_READY_TO_NULL: default: break; } if (GST_ELEMENT_CLASS (parent_class)->change_state) return GST_ELEMENT_CLASS (parent_class)->change_state (element); return GST_STATE_SUCCESS; } 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"); } }