diff --git a/ChangeLog b/ChangeLog index 2e6ca9e3d7..fc07ce8bbc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,24 @@ +2004-01-29 Benjamin Otte + + * ext/ogg/gstoggdemux.c: + lots of changes - mainly support for chained bitstreams, seeking, + querying and bugfixes of course + * ext/vorbis/Makefile.am: + * ext/vorbis/vorbisdec.c: + * ext/vorbis/vorbisdec.h: + add vorbisdec raw vorbis decoder + * ext/vorbis/vorbis.c: (plugin_init): + register vorbisdec as PRIMARY, vorbisfile as SECONDARY + * gst/intfloat/Makefile.am: + * gst/intfloat/float22int.c: + * gst/intfloat/float22int.h: + * gst/intfloat/gstintfloatconvert.c: (plugin_init): + add float2intnew plugin. It converts multichannel interleaved float to + multichannel interleaved int. The name should probably be changed. + * gst/typefind/gsttypefindfunctions.c: (theora_type_find), + (plugin_init): + add typefinding for raw theora video so oggdemux can detect it. + 2004-01-28 Julien MOUTTE * gst-libs/gst/play/gstplay.c: (gst_play_seek_to_time): seek on video diff --git a/ext/ogg/gstoggdemux.c b/ext/ogg/gstoggdemux.c index dfe6bed1ad..b9f38cab15 100644 --- a/ext/ogg/gstoggdemux.c +++ b/ext/ogg/gstoggdemux.c @@ -24,7 +24,8 @@ #endif #include #include -/* memcpy - if someone knows a way to get rid of it, please speak up */ +/* memcpy - if someone knows a way to get rid of it, please speak up + * note: the ogg docs even say you ned this... */ #include GST_DEBUG_CATEGORY_STATIC (gst_ogg_demux_debug); @@ -39,23 +40,61 @@ GST_DEBUG_CATEGORY_STATIC (gst_ogg_demux_debug); typedef struct _GstOggDemux GstOggDemux; typedef struct _GstOggDemuxClass GstOggDemuxClass; +typedef enum { + /* just because you shouldn't make a valid enum value 0 */ + GST_OGG_STATE_INAVLID, + /* just started, we need to decide if we should do setup */ + GST_OGG_STATE_START, + /* setup is analyzing the stream, getting lengths and so on */ + GST_OGG_STATE_SETUP, + /* after a seek, during resyncing */ + GST_OGG_STATE_SEEK, + /* normal playback */ + GST_OGG_STATE_PLAY +} GstOggState; + +/* all information needed for one ogg stream */ typedef struct { GstPad * pad; /* reference for this pad is held by element we belong to */ - int serial; + gint serial; ogg_stream_state stream; guint64 offset; /* end offset of last buffer */ + guint64 known_offset; /* last known offset */ + guint64 length; /* length of stream or 0 */ + glong pages; } GstOggPad; +/* all information needed for one ogg chain (relevant for chained bitstreams) */ +typedef struct { + GSList * pads; /* list of GstOggPad */ +} GstOggChain; +#define CURRENT_CHAIN(ogg) (&g_array_index ((ogg)->chains, GstOggChain, (ogg)->current_chain)) + +typedef enum { + GST_OGG_FLAG_BOS = GST_ELEMENT_FLAG_LAST, + GST_OGG_FLAG_EOS, + GST_OGG_FLAG_WAIT_FOR_DISCONT +} GstOggFlag; + struct _GstOggDemux { GstElement element; - /* pads */ + /* pad */ GstPad * sinkpad; - GSList * srcpads; /* list of GstOggPad */ - + + /* state */ + GstOggState state; + GArray * chains; + gint current_chain; + guint flags; + /* ogg stuff */ ogg_sync_state sync; + + /* seeking */ + GstOggPad * seek_pad; + guint64 seek_to; }; struct _GstOggDemuxClass { @@ -99,12 +138,7 @@ GST_STATIC_PAD_TEMPLATE ( ); -static void gst_ogg_demux_base_init (gpointer g_class); -static void gst_ogg_demux_class_init (gpointer g_class, - gpointer class_data); -static void gst_ogg_demux_init (GTypeInstance * instance, - gpointer g_class); -static void gst_ogg_demux_dispose (GObject * object); +static void gst_ogg_demux_finalize (GObject * object); static gboolean gst_ogg_demux_src_event (GstPad * pad, GstEvent * event); @@ -134,32 +168,14 @@ static void gst_ogg_pad_push (GstOggDemux * ogg, static GstCaps * gst_ogg_type_find (ogg_packet * packet); +static void gst_ogg_print (GstOggDemux * demux); + +#define GST_OGG_SET_STATE(ogg, new_state) G_STMT_START{ \ + GST_DEBUG_OBJECT (ogg, "setting state to %s", G_STRINGIFY (new_state)); \ + ogg->state = new_state; \ +}G_STMT_END -static GstElementClass *parent_class = NULL; -/* static guint gst_ogg_demux_signals[LAST_SIGNAL] = { 0 }; */ - -GType -gst_ogg_demux_get_type (void) -{ - static GType ogg_demux_type = 0; - - if (!ogg_demux_type) { - static const GTypeInfo ogg_demux_info = { - sizeof (GstOggDemuxClass), - gst_ogg_demux_base_init, - NULL, - gst_ogg_demux_class_init, - NULL, - NULL, - sizeof (GstOggDemux), - 0, - gst_ogg_demux_init, - }; - - ogg_demux_type = g_type_register_static(GST_TYPE_ELEMENT, "GstOggDemux", &ogg_demux_info, 0); - } - return ogg_demux_type; -} +GST_BOILERPLATE (GstOggDemux, gst_ogg_demux, GstElement, GST_TYPE_ELEMENT) static void gst_ogg_demux_base_init (gpointer g_class) @@ -174,22 +190,18 @@ gst_ogg_demux_base_init (gpointer g_class) gst_static_pad_template_get (&ogg_demux_src_template_factory)); } static void -gst_ogg_demux_class_init (gpointer g_class, gpointer class_data) +gst_ogg_demux_class_init (GstOggDemuxClass *klass) { - GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class); - GObjectClass *gobject_class = G_OBJECT_CLASS (g_class); + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - parent_class = g_type_class_ref(GST_TYPE_ELEMENT); - gstelement_class->change_state = gst_ogg_demux_change_state; - gobject_class->dispose = gst_ogg_demux_dispose; + gobject_class->finalize = gst_ogg_demux_finalize; } static void -gst_ogg_demux_init (GTypeInstance *instance, gpointer g_class) +gst_ogg_demux_init (GstOggDemux *ogg) { - GstOggDemux *ogg = GST_OGG_DEMUX (instance); - /* create the sink pad */ ogg->sinkpad = gst_pad_new_from_template( gst_static_pad_template_get (&ogg_demux_sink_template_factory), "sink"); @@ -197,19 +209,22 @@ gst_ogg_demux_init (GTypeInstance *instance, gpointer g_class) gst_pad_set_chain_function (ogg->sinkpad, GST_DEBUG_FUNCPTR (gst_ogg_demux_chain)); /* initalize variables */ - ogg->srcpads = NULL; + GST_OGG_SET_STATE (ogg, GST_OGG_STATE_START); + ogg->chains = g_array_new (TRUE, TRUE, sizeof (GstOggChain)); GST_FLAG_SET (ogg, GST_ELEMENT_EVENT_AWARE); } static void -gst_ogg_demux_dispose (GObject *object) +gst_ogg_demux_finalize (GObject *object) { GstOggDemux *ogg; ogg = GST_OGG_DEMUX (object); - /* srcpads are removed when going to READY */ - g_assert (ogg->srcpads == NULL); + /* chains are removed when going to READY */ + g_assert (ogg->current_chain == -1); + g_assert (ogg->chains->len == 0); + g_array_free (ogg->chains, TRUE); } static const GstEventMask* @@ -233,20 +248,41 @@ gst_ogg_demux_get_query_types (GstPad *pad) return gst_ogg_demux_src_query_types; } +static GstOggPad * +gst_ogg_get_pad_by_pad (GstOggDemux *ogg, GstPad *pad) +{ + GSList *walk; + GstOggPad *cur; + + for (walk = CURRENT_CHAIN (ogg)->pads; walk; walk = g_slist_next (walk)) { + cur = (GstOggPad *) walk->data; + if (cur->pad == pad) + return cur; + } + return NULL; +} + static gboolean gst_ogg_demux_src_query (GstPad *pad, GstQueryType type, GstFormat *format, gint64 *value) { gboolean res = FALSE; - GstOggDemux *ogg; - - ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad)); + GstOggDemux *ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad)); + GstOggPad *cur = gst_ogg_get_pad_by_pad (ogg, pad); switch (type) { case GST_QUERY_TOTAL: { + if (*format == GST_FORMAT_DEFAULT) { + *value = cur->known_offset; + res = TRUE; + } break; } case GST_QUERY_POSITION: + if (*format == GST_FORMAT_DEFAULT && cur->length != 0) { + *value = cur->length; + res = TRUE; + } break; default: break; @@ -254,39 +290,114 @@ gst_ogg_demux_src_query (GstPad *pad, GstQueryType type, return res; } +/* The current seeking implementation is the most simple I could come up with: + * - when seeking forwards, just discard data until desired position is reached + * - when seeking backwards, seek to beginning and seek forward from there + * Anyone is free to improve this algorithm as it is quite stupid and probably + * really slow. + */ static gboolean gst_ogg_demux_src_event (GstPad *pad, GstEvent *event) { GstOggDemux *ogg; + GstOggPad *cur; ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad)); - + cur = gst_ogg_get_pad_by_pad (ogg, pad); + /* FIXME: optimize this so events from inactive chains work? + * in theory there shouldn't be an exisiting pad for inactive chains */ + if (cur == NULL) return FALSE; + switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK: - break; + { + gint64 offset; + if (GST_EVENT_SEEK_FORMAT (event) != GST_FORMAT_DEFAULT) + break; + offset = GST_EVENT_SEEK_OFFSET (event); + switch (GST_EVENT_SEEK_METHOD (event)) { + case GST_SEEK_METHOD_END: + if (cur->length == 0 || offset > 0) + goto out; + offset = cur->length + offset; + break; + case GST_SEEK_METHOD_CUR: + offset += cur->known_offset; + break; + case GST_SEEK_METHOD_SET: + break; + default: + g_warning ("invalid seek method in seek event"); + break; + } + if (offset < cur->known_offset) { + GstEvent *restart = gst_event_new_seek (GST_FORMAT_BYTES | GST_SEEK_METHOD_SET, 0); + if (!gst_pad_send_event (GST_PAD_PEER (ogg->sinkpad), restart)) + break; + } + GST_OGG_SET_STATE (ogg, GST_OGG_STATE_SEEK); + ogg->seek_pad = cur; + ogg->seek_to = offset; + gst_event_unref (event); + return TRUE; + } default: break; } +out: gst_event_unref (event); return FALSE; } + +static void +gst_ogg_start_playing (GstOggDemux *ogg) +{ + GST_DEBUG_OBJECT (ogg, "got EOS in setup, changing to playback now"); + if (!gst_pad_send_event (GST_PAD_PEER (ogg->sinkpad), + gst_event_new_seek (GST_FORMAT_BYTES | GST_SEEK_METHOD_SET, 0))) { + gst_element_error (ogg, CORE, SEEK, (NULL), + ("cannot seek to start after EOS")); + } + ogg->current_chain = 0; + GST_FLAG_UNSET (ogg, GST_OGG_FLAG_EOS); + GST_FLAG_SET (ogg, GST_OGG_FLAG_WAIT_FOR_DISCONT); + GST_OGG_SET_STATE (ogg, GST_OGG_STATE_PLAY); + gst_ogg_print (ogg); +} + static void gst_ogg_demux_handle_event (GstPad *pad, GstEvent *event) { GstOggDemux *ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad)); - ogg=ogg; switch (GST_EVENT_TYPE (event)) { case GST_EVENT_DISCONTINUOUS: + GST_DEBUG_OBJECT (ogg, "got a discont event"); + ogg_sync_reset (&ogg->sync); + gst_event_unref (event); + GST_FLAG_UNSET (ogg, GST_OGG_FLAG_WAIT_FOR_DISCONT); break; case GST_EVENT_EOS: - if (ogg->srcpads) - GST_WARNING_OBJECT (ogg, "got EOS in unfinished ogg stream"); - while (ogg->srcpads) { - gst_ogg_pad_remove (ogg, (GstOggPad *) ogg->srcpads->data); + if (ogg->state == GST_OGG_STATE_SETUP) { + gst_ogg_start_playing (ogg); + } else { + guint i; + GSList *walk; + GST_DEBUG_OBJECT (ogg, "got EOS"); + ogg->current_chain = -1; + for (i = 0; i < ogg->chains->len; i++) { + GstOggChain *chain = &g_array_index (ogg->chains, GstOggChain, i); + for (walk = chain->pads; walk; walk = g_slist_next (walk)) { + GstOggPad *pad = (GstOggPad *) walk->data; + gst_event_ref (event); + if (GST_PAD_IS_USABLE (pad->pad)) + gst_pad_push (pad->pad, GST_DATA (event)); + } + } + gst_element_set_eos (GST_ELEMENT (ogg)); } - gst_element_set_eos (GST_ELEMENT (ogg)); + gst_event_unref (event); break; default: gst_pad_event_default (pad, event); @@ -294,12 +405,39 @@ gst_ogg_demux_handle_event (GstPad *pad, GstEvent *event) } return; } + +/* get the pad with the given serial in the current stream or NULL if none */ +static GstOggPad * +gst_ogg_pad_get_in_current_chain (GstOggDemux *ogg, int serial) +{ + GSList *walk; + + if (ogg->current_chain == -1) return NULL; + g_return_val_if_fail (ogg->current_chain < ogg->chains->len, NULL); + + for (walk = CURRENT_CHAIN (ogg)->pads; walk; walk = g_slist_next (walk)) { + GstOggPad *pad = (GstOggPad *) walk->data; + if (pad->serial == serial) + return pad; + } + return NULL; +} + +static void +gst_ogg_add_chain (GstOggDemux *ogg) +{ + GST_LOG_OBJECT (ogg, "adding chain %u", ogg->chains->len); + ogg->current_chain = ogg->chains->len; + g_array_set_size (ogg->chains, ogg->chains->len + 1); +} + static void gst_ogg_demux_chain (GstPad *pad, GstData *buffer) { GstOggDemux *ogg; guint8 *data; int pageout_ret = 1; + guint64 offset_end; /* handle events */ if (GST_IS_EVENT (buffer)) { @@ -309,13 +447,23 @@ gst_ogg_demux_chain (GstPad *pad, GstData *buffer) ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad)); + if (GST_FLAG_IS_SET (ogg, GST_OGG_FLAG_WAIT_FOR_DISCONT)) { + GST_LOG_OBJECT (ogg, "waiting for discont event, discarding buffer"); + gst_data_unref (buffer); + return; + } + + GST_LOG_OBJECT (ogg, "queueing buffer %p with offset %llu", buffer, GST_BUFFER_OFFSET (buffer)); data = (guint8 *) ogg_sync_buffer(&ogg->sync, GST_BUFFER_SIZE (buffer)); memcpy (data, GST_BUFFER_DATA (buffer), GST_BUFFER_SIZE (buffer)); if (ogg_sync_wrote (&ogg->sync, GST_BUFFER_SIZE (buffer)) != 0) { gst_data_unref (buffer); - gst_element_error (ogg, LIBRARY, TOO_LAZY, NULL, ("ogg_sync_wrote failed")); + gst_element_error (ogg, LIBRARY, FAILED /* MEMORY */, NULL, ("ogg_sync_wrote failed")); return; } + offset_end = GST_BUFFER_OFFSET_IS_VALID (buffer) ? + GST_BUFFER_OFFSET (buffer) + GST_BUFFER_SIZE (buffer) : + (guint64) -1; gst_data_unref (buffer); while (pageout_ret != 0) { ogg_page page; @@ -323,12 +471,76 @@ gst_ogg_demux_chain (GstPad *pad, GstData *buffer) pageout_ret = ogg_sync_pageout (&ogg->sync, &page); switch (pageout_ret) { case -1: - /* FIXME: need some kind of discont here, we don't know any values */ + /* FIXME: need some kind of discont here, we don't know any values to send though, + * we only have the END_OFFSET */ break; case 0: break; case 1: - gst_ogg_demux_push (ogg, &page); + GST_LOG_OBJECT (ogg, "processing ogg page (serial %d, packet %ld, granule pos %llu", + ogg_page_serialno (&page), ogg_page_pageno (&page), ogg_page_granulepos (&page)); + switch (ogg->state) { + case GST_OGG_STATE_SETUP: + if (ogg_page_eos (&page)) { + guint64 length; + GstFormat format = GST_FORMAT_BYTES; + GstOggPad *cur = gst_ogg_pad_get_in_current_chain (ogg, ogg_page_serialno (&page)); + GST_FLAG_SET (ogg, GST_OGG_FLAG_EOS); + if (!cur) { + GST_ERROR_OBJECT (ogg, "unknown serial %d", ogg_page_serialno (&page)); + } else { + cur->pages = ogg_page_pageno (&page); + cur->length = ogg_page_granulepos (&page); + } + if (!gst_pad_query (GST_PAD_PEER (ogg->sinkpad), GST_QUERY_TOTAL, &format, &length)) + length = 0; + if (length >= offset_end) { + gst_ogg_start_playing (ogg); + goto out; + } + } else { + if (GST_FLAG_IS_SET (ogg, GST_OGG_FLAG_EOS) && ogg_page_bos (&page)) { + gst_ogg_add_chain (ogg); + } + GST_FLAG_UNSET (ogg, GST_OGG_FLAG_EOS); + } + if (ogg_page_bos (&page)) { + if (gst_ogg_pad_get_in_current_chain (ogg, ogg_page_serialno (&page))) { + GST_ERROR_OBJECT (ogg, "multiple BOS page for serial %d (page %ld)", + ogg_page_serialno (&page), ogg_page_pageno (&page)); + } else { + GstOggPad *pad = gst_ogg_pad_new (ogg, ogg_page_serialno (&page)); + CURRENT_CHAIN (ogg)->pads = g_slist_prepend (CURRENT_CHAIN (ogg)->pads, pad); + } + GST_FLAG_SET (ogg, GST_OGG_FLAG_BOS); + } else { + GST_FLAG_UNSET (ogg, GST_OGG_FLAG_BOS); + } + break; + case GST_OGG_STATE_START: + if (gst_pad_send_event (GST_PAD_PEER (ogg->sinkpad), + gst_event_new_seek (GST_FORMAT_BYTES | GST_SEEK_METHOD_END, 0))) { + GST_OGG_SET_STATE (ogg, GST_OGG_STATE_SETUP); + GST_DEBUG_OBJECT (ogg, "stream can seek, try setup now"); + if (!gst_pad_send_event (GST_PAD_PEER (ogg->sinkpad), + gst_event_new_seek (GST_FORMAT_BYTES | GST_SEEK_METHOD_SET, 0))) { + gst_element_error (ogg, CORE, SEEK, NULL, + ("stream can seek to end, but not to start. Can't handle that.")); + } + gst_ogg_add_chain (ogg); + GST_FLAG_SET (ogg, GST_OGG_FLAG_WAIT_FOR_DISCONT); + goto out; + } + GST_OGG_SET_STATE (ogg, GST_OGG_STATE_PLAY); + /* fall through */ + case GST_OGG_STATE_SEEK: + case GST_OGG_STATE_PLAY: + gst_ogg_demux_push (ogg, &page); + break; + default: + g_assert_not_reached (); + break; + } break; default: GST_WARNING_OBJECT (ogg, "unknown return value %d from ogg_sync_pageout", pageout_ret); @@ -336,12 +548,13 @@ gst_ogg_demux_chain (GstPad *pad, GstData *buffer) break; } } +out: return; } static GstOggPad * gst_ogg_pad_new (GstOggDemux *ogg, int serial) { - GstOggPad *ret = g_new (GstOggPad, 1); + GstOggPad *ret = g_new0 (GstOggPad, 1); ret->serial = serial; if (ogg_stream_init (&ret->stream, serial) != 0) { @@ -349,8 +562,6 @@ gst_ogg_pad_new (GstOggDemux *ogg, int serial) g_free (ret); return NULL; } - ret->pad = NULL; - ret->offset = 0; GST_LOG_OBJECT (ogg, "created new ogg src %p for stream with serial %d", ret, serial); return ret; @@ -358,9 +569,13 @@ gst_ogg_pad_new (GstOggDemux *ogg, int serial) static void gst_ogg_pad_remove (GstOggDemux *ogg, GstOggPad *pad) { - ogg->srcpads = g_slist_remove (ogg->srcpads, pad); if (pad->pad) { - gst_pad_push (pad->pad, GST_DATA (gst_event_new (GST_EVENT_EOS))); + /* FIXME: + * we do it in the EOS signal already - EOS handling needs to be better thought out. + * Correct way would be pushing EOS on eos page, but scheduler doesn't like that + if (GST_PAD_IS_USEABLE (pad->pad)) + gst_pad_push (pad->pad, GST_DATA (gst_event_new (GST_EVENT_EOS))); + */ gst_element_remove_pad (GST_ELEMENT (ogg), pad->pad); } if (ogg_stream_clear (&pad->stream) != 0) @@ -375,32 +590,37 @@ gst_ogg_demux_push (GstOggDemux *ogg, ogg_page* page) GstOggPad *cur; /* find the stream */ - walk = ogg->srcpads; - while (walk) { + for (walk = CURRENT_CHAIN (ogg)->pads; walk; walk = g_slist_next (walk)) { cur = (GstOggPad *) walk->data; if (cur->serial == ogg_page_serialno (page)) { goto br; } - walk = g_slist_next (walk); } cur = NULL; br: /* now we either have a stream (cur) or not */ if (ogg_page_bos (page)) { if (cur) { - /* huh? */ - GST_WARNING_OBJECT (ogg, "Ignoring error: ogg page declared as BOS while stream already existed."); + GST_DEBUG_OBJECT (ogg, "ogg page declared as BOS while stream %d already existed." + "Possibly a seek happened.", cur->serial); + } else if (cur) { + GST_DEBUG_OBJECT (ogg, "reactivating deactivated stream %d.", cur->serial); } else { /* FIXME: monitor if we are still in creation stage? */ cur = gst_ogg_pad_new (ogg, ogg_page_serialno (page)); if (!cur) { - gst_element_error (ogg, LIBRARY, TOO_LAZY, NULL, ("Creating ogg_stream struct failed.")); + gst_element_error (ogg, LIBRARY, FAILED, NULL, ("Creating ogg_stream struct failed.")); + return; } - ogg->srcpads = g_slist_prepend (ogg->srcpads, cur); + if (ogg->current_chain == -1) { + /* add new one at the end */ + gst_ogg_add_chain (ogg); + } + CURRENT_CHAIN (ogg)->pads = g_slist_prepend (CURRENT_CHAIN (ogg)->pads, cur); } } if (cur == NULL) { - gst_element_error (ogg, LIBRARY, TOO_LAZY, NULL, ("invalid ogg stream serial no")); + gst_element_error (ogg, STREAM, DECODE, NULL, ("invalid ogg stream serial no")); return; } if (ogg_stream_pagein (&cur->stream, page) != 0) { @@ -408,14 +628,47 @@ br: gst_ogg_pad_reset (ogg, cur); return; } - gst_ogg_pad_push (ogg, cur); + switch (ogg->state) { + case GST_OGG_STATE_SEEK: + GST_LOG_OBJECT (ogg, "in seek - offset now: %"G_GUINT64_FORMAT" (pad %d) - desired offset %" + G_GUINT64_FORMAT" (pad %d)", cur->known_offset, cur->serial, + ogg->seek_to, ogg->seek_pad->serial); + if (cur == ogg->seek_pad) { + if (ogg_page_granulepos (page) > ogg->seek_to) { + GST_OGG_SET_STATE (ogg, GST_OGG_STATE_PLAY); + ogg->seek_pad = NULL; + ogg->seek_to = 0; + /* send discont everywhere */ + for (walk = CURRENT_CHAIN (ogg)->pads; walk; walk = g_slist_next (walk)) { + GstOggPad *send = (GstOggPad *) walk->data; + GstEvent *event = gst_event_new_discontinuous (FALSE, + GST_FORMAT_DEFAULT, send->known_offset); + if (GST_PAD_IS_USABLE (send->pad)) + gst_pad_push (send->pad, GST_DATA (event)); + } + } + } + /* fallthrough */ + case GST_OGG_STATE_PLAY: + cur->known_offset = ogg_page_granulepos (page); + gst_ogg_pad_push (ogg, cur); + break; + default: + g_assert_not_reached (); + break; + } + if (ogg_page_eos (page)) { + GST_DEBUG_OBJECT (ogg, "got EOS for stream with serial %d, sending EOS now", + cur->serial); #if 0 /* Removing pads while PLAYING doesn't work with current schedulers */ /* remove from list, as this will never be called again */ - if (ogg_page_eos (page)) { gst_ogg_pad_remove (ogg, cur); - } + /* this is also not possible because sending EOS this way confuses the scheduler */ + gst_pad_push (cur->pad, GST_DATA (gst_event_new (GST_EVENT_EOS))); +#else #endif + } } static void gst_ogg_pad_push (GstOggDemux *ogg, GstOggPad *pad) @@ -433,13 +686,16 @@ gst_ogg_pad_push (GstOggDemux *ogg, GstOggPad *pad) gst_ogg_pad_reset (ogg, pad); break; case 1: { + /* only push data when plazing, not during seek or similar */ + if (ogg->state != GST_OGG_STATE_PLAY) + continue; if (!pad->pad) { GstCaps *caps = gst_ogg_type_find (&packet); - gchar *name = g_strdup_printf ("serial %d", pad->serial); + gchar *name = g_strdup_printf ("serial_%d", pad->serial); if (caps == NULL) { - gst_element_error (ogg, STREAM, TYPE_NOT_FOUND, NULL, NULL); - return; + GST_WARNING_OBJECT (ogg, "couldn't find caps for stream with serial %d", pad->serial); + caps = gst_caps_new_simple ("application/octet-stream", NULL); } pad->pad = gst_pad_new_from_template ( gst_static_pad_template_get (&ogg_demux_src_template_factory), @@ -454,12 +710,16 @@ gst_ogg_pad_push (GstOggDemux *ogg, GstOggPad *pad) gst_pad_set_active (pad->pad, TRUE); gst_element_add_pad (GST_ELEMENT (ogg), pad->pad); } - /* optimization: use a bufferpool containing the ogg packet */ - buf = gst_buffer_new_and_alloc (packet.bytes); + /* optimization: use a bufferpool containing the ogg packet? */ + buf = gst_pad_alloc_buffer (pad->pad, GST_BUFFER_OFFSET_NONE, packet.bytes); memcpy (buf->data, packet.packet, packet.bytes); - GST_BUFFER_OFFSET (buf) = pad->offset; - pad->offset = GST_BUFFER_OFFSET_END (buf) = packet.granulepos; - gst_pad_push (pad->pad, GST_DATA (buf)); + if (pad->offset != -1) + GST_BUFFER_OFFSET (buf) = pad->offset; + if (packet.granulepos != -1) + GST_BUFFER_OFFSET_END (buf) = packet.granulepos; + pad->offset = packet.granulepos; + if (GST_PAD_IS_USABLE (pad->pad)) + gst_pad_push (pad->pad, GST_DATA (buf)); break; } default: @@ -476,6 +736,24 @@ gst_ogg_pad_reset (GstOggDemux *ogg, GstOggPad *pad) pad->offset = GST_BUFFER_OFFSET_NONE; /* FIXME: need a discont here */ } + +static void +gst_ogg_chains_clear (GstOggDemux *ogg) +{ + gint i; + GSList *walk; + + for (i = ogg->chains->len - 1; i >= 0; i--) { + GstOggChain *cur = &g_array_index (ogg->chains, GstOggChain, i); + for (walk = cur->pads; walk; walk = g_slist_next (walk)) { + gst_ogg_pad_remove (ogg, (GstOggPad *) walk->data); + } + g_slist_free (cur->pads); + g_array_remove_index (ogg->chains, i); + } + ogg->current_chain = -1; +} + static GstElementStateReturn gst_ogg_demux_change_state (GstElement *element) { @@ -495,9 +773,10 @@ gst_ogg_demux_change_state (GstElement *element) case GST_STATE_PLAYING_TO_PAUSED: break; case GST_STATE_PAUSED_TO_READY: - while (ogg->srcpads) { - gst_ogg_pad_remove (ogg, (GstOggPad *) ogg->srcpads->data); - } + gst_ogg_chains_clear (ogg); + GST_OGG_SET_STATE (ogg, GST_OGG_STATE_START); + ogg->seek_pad = NULL; + ogg->seek_to = 0; break; case GST_STATE_READY_TO_NULL: ogg_sync_clear (&ogg->sync); @@ -590,3 +869,24 @@ GST_PLUGIN_DEFINE ( GST_PACKAGE, GST_ORIGIN ) + +/* prints all info about the element */ +static void +gst_ogg_print (GstOggDemux *ogg) +{ +#if 0 + guint i; + GSList *walk; + + for (i = 0; i < ogg->chains->len; i++) { + GstOggChain *chain = &g_array_index (ogg->chains, GstOggChain, i); + g_print ("chain %d (%u streams):\n", i, g_slist_length (chain->pads)); + for (walk = chain->pads; walk; walk = g_slist_next (walk)) { + GstOggPad *pad = (GstOggPad *) walk->data; + g_print (" stream %d:\n", pad->serial); + g_print (" length %"G_GUINT64_FORMAT"\n", pad->length); + g_print (" pages %ld\n", pad->pages); + } + } +#endif +} diff --git a/ext/vorbis/Makefile.am b/ext/vorbis/Makefile.am index da2d134ff9..82d7686f1d 100644 --- a/ext/vorbis/Makefile.am +++ b/ext/vorbis/Makefile.am @@ -1,10 +1,10 @@ plugin_LTLIBRARIES = libgstvorbis.la -libgstvorbis_la_SOURCES = vorbis.c vorbisenc.c vorbisfile.c +libgstvorbis_la_SOURCES = vorbis.c vorbisdec.c vorbisenc.c vorbisfile.c libgstvorbis_la_CFLAGS = $(GST_CFLAGS) $(VORBIS_CFLAGS) ## AM_PATH_VORBIS also sets VORBISENC_LIBS libgstvorbis_la_LIBADD = $(VORBIS_LIBS) $(VORBISENC_LIBS) $(VORBISFILE_LIBS) libgstvorbis_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) -noinst_HEADERS = vorbisenc.h +noinst_HEADERS = vorbisenc.h vorbisdec.h diff --git a/ext/vorbis/vorbis.c b/ext/vorbis/vorbis.c index 42205d6ec7..a23a48007c 100644 --- a/ext/vorbis/vorbis.c +++ b/ext/vorbis/vorbis.c @@ -21,21 +21,27 @@ #include "config.h" #endif -#include +#include "vorbisenc.h" +#include "vorbisdec.h" extern GType vorbisfile_get_type(void); static gboolean plugin_init (GstPlugin *plugin) { - if (!gst_library_load ("gstbytestream")) return FALSE; + if (!gst_library_load ("gsttags")) + return FALSE; + if (!gst_element_register (plugin, "vorbisenc", GST_RANK_NONE, GST_TYPE_VORBISENC)) return FALSE; - if (!gst_element_register (plugin, "vorbisfile", GST_RANK_PRIMARY + 1, vorbisfile_get_type ())) + if (!gst_element_register (plugin, "vorbisfile", GST_RANK_SECONDARY, vorbisfile_get_type ())) + return FALSE; + + if (!gst_element_register (plugin, "vorbisdec", GST_RANK_PRIMARY, gst_vorbis_dec_get_type ())) return FALSE; return TRUE; diff --git a/ext/vorbis/vorbisdec.c b/ext/vorbis/vorbisdec.c new file mode 100644 index 0000000000..cb917fe57c --- /dev/null +++ b/ext/vorbis/vorbisdec.c @@ -0,0 +1,361 @@ +/* GStreamer + * Copyright (C) 2004 Benjamin Otte + * + * 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 "vorbisdec.h" +#include +#include + +static GstElementDetails vorbis_dec_details = { + "VorbisDec", + "Filter/Decoder/Audio", + "decode raw vorbis streams to float audio", + "Benjamin Otte ", +}; + +/* Filter signals and args */ +enum { + /* FILL ME */ + LAST_SIGNAL +}; + +enum { + ARG_0 +}; + +static GstStaticPadTemplate vorbis_dec_src_factory = +GST_STATIC_PAD_TEMPLATE ( + "src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ( + "audio/x-raw-float, " + "rate = (int) [ 11025, 48000 ], " + "channels = (int) [ 1, 2 ], " + "endianness = (int) BYTE_ORDER, " +#ifdef GST_VORBIS_DEC_SEQUENTIAL + "layout = \"sequential\", " +#endif + "width = (int) 32, " + "buffer-frames = (int) 0" + ) +); + +static GstStaticPadTemplate vorbis_dec_sink_factory = +GST_STATIC_PAD_TEMPLATE ( + "sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ( + "audio/x-vorbis" + ) +); + +GST_BOILERPLATE (GstVorbisDec, gst_vorbis_dec, GstElement, GST_TYPE_ELEMENT); + +static void vorbis_dec_chain (GstPad * pad, + GstData * data); +static GstElementStateReturn + vorbis_dec_change_state (GstElement * element); +static gboolean vorbis_dec_src_event (GstPad * pad, + GstEvent * event); + + +static void +gst_vorbis_dec_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&vorbis_dec_src_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&vorbis_dec_sink_factory)); + gst_element_class_set_details (element_class, &vorbis_dec_details); +} + +static void +gst_vorbis_dec_class_init (GstVorbisDecClass *klass) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + + gstelement_class->change_state = vorbis_dec_change_state; +} + +static GstPadLinkReturn +vorbis_dec_link (GstPad *pad, const GstCaps *caps) +{ + GstVorbisDec *dec = GST_VORBIS_DEC (gst_pad_get_parent (pad)); + + if (dec->packetno < 3) + return GST_PAD_LINK_DELAYED; + + return GST_PAD_LINK_OK; +} + +static GstCaps * +vorbis_dec_getcaps (GstPad *pad) +{ + GstVorbisDec *dec = GST_VORBIS_DEC (gst_pad_get_parent (pad)); + + if (dec->packetno < 3) + return gst_caps_copy (gst_pad_get_pad_template_caps (pad)); + + return gst_caps_new_simple ("audio/x-raw-float", + "rate", G_TYPE_INT, dec->vi.rate, + "channels", G_TYPE_INT, dec->vi.channels, + "endianness", G_TYPE_INT, G_BYTE_ORDER, + "width", G_TYPE_INT, 32, +#ifdef GST_VORBIS_DEC_SEQUENTIAL + "layout", G_TYPE_STRING, "sequential", +#endif + "buffer-frames", G_TYPE_INT, 0, + NULL); +} + +static void +gst_vorbis_dec_init (GstVorbisDec *dec) +{ + dec->sinkpad = gst_pad_new_from_template( + gst_static_pad_template_get (&vorbis_dec_sink_factory), "sink"); + gst_pad_set_chain_function (dec->sinkpad, vorbis_dec_chain); + gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad); + + dec->srcpad = gst_pad_new_from_template( + gst_static_pad_template_get (&vorbis_dec_src_factory), "src"); + gst_pad_set_link_function (dec->srcpad, vorbis_dec_link); + gst_pad_set_getcaps_function (dec->srcpad, vorbis_dec_getcaps); + gst_pad_set_event_function (dec->srcpad, vorbis_dec_src_event); + gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad); + + GST_FLAG_SET (dec, GST_ELEMENT_EVENT_AWARE); +} + +static gboolean +vorbis_dec_to_granulepos (GstVorbisDec *dec, GstFormat format, guint64 from, guint64 *to) +{ + if (dec->packetno < 1) return FALSE; + + switch (format) { + case GST_FORMAT_TIME: + *to = from * dec->vi.rate / GST_SECOND; + return TRUE; + case GST_FORMAT_DEFAULT: + *to = from; + return TRUE; + case GST_FORMAT_BYTES: + *to = from / sizeof (float) / dec->vi.channels; + return TRUE; + default: + return FALSE; + } +} + +static gboolean +vorbis_dec_src_event (GstPad *pad, GstEvent *event) +{ + gboolean res = TRUE; + GstVorbisDec *dec; + + dec = GST_VORBIS_DEC (gst_pad_get_parent (pad)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: { + guint64 value; + + res = vorbis_dec_to_granulepos (dec, GST_EVENT_SEEK_FORMAT (event), + GST_EVENT_SEEK_OFFSET (event), &value); + if (res) { + GstEvent *real_seek = gst_event_new_seek ( + (GST_EVENT_SEEK_TYPE (event) & ~GST_SEEK_FORMAT_MASK) | GST_FORMAT_DEFAULT, + value); + res = gst_pad_send_event (GST_PAD_PEER (dec->sinkpad), real_seek); + } + break; + } + default: + res = gst_pad_event_default (pad, event); + break; + } + + gst_event_unref (event); + return res; +} +static void +vorbis_dec_event (GstVorbisDec *dec, GstEvent *event) +{ + guint64 value; + + GST_LOG_OBJECT (dec, "handling event"); + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_DISCONTINUOUS: + if (gst_event_discont_get_value (event, GST_FORMAT_DEFAULT, &value)) { + dec->granulepos = value; + dec->packetno = 3; + GST_DEBUG_OBJECT (dec, "setting granuleposition to %"G_GUINT64_FORMAT" after discont\n", value); + } else { + GST_WARNING_OBJECT (dec, + "discont event didn't include offset, we might set it wrong now"); + } + break; + default: + break; + } + gst_pad_event_default (dec->sinkpad, event); +} + +static void +vorbis_dec_chain (GstPad *pad, GstData *data) +{ + GstBuffer *buf; + GstVorbisDec *vd; + ogg_packet packet; /* lol */ + + vd = GST_VORBIS_DEC (gst_pad_get_parent (pad)); + if (GST_IS_EVENT (data)) { + vorbis_dec_event (vd, GST_EVENT (data)); + return; + } + + buf = GST_BUFFER (data); + /* make ogg_packet out of the buffer */ + packet.packet = GST_BUFFER_DATA (buf); + packet.bytes = GST_BUFFER_SIZE (buf); + packet.granulepos = GST_BUFFER_OFFSET_END (buf); + packet.packetno = vd->packetno ++; + /* switch depending on packet type */ + if (packet.packet[0] & 1) { + /* header packet */ + if (packet.packet[0] / 2 != packet.packetno) { + /* FIXME: just skip? */ + gst_element_error (GST_ELEMENT (vd), STREAM, DECODE, + (NULL), ("unexpected packet type %d", (gint) packet.packet[0])); + gst_data_unref (data); + return; + } + if (vorbis_synthesis_headerin (&vd->vi, &vd->vc, &packet)) { + gst_element_error (GST_ELEMENT (vd), STREAM, DECODE, + (NULL), ("couldn't read header packet")); + gst_data_unref (data); + return; + } + if (packet.packetno == 1) { + GstTagList *list = gst_tag_list_from_vorbiscomment_buffer (buf, "\003vorbis", 7, NULL); + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_ENCODER_VERSION, vd->vi.version, NULL); + if (vd->vi.bitrate_upper) + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_MAXIMUM_BITRATE, (guint) vd->vi.bitrate_upper, NULL); + if (vd->vi.bitrate_nominal) + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_BITRATE, (guint) vd->vi.bitrate_nominal, NULL); + if (vd->vi.bitrate_lower) + gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, + GST_TAG_MINIMUM_BITRATE, (guint) vd->vi.bitrate_lower, NULL); + gst_element_found_tags_for_pad (GST_ELEMENT (vd), vd->srcpad, 0, list); + } else if (packet.packetno == 2) { + /* done */ + vorbis_synthesis_init (&vd->vd, &vd->vi); + vorbis_block_init (&vd->vd, &vd->vb); + if (gst_pad_is_linked (vd->srcpad)) + gst_pad_renegotiate (vd->srcpad); + } + } else { + float **pcm; + guint sample_count; + + /* normal data packet */ + if (vorbis_synthesis (&vd->vb, &packet)) { + gst_element_error (GST_ELEMENT (vd), STREAM, DECODE, + (NULL), ("couldn't read data packet")); + gst_data_unref (data); + return; + } + if (vorbis_synthesis_blockin (&vd->vd, &vd->vb) < 0) { + gst_element_error (GST_ELEMENT (vd), STREAM, DECODE, + (NULL), ("vorbis decoder did not accept data packet")); + gst_data_unref (data); + return; + } + sample_count = vorbis_synthesis_pcmout (&vd->vd, &pcm); + if (sample_count > 0) { + int i, j; + GstBuffer *out = gst_pad_alloc_buffer (vd->srcpad, GST_BUFFER_OFFSET_NONE, + sample_count * vd->vi.channels * sizeof (float)); + float *out_data = (float *) GST_BUFFER_DATA (out); +#ifdef GST_VORBIS_DEC_SEQUENTIAL + for (i = 0; i < vd->vi.channels; i++) { + memcpy (out_data, pcm[i], sample_count * sizeof (float)); + out_data += sample_count; + } +#else + for (j = 0; j < sample_count; j++) { + for (i = 0; i < vd->vi.channels; i++) { + *out_data = pcm[i][j]; + out_data++; + } + } +#endif + GST_BUFFER_OFFSET (out) = vd->granulepos; + GST_BUFFER_OFFSET_END (out) = vd->granulepos + sample_count; + GST_BUFFER_TIMESTAMP (out) = vd->granulepos * GST_SECOND / vd->vi.rate; + GST_BUFFER_DURATION (out) = sample_count * GST_SECOND / vd->vi.rate; + gst_pad_push (vd->srcpad, GST_DATA (out)); + vorbis_synthesis_read (&vd->vd, sample_count); + vd->granulepos += sample_count; + } + } + gst_data_unref (data); +} + +static GstElementStateReturn +vorbis_dec_change_state (GstElement *element) +{ + GstVorbisDec *vd = GST_VORBIS_DEC (element); + + switch (GST_STATE_TRANSITION (element)) { + case GST_STATE_NULL_TO_READY: + break; + case GST_STATE_READY_TO_PAUSED: + vorbis_info_init (&vd->vi); + vorbis_comment_init (&vd->vc); + break; + case GST_STATE_PAUSED_TO_PLAYING: + break; + case GST_STATE_PLAYING_TO_PAUSED: + break; + case GST_STATE_PAUSED_TO_READY: + vorbis_block_clear (&vd->vb); + vorbis_dsp_clear (&vd->vd); + vorbis_comment_clear (&vd->vc); + vorbis_info_clear (&vd->vi); + vd->packetno = 0; + vd->granulepos = 0; + break; + case GST_STATE_READY_TO_NULL: + break; + default: + g_assert_not_reached (); + break; + } + + return parent_class->change_state (element); +} diff --git a/ext/vorbis/vorbisdec.h b/ext/vorbis/vorbisdec.h new file mode 100644 index 0000000000..4018186e37 --- /dev/null +++ b/ext/vorbis/vorbisdec.h @@ -0,0 +1,73 @@ +/* -*- c-basic-offset: 2 -*- + * 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. + */ + + +#ifndef __GST_VORBIS_DEC_H__ +#define __GST_VORBIS_DEC_H__ + + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define GST_TYPE_VORBIS_DEC \ + (gst_vorbis_dec_get_type()) +#define GST_VORBIS_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_VORBIS_DEC,GstVorbisDec)) +#define GST_VORBIS_DEC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_VORBIS_DEC,GstVorbisDec)) +#define GST_IS_VORBIS_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_VORBIS_DEC)) +#define GST_IS_VORBIS_DEC_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_VORBIS_DEC)) + +typedef struct _GstVorbisDec GstVorbisDec; +typedef struct _GstVorbisDecClass GstVorbisDecClass; + +struct _GstVorbisDec { + GstElement element; + + GstPad * sinkpad; + GstPad * srcpad; + + vorbis_dsp_state vd; + vorbis_info vi; + vorbis_comment vc; + vorbis_block vb; + guint packetno; + guint64 granulepos; +}; + +struct _GstVorbisDecClass { + GstElementClass parent_class; +}; + +GType gst_vorbis_dec_get_type(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __GST_VORBIS_DEC_H__ */ diff --git a/gst/typefind/gsttypefindfunctions.c b/gst/typefind/gsttypefindfunctions.c index df67cec6ca..57bc8e804b 100644 --- a/gst/typefind/gsttypefindfunctions.c +++ b/gst/typefind/gsttypefindfunctions.c @@ -1048,7 +1048,7 @@ dv_type_find (GstTypeFind *tf, gpointer private) } } -/*** application/x-vorbis *****************************************************/ +/*** audio/x-vorbis ***********************************************************/ static GstStaticCaps vorbis_caps = GST_STATIC_CAPS ("audio/x-vorbis"); #define VORBIS_CAPS gst_caps_copy(gst_static_caps_get(&vorbis_caps)) @@ -1084,6 +1084,23 @@ vorbis_type_find (GstTypeFind *tf, gpointer private) } } +/*** video/x-theora ***********************************************************/ + +static GstStaticCaps theora_caps = GST_STATIC_CAPS ("video/x-theora"); +#define THEORA_CAPS gst_caps_copy(gst_static_caps_get(&theora_caps)) +static void +theora_type_find (GstTypeFind *tf, gpointer private) +{ + guint8 *data = gst_type_find_peek (tf, 0, 7); //42); + + if (data) { + if (data[0] != 0x80) return; + if (memcmp (&data[1], "theora", 6) != 0) return; + /* FIXME: make this more reliable when specs are out */ + } + gst_type_find_suggest (tf, GST_TYPE_FIND_MAXIMUM, THEORA_CAPS); +} + /*** generic typefind for streams that have some data at a specific position***/ typedef struct { @@ -1276,6 +1293,8 @@ plugin_init (GstPlugin *plugin) compress_exts, "\037\235", 2, GST_TYPE_FIND_LIKELY); TYPE_FIND_REGISTER (plugin, "audio/x-vorbis", GST_RANK_PRIMARY, vorbis_type_find, NULL, VORBIS_CAPS, NULL); + TYPE_FIND_REGISTER (plugin, "video/x-theora", GST_RANK_PRIMARY, + theora_type_find, NULL, THEORA_CAPS, NULL); TYPE_FIND_REGISTER (plugin, "audio/x-m4a", GST_RANK_PRIMARY, m4a_type_find, m4a_exts, AAC_CAPS, NULL);