diff --git a/ChangeLog b/ChangeLog index 989d124c09..2069658ff2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2006-02-04 Michael Smith + + * ext/ogg/gstoggdemux.c: (gst_ogg_pad_parse_skeleton_fishead), + (gst_ogg_pad_parse_skeleton_fisbone), (gst_ogg_pad_query_convert), + (gst_ogg_demux_chain_peer), (gst_ogg_pad_submit_packet), + (gst_ogg_demux_perform_seek), (gst_ogg_demux_read_chain), + (gst_ogg_demux_read_end_chain), (gst_ogg_demux_collect_chain_info), + (gst_ogg_demux_change_state), (gst_annodex_granule_to_time): + Annodex support in ogg demuxer. Doesn't do very much without the + other annodex patches (to come). + 2006-02-24 Tim-Philipp Müller * gst-libs/gst/riff/riff-media.c: (gst_riff_create_video_caps): diff --git a/ext/ogg/gstoggdemux.c b/ext/ogg/gstoggdemux.c index a4106f4dab..e3e650c8e9 100644 --- a/ext/ogg/gstoggdemux.c +++ b/ext/ogg/gstoggdemux.c @@ -28,6 +28,8 @@ #include #define CHUNKSIZE (8500) /* this is out of vorbisfile */ +#define SKELETON_FISHEAD_SIZE 64 +#define SKELETON_FISBONE_MIN_SIZE 52 enum { @@ -111,6 +113,13 @@ struct _GstOggPad GList *headers; + gboolean is_skeleton; + gboolean have_fisbone; + gint64 granulerate_n; + gint64 granulerate_d; + guint32 preroll; + guint granuleshift; + gint serialno; gint64 packetno; gint64 current_granule; @@ -165,6 +174,10 @@ struct _GstOggDemux gint64 current_granule; GstClockTime current_time; + /* annodex stuff */ + gboolean have_fishead; + gint64 basetime; + /* ogg stuff */ ogg_sync_state sync; }; @@ -205,6 +218,15 @@ static gboolean gst_ogg_pad_src_query (GstPad * pad, GstQuery * query); static gboolean gst_ogg_pad_event (GstPad * pad, GstEvent * event); static GstCaps *gst_ogg_pad_getcaps (GstPad * pad); static GstCaps *gst_ogg_type_find (ogg_packet * packet); +static GstOggPad *gst_ogg_chain_get_stream (GstOggChain * chain, + glong serialno); + +static gboolean gst_ogg_pad_query_convert (GstOggPad * pad, + GstFormat src_format, gint64 src_val, + GstFormat * dest_format, gint64 * dest_val); +static GstClockTime gst_annodex_granule_to_time (gint64 granulepos, + gint64 granulerate_n, gint64 granulerate_d, guint8 granuleshift); + static GstPadClass *ogg_pad_parent_class = NULL; @@ -566,6 +588,142 @@ compare_ranks (GstPluginFeature * f1, GstPluginFeature * f2) gst_plugin_feature_get_name (f1)); } +/* called when the skeleton fishead is found. Caller ensures the packet is + * precisely the correct size; we don't re-check this here. */ +static void +gst_ogg_pad_parse_skeleton_fishead (GstOggPad * pad, ogg_packet * packet) +{ + GstOggDemux *ogg = pad->ogg; + guint8 *data = packet->packet; + guint16 major, minor; + gint64 prestime_n, prestime_d; + gint64 basetime_n, basetime_d; + + /* skip "fishead\0" */ + data += 8; + major = GST_READ_UINT16_LE (data); + data += 2; + minor = GST_READ_UINT16_LE (data); + data += 2; + prestime_n = (gint64) GST_READ_UINT64_LE (data); + data += 8; + prestime_d = (gint64) GST_READ_UINT64_LE (data); + data += 8; + basetime_n = (gint64) GST_READ_UINT64_LE (data); + data += 8; + basetime_d = (gint64) GST_READ_UINT64_LE (data); + data += 8; + + ogg->basetime = gst_util_uint64_scale (GST_SECOND, basetime_n, basetime_d); + ogg->have_fishead = TRUE; + pad->is_skeleton = TRUE; + pad->start_time = GST_CLOCK_TIME_NONE; + pad->first_granule = -1; + pad->first_time = GST_CLOCK_TIME_NONE; + GST_INFO_OBJECT (ogg, "skeleton fishead parsed (basetime: %" + GST_TIME_FORMAT ")", GST_TIME_ARGS (ogg->basetime)); +} + +/* function called when a skeleton fisbone is found. Caller ensures that + * the packet length is sufficient */ +static void +gst_ogg_pad_parse_skeleton_fisbone (GstOggPad * pad, ogg_packet * packet) +{ + GstOggPad *fisbone_pad; + gint64 start_granule; + guint32 serialno; + guint8 *data = packet->packet; + + /* skip "fisbone\0" */ + data += 8; + /* skip headers offset */ + data += 4; + serialno = GST_READ_UINT32_LE (data); + + fisbone_pad = gst_ogg_chain_get_stream (pad->chain, serialno); + if (fisbone_pad) { + if (fisbone_pad->have_fisbone) + /* already parsed */ + return; + + fisbone_pad->have_fisbone = TRUE; + + data += 4; + /* skip number of headers */ + data += 4; + fisbone_pad->granulerate_n = GST_READ_UINT64_LE (data); + data += 8; + fisbone_pad->granulerate_d = GST_READ_UINT64_LE (data); + data += 8; + start_granule = GST_READ_UINT64_LE (data); + data += 8; + fisbone_pad->preroll = GST_READ_UINT32_LE (data); + data += 4; + fisbone_pad->granuleshift = GST_READ_UINT8 (data); + data += 1; + /* padding */ + data += 3; + + fisbone_pad->start_time = gst_annodex_granule_to_time (start_granule, + fisbone_pad->granulerate_n, fisbone_pad->granulerate_d, + fisbone_pad->granuleshift); + + GST_INFO_OBJECT (pad->ogg, "skeleton fisbone parsed " + "(serialno: %" G_GUINT32_FORMAT " start time: %" GST_TIME_FORMAT + " granulerate_n: %" G_GINT64_FORMAT " granulerate_d: %" G_GINT64_FORMAT + " preroll: %" G_GUINT32_FORMAT " granuleshift: %d)", + serialno, GST_TIME_ARGS (fisbone_pad->start_time), + fisbone_pad->granulerate_n, fisbone_pad->granulerate_d, + fisbone_pad->preroll, fisbone_pad->granuleshift); + } else { + GST_WARNING_OBJECT (pad->ogg, + "found skeleton fisbone for an unknown stream %" G_GUINT32_FORMAT, + serialno); + } +} + +/* function called to convert a granulepos to a timestamp */ +static gboolean +gst_ogg_pad_query_convert (GstOggPad * pad, GstFormat src_format, + gint64 src_val, GstFormat * dest_format, gint64 * dest_val) +{ + gboolean res; + + if (src_val == -1) { + *dest_val = -1; + return TRUE; + } + + if (!pad->have_fisbone && pad->elem_pad == NULL) + return FALSE; + + switch (src_format) { + case GST_FORMAT_DEFAULT: + if (pad->have_fisbone && *dest_format == GST_FORMAT_TIME) { + *dest_val = gst_annodex_granule_to_time (src_val, + pad->granulerate_n, pad->granulerate_d, pad->granuleshift); + + res = TRUE; + } else { + if (pad->elem_pad == NULL) + res = FALSE; + else + res = gst_pad_query_convert (pad->elem_pad, src_format, src_val, + dest_format, dest_val); + } + + break; + default: + if (pad->elem_pad == NULL) + res = FALSE; + else + res = gst_pad_query_convert (pad->elem_pad, src_format, src_val, + dest_format, dest_val); + } + + return res; +} + /* function called by the internal decoder elements when it outputs * a buffer. We use it to get the first timestamp of the stream */ @@ -760,23 +918,6 @@ gst_ogg_demux_queue_data (GstOggPad * pad, ogg_packet * packet) return GST_FLOW_OK; } -static gboolean -gst_ogg_pad_query_convert (GstOggPad * ogg_pad, GstFormat from_format, - gint64 from_value, GstFormat * to_format, gint64 * to_value) -{ - if (from_value == -1) { - *to_value = -1; - return TRUE; - } - - /* If we don't know the stream type, we won't have an internal pad to ask */ - if (!ogg_pad->elem_pad) - return FALSE; - - return gst_pad_query_convert (ogg_pad->elem_pad, - from_format, from_value, to_format, to_value); -} - /* send packet to internal element */ static GstFlowReturn gst_ogg_demux_chain_peer (GstOggPad * pad, ogg_packet * packet) @@ -807,13 +948,15 @@ gst_ogg_demux_chain_peer (GstOggPad * pad, ogg_packet * packet) ogg->current_granule = packet->granulepos; format = GST_FORMAT_TIME; - if (!gst_ogg_pad_query_convert (pad, - GST_FORMAT_DEFAULT, packet->granulepos, &format, - (gint64 *) & ogg->current_time)) { - GST_WARNING_OBJECT (ogg, "could not convert granulepos to time"); - } else { - GST_DEBUG ("ogg current time %" GST_TIME_FORMAT, - GST_TIME_ARGS (ogg->current_time)); + if (!pad->is_skeleton) { + if (!gst_ogg_pad_query_convert (pad, + GST_FORMAT_DEFAULT, packet->granulepos, &format, + (gint64 *) & ogg->current_time)) { + GST_WARNING_OBJECT (ogg, "could not convert granulepos to time"); + } else { + GST_DEBUG ("ogg current time %" GST_TIME_FORMAT, + GST_TIME_ARGS (ogg->current_time)); + } } } } else { @@ -840,12 +983,21 @@ gst_ogg_pad_submit_packet (GstOggPad * pad, ogg_packet * packet) GST_DEBUG_OBJECT (ogg, "%p submit packet serial %08lx", pad, pad->serialno); - /* first packet */ if (!pad->have_type) { + if (!ogg->have_fishead && packet->bytes == SKELETON_FISHEAD_SIZE && + !memcmp (packet->packet, "fishead\0", 8)) { + gst_ogg_pad_parse_skeleton_fishead (pad, packet); + } + gst_ogg_pad_typefind (pad, packet); pad->have_type = TRUE; } + if (ogg->have_fishead && packet->bytes >= SKELETON_FISBONE_MIN_SIZE && + !memcmp (packet->packet, "fisbone\0", 8)) { + gst_ogg_pad_parse_skeleton_fisbone (pad, packet); + } + granule = packet->granulepos; if (granule != -1) { ogg->current_granule = granule; @@ -856,8 +1008,8 @@ gst_ogg_pad_submit_packet (GstOggPad * pad, ogg_packet * packet) } /* no start time known, stream to internal plugin to - * get time */ - if (pad->start_time == GST_CLOCK_TIME_NONE) { + * get time. always stream to the skeleton decoder */ + if (pad->start_time == GST_CLOCK_TIME_NONE || pad->is_skeleton) { ret = gst_ogg_demux_chain_elem_pad (pad, packet); } /* we know the start_time of the pad data, see if we @@ -1102,10 +1254,10 @@ GST_STATIC_PAD_TEMPLATE ("src_%d", GST_STATIC_CAPS_ANY); static GstStaticPadTemplate ogg_demux_sink_template_factory = -GST_STATIC_PAD_TEMPLATE ("sink", + GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, - GST_STATIC_CAPS ("application/ogg") + GST_STATIC_CAPS ("application/ogg; application/x-annodex") ); static void gst_ogg_demux_finalize (GObject * object); @@ -1738,7 +1890,7 @@ gst_ogg_demux_perform_seek (GstOggDemux * ogg) continue; pad = gst_ogg_chain_get_stream (chain, ogg_page_serialno (&og)); - if (pad == NULL) + if (pad == NULL || pad->is_skeleton) continue; format = GST_FORMAT_TIME; @@ -1748,6 +1900,9 @@ gst_ogg_demux_perform_seek (GstOggDemux * ogg) GST_WARNING_OBJECT (ogg, "could not convert granulepos to time"); granuletime = target; } else { + if (granuletime < pad->first_time) + continue; + granuletime -= pad->first_time; } @@ -1999,7 +2154,7 @@ gst_ogg_demux_read_chain (GstOggDemux * ogg) if (pad->serialno == serial) { known_serial = TRUE; - if (pad->start_time == -1 && ogg_page_eos (&op)) { + if (!pad->is_skeleton && pad->start_time == -1 && ogg_page_eos (&op)) { /* got EOS on a pad before we could find its start_time. * We have no chance of finding a start_time for every pad so * stop searching for the other start_time(s). @@ -2034,6 +2189,9 @@ gst_ogg_demux_read_chain (GstOggDemux * ogg) GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); GstFormat target; + if (pad->is_skeleton) + continue; + target = GST_FORMAT_TIME; if (!gst_ogg_pad_query_convert (pad, GST_FORMAT_DEFAULT, pad->first_granule, &target, @@ -2082,6 +2240,9 @@ gst_ogg_demux_read_end_chain (GstOggDemux * ogg, GstOggChain * chain) for (i = 0; i < chain->streams->len; i++) { GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + if (pad->is_skeleton) + continue; + if (pad->serialno == ogg_page_serialno (&og)) { gint64 granulepos = ogg_page_granulepos (&og); @@ -2166,6 +2327,9 @@ gst_ogg_demux_collect_chain_info (GstOggDemux * ogg, GstOggChain * chain) for (i = 0; i < chain->streams->len; i++) { GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + if (pad->is_skeleton) + continue; + /* can do this if the pad start time is not defined */ if (pad->start_time == GST_CLOCK_TIME_NONE) goto no_start_time; @@ -2595,6 +2759,8 @@ gst_ogg_demux_change_state (GstElement * element, GstStateChange transition) ogg->segment_start = GST_CLOCK_TIME_NONE; ogg->segment_stop = GST_CLOCK_TIME_NONE; ogg->total_time = GST_CLOCK_TIME_NONE; + ogg->basetime = 0; + ogg->have_fishead = FALSE; ogg->running = FALSE; ogg_sync_init (&ogg->sync); break; @@ -2618,6 +2784,7 @@ gst_ogg_demux_change_state (GstElement * element, GstStateChange transition) gst_ogg_demux_clear_chains (ogg); GST_OBJECT_LOCK (ogg); ogg->running = FALSE; + ogg->have_fishead = FALSE; ogg->segment_rate = 1.0; ogg->segment_flags = GST_SEEK_FLAG_NONE; ogg->segment_start = GST_CLOCK_TIME_NONE; @@ -2633,6 +2800,30 @@ gst_ogg_demux_change_state (GstElement * element, GstStateChange transition) return result; } +static GstClockTime +gst_annodex_granule_to_time (gint64 granulepos, gint64 granulerate_n, + gint64 granulerate_d, guint8 granuleshift) +{ + gint64 keyindex, keyoffset; + gint64 granulerate; + GstClockTime res; + + if (granulepos == 0 || granulerate_n == 0 || granulerate_d == 0) + return 0; + + if (granuleshift != 0) { + keyindex = granulepos >> granuleshift; + keyoffset = granulepos - (keyindex << granuleshift); + granulepos = keyindex + keyoffset; + } + + /* GST_SECOND / (granulerate_n / granulerate_d) */ + granulerate = gst_util_uint64_scale (GST_SECOND, + granulerate_d, granulerate_n); + res = gst_util_uint64_scale (granulepos, granulerate, 1); + return res; +} + /*** typefinding **************************************************************/ /* ogg supports its own typefinding because the ogg spec defines that the first * packet of an ogg stream must identify the stream. Therefore ogg can use a