/* 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 (vorbisfile_debug); #define GST_CAT_DEFAULT vorbisfile_debug #define GST_TYPE_VORBISFILE \ (vorbisfile_get_type()) #define GST_VORBISFILE(obj) \ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_VORBISFILE,VorbisFile)) #define GST_VORBISFILE_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_VORBISFILE,VorbisFileClass)) #define GST_IS_VORBISFILE(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_VORBISFILE)) #define GST_IS_VORBISFILE_CLASS(obj) \ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_VORBISFILE)) typedef struct _VorbisFile VorbisFile; typedef struct _VorbisFileClass VorbisFileClass; struct _VorbisFile { 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; gulong blocksize; GstCaps *metadata; GstCaps *streaminfo; }; struct _VorbisFileClass { GstElementClass parent_class; }; GType vorbisfile_get_type (void); GstPadTemplate *gst_vorbisdec_src_template, *gst_vorbisdec_sink_template; /* elementfactory information */ GstElementDetails vorbisfile_details = { "Ogg Vorbis decoder", "Codec/Decoder/Audio", "Decodes OGG Vorbis audio using the vorbisfile API", "Monty , " "Wim Taymans ", }; /* VorbisFile signals and args */ enum { LAST_SIGNAL }; #define DEFAULT_BLOCKSIZE 4096 enum { ARG_0, ARG_BLOCKSIZE, ARG_METADATA, ARG_STREAMINFO }; static void gst_vorbisfile_base_init (gpointer g_class); static void gst_vorbisfile_class_init (VorbisFileClass *klass); static void gst_vorbisfile_init (VorbisFile *vorbisfile); static GstElementStateReturn gst_vorbisfile_change_state (GstElement *element); static const GstFormat* gst_vorbisfile_get_formats (GstPad *pad); static gboolean gst_vorbisfile_src_convert (GstPad *pad, GstFormat src_format, gint64 src_value, GstFormat *dest_format, gint64 *dest_value); static gboolean gst_vorbisfile_sink_convert (GstPad *pad, GstFormat src_format, gint64 src_value, GstFormat *dest_format, gint64 *dest_value); static const GstQueryType* gst_vorbisfile_get_query_types (GstPad *pad); static gboolean gst_vorbisfile_src_query (GstPad *pad, GstQueryType type, GstFormat *format, gint64 *value); static const GstEventMask* gst_vorbisfile_get_event_masks (GstPad *pad); static gboolean gst_vorbisfile_src_event (GstPad *pad, GstEvent *event); static void gst_vorbisfile_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void gst_vorbisfile_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void gst_vorbisfile_loop (GstElement *element); static GstElementClass *parent_class = NULL; //static guint gst_vorbisfile_signals[LAST_SIGNAL] = { 0 }; static GstFormat logical_stream_format; GType vorbisfile_get_type (void) { static GType vorbisfile_type = 0; if (!vorbisfile_type) { static const GTypeInfo vorbisfile_info = { sizeof (VorbisFileClass), gst_vorbisfile_base_init, NULL, (GClassInitFunc) gst_vorbisfile_class_init, NULL, NULL, sizeof (VorbisFile), 0, (GInstanceInitFunc) gst_vorbisfile_init, }; vorbisfile_type = g_type_register_static (GST_TYPE_ELEMENT, "VorbisFile", &vorbisfile_info, 0); logical_stream_format = gst_format_register ("logical_stream", "The logical stream"); GST_DEBUG_CATEGORY_INIT (vorbisfile_debug, "vorbisfile", 0, "vorbis in ogg decoding element"); } return vorbisfile_type; } static GstCaps* vorbis_caps_factory (void) { return gst_caps_new ( "vorbis_vorbis", "application/ogg", NULL); } static GstCaps* raw_caps_factory (void) { return gst_caps_new ( "vorbis_raw", "audio/x-raw-int", gst_props_new ( "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_RANGE (11025, 48000), "channels", GST_PROPS_INT_RANGE (1, 2), NULL)); } static GstCaps* raw_caps2_factory (void) { return gst_caps_new ( "vorbis_raw_float", "audio/x-raw-float", gst_props_new ( "width", GST_PROPS_INT (32), "endianness", GST_PROPS_INT (G_BYTE_ORDER), "rate", GST_PROPS_INT_RANGE (11025, 48000), "channels", GST_PROPS_INT_RANGE (1, 2), "buffer-frames", GST_PROPS_INT_RANGE (1, G_MAXINT), NULL)); } static void gst_vorbisfile_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 (); gst_vorbisdec_sink_template = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, vorbis_caps, NULL); raw_caps = gst_caps_prepend (raw_caps, raw_caps2); gst_vorbisdec_src_template = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, raw_caps, NULL); 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, &vorbisfile_details); } static void gst_vorbisfile_class_init (VorbisFileClass * 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 (G_OBJECT_CLASS (klass), ARG_BLOCKSIZE, g_param_spec_ulong ("blocksize", "Block size", "Size in bytes to read per buffer", 1, G_MAXULONG, DEFAULT_BLOCKSIZE, G_PARAM_READWRITE)); 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_vorbisfile_get_property; gobject_class->set_property = gst_vorbisfile_set_property; gstelement_class->change_state = gst_vorbisfile_change_state; } static void gst_vorbisfile_init (VorbisFile * vorbisfile) { vorbisfile->sinkpad = gst_pad_new_from_template (gst_vorbisdec_sink_template, "sink"); gst_element_add_pad (GST_ELEMENT (vorbisfile), vorbisfile->sinkpad); gst_pad_set_formats_function (vorbisfile->sinkpad, gst_vorbisfile_get_formats); gst_pad_set_convert_function (vorbisfile->sinkpad, gst_vorbisfile_sink_convert); gst_element_set_loop_function (GST_ELEMENT (vorbisfile), gst_vorbisfile_loop); vorbisfile->srcpad = gst_pad_new_from_template (gst_vorbisdec_src_template, "src"); gst_element_add_pad (GST_ELEMENT (vorbisfile), vorbisfile->srcpad); gst_pad_set_formats_function (vorbisfile->srcpad, gst_vorbisfile_get_formats); gst_pad_set_query_type_function (vorbisfile->srcpad, gst_vorbisfile_get_query_types); gst_pad_set_query_function (vorbisfile->srcpad, gst_vorbisfile_src_query); gst_pad_set_event_mask_function (vorbisfile->srcpad, gst_vorbisfile_get_event_masks); gst_pad_set_event_function (vorbisfile->srcpad, gst_vorbisfile_src_event); gst_pad_set_convert_function (vorbisfile->srcpad, gst_vorbisfile_src_convert); vorbisfile->total_bytes = 0; vorbisfile->offset = 0; vorbisfile->seek_pending = 0; vorbisfile->need_discont = FALSE; vorbisfile->metadata = NULL; vorbisfile->streaminfo = NULL; vorbisfile->current_link = -1; vorbisfile->blocksize = DEFAULT_BLOCKSIZE; GST_FLAG_SET (vorbisfile, GST_ELEMENT_EVENT_AWARE); } /* the next four functions are the ov callbacks we provide to vorbisfile * which interface between GStreamer's handling of the data flow and * vorbis's needs */ static size_t gst_vorbisfile_read (void *ptr, size_t size, size_t nmemb, void *datasource) { guint32 got_bytes; guint8 *data; guint32 read_size = size * nmemb; VorbisFile *vorbisfile = GST_VORBISFILE (datasource); GST_DEBUG ("read %d", read_size); /* make sure we don't go to EOS */ if (!vorbisfile->may_eos && vorbisfile->total_bytes && vorbisfile->offset + read_size > vorbisfile->total_bytes) { read_size = vorbisfile->total_bytes - vorbisfile->offset; } if (read_size == 0 || vorbisfile->eos) return 0; do { got_bytes = gst_bytestream_peek_bytes (vorbisfile->bs, &data, read_size); GST_DEBUG ("peek returned %d", got_bytes); if (got_bytes == 0) { GstEvent *event; guint32 avail; gst_bytestream_get_status (vorbisfile->bs, &avail, &event); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_EOS: GST_DEBUG ("eos"); vorbisfile->eos = TRUE; gst_event_unref (event); goto done; case GST_EVENT_DISCONTINUOUS: GST_DEBUG ("discont"); vorbisfile->need_discont = TRUE; default: GST_DEBUG ("unknown event %d", GST_EVENT_TYPE (event)); break; } gst_event_unref (event); } } while (got_bytes == 0); GST_DEBUG ("read %d got %d bytes", read_size, got_bytes); memcpy (ptr, data, got_bytes); gst_bytestream_flush_fast (vorbisfile->bs, got_bytes); vorbisfile->offset += got_bytes; done: return got_bytes / size; } static int gst_vorbisfile_seek (void *datasource, int64_t offset, int whence) { VorbisFile *vorbisfile = GST_VORBISFILE (datasource); GstSeekType method; guint64 pending_offset = vorbisfile->offset; gboolean need_total = FALSE; if (!vorbisfile->vf.seekable) { return -1; } GST_DEBUG ("seek %" G_GINT64_FORMAT " %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 = vorbisfile->total_bytes - offset; } else return -1; if (!gst_bytestream_seek (vorbisfile->bs, offset, method)) return -1; vorbisfile->eos = FALSE; vorbisfile->offset = pending_offset; if (need_total) vorbisfile->total_bytes = gst_bytestream_tell (vorbisfile->bs) + offset; return 0; } static int gst_vorbisfile_close (void *datasource) { GST_DEBUG ("close"); return 0; } static long gst_vorbisfile_tell (void *datasource) { VorbisFile *vorbisfile = GST_VORBISFILE (datasource); long result; result = gst_bytestream_tell (vorbisfile->bs); GST_DEBUG ("tell %ld", result); return result; } ov_callbacks vorbisfile_ov_callbacks = { gst_vorbisfile_read, gst_vorbisfile_seek, gst_vorbisfile_close, gst_vorbisfile_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_vorbisfile_update_metadata (VorbisFile *vorbisfile, gint link) { OggVorbis_File *vf = &vorbisfile->vf; gchar **ptr; vorbis_comment *vc; GstProps *props = NULL; GstPropsEntry *entry; gchar *name, *value; /* clear old one */ if (vorbisfile->metadata) { gst_caps_unref (vorbisfile->metadata); vorbisfile->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++; } vorbisfile->metadata = gst_caps_new ("vorbisfile_metadata", "application/x-gst-metadata", props); g_object_notify (G_OBJECT (vorbisfile), "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_vorbisfile_update_streaminfo (VorbisFile *vorbisfile, gint link) { OggVorbis_File *vf = &vorbisfile->vf; vorbis_info *vi; GstProps *props = NULL; GstPropsEntry *entry; /* clear old one */ if (vorbisfile->streaminfo) { gst_caps_unref (vorbisfile->streaminfo); vorbisfile->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); vorbisfile->streaminfo = gst_caps_new ("vorbisfile_streaminfo", "application/x-gst-streaminfo", props); g_object_notify (G_OBJECT (vorbisfile), "streaminfo"); return TRUE; } static gboolean gst_vorbisfile_new_link (VorbisFile *vorbisfile, gint link) { vorbis_info *vi = ov_info (&vorbisfile->vf, link); GstCaps *caps; gboolean res = TRUE; /* new logical bitstream */ vorbisfile->current_link = link; gst_vorbisfile_update_metadata (vorbisfile, link); gst_vorbisfile_update_streaminfo (vorbisfile, link); caps = 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)); gst_caps_ref (caps); if (gst_pad_try_set_caps (vorbisfile->srcpad, caps) <= 0) { if (!gst_pad_recover_caps_error (vorbisfile->srcpad, caps)) res = FALSE; } gst_caps_unref (caps); return res; } static void gst_vorbisfile_loop (GstElement *element) { VorbisFile *vorbisfile = GST_VORBISFILE (element); GstBuffer *outbuf; long ret; gint link; /* this function needs to go first since you don't want to be messing * with an unset vf ;) */ if (vorbisfile->restart) { vorbisfile->offset = 0; vorbisfile->total_bytes = 0; vorbisfile->may_eos = FALSE; vorbisfile->vf.seekable = gst_bytestream_seek (vorbisfile->bs, 0, GST_SEEK_METHOD_SET); GST_DEBUG ("vorbisfile: seekable: %s", vorbisfile->vf.seekable ? "yes" : "no"); /* open our custom vorbisfile data object with the callbacks we provide */ if (ov_open_callbacks (vorbisfile, &vorbisfile->vf, NULL, 0, vorbisfile_ov_callbacks) < 0) { gst_element_error (element, "this is not a vorbis file"); return; } vorbisfile->need_discont = TRUE; vorbisfile->restart = FALSE; vorbisfile->current_link = -1; } if (vorbisfile->seek_pending) { /* get time to seek to in seconds */ switch (vorbisfile->seek_format) { case GST_FORMAT_TIME: { gdouble seek_to = (gdouble) vorbisfile->seek_value / GST_SECOND; if (vorbisfile->seek_accurate) { if (ov_time_seek (&vorbisfile->vf, seek_to) == 0) { vorbisfile->need_discont = TRUE; } } else { if (ov_time_seek_page (&vorbisfile->vf, seek_to) == 0) { vorbisfile->need_discont = TRUE; } } break; } case GST_FORMAT_DEFAULT: if (vorbisfile->seek_accurate) { if (ov_pcm_seek (&vorbisfile->vf, vorbisfile->seek_value) == 0) { vorbisfile->need_discont = TRUE; } } else { if (ov_pcm_seek_page (&vorbisfile->vf, vorbisfile->seek_value) == 0) { vorbisfile->need_discont = TRUE; } } break; default: if (vorbisfile->seek_format == logical_stream_format) { gint64 seek_to; seek_to = vorbisfile->vf.offsets[vorbisfile->seek_value]; if (ov_raw_seek (&vorbisfile->vf, seek_to) == 0) { vorbisfile->need_discont = TRUE; vorbisfile->current_link = -1; } else { g_warning ("raw seek failed"); } } else g_warning ("unknown seek method, implement me !"); break; } vorbisfile->seek_pending = FALSE; } /* we update the caps for each logical stream */ if (vorbisfile->vf.current_link != vorbisfile->current_link) { if (!gst_vorbisfile_new_link (vorbisfile, vorbisfile->vf.current_link)) { return; } return; } outbuf = gst_buffer_new_and_alloc (vorbisfile->blocksize); ret = ov_read (&vorbisfile->vf, GST_BUFFER_DATA (outbuf), GST_BUFFER_SIZE (outbuf), (G_BYTE_ORDER == G_LITTLE_ENDIAN ? 0 : 1), sizeof (gint16), 1, &link); if (ret == 0) { GST_DEBUG ("eos"); /* send EOS event */ /*ov_clear (&vorbisfile->vf);*/ vorbisfile->restart = TRUE; gst_buffer_unref (outbuf); /* if the pad is not usable, don't push it out */ if (GST_PAD_IS_USABLE (vorbisfile->srcpad)) { gst_pad_push (vorbisfile->srcpad, GST_DATA (gst_event_new (GST_EVENT_EOS))); } gst_element_set_eos (element); return; } else if (ret < 0) { g_warning ("vorbisfile: decoding error"); gst_buffer_unref (outbuf); return; } else { GstClockTime time; gint64 samples; /* get stream stats */ samples = (gint64) (ov_pcm_tell (&vorbisfile->vf)); time = (GstClockTime) (ov_time_tell (&vorbisfile->vf) * GST_SECOND); if (vorbisfile->need_discont) { GstEvent *discont; vorbisfile->need_discont = FALSE; /* if the pad is not usable, don't push it out */ if (GST_PAD_IS_USABLE (vorbisfile->srcpad)) { discont = gst_event_new_discontinuous (FALSE, GST_FORMAT_TIME, time, GST_FORMAT_DEFAULT, samples, NULL); gst_pad_push (vorbisfile->srcpad, GST_DATA (discont)); } } GST_BUFFER_SIZE (outbuf) = ret; GST_BUFFER_TIMESTAMP (outbuf) = time; GST_BUFFER_OFFSET (outbuf) = samples; { GstClockTime duration; GstFormat format; format = GST_FORMAT_TIME; gst_pad_convert (vorbisfile->srcpad, GST_FORMAT_BYTES, ret, &format, &duration); GST_BUFFER_DURATION (outbuf) = duration; } vorbisfile->may_eos = TRUE; if (!vorbisfile->vf.seekable) { vorbisfile->total_bytes += ret; } if (GST_PAD_IS_USABLE (vorbisfile->srcpad)) gst_pad_push (vorbisfile->srcpad, GST_DATA (outbuf)); else gst_buffer_unref (outbuf); } } static const GstFormat* gst_vorbisfile_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_vorbisfile_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; VorbisFile *vorbisfile; vorbis_info *vi; vorbisfile = GST_VORBISFILE (gst_pad_get_parent (pad)); vi = ov_info (&vorbisfile->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 > vorbisfile->vf.links) { src_value = vorbisfile->vf.links; } for (i = 0; i < src_value; i++) { vi = ov_info (&vorbisfile->vf, i); count += ov_pcm_total (&vorbisfile->vf, i); } *dest_value = count; break; case GST_FORMAT_TIME: { if (src_value > vorbisfile->vf.links) { src_value = vorbisfile->vf.links; } for (i = 0; i < src_value; i++) { vi = ov_info (&vorbisfile->vf, i); if (vi->rate) count += ov_pcm_total (&vorbisfile->vf, i) * GST_SECOND / vi->rate; else count += ov_time_total (&vorbisfile->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_vorbisfile_sink_convert (GstPad *pad, GstFormat src_format, gint64 src_value, GstFormat *dest_format, gint64 *dest_value) { gboolean res = TRUE; VorbisFile *vorbisfile; vorbisfile = GST_VORBISFILE (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_vorbisfile_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_vorbisfile_src_query (GstPad *pad, GstQueryType type, GstFormat *format, gint64 *value) { gboolean res = TRUE; VorbisFile *vorbisfile; vorbis_info *vi; vorbisfile = GST_VORBISFILE (gst_pad_get_parent (pad)); vi = ov_info (&vorbisfile->vf, -1); switch (type) { case GST_QUERY_TOTAL: { switch (*format) { case GST_FORMAT_DEFAULT: if (vorbisfile->vf.seekable) *value = ov_pcm_total (&vorbisfile->vf, -1); else return FALSE; break; case GST_FORMAT_BYTES: if (vorbisfile->vf.seekable) *value = ov_pcm_total (&vorbisfile->vf, -1) * vi->channels * 2; else return FALSE; break; case GST_FORMAT_TIME: if (vorbisfile->vf.seekable) *value = (gint64) (ov_time_total (&vorbisfile->vf, -1) * GST_SECOND); else return FALSE; break; default: if (*format == logical_stream_format) { if (vorbisfile->vf.seekable) *value = vorbisfile->vf.links; else return FALSE; } else res = FALSE; break; } break; } case GST_QUERY_POSITION: switch (*format) { case GST_FORMAT_TIME: if (vorbisfile->vf.seekable) *value = (gint64) (ov_time_tell (&vorbisfile->vf) * GST_SECOND); else *value = vorbisfile->total_bytes * GST_SECOND / (vi->rate * vi->channels * 2); break; case GST_FORMAT_BYTES: if (vorbisfile->vf.seekable) *value = ov_pcm_tell (&vorbisfile->vf) * vi->channels * 2; else *value = vorbisfile->total_bytes; break; case GST_FORMAT_DEFAULT: if (vorbisfile->vf.seekable) *value = ov_pcm_tell (&vorbisfile->vf); else *value = vorbisfile->total_bytes / (vi->channels * 2); break; default: if (*format == logical_stream_format) { if (vorbisfile->vf.seekable) *value = vorbisfile->current_link; else return FALSE; } else res = FALSE; break; } break; default: res = FALSE; break; } return res; } static const GstEventMask* gst_vorbisfile_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_vorbisfile_src_event (GstPad *pad, GstEvent *event) { gboolean res = TRUE; VorbisFile *vorbisfile; vorbisfile = GST_VORBISFILE (gst_pad_get_parent (pad)); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK: { gint64 offset; vorbis_info *vi; GstFormat format; GST_DEBUG ("vorbisfile: handling seek event on pad %s:%s", GST_DEBUG_PAD_NAME (pad)); if (!vorbisfile->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: vorbisfile->seek_pending = TRUE; vorbisfile->seek_value = offset; vorbisfile->seek_format = format; vorbisfile->seek_accurate = GST_EVENT_SEEK_FLAGS (event) & GST_SEEK_FLAG_ACCURATE; break; case GST_FORMAT_BYTES: vi = ov_info (&vorbisfile->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: vorbisfile->seek_pending = TRUE; vorbisfile->seek_value = offset; vorbisfile->seek_format = format; vorbisfile->seek_accurate = GST_EVENT_SEEK_FLAGS (event) & GST_SEEK_FLAG_ACCURATE; break; default: if (format == logical_stream_format) { vorbisfile->seek_pending = TRUE; vorbisfile->seek_value = offset; vorbisfile->seek_format = format; vorbisfile->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_vorbisfile_change_state (GstElement *element) { VorbisFile *vorbisfile = GST_VORBISFILE (element); switch (GST_STATE_TRANSITION (element)) { case GST_STATE_NULL_TO_READY: case GST_STATE_READY_TO_PAUSED: vorbisfile->restart = TRUE; vorbisfile->bs = gst_bytestream_new (vorbisfile->sinkpad); break; case GST_STATE_PAUSED_TO_PLAYING: vorbisfile->eos = FALSE; case GST_STATE_PLAYING_TO_PAUSED: break; case GST_STATE_PAUSED_TO_READY: ov_clear (&vorbisfile->vf); gst_bytestream_destroy (vorbisfile->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_vorbisfile_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { VorbisFile *vorbisfile; g_return_if_fail (GST_IS_VORBISFILE (object)); vorbisfile = GST_VORBISFILE (object); switch (prop_id) { case ARG_BLOCKSIZE: vorbisfile->blocksize = g_value_get_ulong (value); break; default: g_warning ("Unknown property id\n"); } } static void gst_vorbisfile_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { VorbisFile *vorbisfile; g_return_if_fail (GST_IS_VORBISFILE (object)); vorbisfile = GST_VORBISFILE (object); switch (prop_id) { case ARG_BLOCKSIZE: g_value_set_ulong (value, vorbisfile->blocksize); break; case ARG_METADATA: g_value_set_boxed (value, vorbisfile->metadata); break; case ARG_STREAMINFO: g_value_set_boxed (value, vorbisfile->streaminfo); break; default: g_warning ("Unknown property id\n"); } }