2 * Copyright (C) 2004 Wim Taymans <wim@fluendo.com>
4 * gstoggdemux.c: ogg stream demuxer
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
23 * SECTION:element-oggdemux
24 * @see_also: <link linkend="gst-plugins-base-plugins-oggmux">oggmux</link>
26 * This element demuxes ogg files into their encoded audio and video components.
29 * <title>Example pipelines</title>
31 * gst-launch -v filesrc location=test.ogg ! oggdemux ! vorbisdec ! audioconvert ! alsasink
32 * ]| Decodes the vorbis audio stored inside an ogg container.
35 * Last reviewed on 2006-12-30 (0.10.5)
43 #include <gst/gst-i18n-plugin.h>
45 #include "gstoggdemux.h"
47 static const GstElementDetails gst_ogg_demux_details =
48 GST_ELEMENT_DETAILS ("Ogg demuxer",
50 "demux ogg streams (info about ogg: http://xiph.org)",
51 "Wim Taymans <wim@fluendo.com>");
53 #define CHUNKSIZE (8500) /* this is out of vorbisfile */
54 #define SKELETON_FISHEAD_SIZE 64
55 #define SKELETON_FISBONE_MIN_SIZE 52
57 #define GST_FLOW_LIMIT GST_FLOW_CUSTOM_ERROR
59 #define GST_CHAIN_LOCK(ogg) g_mutex_lock((ogg)->chain_lock)
60 #define GST_CHAIN_UNLOCK(ogg) g_mutex_unlock((ogg)->chain_lock)
62 GST_DEBUG_CATEGORY (gst_ogg_demux_debug);
63 GST_DEBUG_CATEGORY (gst_ogg_demux_setup_debug);
64 #define GST_CAT_DEFAULT gst_ogg_demux_debug
67 gst_ogg_page_copy (ogg_page * page)
69 ogg_page *p = g_new0 (ogg_page, 1);
71 /* make a copy of the page */
72 p->header = g_memdup (page->header, page->header_len);
73 p->header_len = page->header_len;
74 p->body = g_memdup (page->body, page->body_len);
75 p->body_len = page->body_len;
81 gst_ogg_page_free (ogg_page * page)
83 g_free (page->header);
88 static gboolean gst_ogg_demux_collect_chain_info (GstOggDemux * ogg,
90 static gboolean gst_ogg_demux_activate_chain (GstOggDemux * ogg,
91 GstOggChain * chain, GstEvent * event);
92 static void gst_ogg_chain_mark_discont (GstOggChain * chain);
94 static gboolean gst_ogg_demux_perform_seek (GstOggDemux * ogg,
96 static gboolean gst_ogg_demux_receive_event (GstElement * element,
99 static void gst_ogg_pad_class_init (GstOggPadClass * klass);
100 static void gst_ogg_pad_init (GstOggPad * pad);
101 static void gst_ogg_pad_dispose (GObject * object);
102 static void gst_ogg_pad_finalize (GObject * object);
104 static const GstQueryType *gst_ogg_pad_query_types (GstPad * pad);
105 static gboolean gst_ogg_pad_src_query (GstPad * pad, GstQuery * query);
106 static gboolean gst_ogg_pad_event (GstPad * pad, GstEvent * event);
107 static GstCaps *gst_ogg_pad_getcaps (GstPad * pad);
108 static GstOggPad *gst_ogg_chain_get_stream (GstOggChain * chain,
111 static GstFlowReturn gst_ogg_demux_combine_flows (GstOggDemux * ogg,
112 GstOggPad * pad, GstFlowReturn ret);
113 static void gst_ogg_demux_sync_streams (GstOggDemux * ogg);
115 G_DEFINE_TYPE (GstOggPad, gst_ogg_pad, GST_TYPE_PAD);
118 gst_ogg_pad_class_init (GstOggPadClass * klass)
120 GObjectClass *gobject_class;
122 gobject_class = (GObjectClass *) klass;
124 gobject_class->dispose = gst_ogg_pad_dispose;
125 gobject_class->finalize = gst_ogg_pad_finalize;
129 gst_ogg_pad_init (GstOggPad * pad)
131 gst_pad_set_event_function (GST_PAD (pad),
132 GST_DEBUG_FUNCPTR (gst_ogg_pad_event));
133 gst_pad_set_getcaps_function (GST_PAD (pad),
134 GST_DEBUG_FUNCPTR (gst_ogg_pad_getcaps));
135 gst_pad_set_query_type_function (GST_PAD (pad),
136 GST_DEBUG_FUNCPTR (gst_ogg_pad_query_types));
137 gst_pad_set_query_function (GST_PAD (pad),
138 GST_DEBUG_FUNCPTR (gst_ogg_pad_src_query));
140 pad->mode = GST_OGG_PAD_MODE_INIT;
142 pad->current_granule = -1;
143 pad->keyframe_granule = -1;
145 pad->start_time = GST_CLOCK_TIME_NONE;
147 pad->last_stop = GST_CLOCK_TIME_NONE;
149 pad->have_type = FALSE;
150 pad->continued = NULL;
151 pad->map.headers = NULL;
152 pad->map.queued = NULL;
156 gst_ogg_pad_dispose (GObject * object)
158 GstOggPad *pad = GST_OGG_PAD (object);
163 g_list_foreach (pad->map.headers, (GFunc) gst_mini_object_unref, NULL);
164 g_list_free (pad->map.headers);
165 pad->map.headers = NULL;
166 g_list_foreach (pad->map.queued, (GFunc) gst_mini_object_unref, NULL);
167 g_list_free (pad->map.queued);
168 pad->map.queued = NULL;
170 /* clear continued pages */
171 g_list_foreach (pad->continued, (GFunc) gst_ogg_page_free, NULL);
172 g_list_free (pad->continued);
173 pad->continued = NULL;
175 ogg_stream_reset (&pad->map.stream);
177 G_OBJECT_CLASS (gst_ogg_pad_parent_class)->dispose (object);
181 gst_ogg_pad_finalize (GObject * object)
183 GstOggPad *pad = GST_OGG_PAD (object);
185 ogg_stream_clear (&pad->map.stream);
187 G_OBJECT_CLASS (gst_ogg_pad_parent_class)->finalize (object);
190 static const GstQueryType *
191 gst_ogg_pad_query_types (GstPad * pad)
193 static const GstQueryType query_types[] = {
203 gst_ogg_pad_getcaps (GstPad * pad)
205 return gst_caps_ref (GST_PAD_CAPS (pad));
209 gst_ogg_pad_src_query (GstPad * pad, GstQuery * query)
214 ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad));
216 switch (GST_QUERY_TYPE (query)) {
217 case GST_QUERY_DURATION:
222 gst_query_parse_duration (query, &format, NULL);
223 /* can only get position in time */
224 if (format != GST_FORMAT_TIME)
228 /* we must return the total seekable length */
229 total_time = ogg->total_time;
231 /* in non-seek mode we can answer the query and we must return -1 */
235 gst_query_set_duration (query, GST_FORMAT_TIME, total_time);
238 case GST_QUERY_SEEKING:
242 gst_query_parse_seeking (query, &format, NULL, NULL, NULL);
243 if (format == GST_FORMAT_TIME) {
244 gst_query_set_seeking (query, GST_FORMAT_TIME, ogg->seekable,
253 res = gst_pad_query_default (pad, query);
257 gst_object_unref (ogg);
264 GST_DEBUG_OBJECT (ogg, "only query duration on TIME is supported");
271 gst_ogg_demux_receive_event (GstElement * element, GstEvent * event)
276 ogg = GST_OGG_DEMUX (element);
278 switch (GST_EVENT_TYPE (event)) {
280 /* can't seek if we are not seekable, FIXME could pass the
281 * seek query upstream after converting it to bytes using
282 * the average bitrate of the stream. */
283 if (!ogg->seekable) {
284 GST_DEBUG_OBJECT (ogg, "seek on non seekable stream");
288 /* now do the seek */
289 res = gst_ogg_demux_perform_seek (ogg, event);
290 gst_event_unref (event);
293 GST_DEBUG_OBJECT (ogg, "We only handle seek events here");
302 GST_DEBUG_OBJECT (ogg, "error handling event");
303 gst_event_unref (event);
309 gst_ogg_pad_event (GstPad * pad, GstEvent * event)
314 ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad));
316 switch (GST_EVENT_TYPE (event)) {
318 /* can't seek if we are not seekable, FIXME could pass the
319 * seek query upstream after converting it to bytes using
320 * the average bitrate of the stream. */
321 if (!ogg->seekable) {
322 GST_DEBUG_OBJECT (ogg, "seek on non seekable stream");
326 /* now do the seek */
327 res = gst_ogg_demux_perform_seek (ogg, event);
328 gst_event_unref (event);
331 res = gst_pad_event_default (pad, event);
335 gst_object_unref (ogg);
342 GST_DEBUG_OBJECT (ogg, "error handling event");
343 gst_event_unref (event);
350 gst_ogg_pad_reset (GstOggPad * pad)
352 ogg_stream_reset (&pad->map.stream);
354 GST_DEBUG_OBJECT (pad, "doing reset");
356 /* clear continued pages */
357 g_list_foreach (pad->continued, (GFunc) gst_ogg_page_free, NULL);
358 g_list_free (pad->continued);
359 pad->continued = NULL;
361 pad->last_ret = GST_FLOW_OK;
362 pad->last_stop = GST_CLOCK_TIME_NONE;
363 pad->current_granule = -1;
364 pad->keyframe_granule = -1;
367 /* called when the skeleton fishead is found. Caller ensures the packet is
368 * precisely the correct size; we don't re-check this here. */
370 gst_ogg_pad_parse_skeleton_fishead (GstOggPad * pad, ogg_packet * packet)
372 GstOggDemux *ogg = pad->ogg;
373 guint8 *data = packet->packet;
374 guint16 major, minor;
375 gint64 prestime_n, prestime_d;
376 gint64 basetime_n, basetime_d;
378 /* skip "fishead\0" */
379 major = GST_READ_UINT16_LE (data + 8);
380 minor = GST_READ_UINT16_LE (data + 10);
381 prestime_n = (gint64) GST_READ_UINT64_LE (data + 12);
382 prestime_d = (gint64) GST_READ_UINT64_LE (data + 20);
383 basetime_n = (gint64) GST_READ_UINT64_LE (data + 28);
384 basetime_d = (gint64) GST_READ_UINT64_LE (data + 36);
386 ogg->basetime = gst_util_uint64_scale (GST_SECOND, basetime_n, basetime_d);
387 ogg->prestime = gst_util_uint64_scale (GST_SECOND, prestime_n, prestime_d);
388 ogg->have_fishead = TRUE;
389 pad->map.is_skeleton = TRUE;
390 pad->start_time = GST_CLOCK_TIME_NONE;
391 GST_INFO_OBJECT (ogg, "skeleton fishead parsed (basetime: %"
392 GST_TIME_FORMAT ", prestime: %" GST_TIME_FORMAT ")",
393 GST_TIME_ARGS (ogg->basetime), GST_TIME_ARGS (ogg->prestime));
396 /* function called when a skeleton fisbone is found. Caller ensures that
397 * the packet length is sufficient */
399 gst_ogg_pad_parse_skeleton_fisbone (GstOggPad * pad, ogg_packet * packet)
401 GstOggPad *fisbone_pad;
402 gint64 start_granule;
404 guint8 *data = packet->packet;
406 serialno = GST_READ_UINT32_LE (data + 12);
408 fisbone_pad = gst_ogg_chain_get_stream (pad->chain, serialno);
410 if (fisbone_pad->map.have_fisbone)
414 fisbone_pad->map.have_fisbone = TRUE;
416 fisbone_pad->map.granulerate_n = GST_READ_UINT64_LE (data + 20);
417 fisbone_pad->map.granulerate_d = GST_READ_UINT64_LE (data + 28);
418 start_granule = GST_READ_UINT64_LE (data + 36);
419 fisbone_pad->map.preroll = GST_READ_UINT32_LE (data + 44);
420 fisbone_pad->map.granuleshift = GST_READ_UINT8 (data + 48);
422 GST_INFO_OBJECT (pad->ogg, "skeleton fisbone parsed "
423 "(serialno: %08x start time: %" GST_TIME_FORMAT
424 " granulerate_n: %d granulerate_d: %d "
425 " preroll: %" G_GUINT32_FORMAT " granuleshift: %d)",
426 serialno, GST_TIME_ARGS (fisbone_pad->start_time),
427 fisbone_pad->map.granulerate_n, fisbone_pad->map.granulerate_d,
428 fisbone_pad->map.preroll, fisbone_pad->map.granuleshift);
430 GST_WARNING_OBJECT (pad->ogg,
431 "found skeleton fisbone for an unknown stream %" G_GUINT32_FORMAT,
437 gst_ogg_demux_buffer_from_packet (ogg_packet * packet)
441 buf = gst_buffer_new_and_alloc (packet->bytes);
442 memcpy (buf->data, packet->packet, packet->bytes);
443 GST_BUFFER_OFFSET (buf) = -1;
444 GST_BUFFER_OFFSET_END (buf) = packet->granulepos;
449 /* queue data, basically takes the packet, puts it in a buffer and store the
450 * buffer in the queued list. */
452 gst_ogg_demux_queue_data (GstOggPad * pad, ogg_packet * packet)
456 #ifndef GST_DISABLE_GST_DEBUG
457 GstOggDemux *ogg = pad->ogg;
460 GST_DEBUG_OBJECT (ogg, "%p queueing data serial %08x", pad,
463 buf = gst_ogg_demux_buffer_from_packet (packet);
464 pad->map.queued = g_list_append (pad->map.queued, buf);
471 gst_ogg_demux_chain_peer (GstOggPad * pad, ogg_packet * packet)
474 GstFlowReturn ret, cret;
475 GstOggDemux *ogg = pad->ogg;
481 GstClockTime out_timestamp, out_duration;
482 guint64 out_offset, out_offset_end;
484 GST_DEBUG_OBJECT (ogg,
485 "%p streaming to peer serial %08x", pad, pad->map.serialno);
487 if (pad->map.is_ogm) {
491 data = packet->packet;
492 bytes = packet->bytes;
498 /* We don't push header packets for OGM */
499 cret = gst_ogg_demux_combine_flows (ogg, pad, GST_FLOW_OK);
503 offset = 1 + (((data[0] & 0xc0) >> 6) | ((data[0] & 0x02) << 1));
506 while (bytes && data[bytes - 1] == 0) {
515 /* get timing info for the packet */
516 duration = gst_ogg_stream_get_packet_duration (&pad->map, packet);
517 GST_DEBUG_OBJECT (ogg, "packet duration %" G_GUINT64_FORMAT, duration);
520 out_timestamp = GST_CLOCK_TIME_NONE;
521 out_duration = GST_CLOCK_TIME_NONE;
525 if (packet->granulepos != -1) {
526 pad->current_granule = gst_ogg_stream_granulepos_to_granule (&pad->map,
528 pad->keyframe_granule =
529 gst_ogg_stream_granulepos_to_key_granule (&pad->map,
531 GST_DEBUG_OBJECT (ogg, "new granule %" G_GUINT64_FORMAT,
532 pad->current_granule);
533 } else if (ogg->segment.rate > 0.0 && pad->current_granule != -1) {
534 pad->current_granule += duration;
535 GST_DEBUG_OBJECT (ogg, "interpollating granule %" G_GUINT64_FORMAT,
536 pad->current_granule);
538 if (ogg->segment.rate < 0.0 && packet->granulepos == -1) {
539 /* negative rates, only set timestamp on the packets with a granulepos */
545 /* we only push buffers after we have a valid granule. This is done so that
546 * we nicely skip packets without a timestamp after a seek. This is ok
547 * because we base or seek on the packet after the page with the smaller
549 if (pad->current_granule == -1)
552 if (pad->map.is_ogm) {
553 out_timestamp = gst_ogg_stream_granule_to_time (&pad->map,
554 pad->current_granule);
555 out_duration = gst_util_uint64_scale (duration,
556 GST_SECOND * pad->map.granulerate_d, pad->map.granulerate_n);
558 out_timestamp = gst_ogg_stream_granule_to_time (&pad->map,
559 pad->current_granule - duration);
561 gst_ogg_stream_granule_to_time (&pad->map,
562 pad->current_granule) - out_timestamp;
565 gst_ogg_stream_granule_to_granulepos (&pad->map, pad->current_granule,
566 pad->keyframe_granule);
568 gst_ogg_stream_granule_to_time (&pad->map, pad->current_granule);
572 /* check for invalid buffer sizes */
573 if (G_UNLIKELY (offset + trim >= packet->bytes))
577 gst_pad_alloc_buffer_and_set_caps (GST_PAD_CAST (pad),
578 GST_BUFFER_OFFSET_NONE, packet->bytes - offset - trim,
579 GST_PAD_CAPS (pad), &buf);
582 cret = gst_ogg_demux_combine_flows (ogg, pad, ret);
583 if (ret != GST_FLOW_OK)
586 /* copy packet in buffer */
587 memcpy (buf->data, packet->packet + offset, packet->bytes - offset - trim);
589 GST_BUFFER_TIMESTAMP (buf) = out_timestamp;
590 GST_BUFFER_DURATION (buf) = out_duration;
591 GST_BUFFER_OFFSET (buf) = out_offset;
592 GST_BUFFER_OFFSET_END (buf) = out_offset_end;
594 /* Mark discont on the buffer */
596 GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
597 pad->discont = FALSE;
600 pad->last_stop = ogg->segment.last_stop;
602 ret = gst_pad_push (GST_PAD_CAST (pad), buf);
605 cret = gst_ogg_demux_combine_flows (ogg, pad, ret);
607 /* we're done with skeleton stuff */
608 if (pad->map.is_skeleton)
611 /* check if valid granulepos, then we can calculate the current
612 * position. We know the granule for each packet but we only want to update
613 * the last_stop when we have a valid granulepos on the packet because else
614 * our time jumps around for the different streams. */
615 if (packet->granulepos < 0)
618 /* convert to time */
619 current_time = gst_ogg_stream_get_end_time_for_granulepos (&pad->map,
622 /* convert to stream time */
623 if ((chain = pad->chain)) {
624 gint64 chain_start = 0;
626 if (chain->segment_start != GST_CLOCK_TIME_NONE)
627 chain_start = chain->segment_start;
629 current_time = current_time - chain_start + chain->begin_time;
632 /* and store as the current position */
633 gst_segment_set_last_stop (&ogg->segment, GST_FORMAT_TIME, current_time);
635 GST_DEBUG_OBJECT (ogg, "ogg current time %" GST_TIME_FORMAT,
636 GST_TIME_ARGS (current_time));
639 /* return combined flow result */
645 GST_DEBUG_OBJECT (ogg, "Skipping empty packet");
646 cret = gst_ogg_demux_combine_flows (ogg, pad, GST_FLOW_OK);
651 GST_DEBUG_OBJECT (ogg, "skipping packet: no valid granule found yet");
652 cret = gst_ogg_demux_combine_flows (ogg, pad, GST_FLOW_OK);
657 GST_DEBUG_OBJECT (ogg,
658 "%p could not get buffer from peer %08x, %d (%s), combined %d (%s)",
659 pad, pad->map.serialno, ret, gst_flow_get_name (ret),
660 cret, gst_flow_get_name (cret));
665 /* submit a packet to the oggpad, this function will run the
666 * typefind code for the pad if this is the first packet for this
670 gst_ogg_pad_submit_packet (GstOggPad * pad, ogg_packet * packet)
673 GstFlowReturn ret = GST_FLOW_OK;
675 GstOggDemux *ogg = pad->ogg;
677 GST_DEBUG_OBJECT (ogg, "%p submit packet serial %08x", pad,
680 if (!pad->have_type) {
681 if (!ogg->have_fishead && packet->bytes == SKELETON_FISHEAD_SIZE &&
682 !memcmp (packet->packet, "fishead\0", 8)) {
683 gst_ogg_pad_parse_skeleton_fishead (pad, packet);
685 pad->have_type = gst_ogg_stream_setup_map (&pad->map, packet);
686 if (!pad->have_type) {
687 pad->map.caps = gst_caps_new_simple ("application/x-unknown", NULL);
690 gst_pad_set_caps (GST_PAD (pad), pad->map.caps);
692 GST_WARNING_OBJECT (ogg, "stream parser didn't create src pad caps");
696 if (ogg->have_fishead && packet->bytes >= SKELETON_FISBONE_MIN_SIZE &&
697 !memcmp (packet->packet, "fisbone\0", 8)) {
698 gst_ogg_pad_parse_skeleton_fisbone (pad, packet);
701 granule = gst_ogg_stream_granulepos_to_granule (&pad->map,
704 GST_DEBUG_OBJECT (ogg, "%p has granulepos %" G_GINT64_FORMAT, pad, granule);
705 pad->current_granule = granule;
708 /* restart header packet count when seeing a b_o_s page;
709 * particularly useful following a seek or even following chain finding */
711 GST_DEBUG_OBJECT (ogg, "b_o_s packet, resetting header packet count");
712 pad->map.n_header_packets_seen = 0;
713 if (!pad->map.have_headers) {
714 GST_DEBUG_OBJECT (ogg, "clearing header packets");
715 g_list_foreach (pad->map.headers, (GFunc) gst_mini_object_unref, NULL);
716 g_list_free (pad->map.headers);
717 pad->map.headers = NULL;
721 /* Overload the value of b_o_s in ogg_packet with a flag whether or
722 * not this is a header packet. Maybe some day this could be cleaned
724 packet->b_o_s = gst_ogg_stream_packet_is_header (&pad->map, packet);
725 if (!packet->b_o_s) {
726 pad->map.have_headers = TRUE;
727 if (pad->start_time == GST_CLOCK_TIME_NONE) {
728 gint64 duration = gst_ogg_stream_get_packet_duration (&pad->map, packet);
729 GST_DEBUG ("duration %" G_GINT64_FORMAT, duration);
730 if (duration != -1) {
731 pad->map.accumulated_granule += duration;
732 GST_DEBUG ("accumulated granule %" G_GINT64_FORMAT,
733 pad->map.accumulated_granule);
736 if (packet->granulepos != -1) {
737 ogg_int64_t start_granule;
740 granule = gst_ogg_stream_granulepos_to_granule (&pad->map,
743 if (granule > pad->map.accumulated_granule)
744 start_granule = granule - pad->map.accumulated_granule;
748 pad->start_time = gst_ogg_stream_granule_to_time (&pad->map,
750 GST_DEBUG ("start time %" G_GINT64_FORMAT, pad->start_time);
752 packet->granulepos = gst_ogg_stream_granule_to_granulepos (&pad->map,
753 pad->map.accumulated_granule, pad->keyframe_granule);
759 pad->map.n_header_packets_seen++;
760 if (!pad->map.have_headers) {
761 buf = gst_ogg_demux_buffer_from_packet (packet);
762 pad->map.headers = g_list_append (pad->map.headers, buf);
763 GST_DEBUG ("keeping header packet %d", pad->map.n_header_packets_seen);
767 /* we know the start_time of the pad data, see if we
768 * can activate the complete chain if this is a dynamic
770 if (pad->start_time != GST_CLOCK_TIME_NONE) {
771 GstOggChain *chain = pad->chain;
773 /* check if complete chain has start time */
774 if (chain == ogg->building_chain) {
776 /* see if we have enough info to activate the chain, we have enough info
777 * when all streams have a valid start time. */
778 if (gst_ogg_demux_collect_chain_info (ogg, chain)) {
781 GST_DEBUG_OBJECT (ogg, "segment_start: %" GST_TIME_FORMAT,
782 GST_TIME_ARGS (chain->segment_start));
783 GST_DEBUG_OBJECT (ogg, "segment_stop: %" GST_TIME_FORMAT,
784 GST_TIME_ARGS (chain->segment_stop));
785 GST_DEBUG_OBJECT (ogg, "segment_time: %" GST_TIME_FORMAT,
786 GST_TIME_ARGS (chain->begin_time));
788 /* create the newsegment event we are going to send out */
789 event = gst_event_new_new_segment (FALSE, ogg->segment.rate,
790 GST_FORMAT_TIME, chain->segment_start, chain->segment_stop,
792 gst_event_set_seqnum (event, ogg->seqnum);
794 gst_ogg_demux_activate_chain (ogg, chain, event);
796 ogg->building_chain = NULL;
801 /* if we are building a chain, store buffer for when we activate
802 * it. This path is taken if we operate in streaming mode. */
803 if (ogg->building_chain) {
804 /* bos packets where stored in the header list */
806 ret = gst_ogg_demux_queue_data (pad, packet);
808 /* else we are completely streaming to the peer */
810 ret = gst_ogg_demux_chain_peer (pad, packet);
815 /* flush at most @npackets from the stream layer. All packets if
819 gst_ogg_pad_stream_out (GstOggPad * pad, gint npackets)
821 GstFlowReturn result = GST_FLOW_OK;
822 gboolean done = FALSE;
831 ret = ogg_stream_packetout (&pad->map.stream, &packet);
834 GST_LOG_OBJECT (ogg, "packetout done");
838 GST_LOG_OBJECT (ogg, "packetout discont");
839 gst_ogg_chain_mark_discont (pad->chain);
842 GST_LOG_OBJECT (ogg, "packetout gave packet of size %ld", packet.bytes);
843 result = gst_ogg_pad_submit_packet (pad, &packet);
844 if (GST_FLOW_IS_FATAL (result))
845 goto could_not_submit;
848 GST_WARNING_OBJECT (ogg,
849 "invalid return value %d for ogg_stream_packetout, resetting stream",
851 gst_ogg_pad_reset (pad);
856 done = (npackets == 0);
864 GST_WARNING_OBJECT (ogg,
865 "could not submit packet for stream %08x, error: %d", pad->map.serialno,
867 gst_ogg_pad_reset (pad);
872 /* submit a page to an oggpad, this function will then submit all
873 * the packets in the page.
876 gst_ogg_pad_submit_page (GstOggPad * pad, ogg_page * page)
878 GstFlowReturn result = GST_FLOW_OK;
880 gboolean continued = FALSE;
884 /* for negative rates we read pages backwards and must therefore be carefull
885 * with continued pages */
886 if (ogg->segment.rate < 0.0) {
889 continued = ogg_page_continued (page);
891 /* number of completed packets in the page */
892 npackets = ogg_page_packets (page);
894 /* page is not continued so it contains at least one packet start. It's
895 * possible that no packet ends on this page (npackets == 0). In that
896 * case, the next (continued) page(s) we kept contain the remainder of the
897 * packets. We mark npackets=1 to make us start decoding the pages in the
898 * remainder of the algorithm. */
902 GST_LOG_OBJECT (ogg, "continued: %d, %d packets", continued, npackets);
905 GST_LOG_OBJECT (ogg, "no decodable packets, we need a previous page");
910 if (ogg_stream_pagein (&pad->map.stream, page) != 0)
913 /* flush all packets in the stream layer, this might not give a packet if
914 * the page had no packets finishing on the page (npackets == 0). */
915 result = gst_ogg_pad_stream_out (pad, 0);
917 if (pad->continued) {
920 /* now send the continued pages to the stream layer */
921 while (pad->continued) {
922 ogg_page *p = (ogg_page *) pad->continued->data;
924 GST_LOG_OBJECT (ogg, "submitting continued page %p", p);
925 if (ogg_stream_pagein (&pad->map.stream, p) != 0)
928 pad->continued = g_list_delete_link (pad->continued, pad->continued);
931 gst_ogg_page_free (p);
934 GST_LOG_OBJECT (ogg, "flushing last continued packet");
935 /* flush 1 continued packet in the stream layer */
936 result = gst_ogg_pad_stream_out (pad, 1);
938 /* flush all remaining packets, we pushed them in the previous round.
939 * We don't use _reset() because we still want to get the discont when
940 * we submit a next page. */
941 while (ogg_stream_packetout (&pad->map.stream, &packet) != 0);
945 /* keep continued pages (only in reverse mode) */
947 ogg_page *p = gst_ogg_page_copy (page);
949 GST_LOG_OBJECT (ogg, "keeping continued page %p", p);
950 pad->continued = g_list_prepend (pad->continued, p);
957 GST_WARNING_OBJECT (ogg,
958 "ogg stream choked on page (serial %08x), resetting stream",
960 gst_ogg_pad_reset (pad);
961 /* we continue to recover */
968 gst_ogg_chain_new (GstOggDemux * ogg)
970 GstOggChain *chain = g_new0 (GstOggChain, 1);
972 GST_DEBUG_OBJECT (ogg, "creating new chain %p", chain);
976 chain->have_bos = FALSE;
977 chain->streams = g_array_new (FALSE, TRUE, sizeof (GstOggPad *));
978 chain->begin_time = GST_CLOCK_TIME_NONE;
979 chain->segment_start = GST_CLOCK_TIME_NONE;
980 chain->segment_stop = GST_CLOCK_TIME_NONE;
981 chain->total_time = GST_CLOCK_TIME_NONE;
987 gst_ogg_chain_free (GstOggChain * chain)
991 for (i = 0; i < chain->streams->len; i++) {
992 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);
994 gst_object_unref (pad);
996 g_array_free (chain->streams, TRUE);
1001 gst_ogg_chain_mark_discont (GstOggChain * chain)
1005 for (i = 0; i < chain->streams->len; i++) {
1006 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);
1008 pad->discont = TRUE;
1009 pad->map.last_size = 0;
1014 gst_ogg_chain_reset (GstOggChain * chain)
1018 for (i = 0; i < chain->streams->len; i++) {
1019 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);
1021 gst_ogg_pad_reset (pad);
1026 gst_ogg_chain_new_stream (GstOggChain * chain, glong serialno)
1032 GST_DEBUG_OBJECT (chain->ogg, "creating new stream %08lx in chain %p",
1035 ret = g_object_new (GST_TYPE_OGG_PAD, NULL);
1036 /* we own this one */
1037 gst_object_ref (ret);
1038 gst_object_sink (ret);
1040 GST_PAD_DIRECTION (ret) = GST_PAD_SRC;
1041 ret->discont = TRUE;
1042 ret->map.last_size = 0;
1045 ret->ogg = chain->ogg;
1047 ret->map.serialno = serialno;
1048 if (ogg_stream_init (&ret->map.stream, serialno) != 0)
1051 name = g_strdup_printf ("serial_%08lx", serialno);
1052 gst_object_set_name (GST_OBJECT (ret), name);
1055 /* FIXME: either do something with it or remove it */
1056 list = gst_tag_list_new ();
1057 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_SERIAL, serialno,
1059 gst_tag_list_free (list);
1061 GST_DEBUG_OBJECT (chain->ogg,
1062 "created new ogg src %p for stream with serial %08lx", ret, serialno);
1064 g_array_append_val (chain->streams, ret);
1071 GST_ERROR ("Could not initialize ogg_stream struct for serial %08lx.",
1073 gst_object_unref (ret);
1079 gst_ogg_chain_get_stream (GstOggChain * chain, glong serialno)
1083 for (i = 0; i < chain->streams->len; i++) {
1084 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);
1086 if (pad->map.serialno == serialno)
1093 gst_ogg_chain_has_stream (GstOggChain * chain, glong serialno)
1095 return gst_ogg_chain_get_stream (chain, serialno) != NULL;
1098 #define CURRENT_CHAIN(ogg) (&g_array_index ((ogg)->chains, GstOggChain, (ogg)->current_chain))
1100 /* signals and args */
1113 static GstStaticPadTemplate ogg_demux_src_template_factory =
1114 GST_STATIC_PAD_TEMPLATE ("src_%d",
1117 GST_STATIC_CAPS_ANY);
1119 static GstStaticPadTemplate ogg_demux_sink_template_factory =
1120 GST_STATIC_PAD_TEMPLATE ("sink",
1123 GST_STATIC_CAPS ("application/ogg; application/x-annodex")
1126 static void gst_ogg_demux_finalize (GObject * object);
1128 static GstFlowReturn gst_ogg_demux_read_chain (GstOggDemux * ogg,
1129 GstOggChain ** chain);
1130 static GstFlowReturn gst_ogg_demux_read_end_chain (GstOggDemux * ogg,
1131 GstOggChain * chain);
1133 static gboolean gst_ogg_demux_sink_event (GstPad * pad, GstEvent * event);
1134 static void gst_ogg_demux_loop (GstOggPad * pad);
1135 static GstFlowReturn gst_ogg_demux_chain (GstPad * pad, GstBuffer * buffer);
1136 static gboolean gst_ogg_demux_sink_activate (GstPad * sinkpad);
1137 static gboolean gst_ogg_demux_sink_activate_pull (GstPad * sinkpad,
1139 static gboolean gst_ogg_demux_sink_activate_push (GstPad * sinkpad,
1141 static GstStateChangeReturn gst_ogg_demux_change_state (GstElement * element,
1142 GstStateChange transition);
1143 static void gst_ogg_demux_send_event (GstOggDemux * ogg, GstEvent * event);
1145 static void gst_ogg_print (GstOggDemux * demux);
1147 GST_BOILERPLATE (GstOggDemux, gst_ogg_demux, GstElement, GST_TYPE_ELEMENT);
1150 gst_ogg_demux_base_init (gpointer g_class)
1152 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
1154 gst_element_class_set_details (element_class, &gst_ogg_demux_details);
1156 gst_element_class_add_pad_template (element_class,
1157 gst_static_pad_template_get (&ogg_demux_sink_template_factory));
1158 gst_element_class_add_pad_template (element_class,
1159 gst_static_pad_template_get (&ogg_demux_src_template_factory));
1163 gst_ogg_demux_class_init (GstOggDemuxClass * klass)
1165 GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
1166 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1168 gstelement_class->change_state = gst_ogg_demux_change_state;
1169 gstelement_class->send_event = gst_ogg_demux_receive_event;
1171 gobject_class->finalize = gst_ogg_demux_finalize;
1175 gst_ogg_demux_init (GstOggDemux * ogg, GstOggDemuxClass * g_class)
1177 /* create the sink pad */
1179 gst_pad_new_from_static_template (&ogg_demux_sink_template_factory,
1182 gst_pad_set_event_function (ogg->sinkpad, gst_ogg_demux_sink_event);
1183 gst_pad_set_chain_function (ogg->sinkpad, gst_ogg_demux_chain);
1184 gst_pad_set_activate_function (ogg->sinkpad, gst_ogg_demux_sink_activate);
1185 gst_pad_set_activatepull_function (ogg->sinkpad,
1186 gst_ogg_demux_sink_activate_pull);
1187 gst_pad_set_activatepush_function (ogg->sinkpad,
1188 gst_ogg_demux_sink_activate_push);
1189 gst_element_add_pad (GST_ELEMENT (ogg), ogg->sinkpad);
1191 ogg->chain_lock = g_mutex_new ();
1192 ogg->chains = g_array_new (FALSE, TRUE, sizeof (GstOggChain *));
1194 ogg->newsegment = NULL;
1198 gst_ogg_demux_finalize (GObject * object)
1202 ogg = GST_OGG_DEMUX (object);
1204 g_array_free (ogg->chains, TRUE);
1205 g_mutex_free (ogg->chain_lock);
1206 ogg_sync_clear (&ogg->sync);
1208 if (ogg->newsegment)
1209 gst_event_unref (ogg->newsegment);
1211 G_OBJECT_CLASS (parent_class)->finalize (object);
1215 gst_ogg_demux_sink_event (GstPad * pad, GstEvent * event)
1220 ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad));
1222 switch (GST_EVENT_TYPE (event)) {
1223 case GST_EVENT_NEWSEGMENT:
1225 GST_DEBUG_OBJECT (ogg, "got a new segment event");
1226 ogg_sync_reset (&ogg->sync);
1227 gst_event_unref (event);
1232 GST_DEBUG_OBJECT (ogg, "got an EOS event");
1233 res = gst_pad_event_default (pad, event);
1234 if (ogg->current_chain == NULL) {
1235 GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL),
1236 ("can't get first chain"));
1241 res = gst_pad_event_default (pad, event);
1244 gst_object_unref (ogg);
1249 /* submit the given buffer to the ogg sync.
1251 * Returns the number of bytes submited.
1253 static GstFlowReturn
1254 gst_ogg_demux_submit_buffer (GstOggDemux * ogg, GstBuffer * buffer)
1259 GstFlowReturn ret = GST_FLOW_OK;
1261 size = GST_BUFFER_SIZE (buffer);
1262 data = GST_BUFFER_DATA (buffer);
1264 GST_DEBUG_OBJECT (ogg, "submitting %u bytes", size);
1265 if (G_UNLIKELY (size == 0))
1268 oggbuffer = ogg_sync_buffer (&ogg->sync, size);
1269 if (G_UNLIKELY (oggbuffer == NULL))
1272 memcpy (oggbuffer, data, size);
1273 if (G_UNLIKELY (ogg_sync_wrote (&ogg->sync, size) < 0))
1277 gst_buffer_unref (buffer);
1284 GST_ELEMENT_ERROR (ogg, STREAM, DECODE,
1285 (NULL), ("failed to get ogg sync buffer"));
1286 ret = GST_FLOW_ERROR;
1291 GST_ELEMENT_ERROR (ogg, STREAM, DECODE,
1292 (NULL), ("failed to write %d bytes to the sync buffer", size));
1293 ret = GST_FLOW_ERROR;
1298 /* in random access mode this code updates the current read position
1299 * and resets the ogg sync buffer so that the next read will happen
1300 * from this new location.
1303 gst_ogg_demux_seek (GstOggDemux * ogg, gint64 offset)
1305 GST_LOG_OBJECT (ogg, "seeking to %" G_GINT64_FORMAT, offset);
1307 ogg->offset = offset;
1308 ogg->read_offset = offset;
1309 ogg_sync_reset (&ogg->sync);
1312 /* read more data from the current offset and submit to
1313 * the ogg sync layer.
1315 static GstFlowReturn
1316 gst_ogg_demux_get_data (GstOggDemux * ogg, gint64 end_offset)
1321 GST_LOG_OBJECT (ogg,
1322 "get data %" G_GINT64_FORMAT " %" G_GINT64_FORMAT " %" G_GINT64_FORMAT,
1323 ogg->read_offset, ogg->length, end_offset);
1325 if (end_offset > 0 && ogg->read_offset >= end_offset)
1326 goto boundary_reached;
1328 if (ogg->read_offset == ogg->length)
1331 ret = gst_pad_pull_range (ogg->sinkpad, ogg->read_offset, CHUNKSIZE, &buffer);
1332 if (ret != GST_FLOW_OK)
1335 ogg->read_offset += GST_BUFFER_SIZE (buffer);
1337 ret = gst_ogg_demux_submit_buffer (ogg, buffer);
1344 GST_LOG_OBJECT (ogg, "reached boundary");
1345 return GST_FLOW_LIMIT;
1349 GST_LOG_OBJECT (ogg, "reached EOS");
1350 return GST_FLOW_UNEXPECTED;
1354 GST_WARNING_OBJECT (ogg, "got %d (%s) from pull range", ret,
1355 gst_flow_get_name (ret));
1360 /* Read the next page from the current offset.
1361 * boundary: number of bytes ahead we allow looking for;
1364 * @offset will contain the offset the next page starts at when this function
1365 * returns GST_FLOW_OK.
1367 * GST_FLOW_UNEXPECTED is returned on EOS.
1369 * GST_FLOW_LIMIT is returned when we did not find a page before the
1370 * boundary. If @boundary is -1, this is never returned.
1372 * Any other error returned while retrieving data from the peer is returned as
1375 static GstFlowReturn
1376 gst_ogg_demux_get_next_page (GstOggDemux * ogg, ogg_page * og, gint64 boundary,
1379 gint64 end_offset = -1;
1382 GST_LOG_OBJECT (ogg,
1383 "get next page, current offset %" G_GINT64_FORMAT ", bytes boundary %"
1384 G_GINT64_FORMAT, ogg->offset, boundary);
1387 end_offset = ogg->offset + boundary;
1392 if (end_offset > 0 && ogg->offset >= end_offset)
1393 goto boundary_reached;
1395 more = ogg_sync_pageseek (&ogg->sync, og);
1397 GST_LOG_OBJECT (ogg, "pageseek gave %ld", more);
1400 /* skipped n bytes */
1401 ogg->offset -= more;
1402 GST_LOG_OBJECT (ogg, "skipped %ld bytes, offset %" G_GINT64_FORMAT, more,
1404 } else if (more == 0) {
1405 /* we need more data */
1407 goto boundary_reached;
1409 GST_LOG_OBJECT (ogg, "need more data");
1410 ret = gst_ogg_demux_get_data (ogg, end_offset);
1411 if (ret != GST_FLOW_OK)
1414 gint64 res_offset = ogg->offset;
1416 /* got a page. Return the offset at the page beginning,
1417 advance the internal offset past the page end */
1419 *offset = res_offset;
1422 ogg->offset += more;
1424 GST_LOG_OBJECT (ogg,
1425 "got page at %" G_GINT64_FORMAT ", serial %08x, end at %"
1426 G_GINT64_FORMAT ", granule %" G_GINT64_FORMAT, res_offset,
1427 ogg_page_serialno (og), ogg->offset,
1428 (gint64) ogg_page_granulepos (og));
1432 GST_LOG_OBJECT (ogg, "returning %d", ret);
1439 GST_LOG_OBJECT (ogg,
1440 "offset %" G_GINT64_FORMAT " >= end_offset %" G_GINT64_FORMAT,
1441 ogg->offset, end_offset);
1442 return GST_FLOW_LIMIT;
1446 /* from the current offset, find the previous page, seeking backwards
1447 * until we find the page.
1449 static GstFlowReturn
1450 gst_ogg_demux_get_prev_page (GstOggDemux * ogg, ogg_page * og, gint64 * offset)
1453 gint64 begin = ogg->offset;
1455 gint64 cur_offset = -1;
1457 GST_LOG_OBJECT (ogg, "getting page before %" G_GINT64_FORMAT, begin);
1459 while (cur_offset == -1) {
1464 /* seek CHUNKSIZE back */
1465 gst_ogg_demux_seek (ogg, begin);
1467 /* now continue reading until we run out of data, if we find a page
1468 * start, we save it. It might not be the final page as there could be
1469 * another page after this one. */
1470 while (ogg->offset < end) {
1474 gst_ogg_demux_get_next_page (ogg, og, end - ogg->offset, &new_offset);
1475 /* we hit the upper limit, offset contains the last page start */
1476 if (ret == GST_FLOW_LIMIT) {
1477 GST_LOG_OBJECT (ogg, "hit limit");
1480 /* something went wrong */
1481 if (ret == GST_FLOW_UNEXPECTED) {
1483 GST_LOG_OBJECT (ogg, "got unexpected");
1484 } else if (ret != GST_FLOW_OK) {
1485 GST_LOG_OBJECT (ogg, "got error %d", ret);
1489 GST_LOG_OBJECT (ogg, "found page at %" G_GINT64_FORMAT, new_offset);
1491 /* offset is next page start */
1492 cur_offset = new_offset;
1496 GST_LOG_OBJECT (ogg, "found previous page at %" G_GINT64_FORMAT, cur_offset);
1498 /* we have the offset. Actually snork and hold the page now */
1499 gst_ogg_demux_seek (ogg, cur_offset);
1500 ret = gst_ogg_demux_get_next_page (ogg, og, -1, NULL);
1501 if (ret != GST_FLOW_OK) {
1502 GST_WARNING_OBJECT (ogg, "can't get last page at %" G_GINT64_FORMAT,
1504 /* this shouldn't be possible */
1509 *offset = cur_offset;
1515 gst_ogg_demux_deactivate_current_chain (GstOggDemux * ogg)
1518 GstOggChain *chain = ogg->current_chain;
1523 GST_DEBUG_OBJECT (ogg, "deactivating chain %p", chain);
1525 /* send EOS on all the pads */
1526 for (i = 0; i < chain->streams->len; i++) {
1527 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);
1530 if (pad->map.is_skeleton)
1533 event = gst_event_new_eos ();
1534 gst_event_set_seqnum (event, ogg->seqnum);
1535 gst_pad_push_event (GST_PAD_CAST (pad), event);
1537 GST_DEBUG_OBJECT (ogg, "removing pad %" GST_PTR_FORMAT, pad);
1539 /* deactivate first */
1540 gst_pad_set_active (GST_PAD_CAST (pad), FALSE);
1542 gst_element_remove_pad (GST_ELEMENT (ogg), GST_PAD_CAST (pad));
1544 /* if we cannot seek back to the chain, we can destroy the chain
1546 if (!ogg->seekable) {
1547 gst_ogg_chain_free (chain);
1549 ogg->current_chain = NULL;
1555 gst_ogg_demux_activate_chain (GstOggDemux * ogg, GstOggChain * chain,
1560 if (chain == ogg->current_chain) {
1562 gst_event_unref (event);
1566 /* FIXME, should not be called with NULL */
1567 if (chain != NULL) {
1568 GST_DEBUG_OBJECT (ogg, "activating chain %p", chain);
1570 /* first add the pads */
1571 for (i = 0; i < chain->streams->len; i++) {
1573 GstStructure *structure;
1575 pad = g_array_index (chain->streams, GstOggPad *, i);
1577 if (pad->map.is_skeleton)
1580 GST_DEBUG_OBJECT (ogg, "adding pad %" GST_PTR_FORMAT, pad);
1583 pad->discont = TRUE;
1584 pad->map.last_size = 0;
1585 pad->last_ret = GST_FLOW_OK;
1587 structure = gst_caps_get_structure (GST_PAD_CAPS (pad), 0);
1589 gst_structure_has_name (structure, "application/x-ogm-text") ||
1590 gst_structure_has_name (structure, "text/x-cmml") ||
1591 gst_structure_has_name (structure, "subtitle/x-kate") ||
1592 gst_structure_has_name (structure, "application/x-kate");
1594 /* activate first */
1595 gst_pad_set_active (GST_PAD_CAST (pad), TRUE);
1597 gst_element_add_pad (GST_ELEMENT (ogg), GST_PAD_CAST (pad));
1601 /* after adding the new pads, remove the old pads */
1602 gst_ogg_demux_deactivate_current_chain (ogg);
1604 ogg->current_chain = chain;
1606 /* we are finished now */
1607 gst_element_no_more_pads (GST_ELEMENT (ogg));
1609 /* FIXME, must be sent from the streaming thread */
1611 gst_ogg_demux_send_event (ogg, event);
1613 gst_element_found_tags (GST_ELEMENT_CAST (ogg),
1614 gst_tag_list_new_full (GST_TAG_CONTAINER_FORMAT, "Ogg", NULL));
1617 GST_DEBUG_OBJECT (ogg, "starting chain");
1619 /* then send out any headers and queued buffers */
1620 for (i = 0; i < chain->streams->len; i++) {
1624 pad = g_array_index (chain->streams, GstOggPad *, i);
1626 GST_DEBUG_OBJECT (ogg, "pushing headers");
1627 /* ref and push headers */
1628 for (walk = pad->map.headers; walk; walk = g_list_next (walk)) {
1629 GstBuffer *buffer = GST_BUFFER (walk->data);
1632 GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
1633 pad->discont = FALSE;
1635 /* we don't care about the return value here */
1636 gst_pad_push (GST_PAD_CAST (pad), gst_buffer_ref (buffer));
1639 GST_DEBUG_OBJECT (ogg, "pushing queued buffers");
1640 /* push queued buffers */
1641 for (walk = pad->map.queued; walk; walk = g_list_next (walk)) {
1642 GstBuffer *buffer = GST_BUFFER (walk->data);
1644 /* we don't care about the return value here */
1645 gst_pad_push (GST_PAD_CAST (pad), buffer);
1647 /* and free the queued buffers */
1648 g_list_free (pad->map.queued);
1649 pad->map.queued = NULL;
1655 do_binary_search (GstOggDemux * ogg, GstOggChain * chain, gint64 begin,
1656 gint64 end, gint64 begintime, gint64 endtime, gint64 target,
1665 GST_DEBUG_OBJECT (ogg,
1666 "chain offset %" G_GINT64_FORMAT ", end offset %" G_GINT64_FORMAT, begin,
1668 GST_DEBUG_OBJECT (ogg,
1669 "chain begin time %" GST_TIME_FORMAT ", end time %" GST_TIME_FORMAT,
1670 GST_TIME_ARGS (begintime), GST_TIME_ARGS (endtime));
1671 GST_DEBUG_OBJECT (ogg, "target %" GST_TIME_FORMAT, GST_TIME_ARGS (target));
1673 /* perform the seek */
1674 while (begin < end) {
1677 if ((end - begin < CHUNKSIZE) || (endtime == begintime)) {
1680 /* take a (pretty decent) guess, avoiding overflow */
1681 gint64 rate = (end - begin) * GST_MSECOND / (endtime - begintime);
1683 bisect = (target - begintime) / GST_MSECOND * rate + begin - CHUNKSIZE;
1685 if (bisect <= begin)
1687 GST_DEBUG_OBJECT (ogg, "Initial guess: %" G_GINT64_FORMAT, bisect);
1689 gst_ogg_demux_seek (ogg, bisect);
1691 while (begin < end) {
1694 GST_DEBUG_OBJECT (ogg,
1695 "after seek, bisect %" G_GINT64_FORMAT ", begin %" G_GINT64_FORMAT
1696 ", end %" G_GINT64_FORMAT, bisect, begin, end);
1698 ret = gst_ogg_demux_get_next_page (ogg, &og, end - ogg->offset, &result);
1699 GST_LOG_OBJECT (ogg, "looking for next page returned %" G_GINT64_FORMAT,
1702 if (ret == GST_FLOW_LIMIT) {
1703 /* we hit the upper limit, go back a bit */
1704 if (bisect <= begin + 1) {
1705 end = begin; /* found it */
1710 bisect -= CHUNKSIZE;
1711 if (bisect <= begin)
1714 gst_ogg_demux_seek (ogg, bisect);
1716 } else if (ret == GST_FLOW_OK) {
1717 /* found offset of next ogg page */
1719 GstClockTime granuletime;
1722 /* get the granulepos */
1723 GST_LOG_OBJECT (ogg, "found next ogg page at %" G_GINT64_FORMAT,
1725 granulepos = ogg_page_granulepos (&og);
1726 if (granulepos == -1) {
1727 GST_LOG_OBJECT (ogg, "granulepos of next page is -1");
1731 /* get the stream */
1732 pad = gst_ogg_chain_get_stream (chain, ogg_page_serialno (&og));
1733 if (pad == NULL || pad->map.is_skeleton)
1736 /* convert granulepos to time */
1737 granuletime = gst_ogg_stream_get_end_time_for_granulepos (&pad->map,
1739 if (granuletime < pad->start_time)
1742 GST_LOG_OBJECT (ogg, "granulepos %" G_GINT64_FORMAT " maps to time %"
1743 GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (granuletime));
1745 granuletime -= pad->start_time;
1746 granuletime += begintime;
1748 GST_DEBUG_OBJECT (ogg,
1749 "found page with granule %" G_GINT64_FORMAT " and time %"
1750 GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (granuletime));
1752 if (granuletime < target) {
1753 best = result; /* raw offset of packet with granulepos */
1754 begin = ogg->offset; /* raw offset of next page */
1755 begintime = granuletime;
1757 bisect = begin; /* *not* begin + 1 */
1759 if (bisect <= begin + 1) {
1760 end = begin; /* found it */
1762 if (end == ogg->offset) { /* we're pretty close - we'd be stuck in */
1764 bisect -= CHUNKSIZE; /* an endless loop otherwise. */
1765 if (bisect <= begin)
1767 gst_ogg_demux_seek (ogg, bisect);
1770 endtime = granuletime;
1779 GST_DEBUG_OBJECT (ogg, "seeking to %" G_GINT64_FORMAT, best);
1780 gst_ogg_demux_seek (ogg, best);
1788 GST_DEBUG_OBJECT (ogg, "got a seek error");
1794 * do seek to time @position, return FALSE or chain and TRUE
1797 gst_ogg_demux_do_seek (GstOggDemux * ogg, GstSegment * segment,
1798 gboolean accurate, gboolean keyframe, GstOggChain ** rchain)
1801 GstOggChain *chain = NULL;
1803 gint64 begintime, endtime;
1804 gint64 target, keytarget;
1809 gint i, pending, len;
1811 position = segment->last_stop;
1813 /* first find the chain to search in */
1814 total = ogg->total_time;
1815 if (ogg->chains->len == 0)
1818 for (i = ogg->chains->len - 1; i >= 0; i--) {
1819 chain = g_array_index (ogg->chains, GstOggChain *, i);
1820 total -= chain->total_time;
1821 if (position >= total)
1825 /* first step, locate page containing the required data */
1826 begin = chain->offset;
1827 end = chain->end_offset;
1828 begintime = chain->begin_time;
1829 endtime = begintime + chain->total_time;
1830 target = position - total + begintime;
1832 if (!do_binary_search (ogg, chain, begin, end, begintime, endtime, target,
1836 /* second step: find pages for all streams, we use the keyframe_granule to keep
1837 * track of which ones we saw. If we have seen a page for each stream we can
1838 * calculate the positions of each keyframe. */
1839 GST_DEBUG_OBJECT (ogg, "find keyframes");
1840 len = pending = chain->streams->len;
1842 /* figure out where the keyframes are */
1849 GstClockTime keyframe_time, granule_time;
1851 ret = gst_ogg_demux_get_next_page (ogg, &og, end - ogg->offset, &result);
1852 GST_LOG_OBJECT (ogg, "looking for next page returned %" G_GINT64_FORMAT,
1854 if (ret == GST_FLOW_LIMIT) {
1855 GST_LOG_OBJECT (ogg, "reached limit");
1859 /* get the stream */
1860 pad = gst_ogg_chain_get_stream (chain, ogg_page_serialno (&og));
1864 if (pad->map.is_skeleton)
1867 granulepos = ogg_page_granulepos (&og);
1868 if (granulepos == -1) {
1869 GST_LOG_OBJECT (ogg, "granulepos of next page is -1");
1873 /* in reverse we want to go past the page with the lower timestamp */
1874 if (segment->rate < 0.0) {
1875 /* get time for this pad */
1876 granule_time = gst_ogg_stream_get_end_time_for_granulepos (&pad->map,
1879 GST_LOG_OBJECT (ogg,
1880 "looking at page with ts %" GST_TIME_FORMAT ", target %"
1881 GST_TIME_FORMAT, GST_TIME_ARGS (granule_time),
1882 GST_TIME_ARGS (target));
1883 if (granule_time < target)
1887 /* we've seen this pad before */
1888 if (pad->keyframe_granule != -1)
1891 /* convert granule of this pad to the granule of the keyframe */
1892 pad->keyframe_granule = gst_ogg_stream_granulepos_to_key_granule (&pad->map,
1894 GST_LOG_OBJECT (ogg, "marking stream granule %" G_GINT64_FORMAT,
1895 pad->keyframe_granule);
1897 /* get time of the keyframe */
1899 gst_ogg_stream_granule_to_time (&pad->map, pad->keyframe_granule);
1900 GST_LOG_OBJECT (ogg, "stream %08x granule time %" GST_TIME_FORMAT,
1901 pad->map.serialno, GST_TIME_ARGS (keyframe_time));
1903 /* collect smallest value */
1904 if (keyframe_time != -1) {
1905 keyframe_time += begintime;
1906 if (keyframe_time < keytarget)
1907 keytarget = keyframe_time;
1916 /* for negative rates we will get to the keyframe backwards */
1917 if (segment->rate < 0.0)
1920 if (keytarget != target) {
1921 GST_LOG_OBJECT (ogg, "final seek to target %" GST_TIME_FORMAT,
1922 GST_TIME_ARGS (keytarget));
1924 /* last step, seek to the location of the keyframe */
1925 if (!do_binary_search (ogg, chain, begin, end, begintime, endtime,
1929 /* seek back to previous position */
1930 GST_LOG_OBJECT (ogg, "keyframe on target");
1931 gst_ogg_demux_seek (ogg, best);
1936 if (segment->rate > 0.0)
1937 segment->time = keytarget;
1938 segment->last_stop = keytarget - begintime;
1947 GST_DEBUG_OBJECT (ogg, "no chains");
1952 GST_DEBUG_OBJECT (ogg, "got a seek error");
1957 /* does not take ownership of the event */
1959 gst_ogg_demux_perform_seek (GstOggDemux * ogg, GstEvent * event)
1961 GstOggChain *chain = NULL;
1963 gboolean flush, accurate, keyframe;
1967 GstSeekType cur_type, stop_type;
1974 GST_DEBUG_OBJECT (ogg, "seek with event");
1976 gst_event_parse_seek (event, &rate, &format, &flags,
1977 &cur_type, &cur, &stop_type, &stop);
1979 /* we can only seek on time */
1980 if (format != GST_FORMAT_TIME) {
1981 GST_DEBUG_OBJECT (ogg, "can only seek on TIME");
1984 seqnum = gst_event_get_seqnum (event);
1986 GST_DEBUG_OBJECT (ogg, "seek without event");
1990 seqnum = gst_util_seqnum_next ();
1993 GST_DEBUG_OBJECT (ogg, "seek, rate %g", rate);
1995 flush = flags & GST_SEEK_FLAG_FLUSH;
1996 accurate = flags & GST_SEEK_FLAG_ACCURATE;
1997 keyframe = flags & GST_SEEK_FLAG_KEY_UNIT;
1999 /* first step is to unlock the streaming thread if it is
2000 * blocked in a chain call, we do this by starting the flush. because
2001 * we cannot yet hold any streaming lock, we have to protect the chains
2002 * with their own lock. */
2006 tevent = gst_event_new_flush_start ();
2007 gst_event_set_seqnum (tevent, seqnum);
2009 gst_event_ref (tevent);
2010 gst_pad_push_event (ogg->sinkpad, tevent);
2012 GST_CHAIN_LOCK (ogg);
2013 for (i = 0; i < ogg->chains->len; i++) {
2014 GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i);
2017 for (j = 0; j < chain->streams->len; j++) {
2018 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, j);
2020 gst_event_ref (tevent);
2021 gst_pad_push_event (GST_PAD (pad), tevent);
2024 GST_CHAIN_UNLOCK (ogg);
2026 gst_event_unref (tevent);
2028 gst_pad_pause_task (ogg->sinkpad);
2031 /* now grab the stream lock so that streaming cannot continue, for
2032 * non flushing seeks when the element is in PAUSED this could block
2034 GST_PAD_STREAM_LOCK (ogg->sinkpad);
2036 if (ogg->segment_running && !flush) {
2037 /* create the segment event to close the current segment */
2038 if ((chain = ogg->current_chain)) {
2040 gint64 chain_start = 0;
2042 if (chain->segment_start != GST_CLOCK_TIME_NONE)
2043 chain_start = chain->segment_start;
2045 newseg = gst_event_new_new_segment (TRUE, ogg->segment.rate,
2046 GST_FORMAT_TIME, ogg->segment.start + chain_start,
2047 ogg->segment.last_stop + chain_start, ogg->segment.time);
2048 /* set the seqnum of the running segment */
2049 gst_event_set_seqnum (newseg, ogg->seqnum);
2051 /* send segment on old chain, FIXME, must be sent from streaming thread. */
2052 gst_ogg_demux_send_event (ogg, newseg);
2057 gst_segment_set_seek (&ogg->segment, rate, format, flags,
2058 cur_type, cur, stop_type, stop, &update);
2061 GST_DEBUG_OBJECT (ogg, "segment positions set to %" GST_TIME_FORMAT "-%"
2062 GST_TIME_FORMAT, GST_TIME_ARGS (ogg->segment.start),
2063 GST_TIME_ARGS (ogg->segment.stop));
2065 /* we need to stop flushing on the srcpad as we're going to use it
2066 * next. We can do this as we have the STREAM lock now. */
2068 tevent = gst_event_new_flush_stop ();
2069 gst_event_set_seqnum (tevent, seqnum);
2070 gst_pad_push_event (ogg->sinkpad, tevent);
2076 /* reset all ogg streams now, need to do this from within the lock to
2077 * make sure the streaming thread is not messing with the stream */
2078 for (i = 0; i < ogg->chains->len; i++) {
2079 GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i);
2081 gst_ogg_chain_reset (chain);
2085 /* for reverse we will already seek accurately */
2086 res = gst_ogg_demux_do_seek (ogg, &ogg->segment, accurate, keyframe, &chain);
2088 /* seek failed, make sure we continue the current chain */
2090 GST_DEBUG_OBJECT (ogg, "seek failed");
2091 chain = ogg->current_chain;
2093 GST_DEBUG_OBJECT (ogg, "seek success");
2099 /* now we have a new position, prepare for streaming again */
2104 gint64 last_stop, begin_time;
2106 /* we have to send the flush to the old chain, not the new one */
2108 tevent = gst_event_new_flush_stop ();
2109 gst_event_set_seqnum (tevent, seqnum);
2110 gst_ogg_demux_send_event (ogg, tevent);
2113 /* we need this to see how far inside the chain we need to start */
2114 if (chain->begin_time != GST_CLOCK_TIME_NONE)
2115 begin_time = chain->begin_time;
2119 /* segment.start gives the start over all chains, we calculate the amount
2120 * of time into this chain we need to start */
2121 start = ogg->segment.start - begin_time;
2122 if (chain->segment_start != GST_CLOCK_TIME_NONE)
2123 start += chain->segment_start;
2125 if ((stop = ogg->segment.stop) == -1)
2126 stop = ogg->segment.duration;
2128 /* segment.stop gives the stop time over all chains, calculate the amount of
2129 * time we need to stop in this chain */
2131 if (stop > begin_time)
2135 stop += chain->segment_start;
2136 /* we must stop when this chain ends and switch to the next chain to play
2137 * the remainder of the segment. */
2138 stop = MIN (stop, chain->segment_stop);
2141 last_stop = ogg->segment.last_stop - begin_time;
2142 if (chain->segment_start != GST_CLOCK_TIME_NONE)
2143 last_stop += chain->segment_start;
2145 /* create the segment event we are going to send out */
2146 if (ogg->segment.rate >= 0.0)
2147 event = gst_event_new_new_segment (FALSE, ogg->segment.rate,
2148 ogg->segment.format, last_stop, stop, ogg->segment.time);
2150 event = gst_event_new_new_segment (FALSE, ogg->segment.rate,
2151 ogg->segment.format, start, last_stop, ogg->segment.time);
2153 gst_event_set_seqnum (event, seqnum);
2155 if (chain != ogg->current_chain) {
2156 /* switch to different chain, send segment on new chain */
2157 gst_ogg_demux_activate_chain (ogg, chain, event);
2159 /* mark discont and send segment on current chain */
2160 gst_ogg_chain_mark_discont (chain);
2161 /* This event should be sent from the streaming thread (sink pad task) */
2162 if (ogg->newsegment)
2163 gst_event_unref (ogg->newsegment);
2164 ogg->newsegment = event;
2167 /* notify start of new segment */
2168 if (ogg->segment.flags & GST_SEEK_FLAG_SEGMENT) {
2169 GstMessage *message;
2171 message = gst_message_new_segment_start (GST_OBJECT (ogg),
2172 GST_FORMAT_TIME, ogg->segment.last_stop);
2173 gst_message_set_seqnum (message, seqnum);
2175 gst_element_post_message (GST_ELEMENT (ogg), message);
2178 ogg->segment_running = TRUE;
2179 ogg->seqnum = seqnum;
2180 /* restart our task since it might have been stopped when we did the
2182 gst_pad_start_task (ogg->sinkpad, (GstTaskFunction) gst_ogg_demux_loop,
2186 /* streaming can continue now */
2187 GST_PAD_STREAM_UNLOCK (ogg->sinkpad);
2193 GST_DEBUG_OBJECT (ogg, "seek failed");
2198 GST_DEBUG_OBJECT (ogg, "no chain to seek in");
2199 GST_PAD_STREAM_UNLOCK (ogg->sinkpad);
2204 /* finds each bitstream link one at a time using a bisection search
2205 * (has to begin by knowing the offset of the lb's initial page).
2206 * Recurses for each link so it can alloc the link storage after
2207 * finding them all, then unroll and fill the cache at the same time
2209 static GstFlowReturn
2210 gst_ogg_demux_bisect_forward_serialno (GstOggDemux * ogg,
2211 gint64 begin, gint64 searched, gint64 end, GstOggChain * chain, glong m)
2213 gint64 endsearched = end;
2218 GstOggChain *nextchain;
2220 GST_LOG_OBJECT (ogg,
2221 "bisect begin: %" G_GINT64_FORMAT ", searched: %" G_GINT64_FORMAT
2222 ", end %" G_GINT64_FORMAT ", chain: %p", begin, searched, end, chain);
2224 /* the below guards against garbage seperating the last and
2225 * first pages of two links. */
2226 while (searched < endsearched) {
2229 if (endsearched - searched < CHUNKSIZE) {
2232 bisect = (searched + endsearched) / 2;
2235 gst_ogg_demux_seek (ogg, bisect);
2236 ret = gst_ogg_demux_get_next_page (ogg, &og, -1, &offset);
2238 if (ret == GST_FLOW_UNEXPECTED) {
2239 endsearched = bisect;
2240 } else if (ret == GST_FLOW_OK) {
2241 glong serial = ogg_page_serialno (&og);
2243 if (!gst_ogg_chain_has_stream (chain, serial)) {
2244 endsearched = bisect;
2247 searched = offset + og.header_len + og.body_len;
2253 GST_LOG_OBJECT (ogg, "current chain ends at %" G_GINT64_FORMAT, searched);
2255 chain->end_offset = searched;
2256 ret = gst_ogg_demux_read_end_chain (ogg, chain);
2257 if (ret != GST_FLOW_OK)
2260 GST_LOG_OBJECT (ogg, "found begin at %" G_GINT64_FORMAT, next);
2262 gst_ogg_demux_seek (ogg, next);
2263 ret = gst_ogg_demux_read_chain (ogg, &nextchain);
2264 if (ret == GST_FLOW_UNEXPECTED) {
2267 GST_LOG_OBJECT (ogg, "no next chain");
2268 } else if (ret != GST_FLOW_OK)
2271 if (searched < end && nextchain != NULL) {
2272 ret = gst_ogg_demux_bisect_forward_serialno (ogg, next, ogg->offset,
2273 end, nextchain, m + 1);
2274 if (ret != GST_FLOW_OK)
2277 GST_LOG_OBJECT (ogg, "adding chain %p", chain);
2279 g_array_insert_val (ogg->chains, 0, chain);
2285 /* read a chain from the ogg file. This code will
2286 * read all BOS pages and will create and return a GstOggChain
2287 * structure with the results.
2289 * This function will also read N pages from each stream in the
2290 * chain and submit them to the decoders. When the decoder has
2291 * decoded the first buffer, we know the timestamp of the first
2292 * page in the chain.
2294 static GstFlowReturn
2295 gst_ogg_demux_read_chain (GstOggDemux * ogg, GstOggChain ** res_chain)
2298 GstOggChain *chain = NULL;
2299 gint64 offset = ogg->offset;
2304 GST_LOG_OBJECT (ogg, "reading chain at %" G_GINT64_FORMAT, offset);
2306 /* first read the BOS pages, do typefind on them, create
2307 * the decoders, send data to the decoders. */
2312 ret = gst_ogg_demux_get_next_page (ogg, &op, -1, NULL);
2313 if (ret != GST_FLOW_OK) {
2314 GST_WARNING_OBJECT (ogg, "problem reading BOS page: ret=%d", ret);
2317 if (!ogg_page_bos (&op)) {
2318 GST_WARNING_OBJECT (ogg, "page is not BOS page");
2319 /* if we did not find a chain yet, assume this is a bogus stream and
2322 ret = GST_FLOW_UNEXPECTED;
2326 if (chain == NULL) {
2327 chain = gst_ogg_chain_new (ogg);
2328 chain->offset = offset;
2331 serial = ogg_page_serialno (&op);
2332 if (gst_ogg_chain_get_stream (chain, serial) != NULL) {
2333 GST_WARNING_OBJECT (ogg, "found serial %08lx BOS page twice, ignoring",
2338 pad = gst_ogg_chain_new_stream (chain, serial);
2339 gst_ogg_pad_submit_page (pad, &op);
2342 if (ret != GST_FLOW_OK || chain == NULL) {
2343 if (ret == GST_FLOW_OK) {
2344 GST_WARNING_OBJECT (ogg, "no chain was found");
2345 ret = GST_FLOW_ERROR;
2346 } else if (ret != GST_FLOW_UNEXPECTED) {
2347 GST_WARNING_OBJECT (ogg, "failed to read chain");
2349 GST_DEBUG_OBJECT (ogg, "done reading chains");
2352 gst_ogg_chain_free (chain);
2359 chain->have_bos = TRUE;
2360 GST_LOG_OBJECT (ogg, "read bos pages, init decoder now");
2362 /* now read pages until we receive a buffer from each of the
2363 * stream decoders, this will tell us the timestamp of the
2364 * first packet in the chain then */
2366 /* save the offset to the first non bos page in the chain: if searching for
2367 * pad->first_time we read past the end of the chain, we'll seek back to this
2370 offset = ogg->offset;
2375 gboolean known_serial = FALSE;
2378 serial = ogg_page_serialno (&op);
2380 for (i = 0; i < chain->streams->len; i++) {
2381 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);
2383 GST_LOG_OBJECT (ogg, "serial %08x time %" GST_TIME_FORMAT,
2384 pad->map.serialno, GST_TIME_ARGS (pad->start_time));
2386 if (pad->map.serialno == serial) {
2387 known_serial = TRUE;
2389 /* submit the page now, this will fill in the start_time when the
2390 * internal decoder finds it */
2391 gst_ogg_pad_submit_page (pad, &op);
2393 if (!pad->map.is_skeleton && pad->start_time == -1
2394 && ogg_page_eos (&op)) {
2395 /* got EOS on a pad before we could find its start_time.
2396 * We have no chance of finding a start_time for every pad so
2397 * stop searching for the other start_time(s).
2403 /* the timestamp will be filled in when we submit the pages */
2404 if (!pad->map.is_skeleton)
2405 done &= (pad->start_time != GST_CLOCK_TIME_NONE);
2407 GST_LOG_OBJECT (ogg, "done %08x now %d", pad->map.serialno, done);
2410 /* we read a page not belonging to the current chain: seek back to the
2411 * beginning of the chain
2413 if (!known_serial) {
2414 GST_LOG_OBJECT (ogg, "unknown serial %08lx", serial);
2415 gst_ogg_demux_seek (ogg, offset);
2420 ret = gst_ogg_demux_get_next_page (ogg, &op, -1, NULL);
2421 if (ret != GST_FLOW_OK)
2425 GST_LOG_OBJECT (ogg, "done reading chain");
2426 /* now we can fill in the missing info using queries */
2427 for (i = 0; i < chain->streams->len; i++) {
2428 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);
2430 if (pad->map.is_skeleton)
2433 pad->mode = GST_OGG_PAD_MODE_STREAMING;
2442 /* read the last pages from the ogg stream to get the final
2445 static GstFlowReturn
2446 gst_ogg_demux_read_end_chain (GstOggDemux * ogg, GstOggChain * chain)
2448 gint64 begin = chain->end_offset;
2450 gint64 last_granule = -1;
2451 GstOggPad *last_pad = NULL;
2453 gboolean done = FALSE;
2462 gst_ogg_demux_seek (ogg, begin);
2464 /* now continue reading until we run out of data, if we find a page
2465 * start, we save it. It might not be the final page as there could be
2466 * another page after this one. */
2467 while (ogg->offset < end) {
2468 ret = gst_ogg_demux_get_next_page (ogg, &og, end - ogg->offset, NULL);
2470 if (ret == GST_FLOW_LIMIT)
2472 if (ret != GST_FLOW_OK)
2475 for (i = 0; i < chain->streams->len; i++) {
2476 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);
2478 if (pad->map.is_skeleton)
2481 if (pad->map.serialno == ogg_page_serialno (&og)) {
2482 gint64 granulepos = ogg_page_granulepos (&og);
2484 if (last_granule == -1 || last_granule < granulepos) {
2485 last_granule = granulepos;
2488 if (last_granule != -1) {
2498 chain->segment_stop =
2499 gst_ogg_stream_get_end_time_for_granulepos (&last_pad->map,
2502 chain->segment_stop = GST_CLOCK_TIME_NONE;
2505 GST_INFO ("segment stop %" G_GUINT64_FORMAT, chain->segment_stop);
2510 /* find a pad with a given serial number
2513 gst_ogg_demux_find_pad (GstOggDemux * ogg, int serialno)
2518 /* first look in building chain if any */
2519 if (ogg->building_chain) {
2520 pad = gst_ogg_chain_get_stream (ogg->building_chain, serialno);
2525 /* then look in current chain if any */
2526 if (ogg->current_chain) {
2527 pad = gst_ogg_chain_get_stream (ogg->current_chain, serialno);
2532 for (i = 0; i < ogg->chains->len; i++) {
2533 GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i);
2535 pad = gst_ogg_chain_get_stream (chain, serialno);
2542 /* find a chain with a given serial number
2544 static GstOggChain *
2545 gst_ogg_demux_find_chain (GstOggDemux * ogg, int serialno)
2549 pad = gst_ogg_demux_find_pad (ogg, serialno);
2556 /* returns TRUE if all streams have valid start time */
2558 gst_ogg_demux_collect_chain_info (GstOggDemux * ogg, GstOggChain * chain)
2561 gboolean res = TRUE;
2563 chain->total_time = GST_CLOCK_TIME_NONE;
2564 chain->segment_start = G_MAXUINT64;
2566 GST_DEBUG_OBJECT (ogg, "trying to collect chain info");
2568 for (i = 0; i < chain->streams->len; i++) {
2569 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);
2571 if (pad->map.is_skeleton)
2574 /* can do this if the pad start time is not defined */
2575 if (pad->start_time == GST_CLOCK_TIME_NONE)
2578 chain->segment_start = MIN (chain->segment_start, pad->start_time);
2581 if (chain->segment_stop != GST_CLOCK_TIME_NONE
2582 && chain->segment_start != G_MAXUINT64)
2583 chain->total_time = chain->segment_stop - chain->segment_start;
2585 GST_DEBUG ("total time %" G_GUINT64_FORMAT, chain->total_time);
2587 GST_DEBUG_OBJECT (ogg, "return %d", res);
2593 gst_ogg_demux_collect_info (GstOggDemux * ogg)
2597 /* collect all info */
2598 ogg->total_time = 0;
2600 for (i = 0; i < ogg->chains->len; i++) {
2601 GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i);
2603 chain->begin_time = ogg->total_time;
2605 gst_ogg_demux_collect_chain_info (ogg, chain);
2607 ogg->total_time += chain->total_time;
2609 gst_segment_set_duration (&ogg->segment, GST_FORMAT_TIME, ogg->total_time);
2612 /* find all the chains in the ogg file, this reads the first and
2613 * last page of the ogg stream, if they match then the ogg file has
2614 * just one chain, else we do a binary search for all chains.
2616 static GstFlowReturn
2617 gst_ogg_demux_find_chains (GstOggDemux * ogg)
2627 /* get peer to figure out length */
2628 if ((peer = gst_pad_get_peer (ogg->sinkpad)) == NULL)
2631 /* find length to read last page, we store this for later use. */
2632 format = GST_FORMAT_BYTES;
2633 res = gst_pad_query_duration (peer, &format, &ogg->length);
2634 gst_object_unref (peer);
2635 if (!res || ogg->length <= 0)
2638 GST_DEBUG_OBJECT (ogg, "file length %" G_GINT64_FORMAT, ogg->length);
2640 /* read chain from offset 0, this is the first chain of the
2642 gst_ogg_demux_seek (ogg, 0);
2643 ret = gst_ogg_demux_read_chain (ogg, &chain);
2644 if (ret != GST_FLOW_OK)
2645 goto no_first_chain;
2647 /* read page from end offset, we use this page to check if its serial
2648 * number is contained in the first chain. If this is the case then
2649 * this ogg is not a chained ogg and we can skip the scanning. */
2650 gst_ogg_demux_seek (ogg, ogg->length);
2651 ret = gst_ogg_demux_get_prev_page (ogg, &og, NULL);
2652 if (ret != GST_FLOW_OK)
2655 serialno = ogg_page_serialno (&og);
2657 if (!gst_ogg_chain_has_stream (chain, serialno)) {
2658 /* the last page is not in the first stream, this means we should
2659 * find all the chains in this chained ogg. */
2661 gst_ogg_demux_bisect_forward_serialno (ogg, 0, 0, ogg->length, chain,
2664 /* we still call this function here but with an empty range so that
2665 * we can reuse the setup code in this routine. */
2667 gst_ogg_demux_bisect_forward_serialno (ogg, 0, ogg->length, ogg->length,
2670 if (ret != GST_FLOW_OK)
2673 /* all fine, collect and print */
2674 gst_ogg_demux_collect_info (ogg);
2676 /* dump our chains and streams */
2677 gst_ogg_print (ogg);
2682 /*** error cases ***/
2685 GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL), ("we don't have a peer"));
2686 return GST_FLOW_NOT_LINKED;
2690 GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL), ("can't get file length"));
2691 return GST_FLOW_NOT_SUPPORTED;
2695 GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL), ("can't get first chain"));
2696 return GST_FLOW_ERROR;
2700 GST_DEBUG_OBJECT (ogg, "can't get last page");
2702 gst_ogg_chain_free (chain);
2707 static GstFlowReturn
2708 gst_ogg_demux_handle_page (GstOggDemux * ogg, ogg_page * page)
2713 GstFlowReturn result = GST_FLOW_OK;
2715 serialno = ogg_page_serialno (page);
2716 granule = ogg_page_granulepos (page);
2718 GST_LOG_OBJECT (ogg,
2719 "processing ogg page (serial %08x, pageno %ld, granulepos %"
2720 G_GINT64_FORMAT ", bos %d)",
2721 serialno, ogg_page_pageno (page), granule, ogg_page_bos (page));
2723 if (ogg_page_bos (page)) {
2727 /* see if we know about the chain already */
2728 chain = gst_ogg_demux_find_chain (ogg, serialno);
2733 if (chain->segment_start != GST_CLOCK_TIME_NONE)
2734 start = chain->segment_start;
2736 /* create the newsegment event we are going to send out */
2737 event = gst_event_new_new_segment (FALSE, ogg->segment.rate,
2738 GST_FORMAT_TIME, start, chain->segment_stop, chain->begin_time);
2739 gst_event_set_seqnum (event, ogg->seqnum);
2741 GST_DEBUG_OBJECT (ogg,
2742 "segment: start %" GST_TIME_FORMAT ", stop %" GST_TIME_FORMAT
2743 ", time %" GST_TIME_FORMAT, GST_TIME_ARGS (start),
2744 GST_TIME_ARGS (chain->segment_stop),
2745 GST_TIME_ARGS (chain->begin_time));
2747 /* activate it as it means we have a non-header, this will also deactivate
2748 * the currently running chain. */
2749 gst_ogg_demux_activate_chain (ogg, chain, event);
2750 pad = gst_ogg_demux_find_pad (ogg, serialno);
2752 GstClockTime chain_time;
2753 GstOggChain *current_chain;
2754 gint64 current_time;
2756 /* this can only happen in non-seekabe mode */
2760 current_chain = ogg->current_chain;
2761 current_time = ogg->segment.last_stop;
2763 /* time of new chain is current time */
2764 chain_time = current_time;
2766 if (ogg->building_chain == NULL) {
2767 GstOggChain *newchain;
2769 newchain = gst_ogg_chain_new (ogg);
2770 newchain->offset = 0;
2771 /* set new chain begin time aligned with end time of old chain */
2772 newchain->begin_time = chain_time;
2773 GST_DEBUG_OBJECT (ogg, "new chain, begin time %" GST_TIME_FORMAT,
2774 GST_TIME_ARGS (chain_time));
2776 /* and this is the one we are building now */
2777 ogg->building_chain = newchain;
2779 pad = gst_ogg_chain_new_stream (ogg->building_chain, serialno);
2782 pad = gst_ogg_demux_find_pad (ogg, serialno);
2785 result = gst_ogg_pad_submit_page (pad, page);
2787 /* no pad. This means an ogg page without bos has been seen for this
2788 * serialno. we just ignore it but post a warning... */
2789 GST_ELEMENT_WARNING (ogg, STREAM, DECODE,
2790 (NULL), ("unknown ogg pad for serial %08x detected", serialno));
2798 GST_ELEMENT_ERROR (ogg, STREAM, DECODE,
2799 (NULL), ("unknown ogg chain for serial %08x detected", serialno));
2800 return GST_FLOW_ERROR;
2804 /* streaming mode, receive a buffer, parse it, create pads for
2805 * the serialno, submit pages and packets to the oggpads
2807 static GstFlowReturn
2808 gst_ogg_demux_chain (GstPad * pad, GstBuffer * buffer)
2812 GstFlowReturn result = GST_FLOW_OK;
2814 ogg = GST_OGG_DEMUX (GST_OBJECT_PARENT (pad));
2816 GST_DEBUG_OBJECT (ogg, "chain");
2817 result = gst_ogg_demux_submit_buffer (ogg, buffer);
2819 while (result == GST_FLOW_OK) {
2822 ret = ogg_sync_pageout (&ogg->sync, &page);
2824 /* need more data */
2827 /* discontinuity in the pages */
2828 GST_DEBUG_OBJECT (ogg, "discont in page found, continuing");
2830 result = gst_ogg_demux_handle_page (ogg, &page);
2833 if (ret == 0 || result == GST_FLOW_OK) {
2834 gst_ogg_demux_sync_streams (ogg);
2840 gst_ogg_demux_send_event (GstOggDemux * ogg, GstEvent * event)
2842 GstOggChain *chain = ogg->current_chain;
2847 for (i = 0; i < chain->streams->len; i++) {
2848 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);
2850 gst_event_ref (event);
2851 GST_DEBUG_OBJECT (pad, "Pushing event %" GST_PTR_FORMAT, event);
2852 gst_pad_push_event (GST_PAD (pad), event);
2855 gst_event_unref (event);
2858 static GstFlowReturn
2859 gst_ogg_demux_combine_flows (GstOggDemux * ogg, GstOggPad * pad,
2864 /* store the value */
2865 pad->last_ret = ret;
2867 /* any other error that is not-linked can be returned right
2869 if (ret != GST_FLOW_NOT_LINKED)
2872 /* only return NOT_LINKED if all other pads returned NOT_LINKED */
2873 chain = ogg->current_chain;
2877 for (i = 0; i < chain->streams->len; i++) {
2878 GstOggPad *opad = g_array_index (chain->streams, GstOggPad *, i);
2880 ret = opad->last_ret;
2881 /* some other return value (must be SUCCESS but we can return
2882 * other values as well) */
2883 if (ret != GST_FLOW_NOT_LINKED)
2886 /* if we get here, all other pads were unlinked and we return
2887 * NOT_LINKED then */
2893 static GstFlowReturn
2894 gst_ogg_demux_loop_forward (GstOggDemux * ogg)
2899 if (ogg->offset == ogg->length) {
2900 GST_LOG_OBJECT (ogg, "no more data to pull %" G_GINT64_FORMAT
2901 " == %" G_GINT64_FORMAT, ogg->offset, ogg->length);
2902 ret = GST_FLOW_UNEXPECTED;
2906 GST_LOG_OBJECT (ogg, "pull data %" G_GINT64_FORMAT, ogg->offset);
2907 ret = gst_pad_pull_range (ogg->sinkpad, ogg->offset, CHUNKSIZE, &buffer);
2908 if (ret != GST_FLOW_OK) {
2909 GST_LOG_OBJECT (ogg, "Failed pull_range");
2913 ogg->offset += GST_BUFFER_SIZE (buffer);
2915 if (G_UNLIKELY (ogg->newsegment)) {
2916 gst_ogg_demux_send_event (ogg, ogg->newsegment);
2917 ogg->newsegment = NULL;
2920 ret = gst_ogg_demux_chain (ogg->sinkpad, buffer);
2921 if (ret != GST_FLOW_OK) {
2922 GST_LOG_OBJECT (ogg, "Failed demux_chain");
2926 /* check for the end of the segment */
2927 if (ogg->segment.stop != -1 && ogg->segment.last_stop != -1) {
2928 if (ogg->segment.last_stop > ogg->segment.stop) {
2929 ret = GST_FLOW_UNEXPECTED;
2939 * We read the pages backwards and send the packets forwards. The first packet
2940 * in the page will be pushed with the DISCONT flag set.
2942 * Special care has to be taken for continued pages, which we can only decode
2943 * when we have the previous page(s).
2945 static GstFlowReturn
2946 gst_ogg_demux_loop_reverse (GstOggDemux * ogg)
2952 if (ogg->offset == 0) {
2953 GST_LOG_OBJECT (ogg, "no more data to pull %" G_GINT64_FORMAT
2954 " == 0", ogg->offset);
2955 ret = GST_FLOW_UNEXPECTED;
2959 GST_LOG_OBJECT (ogg, "read page from %" G_GINT64_FORMAT, ogg->offset);
2960 ret = gst_ogg_demux_get_prev_page (ogg, &page, &offset);
2961 if (ret != GST_FLOW_OK)
2964 ogg->offset = offset;
2966 if (G_UNLIKELY (ogg->newsegment)) {
2967 gst_ogg_demux_send_event (ogg, ogg->newsegment);
2968 ogg->newsegment = NULL;
2971 ret = gst_ogg_demux_handle_page (ogg, &page);
2972 if (ret != GST_FLOW_OK)
2975 /* check for the end of the segment */
2976 if (ogg->segment.start != -1 && ogg->segment.last_stop != -1) {
2977 if (ogg->segment.last_stop <= ogg->segment.start) {
2978 ret = GST_FLOW_UNEXPECTED;
2987 gst_ogg_demux_sync_streams (GstOggDemux * ogg)
2993 chain = ogg->current_chain;
2994 cur = ogg->segment.last_stop;
2995 if (chain == NULL || cur == -1)
2998 for (i = 0; i < chain->streams->len; i++) {
2999 GstOggPad *stream = g_array_index (chain->streams, GstOggPad *, i);
3001 /* Theoretically, we should be doing this for all streams, but we're only
3002 * doing it for known-to-be-sparse streams at the moment in order not to
3003 * break things for wrongly-muxed streams (like we used to produce once) */
3004 if (stream->is_sparse && stream->last_stop != GST_CLOCK_TIME_NONE) {
3006 /* Does this stream lag? Random threshold of 2 seconds */
3007 if (GST_CLOCK_DIFF (stream->last_stop, cur) > (2 * GST_SECOND)) {
3008 GST_DEBUG_OBJECT (stream, "synchronizing stream with others by "
3009 "advancing time from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
3010 GST_TIME_ARGS (stream->last_stop), GST_TIME_ARGS (cur));
3011 stream->last_stop = cur;
3012 /* advance stream time (FIXME: is this right, esp. time_pos?) */
3013 gst_pad_push_event (GST_PAD_CAST (stream),
3014 gst_event_new_new_segment (TRUE, ogg->segment.rate,
3015 GST_FORMAT_TIME, stream->last_stop, -1, stream->last_stop));
3021 /* random access code
3023 * - first find all the chains and streams by scanning the file.
3024 * - then get and chain buffers, just like the streaming case.
3025 * - when seeking, we can use the chain info to perform the seek.
3028 gst_ogg_demux_loop (GstOggPad * pad)
3034 ogg = GST_OGG_DEMUX (GST_OBJECT_PARENT (pad));
3036 if (ogg->need_chains) {
3039 /* this is the only place where we write chains and thus need to lock. */
3040 GST_CHAIN_LOCK (ogg);
3041 ret = gst_ogg_demux_find_chains (ogg);
3042 GST_CHAIN_UNLOCK (ogg);
3043 if (ret != GST_FLOW_OK)
3044 goto chain_read_failed;
3046 ogg->need_chains = FALSE;
3048 GST_OBJECT_LOCK (ogg);
3049 ogg->running = TRUE;
3052 GST_OBJECT_UNLOCK (ogg);
3054 /* and seek to configured positions without FLUSH */
3055 res = gst_ogg_demux_perform_seek (ogg, event);
3057 gst_event_unref (event);
3063 if (ogg->segment.rate >= 0.0)
3064 ret = gst_ogg_demux_loop_forward (ogg);
3066 ret = gst_ogg_demux_loop_reverse (ogg);
3068 if (ret != GST_FLOW_OK)
3071 gst_ogg_demux_sync_streams (ogg);
3077 /* error was posted */
3082 GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL),
3083 ("failed to start demuxing ogg"));
3084 ret = GST_FLOW_ERROR;
3089 const gchar *reason = gst_flow_get_name (ret);
3090 GstEvent *event = NULL;
3092 GST_LOG_OBJECT (ogg, "pausing task, reason %s", reason);
3093 ogg->segment_running = FALSE;
3094 gst_pad_pause_task (ogg->sinkpad);
3096 if (GST_FLOW_IS_FATAL (ret) || ret == GST_FLOW_NOT_LINKED) {
3097 if (ret == GST_FLOW_UNEXPECTED) {
3098 /* perform EOS logic */
3099 if (ogg->segment.flags & GST_SEEK_FLAG_SEGMENT) {
3101 GstMessage *message;
3103 /* for segment playback we need to post when (in stream time)
3104 * we stopped, this is either stop (when set) or the duration. */
3105 if ((stop = ogg->segment.stop) == -1)
3106 stop = ogg->segment.duration;
3108 GST_LOG_OBJECT (ogg, "Sending segment done, at end of segment");
3110 gst_message_new_segment_done (GST_OBJECT (ogg), GST_FORMAT_TIME,
3112 gst_message_set_seqnum (message, ogg->seqnum);
3114 gst_element_post_message (GST_ELEMENT (ogg), message);
3116 /* normal playback, send EOS to all linked pads */
3117 GST_LOG_OBJECT (ogg, "Sending EOS, at end of stream");
3118 event = gst_event_new_eos ();
3121 GST_ELEMENT_ERROR (ogg, STREAM, FAILED,
3122 (_("Internal data stream error.")),
3123 ("stream stopped, reason %s", reason));
3124 event = gst_event_new_eos ();
3127 gst_event_set_seqnum (event, ogg->seqnum);
3128 gst_ogg_demux_send_event (ogg, event);
3136 gst_ogg_demux_clear_chains (GstOggDemux * ogg)
3140 gst_ogg_demux_deactivate_current_chain (ogg);
3142 GST_CHAIN_LOCK (ogg);
3143 for (i = 0; i < ogg->chains->len; i++) {
3144 GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i);
3146 gst_ogg_chain_free (chain);
3148 ogg->chains = g_array_set_size (ogg->chains, 0);
3149 GST_CHAIN_UNLOCK (ogg);
3152 /* this function is called when the pad is activated and should start
3155 * We check if we can do random access to decide if we work push or
3159 gst_ogg_demux_sink_activate (GstPad * sinkpad)
3161 if (gst_pad_check_pull_range (sinkpad)) {
3162 GST_DEBUG_OBJECT (sinkpad, "activating pull");
3163 return gst_pad_activate_pull (sinkpad, TRUE);
3165 GST_DEBUG_OBJECT (sinkpad, "activating push");
3166 return gst_pad_activate_push (sinkpad, TRUE);
3170 /* this function gets called when we activate ourselves in push mode.
3171 * We cannot seek (ourselves) in the stream */
3173 gst_ogg_demux_sink_activate_push (GstPad * sinkpad, gboolean active)
3177 ogg = GST_OGG_DEMUX (GST_OBJECT_PARENT (sinkpad));
3179 ogg->seekable = FALSE;
3184 /* this function gets called when we activate ourselves in pull mode.
3185 * We can perform random access to the resource and we start a task
3186 * to start reading */
3188 gst_ogg_demux_sink_activate_pull (GstPad * sinkpad, gboolean active)
3192 ogg = GST_OGG_DEMUX (GST_OBJECT_PARENT (sinkpad));
3195 ogg->need_chains = TRUE;
3196 ogg->seekable = TRUE;
3198 return gst_pad_start_task (sinkpad, (GstTaskFunction) gst_ogg_demux_loop,
3201 return gst_pad_stop_task (sinkpad);
3205 static GstStateChangeReturn
3206 gst_ogg_demux_change_state (GstElement * element, GstStateChange transition)
3209 GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE;
3211 ogg = GST_OGG_DEMUX (element);
3213 switch (transition) {
3214 case GST_STATE_CHANGE_NULL_TO_READY:
3216 ogg->have_fishead = FALSE;
3217 ogg_sync_init (&ogg->sync);
3219 case GST_STATE_CHANGE_READY_TO_PAUSED:
3220 ogg_sync_reset (&ogg->sync);
3221 ogg->running = FALSE;
3222 ogg->segment_running = FALSE;
3223 gst_segment_init (&ogg->segment, GST_FORMAT_TIME);
3225 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
3231 result = parent_class->change_state (element, transition);
3233 switch (transition) {
3234 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
3236 case GST_STATE_CHANGE_PAUSED_TO_READY:
3237 gst_ogg_demux_clear_chains (ogg);
3238 GST_OBJECT_LOCK (ogg);
3239 ogg->running = FALSE;
3240 ogg->segment_running = FALSE;
3241 ogg->have_fishead = FALSE;
3242 GST_OBJECT_UNLOCK (ogg);
3244 case GST_STATE_CHANGE_READY_TO_NULL:
3245 ogg_sync_clear (&ogg->sync);
3254 gst_ogg_demux_plugin_init (GstPlugin * plugin)
3256 GST_DEBUG_CATEGORY_INIT (gst_ogg_demux_debug, "oggdemux", 0, "ogg demuxer");
3257 GST_DEBUG_CATEGORY_INIT (gst_ogg_demux_setup_debug, "oggdemux_setup", 0,
3258 "ogg demuxer setup stage when parsing pipeline");
3261 GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE,
3263 bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
3264 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
3267 return gst_element_register (plugin, "oggdemux", GST_RANK_PRIMARY,
3268 GST_TYPE_OGG_DEMUX);
3271 /* prints all info about the element */
3272 #undef GST_CAT_DEFAULT
3273 #define GST_CAT_DEFAULT gst_ogg_demux_setup_debug
3275 #ifdef GST_DISABLE_GST_DEBUG
3278 gst_ogg_print (GstOggDemux * ogg)
3283 #else /* !GST_DISABLE_GST_DEBUG */
3286 gst_ogg_print (GstOggDemux * ogg)
3290 GST_INFO_OBJECT (ogg, "%u chains", ogg->chains->len);
3291 GST_INFO_OBJECT (ogg, " total time: %" GST_TIME_FORMAT,
3292 GST_TIME_ARGS (ogg->total_time));
3294 for (i = 0; i < ogg->chains->len; i++) {
3295 GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i);
3297 GST_INFO_OBJECT (ogg, " chain %d (%u streams):", i, chain->streams->len);
3298 GST_INFO_OBJECT (ogg, " offset: %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT,
3299 chain->offset, chain->end_offset);
3300 GST_INFO_OBJECT (ogg, " begin time: %" GST_TIME_FORMAT,
3301 GST_TIME_ARGS (chain->begin_time));
3302 GST_INFO_OBJECT (ogg, " total time: %" GST_TIME_FORMAT,
3303 GST_TIME_ARGS (chain->total_time));
3304 GST_INFO_OBJECT (ogg, " segment start: %" GST_TIME_FORMAT,
3305 GST_TIME_ARGS (chain->segment_start));
3306 GST_INFO_OBJECT (ogg, " segment stop: %" GST_TIME_FORMAT,
3307 GST_TIME_ARGS (chain->segment_stop));
3309 for (j = 0; j < chain->streams->len; j++) {
3310 GstOggPad *stream = g_array_index (chain->streams, GstOggPad *, j);
3312 GST_INFO_OBJECT (ogg, " stream %08x:", stream->map.serialno);
3313 GST_INFO_OBJECT (ogg, " start time: %" GST_TIME_FORMAT,
3314 GST_TIME_ARGS (stream->start_time));
3318 #endif /* GST_DISABLE_GST_DEBUG */