1 /* GStreamer |
|
2 * Copyright (C) 2004 Wim Taymans <wim@fluendo.com> |
|
3 * |
|
4 * gstoggdemux.c: ogg stream demuxer |
|
5 * |
|
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. |
|
10 * |
|
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. |
|
15 * |
|
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. |
|
20 */ |
|
21 |
|
22 /** |
|
23 * SECTION:element-oggdemux |
|
24 * @short_description: a demuxer for ogg files |
|
25 * |
|
26 * <refsect2> |
|
27 * <para> |
|
28 * This element demuxes ogg files into their encoded audio and video components. |
|
29 * </para> |
|
30 * <title>Example pipelines</title> |
|
31 * <para> |
|
32 * <programlisting> |
|
33 * gst-launch -v filesrc location=test.ogg ! oggdemux ! vorbisdec ! audioconvert ! alsasink |
|
34 * </programlisting> |
|
35 * Decodes the vorbis audio stored inside an ogg container. |
|
36 * </para> |
|
37 * </refsect2> |
|
38 * |
|
39 * Last reviewed on 2006-12-30 (0.10.5) |
|
40 */ |
|
41 |
|
42 |
|
43 #ifdef HAVE_CONFIG_H |
|
44 #include "config.h" |
|
45 #endif |
|
46 #include <string.h> |
|
47 #include <gst/gst-i18n-plugin.h> |
|
48 #include <gst/base/gsttypefindhelper.h> |
|
49 |
|
50 #include "gstoggdemux.h" |
|
51 |
|
52 static const GstElementDetails gst_ogg_demux_details = |
|
53 GST_ELEMENT_DETAILS ("Ogg demuxer", |
|
54 "Codec/Demuxer", |
|
55 "demux ogg streams (info about ogg: http://xiph.org)", |
|
56 "Wim Taymans <wim@fluendo.com>"); |
|
57 |
|
58 #define CHUNKSIZE (8500) /* this is out of vorbisfile */ |
|
59 #define SKELETON_FISHEAD_SIZE 64 |
|
60 #define SKELETON_FISBONE_MIN_SIZE 52 |
|
61 |
|
62 #define GST_FLOW_LIMIT GST_FLOW_CUSTOM_ERROR |
|
63 |
|
64 GST_DEBUG_CATEGORY_STATIC (gst_ogg_demux_debug); |
|
65 GST_DEBUG_CATEGORY_STATIC (gst_ogg_demux_setup_debug); |
|
66 #define GST_CAT_DEFAULT gst_ogg_demux_debug |
|
67 |
|
68 static ogg_page * |
|
69 gst_ogg_page_copy (ogg_page * page) |
|
70 { |
|
71 ogg_page *p = g_new0 (ogg_page, 1); |
|
72 |
|
73 /* make a copy of the page */ |
|
74 p->header = g_memdup (page->header, page->header_len); |
|
75 p->header_len = page->header_len; |
|
76 p->body = g_memdup (page->body, page->body_len); |
|
77 p->body_len = page->body_len; |
|
78 |
|
79 return p; |
|
80 } |
|
81 |
|
82 static void |
|
83 gst_ogg_page_free (ogg_page * page) |
|
84 { |
|
85 g_free (page->header); |
|
86 g_free (page->body); |
|
87 g_free (page); |
|
88 } |
|
89 |
|
90 static GstStaticPadTemplate internaltemplate = |
|
91 GST_STATIC_PAD_TEMPLATE ("internal", |
|
92 GST_PAD_SINK, |
|
93 GST_PAD_ALWAYS, |
|
94 GST_STATIC_CAPS_ANY); |
|
95 |
|
96 static gboolean gst_ogg_demux_collect_chain_info (GstOggDemux * ogg, |
|
97 GstOggChain * chain); |
|
98 static gboolean gst_ogg_demux_activate_chain (GstOggDemux * ogg, |
|
99 GstOggChain * chain, GstEvent * event); |
|
100 static void gst_ogg_chain_mark_discont (GstOggChain * chain); |
|
101 |
|
102 static gboolean gst_ogg_demux_perform_seek (GstOggDemux * ogg, |
|
103 GstEvent * event); |
|
104 static gboolean gst_ogg_demux_receive_event (GstElement * element, |
|
105 GstEvent * event); |
|
106 |
|
107 static void gst_ogg_pad_class_init (GstOggPadClass * klass); |
|
108 static void gst_ogg_pad_init (GstOggPad * pad); |
|
109 static void gst_ogg_pad_dispose (GObject * object); |
|
110 static void gst_ogg_pad_finalize (GObject * object); |
|
111 |
|
112 #if 0 |
|
113 static const GstFormat *gst_ogg_pad_formats (GstPad * pad); |
|
114 static const GstEventMask *gst_ogg_pad_event_masks (GstPad * pad); |
|
115 #endif |
|
116 static const GstQueryType *gst_ogg_pad_query_types (GstPad * pad); |
|
117 static gboolean gst_ogg_pad_src_query (GstPad * pad, GstQuery * query); |
|
118 static gboolean gst_ogg_pad_event (GstPad * pad, GstEvent * event); |
|
119 static GstCaps *gst_ogg_pad_getcaps (GstPad * pad); |
|
120 static GstOggPad *gst_ogg_chain_get_stream (GstOggChain * chain, |
|
121 glong serialno); |
|
122 |
|
123 static gboolean gst_ogg_pad_query_convert (GstOggPad * pad, |
|
124 GstFormat src_format, gint64 src_val, |
|
125 GstFormat * dest_format, gint64 * dest_val); |
|
126 static GstClockTime gst_annodex_granule_to_time (gint64 granulepos, |
|
127 gint64 granulerate_n, gint64 granulerate_d, guint8 granuleshift); |
|
128 |
|
129 static GstFlowReturn gst_ogg_demux_combine_flows (GstOggDemux * ogg, |
|
130 GstOggPad * pad, GstFlowReturn ret); |
|
131 |
|
132 static GstPadClass *ogg_pad_parent_class = NULL; |
|
133 |
|
134 static GType |
|
135 gst_ogg_pad_get_type (void) |
|
136 { |
|
137 static GType ogg_pad_type = 0; |
|
138 |
|
139 if (!ogg_pad_type) { |
|
140 static const GTypeInfo ogg_pad_info = { |
|
141 sizeof (GstOggPadClass), |
|
142 NULL, |
|
143 NULL, |
|
144 (GClassInitFunc) gst_ogg_pad_class_init, |
|
145 NULL, |
|
146 NULL, |
|
147 sizeof (GstOggPad), |
|
148 0, |
|
149 (GInstanceInitFunc) gst_ogg_pad_init, |
|
150 }; |
|
151 |
|
152 ogg_pad_type = |
|
153 g_type_register_static (GST_TYPE_PAD, "GstOggPad", &ogg_pad_info, 0); |
|
154 } |
|
155 return ogg_pad_type; |
|
156 } |
|
157 |
|
158 static void |
|
159 gst_ogg_pad_class_init (GstOggPadClass * klass) |
|
160 { |
|
161 GObjectClass *gobject_class; |
|
162 |
|
163 gobject_class = (GObjectClass *) klass; |
|
164 |
|
165 ogg_pad_parent_class = g_type_class_peek_parent (klass); |
|
166 |
|
167 gobject_class->dispose = gst_ogg_pad_dispose; |
|
168 gobject_class->finalize = gst_ogg_pad_finalize; |
|
169 } |
|
170 |
|
171 static void |
|
172 gst_ogg_pad_init (GstOggPad * pad) |
|
173 { |
|
174 gst_pad_set_event_function (GST_PAD (pad), |
|
175 GST_DEBUG_FUNCPTR (gst_ogg_pad_event)); |
|
176 gst_pad_set_getcaps_function (GST_PAD (pad), |
|
177 GST_DEBUG_FUNCPTR (gst_ogg_pad_getcaps)); |
|
178 gst_pad_set_query_type_function (GST_PAD (pad), |
|
179 GST_DEBUG_FUNCPTR (gst_ogg_pad_query_types)); |
|
180 gst_pad_set_query_function (GST_PAD (pad), |
|
181 GST_DEBUG_FUNCPTR (gst_ogg_pad_src_query)); |
|
182 |
|
183 pad->mode = GST_OGG_PAD_MODE_INIT; |
|
184 |
|
185 pad->first_granule = -1; |
|
186 pad->current_granule = -1; |
|
187 |
|
188 pad->start_time = GST_CLOCK_TIME_NONE; |
|
189 pad->first_time = GST_CLOCK_TIME_NONE; |
|
190 |
|
191 pad->have_type = FALSE; |
|
192 pad->continued = NULL; |
|
193 pad->headers = NULL; |
|
194 } |
|
195 |
|
196 static void |
|
197 gst_ogg_pad_dispose (GObject * object) |
|
198 { |
|
199 GstOggPad *pad = GST_OGG_PAD (object); |
|
200 GstPad **elem_pad_p; |
|
201 GstElement **element_p; |
|
202 GstPad **elem_out_p; |
|
203 |
|
204 if (pad->element) |
|
205 gst_element_set_state (pad->element, GST_STATE_NULL); |
|
206 |
|
207 elem_pad_p = &pad->elem_pad; |
|
208 element_p = &pad->element; |
|
209 elem_out_p = &pad->elem_out; |
|
210 gst_object_replace ((GstObject **) elem_pad_p, NULL); |
|
211 gst_object_replace ((GstObject **) element_p, NULL); |
|
212 gst_object_replace ((GstObject **) elem_out_p, NULL); |
|
213 |
|
214 pad->chain = NULL; |
|
215 pad->ogg = NULL; |
|
216 |
|
217 g_list_foreach (pad->headers, (GFunc) gst_mini_object_unref, NULL); |
|
218 g_list_free (pad->headers); |
|
219 pad->headers = NULL; |
|
220 |
|
221 /* clear continued pages */ |
|
222 g_list_foreach (pad->continued, (GFunc) gst_ogg_page_free, NULL); |
|
223 g_list_free (pad->continued); |
|
224 pad->continued = NULL; |
|
225 |
|
226 ogg_stream_reset (&pad->stream); |
|
227 |
|
228 G_OBJECT_CLASS (ogg_pad_parent_class)->dispose (object); |
|
229 } |
|
230 |
|
231 static void |
|
232 gst_ogg_pad_finalize (GObject * object) |
|
233 { |
|
234 GstOggPad *pad = GST_OGG_PAD (object); |
|
235 |
|
236 ogg_stream_clear (&pad->stream); |
|
237 |
|
238 G_OBJECT_CLASS (ogg_pad_parent_class)->finalize (object); |
|
239 } |
|
240 |
|
241 #if 0 |
|
242 static const GstFormat * |
|
243 gst_ogg_pad_formats (GstPad * pad) |
|
244 { |
|
245 static GstFormat src_formats[] = { |
|
246 GST_FORMAT_DEFAULT, /* time */ |
|
247 GST_FORMAT_TIME, /* granulepos */ |
|
248 0 |
|
249 }; |
|
250 static GstFormat sink_formats[] = { |
|
251 GST_FORMAT_BYTES, |
|
252 GST_FORMAT_DEFAULT, /* bytes */ |
|
253 0 |
|
254 }; |
|
255 |
|
256 return (GST_PAD_IS_SRC (pad) ? src_formats : sink_formats); |
|
257 } |
|
258 #endif |
|
259 |
|
260 #if 0 |
|
261 static const GstEventMask * |
|
262 gst_ogg_pad_event_masks (GstPad * pad) |
|
263 { |
|
264 static const GstEventMask src_event_masks[] = { |
|
265 {GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_FLUSH}, |
|
266 {0,} |
|
267 }; |
|
268 |
|
269 return src_event_masks; |
|
270 } |
|
271 #endif |
|
272 |
|
273 static const GstQueryType * |
|
274 gst_ogg_pad_query_types (GstPad * pad) |
|
275 { |
|
276 static const GstQueryType query_types[] = { |
|
277 GST_QUERY_DURATION, |
|
278 GST_QUERY_SEEKING, |
|
279 0 |
|
280 }; |
|
281 |
|
282 return query_types; |
|
283 } |
|
284 |
|
285 static GstCaps * |
|
286 gst_ogg_pad_getcaps (GstPad * pad) |
|
287 { |
|
288 return gst_caps_ref (GST_PAD_CAPS (pad)); |
|
289 } |
|
290 |
|
291 static gboolean |
|
292 gst_ogg_pad_src_query (GstPad * pad, GstQuery * query) |
|
293 { |
|
294 gboolean res = TRUE; |
|
295 GstOggDemux *ogg; |
|
296 GstOggPad *cur; |
|
297 |
|
298 ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad)); |
|
299 cur = GST_OGG_PAD (pad); |
|
300 |
|
301 switch (GST_QUERY_TYPE (query)) { |
|
302 case GST_QUERY_DURATION: |
|
303 { |
|
304 GstFormat format; |
|
305 |
|
306 gst_query_parse_duration (query, &format, NULL); |
|
307 /* can only get position in time */ |
|
308 if (format != GST_FORMAT_TIME) |
|
309 goto wrong_format; |
|
310 |
|
311 /* can only return the total time position */ |
|
312 /* FIXME, return time for this specific stream */ |
|
313 gst_query_set_duration (query, GST_FORMAT_TIME, ogg->total_time); |
|
314 break; |
|
315 } |
|
316 case GST_QUERY_SEEKING: |
|
317 { |
|
318 GstFormat format; |
|
319 |
|
320 gst_query_parse_seeking (query, &format, NULL, NULL, NULL); |
|
321 if (format == GST_FORMAT_TIME) { |
|
322 gst_query_set_seeking (query, GST_FORMAT_TIME, ogg->seekable, |
|
323 0, ogg->total_time); |
|
324 } else { |
|
325 res = FALSE; |
|
326 } |
|
327 break; |
|
328 } |
|
329 |
|
330 default: |
|
331 res = gst_pad_query_default (pad, query); |
|
332 break; |
|
333 } |
|
334 done: |
|
335 gst_object_unref (ogg); |
|
336 |
|
337 return res; |
|
338 |
|
339 /* ERRORS */ |
|
340 wrong_format: |
|
341 { |
|
342 GST_DEBUG_OBJECT (ogg, "only query duration on TIME is supported"); |
|
343 res = FALSE; |
|
344 goto done; |
|
345 } |
|
346 } |
|
347 |
|
348 static gboolean |
|
349 gst_ogg_demux_receive_event (GstElement * element, GstEvent * event) |
|
350 { |
|
351 gboolean res; |
|
352 GstOggDemux *ogg; |
|
353 |
|
354 ogg = GST_OGG_DEMUX (element); |
|
355 |
|
356 switch (GST_EVENT_TYPE (event)) { |
|
357 case GST_EVENT_SEEK: |
|
358 /* can't seek if we are not seekable, FIXME could pass the |
|
359 * seek query upstream after converting it to bytes using |
|
360 * the average bitrate of the stream. */ |
|
361 if (!ogg->seekable) { |
|
362 GST_DEBUG_OBJECT (ogg, "seek on non seekable stream"); |
|
363 goto error; |
|
364 } |
|
365 |
|
366 /* now do the seek */ |
|
367 res = gst_ogg_demux_perform_seek (ogg, event); |
|
368 gst_event_unref (event); |
|
369 break; |
|
370 default: |
|
371 GST_DEBUG_OBJECT (ogg, "We only handle seek events here"); |
|
372 goto error; |
|
373 } |
|
374 |
|
375 return res; |
|
376 |
|
377 /* ERRORS */ |
|
378 error: |
|
379 { |
|
380 GST_DEBUG_OBJECT (ogg, "error handling event"); |
|
381 gst_event_unref (event); |
|
382 return FALSE; |
|
383 } |
|
384 } |
|
385 |
|
386 static gboolean |
|
387 gst_ogg_pad_event (GstPad * pad, GstEvent * event) |
|
388 { |
|
389 gboolean res; |
|
390 GstOggDemux *ogg; |
|
391 GstOggPad *cur; |
|
392 |
|
393 ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad)); |
|
394 cur = GST_OGG_PAD (pad); |
|
395 |
|
396 switch (GST_EVENT_TYPE (event)) { |
|
397 case GST_EVENT_SEEK: |
|
398 /* can't seek if we are not seekable, FIXME could pass the |
|
399 * seek query upstream after converting it to bytes using |
|
400 * the average bitrate of the stream. */ |
|
401 if (!ogg->seekable) { |
|
402 GST_DEBUG_OBJECT (ogg, "seek on non seekable stream"); |
|
403 goto error; |
|
404 } |
|
405 |
|
406 /* now do the seek */ |
|
407 res = gst_ogg_demux_perform_seek (ogg, event); |
|
408 gst_event_unref (event); |
|
409 break; |
|
410 default: |
|
411 res = gst_pad_event_default (pad, event); |
|
412 break; |
|
413 } |
|
414 done: |
|
415 gst_object_unref (ogg); |
|
416 |
|
417 return res; |
|
418 |
|
419 /* ERRORS */ |
|
420 error: |
|
421 { |
|
422 GST_DEBUG_OBJECT (ogg, "error handling event"); |
|
423 gst_event_unref (event); |
|
424 res = FALSE; |
|
425 goto done; |
|
426 } |
|
427 } |
|
428 |
|
429 static void |
|
430 gst_ogg_pad_reset (GstOggPad * pad) |
|
431 { |
|
432 ogg_stream_reset (&pad->stream); |
|
433 |
|
434 /* clear continued pages */ |
|
435 g_list_foreach (pad->continued, (GFunc) gst_ogg_page_free, NULL); |
|
436 g_list_free (pad->continued); |
|
437 pad->continued = NULL; |
|
438 |
|
439 pad->last_ret = GST_FLOW_OK; |
|
440 } |
|
441 |
|
442 /* the filter function for selecting the elements we can use in |
|
443 * autoplugging */ |
|
444 static gboolean |
|
445 gst_ogg_demux_factory_filter (GstPluginFeature * feature, GstCaps * caps) |
|
446 { |
|
447 guint rank; |
|
448 const gchar *klass; |
|
449 |
|
450 /* we only care about element factories */ |
|
451 if (!GST_IS_ELEMENT_FACTORY (feature)) |
|
452 return FALSE; |
|
453 |
|
454 klass = gst_element_factory_get_klass (GST_ELEMENT_FACTORY (feature)); |
|
455 /* only demuxers and decoders can play */ |
|
456 if (strstr (klass, "Demux") == NULL && |
|
457 strstr (klass, "Decoder") == NULL && strstr (klass, "Parse") == NULL) { |
|
458 return FALSE; |
|
459 } |
|
460 |
|
461 /* only select elements with autoplugging rank */ |
|
462 rank = gst_plugin_feature_get_rank (feature); |
|
463 if (rank < GST_RANK_MARGINAL) |
|
464 return FALSE; |
|
465 |
|
466 GST_DEBUG ("checking factory %s", GST_PLUGIN_FEATURE_NAME (feature)); |
|
467 /* now see if it is compatible with the caps */ |
|
468 { |
|
469 GstElementFactory *factory = GST_ELEMENT_FACTORY (feature); |
|
470 const GList *templates; |
|
471 GList *walk; |
|
472 |
|
473 /* get the templates from the element factory */ |
|
474 templates = gst_element_factory_get_static_pad_templates (factory); |
|
475 |
|
476 for (walk = (GList *) templates; walk; walk = g_list_next (walk)) { |
|
477 GstStaticPadTemplate *templ = walk->data; |
|
478 |
|
479 /* we only care about the sink templates */ |
|
480 if (templ->direction == GST_PAD_SINK) { |
|
481 GstCaps *intersect; |
|
482 GstCaps *scaps; |
|
483 gboolean empty; |
|
484 |
|
485 /* try to intersect the caps with the caps of the template */ |
|
486 scaps = gst_static_caps_get (&templ->static_caps); |
|
487 intersect = gst_caps_intersect (caps, scaps); |
|
488 gst_caps_unref (scaps); |
|
489 |
|
490 empty = gst_caps_is_empty (intersect); |
|
491 gst_caps_unref (intersect); |
|
492 |
|
493 /* check if the intersection is empty */ |
|
494 if (!empty) { |
|
495 /* non empty intersection, we can use this element */ |
|
496 goto found; |
|
497 } |
|
498 } |
|
499 } |
|
500 } |
|
501 return FALSE; |
|
502 |
|
503 found: |
|
504 return TRUE; |
|
505 } |
|
506 |
|
507 /* function used to sort element features */ |
|
508 static gint |
|
509 compare_ranks (GstPluginFeature * f1, GstPluginFeature * f2) |
|
510 { |
|
511 gint diff; |
|
512 |
|
513 diff = gst_plugin_feature_get_rank (f2) - gst_plugin_feature_get_rank (f1); |
|
514 if (diff != 0) |
|
515 return diff; |
|
516 return strcmp (gst_plugin_feature_get_name (f2), |
|
517 gst_plugin_feature_get_name (f1)); |
|
518 } |
|
519 |
|
520 /* called when the skeleton fishead is found. Caller ensures the packet is |
|
521 * precisely the correct size; we don't re-check this here. */ |
|
522 static void |
|
523 gst_ogg_pad_parse_skeleton_fishead (GstOggPad * pad, ogg_packet * packet) |
|
524 { |
|
525 GstOggDemux *ogg = pad->ogg; |
|
526 guint8 *data = packet->packet; |
|
527 guint16 major, minor; |
|
528 gint64 prestime_n, prestime_d; |
|
529 gint64 basetime_n, basetime_d; |
|
530 |
|
531 /* skip "fishead\0" */ |
|
532 data += 8; |
|
533 major = GST_READ_UINT16_LE (data); |
|
534 data += 2; |
|
535 minor = GST_READ_UINT16_LE (data); |
|
536 data += 2; |
|
537 prestime_n = (gint64) GST_READ_UINT64_LE (data); |
|
538 data += 8; |
|
539 prestime_d = (gint64) GST_READ_UINT64_LE (data); |
|
540 data += 8; |
|
541 basetime_n = (gint64) GST_READ_UINT64_LE (data); |
|
542 data += 8; |
|
543 basetime_d = (gint64) GST_READ_UINT64_LE (data); |
|
544 data += 8; |
|
545 |
|
546 ogg->basetime = gst_util_uint64_scale (GST_SECOND, basetime_n, basetime_d); |
|
547 ogg->have_fishead = TRUE; |
|
548 pad->is_skeleton = TRUE; |
|
549 pad->start_time = GST_CLOCK_TIME_NONE; |
|
550 pad->first_granule = -1; |
|
551 pad->first_time = GST_CLOCK_TIME_NONE; |
|
552 GST_INFO_OBJECT (ogg, "skeleton fishead parsed (basetime: %" |
|
553 GST_TIME_FORMAT ")", GST_TIME_ARGS (ogg->basetime)); |
|
554 } |
|
555 |
|
556 /* function called when a skeleton fisbone is found. Caller ensures that |
|
557 * the packet length is sufficient */ |
|
558 static void |
|
559 gst_ogg_pad_parse_skeleton_fisbone (GstOggPad * pad, ogg_packet * packet) |
|
560 { |
|
561 GstOggPad *fisbone_pad; |
|
562 gint64 start_granule; |
|
563 guint32 serialno; |
|
564 guint8 *data = packet->packet; |
|
565 |
|
566 /* skip "fisbone\0" */ |
|
567 data += 8; |
|
568 /* skip headers offset */ |
|
569 data += 4; |
|
570 serialno = GST_READ_UINT32_LE (data); |
|
571 |
|
572 fisbone_pad = gst_ogg_chain_get_stream (pad->chain, serialno); |
|
573 if (fisbone_pad) { |
|
574 if (fisbone_pad->have_fisbone) |
|
575 /* already parsed */ |
|
576 return; |
|
577 |
|
578 fisbone_pad->have_fisbone = TRUE; |
|
579 |
|
580 data += 4; |
|
581 /* skip number of headers */ |
|
582 data += 4; |
|
583 fisbone_pad->granulerate_n = GST_READ_UINT64_LE (data); |
|
584 data += 8; |
|
585 fisbone_pad->granulerate_d = GST_READ_UINT64_LE (data); |
|
586 data += 8; |
|
587 start_granule = GST_READ_UINT64_LE (data); |
|
588 data += 8; |
|
589 fisbone_pad->preroll = GST_READ_UINT32_LE (data); |
|
590 data += 4; |
|
591 fisbone_pad->granuleshift = GST_READ_UINT8 (data); |
|
592 data += 1; |
|
593 /* padding */ |
|
594 data += 3; |
|
595 |
|
596 fisbone_pad->start_time = gst_annodex_granule_to_time (start_granule, |
|
597 fisbone_pad->granulerate_n, fisbone_pad->granulerate_d, |
|
598 fisbone_pad->granuleshift); |
|
599 |
|
600 GST_INFO_OBJECT (pad->ogg, "skeleton fisbone parsed " |
|
601 "(serialno: %08x start time: %" GST_TIME_FORMAT |
|
602 " granulerate_n: %" G_GINT64_FORMAT " granulerate_d: %" G_GINT64_FORMAT |
|
603 " preroll: %" G_GUINT32_FORMAT " granuleshift: %d)", |
|
604 serialno, GST_TIME_ARGS (fisbone_pad->start_time), |
|
605 fisbone_pad->granulerate_n, fisbone_pad->granulerate_d, |
|
606 fisbone_pad->preroll, fisbone_pad->granuleshift); |
|
607 } else { |
|
608 GST_WARNING_OBJECT (pad->ogg, |
|
609 "found skeleton fisbone for an unknown stream %" G_GUINT32_FORMAT, |
|
610 serialno); |
|
611 } |
|
612 } |
|
613 |
|
614 /* function called to convert a granulepos to a timestamp */ |
|
615 static gboolean |
|
616 gst_ogg_pad_query_convert (GstOggPad * pad, GstFormat src_format, |
|
617 gint64 src_val, GstFormat * dest_format, gint64 * dest_val) |
|
618 { |
|
619 gboolean res; |
|
620 |
|
621 if (src_val == -1) { |
|
622 *dest_val = -1; |
|
623 return TRUE; |
|
624 } |
|
625 |
|
626 if (!pad->have_fisbone && pad->elem_pad == NULL) |
|
627 return FALSE; |
|
628 |
|
629 switch (src_format) { |
|
630 case GST_FORMAT_DEFAULT: |
|
631 if (pad->have_fisbone && *dest_format == GST_FORMAT_TIME) { |
|
632 *dest_val = gst_annodex_granule_to_time (src_val, |
|
633 pad->granulerate_n, pad->granulerate_d, pad->granuleshift); |
|
634 |
|
635 res = TRUE; |
|
636 } else { |
|
637 if (pad->elem_pad == NULL) |
|
638 res = FALSE; |
|
639 else |
|
640 res = gst_pad_query_convert (pad->elem_pad, src_format, src_val, |
|
641 dest_format, dest_val); |
|
642 } |
|
643 |
|
644 break; |
|
645 default: |
|
646 if (pad->elem_pad == NULL) |
|
647 res = FALSE; |
|
648 else |
|
649 res = gst_pad_query_convert (pad->elem_pad, src_format, src_val, |
|
650 dest_format, dest_val); |
|
651 } |
|
652 |
|
653 return res; |
|
654 } |
|
655 |
|
656 /* function called by the internal decoder elements when it outputs |
|
657 * a buffer. We use it to get the first timestamp of the stream |
|
658 */ |
|
659 static GstFlowReturn |
|
660 gst_ogg_pad_internal_chain (GstPad * pad, GstBuffer * buffer) |
|
661 { |
|
662 GstOggPad *oggpad; |
|
663 GstOggDemux *ogg; |
|
664 GstClockTime timestamp; |
|
665 |
|
666 oggpad = gst_pad_get_element_private (pad); |
|
667 ogg = GST_OGG_DEMUX (oggpad->ogg); |
|
668 |
|
669 timestamp = GST_BUFFER_TIMESTAMP (buffer); |
|
670 GST_DEBUG_OBJECT (oggpad, "received buffer from internal pad, TS=%" |
|
671 GST_TIME_FORMAT "=%" G_GINT64_FORMAT, GST_TIME_ARGS (timestamp), |
|
672 timestamp); |
|
673 |
|
674 if (oggpad->start_time == GST_CLOCK_TIME_NONE) { |
|
675 oggpad->start_time = timestamp; |
|
676 GST_DEBUG_OBJECT (oggpad, "new start time: %" GST_TIME_FORMAT, |
|
677 GST_TIME_ARGS (timestamp)); |
|
678 } |
|
679 |
|
680 gst_buffer_unref (buffer); |
|
681 |
|
682 return GST_FLOW_OK; |
|
683 } |
|
684 |
|
685 static void |
|
686 internal_element_pad_added_cb (GstElement * element, GstPad * pad, |
|
687 GstOggPad * oggpad) |
|
688 { |
|
689 if (GST_PAD_DIRECTION (pad) == GST_PAD_SRC) { |
|
690 if (!(gst_pad_link (pad, oggpad->elem_out) == GST_PAD_LINK_OK)) { |
|
691 GST_ERROR ("Really couldn't find a valid pad"); |
|
692 } |
|
693 oggpad->dynamic = FALSE; |
|
694 g_signal_handler_disconnect (element, oggpad->padaddedid); |
|
695 oggpad->padaddedid = 0; |
|
696 } |
|
697 } |
|
698 |
|
699 /* runs typefind on the packet, which is assumed to be the first |
|
700 * packet in the stream. |
|
701 * |
|
702 * Based on the type returned from the typefind function, an element |
|
703 * is created to help in conversion between granulepos and timestamps |
|
704 * so that we can do decent seeking. |
|
705 */ |
|
706 static gboolean |
|
707 gst_ogg_pad_typefind (GstOggPad * pad, ogg_packet * packet) |
|
708 { |
|
709 GstBuffer *buf; |
|
710 GstCaps *caps; |
|
711 GstElement *element = NULL; |
|
712 |
|
713 #ifndef GST_DISABLE_GST_DEBUG |
|
714 GstOggDemux *ogg = pad->ogg; |
|
715 #endif |
|
716 |
|
717 if (GST_PAD_CAPS (pad) != NULL) |
|
718 return TRUE; |
|
719 |
|
720 /* The ogg spec defines that the first packet of an ogg stream must identify |
|
721 * the stream. Therefore ogg can use a simplified approach to typefinding |
|
722 * and only needs to check the first packet */ |
|
723 buf = gst_buffer_new (); |
|
724 GST_BUFFER_DATA (buf) = packet->packet; |
|
725 GST_BUFFER_SIZE (buf) = packet->bytes; |
|
726 GST_BUFFER_OFFSET (buf) = 0; |
|
727 |
|
728 caps = gst_type_find_helper_for_buffer (GST_OBJECT (pad), buf, NULL); |
|
729 gst_buffer_unref (buf); |
|
730 |
|
731 if (caps == NULL) { |
|
732 GST_WARNING_OBJECT (ogg, |
|
733 "couldn't find caps for stream with serial %08x", pad->serialno); |
|
734 caps = gst_caps_new_simple ("application/octet-stream", NULL); |
|
735 } else { |
|
736 /* ogg requires you to use a decoder element to define the |
|
737 * meaning of granulepos etc so we make one. We also do this if |
|
738 * we are in the streaming mode to calculate the first timestamp. */ |
|
739 GList *factories; |
|
740 |
|
741 GST_LOG_OBJECT (ogg, "found caps: %" GST_PTR_FORMAT, caps); |
|
742 |
|
743 /* first filter out the interesting element factories */ |
|
744 factories = gst_default_registry_feature_filter ( |
|
745 (GstPluginFeatureFilter) gst_ogg_demux_factory_filter, FALSE, caps); |
|
746 |
|
747 /* sort them according to their ranks */ |
|
748 factories = g_list_sort (factories, (GCompareFunc) compare_ranks); |
|
749 |
|
750 /* then pick the first factory to create an element */ |
|
751 if (factories) { |
|
752 element = |
|
753 gst_element_factory_create (GST_ELEMENT_FACTORY (factories->data), |
|
754 NULL); |
|
755 if (element) { |
|
756 GstPadTemplate *template; |
|
757 |
|
758 /* this is ours */ |
|
759 gst_object_ref (element); |
|
760 gst_object_sink (GST_OBJECT (element)); |
|
761 |
|
762 /* FIXME, it might not be named "sink" */ |
|
763 pad->elem_pad = gst_element_get_pad (element, "sink"); |
|
764 gst_element_set_state (element, GST_STATE_PAUSED); |
|
765 template = gst_static_pad_template_get (&internaltemplate); |
|
766 pad->elem_out = gst_pad_new_from_template (template, "internal"); |
|
767 gst_pad_set_chain_function (pad->elem_out, gst_ogg_pad_internal_chain); |
|
768 gst_pad_set_element_private (pad->elem_out, pad); |
|
769 gst_pad_set_active (pad->elem_out, TRUE); |
|
770 gst_object_unref (template); |
|
771 |
|
772 /* and this pad may not be named src.. */ |
|
773 /* And it might also not exist at this time... */ |
|
774 { |
|
775 GstPad *p; |
|
776 |
|
777 p = gst_element_get_pad (element, "src"); |
|
778 if (p) { |
|
779 gst_pad_link (p, pad->elem_out); |
|
780 gst_object_unref (p); |
|
781 } else { |
|
782 pad->dynamic = TRUE; |
|
783 pad->padaddedid = g_signal_connect (G_OBJECT (element), |
|
784 "pad-added", G_CALLBACK (internal_element_pad_added_cb), pad); |
|
785 } |
|
786 } |
|
787 } |
|
788 } |
|
789 g_list_free (factories); |
|
790 } |
|
791 pad->element = element; |
|
792 |
|
793 gst_pad_set_caps (GST_PAD (pad), caps); |
|
794 gst_caps_unref (caps); |
|
795 |
|
796 return TRUE; |
|
797 } |
|
798 |
|
799 /* send packet to internal element */ |
|
800 static GstFlowReturn |
|
801 gst_ogg_demux_chain_elem_pad (GstOggPad * pad, ogg_packet * packet) |
|
802 { |
|
803 GstBuffer *buf; |
|
804 GstFlowReturn ret; |
|
805 |
|
806 #ifndef GST_DISABLE_GST_DEBUG |
|
807 GstOggDemux *ogg = pad->ogg; |
|
808 #endif |
|
809 |
|
810 /* initialize our internal decoder with packets */ |
|
811 if (!pad->elem_pad) |
|
812 goto no_decoder; |
|
813 |
|
814 GST_DEBUG_OBJECT (ogg, "%p init decoder serial %08x", pad, pad->serialno); |
|
815 |
|
816 buf = gst_buffer_new_and_alloc (packet->bytes); |
|
817 memcpy (GST_BUFFER_DATA (buf), packet->packet, packet->bytes); |
|
818 gst_buffer_set_caps (buf, GST_PAD_CAPS (pad)); |
|
819 GST_BUFFER_OFFSET (buf) = -1; |
|
820 GST_BUFFER_OFFSET_END (buf) = packet->granulepos; |
|
821 |
|
822 ret = gst_pad_chain (pad->elem_pad, buf); |
|
823 if (GST_FLOW_IS_FATAL (ret)) |
|
824 goto decoder_error; |
|
825 |
|
826 return ret; |
|
827 |
|
828 no_decoder: |
|
829 { |
|
830 GST_WARNING_OBJECT (ogg, |
|
831 "pad %p does not have elem_pad, no decoder ?", pad); |
|
832 return GST_FLOW_ERROR; |
|
833 } |
|
834 decoder_error: |
|
835 { |
|
836 GST_WARNING_OBJECT (ogg, "internal decoder error"); |
|
837 return GST_FLOW_ERROR; |
|
838 } |
|
839 } |
|
840 |
|
841 /* queue data, basically takes the packet, puts it in a buffer and store the |
|
842 * buffer in the headers list. |
|
843 */ |
|
844 static GstFlowReturn |
|
845 gst_ogg_demux_queue_data (GstOggPad * pad, ogg_packet * packet) |
|
846 { |
|
847 GstBuffer *buf; |
|
848 |
|
849 #ifndef GST_DISABLE_GST_DEBUG |
|
850 GstOggDemux *ogg = pad->ogg; |
|
851 #endif |
|
852 |
|
853 GST_DEBUG_OBJECT (ogg, "%p queueing data serial %08x", pad, pad->serialno); |
|
854 |
|
855 buf = gst_buffer_new_and_alloc (packet->bytes); |
|
856 memcpy (buf->data, packet->packet, packet->bytes); |
|
857 GST_BUFFER_OFFSET (buf) = -1; |
|
858 GST_BUFFER_OFFSET_END (buf) = packet->granulepos; |
|
859 pad->headers = g_list_append (pad->headers, buf); |
|
860 |
|
861 /* we are ok now */ |
|
862 return GST_FLOW_OK; |
|
863 } |
|
864 |
|
865 /* send packet to internal element */ |
|
866 static GstFlowReturn |
|
867 gst_ogg_demux_chain_peer (GstOggPad * pad, ogg_packet * packet) |
|
868 { |
|
869 GstBuffer *buf; |
|
870 GstFlowReturn ret, cret; |
|
871 GstOggDemux *ogg = pad->ogg; |
|
872 GstFormat format; |
|
873 gint64 current_time; |
|
874 GstOggChain *chain; |
|
875 |
|
876 GST_DEBUG_OBJECT (ogg, |
|
877 "%p streaming to peer serial %08x", pad, pad->serialno); |
|
878 |
|
879 ret = |
|
880 gst_pad_alloc_buffer_and_set_caps (GST_PAD_CAST (pad), |
|
881 GST_BUFFER_OFFSET_NONE, packet->bytes, GST_PAD_CAPS (pad), &buf); |
|
882 |
|
883 /* combine flows */ |
|
884 cret = gst_ogg_demux_combine_flows (ogg, pad, ret); |
|
885 if (ret != GST_FLOW_OK) |
|
886 goto no_buffer; |
|
887 |
|
888 /* copy packet in buffer */ |
|
889 memcpy (buf->data, packet->packet, packet->bytes); |
|
890 |
|
891 GST_BUFFER_OFFSET (buf) = -1; |
|
892 GST_BUFFER_OFFSET_END (buf) = packet->granulepos; |
|
893 |
|
894 /* Mark discont on the buffer */ |
|
895 if (pad->discont) { |
|
896 GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); |
|
897 pad->discont = FALSE; |
|
898 } |
|
899 |
|
900 ret = gst_pad_push (GST_PAD_CAST (pad), buf); |
|
901 |
|
902 /* combine flows */ |
|
903 cret = gst_ogg_demux_combine_flows (ogg, pad, ret); |
|
904 |
|
905 /* we're done with skeleton stuff */ |
|
906 if (pad->is_skeleton) |
|
907 goto done; |
|
908 |
|
909 /* check if valid granulepos, then we can calculate the current |
|
910 * position */ |
|
911 if (packet->granulepos < 0) |
|
912 goto done; |
|
913 |
|
914 /* store current granule pos */ |
|
915 ogg->current_granule = packet->granulepos; |
|
916 |
|
917 /* convert to time */ |
|
918 format = GST_FORMAT_TIME; |
|
919 if (!gst_ogg_pad_query_convert (pad, |
|
920 GST_FORMAT_DEFAULT, packet->granulepos, &format, |
|
921 (gint64 *) & current_time)) |
|
922 goto convert_failed; |
|
923 |
|
924 /* convert to stream time */ |
|
925 if ((chain = pad->chain)) |
|
926 current_time = current_time - chain->segment_start + chain->begin_time; |
|
927 |
|
928 /* and store as the current position */ |
|
929 gst_segment_set_last_stop (&ogg->segment, GST_FORMAT_TIME, current_time); |
|
930 |
|
931 GST_DEBUG_OBJECT (ogg, "ogg current time %" GST_TIME_FORMAT, |
|
932 GST_TIME_ARGS (current_time)); |
|
933 |
|
934 done: |
|
935 /* return combined flow result */ |
|
936 return cret; |
|
937 |
|
938 /* special cases */ |
|
939 no_buffer: |
|
940 { |
|
941 GST_DEBUG_OBJECT (ogg, |
|
942 "%p could not get buffer from peer %08x, %d (%s), combined %d (%s)", |
|
943 pad, pad->serialno, ret, gst_flow_get_name (ret), |
|
944 cret, gst_flow_get_name (cret)); |
|
945 goto done; |
|
946 } |
|
947 convert_failed: |
|
948 { |
|
949 GST_WARNING_OBJECT (ogg, "could not convert granulepos to time"); |
|
950 goto done; |
|
951 } |
|
952 } |
|
953 |
|
954 /* submit a packet to the oggpad, this function will run the |
|
955 * typefind code for the pad if this is the first packet for this |
|
956 * stream |
|
957 */ |
|
958 static GstFlowReturn |
|
959 gst_ogg_pad_submit_packet (GstOggPad * pad, ogg_packet * packet) |
|
960 { |
|
961 gint64 granule; |
|
962 GstFlowReturn ret; |
|
963 |
|
964 GstOggDemux *ogg = pad->ogg; |
|
965 |
|
966 GST_DEBUG_OBJECT (ogg, "%p submit packet serial %08x", pad, pad->serialno); |
|
967 |
|
968 if (!pad->have_type) { |
|
969 if (!ogg->have_fishead && packet->bytes == SKELETON_FISHEAD_SIZE && |
|
970 !memcmp (packet->packet, "fishead\0", 8)) { |
|
971 gst_ogg_pad_parse_skeleton_fishead (pad, packet); |
|
972 } |
|
973 gst_ogg_pad_typefind (pad, packet); |
|
974 pad->have_type = TRUE; |
|
975 } |
|
976 |
|
977 if (ogg->have_fishead && packet->bytes >= SKELETON_FISBONE_MIN_SIZE && |
|
978 !memcmp (packet->packet, "fisbone\0", 8)) { |
|
979 gst_ogg_pad_parse_skeleton_fisbone (pad, packet); |
|
980 } |
|
981 |
|
982 granule = packet->granulepos; |
|
983 if (granule != -1) { |
|
984 GST_DEBUG_OBJECT (ogg, "%p has granulepos %" G_GINT64_FORMAT, pad, granule); |
|
985 ogg->current_granule = granule; |
|
986 pad->current_granule = granule; |
|
987 /* granulepos 0 and -1 are considered header packets. |
|
988 * Note that since theora is busted, it can create non-header |
|
989 * packets with 0 granulepos. We will correct for this when our |
|
990 * internal decoder produced a frame and we don't have a |
|
991 * granulepos because in that case the granulpos must have been 0 */ |
|
992 if (pad->first_granule == -1 && granule != 0) { |
|
993 GST_DEBUG_OBJECT (ogg, "%p found first granulepos %" G_GINT64_FORMAT, pad, |
|
994 granule); |
|
995 pad->first_granule = granule; |
|
996 } |
|
997 } |
|
998 |
|
999 if (granule != -1 && memcmp (packet->packet, "KW-DIRAC", 8) == 0) { |
|
1000 return GST_FLOW_OK; |
|
1001 } |
|
1002 |
|
1003 /* no start time known, stream to internal plugin to |
|
1004 * get time. always stream to the skeleton decoder */ |
|
1005 if (pad->start_time == GST_CLOCK_TIME_NONE || pad->is_skeleton) { |
|
1006 ret = gst_ogg_demux_chain_elem_pad (pad, packet); |
|
1007 } |
|
1008 /* we know the start_time of the pad data, see if we |
|
1009 * can activate the complete chain if this is a dynamic |
|
1010 * chain. */ |
|
1011 if (pad->start_time != GST_CLOCK_TIME_NONE) { |
|
1012 GstOggChain *chain = pad->chain; |
|
1013 |
|
1014 /* correction for busted ogg, if the internal decoder outputed |
|
1015 * a timestamp but we did not get a granulepos, it must have |
|
1016 * been 0 and the time is therefore also 0 */ |
|
1017 if (pad->first_granule == -1) { |
|
1018 GST_DEBUG_OBJECT (ogg, "%p found start time without granulepos", pad); |
|
1019 pad->first_granule = 0; |
|
1020 pad->first_time = 0; |
|
1021 } |
|
1022 |
|
1023 /* check if complete chain has start time */ |
|
1024 if (chain == ogg->building_chain) { |
|
1025 |
|
1026 /* see if we have enough info to activate the chain, we have enough info |
|
1027 * when all streams have a valid start time. */ |
|
1028 if (gst_ogg_demux_collect_chain_info (ogg, chain)) { |
|
1029 GstEvent *event; |
|
1030 |
|
1031 GST_DEBUG_OBJECT (ogg, "segment_start: %" GST_TIME_FORMAT, |
|
1032 GST_TIME_ARGS (chain->segment_start)); |
|
1033 GST_DEBUG_OBJECT (ogg, "segment_stop: %" GST_TIME_FORMAT, |
|
1034 GST_TIME_ARGS (chain->segment_stop)); |
|
1035 GST_DEBUG_OBJECT (ogg, "segment_time: %" GST_TIME_FORMAT, |
|
1036 GST_TIME_ARGS (chain->begin_time)); |
|
1037 |
|
1038 /* create the newsegment event we are going to send out */ |
|
1039 event = gst_event_new_new_segment (FALSE, ogg->segment.rate, |
|
1040 GST_FORMAT_TIME, chain->segment_start, chain->segment_stop, |
|
1041 chain->begin_time); |
|
1042 |
|
1043 gst_ogg_demux_activate_chain (ogg, chain, event); |
|
1044 |
|
1045 ogg->building_chain = NULL; |
|
1046 } |
|
1047 } |
|
1048 } |
|
1049 |
|
1050 /* if we are building a chain, store buffer for when we activate |
|
1051 * it. This path is taken if we operate in streaming mode. */ |
|
1052 if (ogg->building_chain) { |
|
1053 ret = gst_ogg_demux_queue_data (pad, packet); |
|
1054 } |
|
1055 /* else we are completely streaming to the peer */ |
|
1056 else { |
|
1057 ret = gst_ogg_demux_chain_peer (pad, packet); |
|
1058 } |
|
1059 return ret; |
|
1060 } |
|
1061 |
|
1062 /* flush at most @npackets from the stream layer. All packets if |
|
1063 * @npackets is 0; |
|
1064 */ |
|
1065 static GstFlowReturn |
|
1066 gst_ogg_pad_stream_out (GstOggPad * pad, gint npackets) |
|
1067 { |
|
1068 GstFlowReturn result = GST_FLOW_OK; |
|
1069 gboolean done = FALSE; |
|
1070 GstOggDemux *ogg; |
|
1071 |
|
1072 ogg = pad->ogg; |
|
1073 |
|
1074 while (!done) { |
|
1075 int ret; |
|
1076 ogg_packet packet; |
|
1077 |
|
1078 ret = ogg_stream_packetout (&pad->stream, &packet); |
|
1079 switch (ret) { |
|
1080 case 0: |
|
1081 GST_LOG_OBJECT (ogg, "packetout done"); |
|
1082 done = TRUE; |
|
1083 break; |
|
1084 case -1: |
|
1085 GST_LOG_OBJECT (ogg, "packetout discont"); |
|
1086 gst_ogg_chain_mark_discont (pad->chain); |
|
1087 break; |
|
1088 case 1: |
|
1089 GST_LOG_OBJECT (ogg, "packetout gave packet of size %ld", packet.bytes); |
|
1090 result = gst_ogg_pad_submit_packet (pad, &packet); |
|
1091 if (GST_FLOW_IS_FATAL (result)) |
|
1092 goto could_not_submit; |
|
1093 break; |
|
1094 default: |
|
1095 GST_WARNING_OBJECT (ogg, |
|
1096 "invalid return value %d for ogg_stream_packetout, resetting stream", |
|
1097 ret); |
|
1098 gst_ogg_pad_reset (pad); |
|
1099 break; |
|
1100 } |
|
1101 if (npackets > 0) { |
|
1102 npackets--; |
|
1103 done = (npackets == 0); |
|
1104 } |
|
1105 } |
|
1106 return result; |
|
1107 |
|
1108 /* ERRORS */ |
|
1109 could_not_submit: |
|
1110 { |
|
1111 GST_WARNING_OBJECT (ogg, |
|
1112 "could not submit packet for stream %08x, error: %d", pad->serialno, |
|
1113 result); |
|
1114 gst_ogg_pad_reset (pad); |
|
1115 return result; |
|
1116 } |
|
1117 } |
|
1118 |
|
1119 /* submit a page to an oggpad, this function will then submit all |
|
1120 * the packets in the page. |
|
1121 */ |
|
1122 static GstFlowReturn |
|
1123 gst_ogg_pad_submit_page (GstOggPad * pad, ogg_page * page) |
|
1124 { |
|
1125 GstFlowReturn result = GST_FLOW_OK; |
|
1126 GstOggDemux *ogg; |
|
1127 gboolean continued = FALSE; |
|
1128 |
|
1129 ogg = pad->ogg; |
|
1130 |
|
1131 /* for negative rates we read pages backwards and must therefore be carefull |
|
1132 * with continued pages */ |
|
1133 if (ogg->segment.rate < 0.0) { |
|
1134 gint npackets; |
|
1135 |
|
1136 continued = ogg_page_continued (page); |
|
1137 |
|
1138 /* number of completed packets in the page */ |
|
1139 npackets = ogg_page_packets (page); |
|
1140 if (!continued) { |
|
1141 /* page is not continued so it contains at least one packet start. It's |
|
1142 * possible that no packet ends on this page (npackets == 0). In that |
|
1143 * case, the next (continued) page(s) we kept contain the remainder of the |
|
1144 * packets. We mark npackets=1 to make us start decoding the pages in the |
|
1145 * remainder of the algorithm. */ |
|
1146 if (npackets == 0) |
|
1147 npackets = 1; |
|
1148 } |
|
1149 GST_LOG_OBJECT (ogg, "continued: %d, %d packets", continued, npackets); |
|
1150 |
|
1151 if (npackets == 0) { |
|
1152 GST_LOG_OBJECT (ogg, "no decodable packets, we need a previous page"); |
|
1153 goto done; |
|
1154 } |
|
1155 } |
|
1156 |
|
1157 if (ogg_stream_pagein (&pad->stream, page) != 0) |
|
1158 goto choked; |
|
1159 |
|
1160 /* flush all packets in the stream layer, this might not give a packet if |
|
1161 * the page had no packets finishing on the page (npackets == 0). */ |
|
1162 result = gst_ogg_pad_stream_out (pad, 0); |
|
1163 |
|
1164 if (pad->continued) { |
|
1165 ogg_packet packet; |
|
1166 |
|
1167 /* now send the continued pages to the stream layer */ |
|
1168 while (pad->continued) { |
|
1169 ogg_page *p = (ogg_page *) pad->continued->data; |
|
1170 |
|
1171 GST_LOG_OBJECT (ogg, "submitting continued page %p", p); |
|
1172 if (ogg_stream_pagein (&pad->stream, p) != 0) |
|
1173 goto choked; |
|
1174 |
|
1175 pad->continued = g_list_delete_link (pad->continued, pad->continued); |
|
1176 |
|
1177 /* free the page */ |
|
1178 gst_ogg_page_free (p); |
|
1179 } |
|
1180 |
|
1181 GST_LOG_OBJECT (ogg, "flushing last continued packet"); |
|
1182 /* flush 1 continued packet in the stream layer */ |
|
1183 result = gst_ogg_pad_stream_out (pad, 1); |
|
1184 |
|
1185 /* flush all remaining packets, we pushed them in the previous round. |
|
1186 * We don't use _reset() because we still want to get the discont when |
|
1187 * we submit a next page. */ |
|
1188 while (ogg_stream_packetout (&pad->stream, &packet) != 0); |
|
1189 } |
|
1190 |
|
1191 done: |
|
1192 /* keep continued pages (only in reverse mode) */ |
|
1193 if (continued) { |
|
1194 ogg_page *p = gst_ogg_page_copy (page); |
|
1195 |
|
1196 GST_LOG_OBJECT (ogg, "keeping continued page %p", p); |
|
1197 pad->continued = g_list_prepend (pad->continued, p); |
|
1198 } |
|
1199 |
|
1200 return result; |
|
1201 |
|
1202 choked: |
|
1203 { |
|
1204 GST_WARNING_OBJECT (ogg, |
|
1205 "ogg stream choked on page (serial %08x), resetting stream", |
|
1206 pad->serialno); |
|
1207 gst_ogg_pad_reset (pad); |
|
1208 /* we continue to recover */ |
|
1209 return GST_FLOW_OK; |
|
1210 } |
|
1211 } |
|
1212 |
|
1213 |
|
1214 static GstOggChain * |
|
1215 gst_ogg_chain_new (GstOggDemux * ogg) |
|
1216 { |
|
1217 GstOggChain *chain = g_new0 (GstOggChain, 1); |
|
1218 |
|
1219 GST_DEBUG_OBJECT (ogg, "creating new chain %p", chain); |
|
1220 chain->ogg = ogg; |
|
1221 chain->offset = -1; |
|
1222 chain->bytes = -1; |
|
1223 chain->have_bos = FALSE; |
|
1224 chain->streams = g_array_new (FALSE, TRUE, sizeof (GstOggPad *)); |
|
1225 chain->begin_time = GST_CLOCK_TIME_NONE; |
|
1226 chain->segment_start = GST_CLOCK_TIME_NONE; |
|
1227 chain->segment_stop = GST_CLOCK_TIME_NONE; |
|
1228 chain->total_time = GST_CLOCK_TIME_NONE; |
|
1229 |
|
1230 return chain; |
|
1231 } |
|
1232 |
|
1233 static void |
|
1234 gst_ogg_chain_free (GstOggChain * chain) |
|
1235 { |
|
1236 gint i; |
|
1237 |
|
1238 for (i = 0; i < chain->streams->len; i++) { |
|
1239 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
|
1240 |
|
1241 gst_object_unref (pad); |
|
1242 } |
|
1243 g_array_free (chain->streams, TRUE); |
|
1244 g_free (chain); |
|
1245 } |
|
1246 |
|
1247 static void |
|
1248 gst_ogg_chain_mark_discont (GstOggChain * chain) |
|
1249 { |
|
1250 gint i; |
|
1251 |
|
1252 for (i = 0; i < chain->streams->len; i++) { |
|
1253 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
|
1254 |
|
1255 pad->discont = TRUE; |
|
1256 } |
|
1257 } |
|
1258 |
|
1259 static void |
|
1260 gst_ogg_chain_reset (GstOggChain * chain) |
|
1261 { |
|
1262 gint i; |
|
1263 |
|
1264 for (i = 0; i < chain->streams->len; i++) { |
|
1265 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
|
1266 |
|
1267 gst_ogg_pad_reset (pad); |
|
1268 } |
|
1269 } |
|
1270 |
|
1271 static GstOggPad * |
|
1272 gst_ogg_chain_new_stream (GstOggChain * chain, glong serialno) |
|
1273 { |
|
1274 GstOggPad *ret; |
|
1275 GstTagList *list; |
|
1276 gchar *name; |
|
1277 |
|
1278 GST_DEBUG_OBJECT (chain->ogg, "creating new stream %08lx in chain %p", |
|
1279 serialno, chain); |
|
1280 |
|
1281 ret = g_object_new (GST_TYPE_OGG_PAD, NULL); |
|
1282 /* we own this one */ |
|
1283 gst_object_ref (ret); |
|
1284 gst_object_sink (ret); |
|
1285 |
|
1286 GST_PAD_DIRECTION (ret) = GST_PAD_SRC; |
|
1287 ret->discont = TRUE; |
|
1288 |
|
1289 ret->chain = chain; |
|
1290 ret->ogg = chain->ogg; |
|
1291 |
|
1292 ret->serialno = serialno; |
|
1293 if (ogg_stream_init (&ret->stream, serialno) != 0) |
|
1294 goto init_failed; |
|
1295 |
|
1296 name = g_strdup_printf ("serial_%08lx", serialno); |
|
1297 gst_object_set_name (GST_OBJECT (ret), name); |
|
1298 g_free (name); |
|
1299 |
|
1300 /* FIXME: either do something with it or remove it */ |
|
1301 list = gst_tag_list_new (); |
|
1302 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_SERIAL, serialno, |
|
1303 NULL); |
|
1304 gst_tag_list_free (list); |
|
1305 |
|
1306 GST_DEBUG_OBJECT (chain->ogg, |
|
1307 "created new ogg src %p for stream with serial %08lx", ret, serialno); |
|
1308 |
|
1309 g_array_append_val (chain->streams, ret); |
|
1310 |
|
1311 return ret; |
|
1312 |
|
1313 /* ERRORS */ |
|
1314 init_failed: |
|
1315 { |
|
1316 GST_ERROR ("Could not initialize ogg_stream struct for serial %08lx.", |
|
1317 serialno); |
|
1318 gst_object_unref (ret); |
|
1319 return NULL; |
|
1320 } |
|
1321 } |
|
1322 |
|
1323 static GstOggPad * |
|
1324 gst_ogg_chain_get_stream (GstOggChain * chain, glong serialno) |
|
1325 { |
|
1326 gint i; |
|
1327 |
|
1328 for (i = 0; i < chain->streams->len; i++) { |
|
1329 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
|
1330 |
|
1331 if (pad->serialno == serialno) |
|
1332 return pad; |
|
1333 } |
|
1334 return NULL; |
|
1335 } |
|
1336 |
|
1337 static gboolean |
|
1338 gst_ogg_chain_has_stream (GstOggChain * chain, glong serialno) |
|
1339 { |
|
1340 return gst_ogg_chain_get_stream (chain, serialno) != NULL; |
|
1341 } |
|
1342 |
|
1343 #define CURRENT_CHAIN(ogg) (&g_array_index ((ogg)->chains, GstOggChain, (ogg)->current_chain)) |
|
1344 |
|
1345 /* signals and args */ |
|
1346 enum |
|
1347 { |
|
1348 /* FILL ME */ |
|
1349 LAST_SIGNAL |
|
1350 }; |
|
1351 |
|
1352 enum |
|
1353 { |
|
1354 ARG_0 |
|
1355 /* FILL ME */ |
|
1356 }; |
|
1357 |
|
1358 static GstStaticPadTemplate ogg_demux_src_template_factory = |
|
1359 GST_STATIC_PAD_TEMPLATE ("src_%d", |
|
1360 GST_PAD_SRC, |
|
1361 GST_PAD_SOMETIMES, |
|
1362 GST_STATIC_CAPS_ANY); |
|
1363 |
|
1364 static GstStaticPadTemplate ogg_demux_sink_template_factory = |
|
1365 GST_STATIC_PAD_TEMPLATE ("sink", |
|
1366 GST_PAD_SINK, |
|
1367 GST_PAD_ALWAYS, |
|
1368 GST_STATIC_CAPS ("application/ogg; application/x-annodex") |
|
1369 ); |
|
1370 |
|
1371 static void gst_ogg_demux_finalize (GObject * object); |
|
1372 |
|
1373 //static const GstEventMask *gst_ogg_demux_get_event_masks (GstPad * pad); |
|
1374 //static const GstQueryType *gst_ogg_demux_get_query_types (GstPad * pad); |
|
1375 static GstFlowReturn gst_ogg_demux_read_chain (GstOggDemux * ogg, |
|
1376 GstOggChain ** chain); |
|
1377 static GstFlowReturn gst_ogg_demux_read_end_chain (GstOggDemux * ogg, |
|
1378 GstOggChain * chain); |
|
1379 |
|
1380 static gboolean gst_ogg_demux_sink_event (GstPad * pad, GstEvent * event); |
|
1381 static void gst_ogg_demux_loop (GstOggPad * pad); |
|
1382 static GstFlowReturn gst_ogg_demux_chain (GstPad * pad, GstBuffer * buffer); |
|
1383 static gboolean gst_ogg_demux_sink_activate (GstPad * sinkpad); |
|
1384 static gboolean gst_ogg_demux_sink_activate_pull (GstPad * sinkpad, |
|
1385 gboolean active); |
|
1386 static gboolean gst_ogg_demux_sink_activate_push (GstPad * sinkpad, |
|
1387 gboolean active); |
|
1388 static GstStateChangeReturn gst_ogg_demux_change_state (GstElement * element, |
|
1389 GstStateChange transition); |
|
1390 static void gst_ogg_demux_send_event (GstOggDemux * ogg, GstEvent * event); |
|
1391 |
|
1392 static void gst_ogg_print (GstOggDemux * demux); |
|
1393 |
|
1394 GST_BOILERPLATE (GstOggDemux, gst_ogg_demux, GstElement, GST_TYPE_ELEMENT); |
|
1395 |
|
1396 static void |
|
1397 gst_ogg_demux_base_init (gpointer g_class) |
|
1398 { |
|
1399 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); |
|
1400 |
|
1401 gst_element_class_set_details (element_class, &gst_ogg_demux_details); |
|
1402 |
|
1403 gst_element_class_add_pad_template (element_class, |
|
1404 gst_static_pad_template_get (&ogg_demux_sink_template_factory)); |
|
1405 gst_element_class_add_pad_template (element_class, |
|
1406 gst_static_pad_template_get (&ogg_demux_src_template_factory)); |
|
1407 } |
|
1408 static void |
|
1409 gst_ogg_demux_class_init (GstOggDemuxClass * klass) |
|
1410 { |
|
1411 GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); |
|
1412 GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
|
1413 |
|
1414 gstelement_class->change_state = gst_ogg_demux_change_state; |
|
1415 gstelement_class->send_event = gst_ogg_demux_receive_event; |
|
1416 |
|
1417 gobject_class->finalize = gst_ogg_demux_finalize; |
|
1418 } |
|
1419 |
|
1420 static void |
|
1421 gst_ogg_demux_init (GstOggDemux * ogg, GstOggDemuxClass * g_class) |
|
1422 { |
|
1423 /* create the sink pad */ |
|
1424 ogg->sinkpad = |
|
1425 gst_pad_new_from_static_template (&ogg_demux_sink_template_factory, |
|
1426 "sink"); |
|
1427 |
|
1428 gst_pad_set_event_function (ogg->sinkpad, gst_ogg_demux_sink_event); |
|
1429 gst_pad_set_chain_function (ogg->sinkpad, gst_ogg_demux_chain); |
|
1430 gst_pad_set_activate_function (ogg->sinkpad, gst_ogg_demux_sink_activate); |
|
1431 gst_pad_set_activatepull_function (ogg->sinkpad, |
|
1432 gst_ogg_demux_sink_activate_pull); |
|
1433 gst_pad_set_activatepush_function (ogg->sinkpad, |
|
1434 gst_ogg_demux_sink_activate_push); |
|
1435 gst_element_add_pad (GST_ELEMENT (ogg), ogg->sinkpad); |
|
1436 |
|
1437 ogg->chain_lock = g_mutex_new (); |
|
1438 ogg->chains = g_array_new (FALSE, TRUE, sizeof (GstOggChain *)); |
|
1439 |
|
1440 ogg->newsegment = NULL; |
|
1441 } |
|
1442 |
|
1443 static void |
|
1444 gst_ogg_demux_finalize (GObject * object) |
|
1445 { |
|
1446 GstOggDemux *ogg; |
|
1447 |
|
1448 ogg = GST_OGG_DEMUX (object); |
|
1449 |
|
1450 g_array_free (ogg->chains, TRUE); |
|
1451 g_mutex_free (ogg->chain_lock); |
|
1452 ogg_sync_clear (&ogg->sync); |
|
1453 |
|
1454 if (ogg->newsegment) |
|
1455 gst_event_unref (ogg->newsegment); |
|
1456 |
|
1457 G_OBJECT_CLASS (parent_class)->finalize (object); |
|
1458 } |
|
1459 |
|
1460 static gboolean |
|
1461 gst_ogg_demux_sink_event (GstPad * pad, GstEvent * event) |
|
1462 { |
|
1463 gboolean res; |
|
1464 GstOggDemux *ogg; |
|
1465 |
|
1466 ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad)); |
|
1467 |
|
1468 switch (GST_EVENT_TYPE (event)) { |
|
1469 case GST_EVENT_NEWSEGMENT: |
|
1470 /* FIXME */ |
|
1471 GST_DEBUG_OBJECT (ogg, "got a new segment event"); |
|
1472 ogg_sync_reset (&ogg->sync); |
|
1473 gst_event_unref (event); |
|
1474 res = TRUE; |
|
1475 break; |
|
1476 case GST_EVENT_EOS: |
|
1477 default: |
|
1478 res = gst_pad_event_default (pad, event); |
|
1479 break; |
|
1480 } |
|
1481 gst_object_unref (ogg); |
|
1482 |
|
1483 return res; |
|
1484 } |
|
1485 |
|
1486 /* submit the given buffer to the ogg sync. |
|
1487 * |
|
1488 * Returns the number of bytes submited. |
|
1489 */ |
|
1490 static GstFlowReturn |
|
1491 gst_ogg_demux_submit_buffer (GstOggDemux * ogg, GstBuffer * buffer) |
|
1492 { |
|
1493 gint size; |
|
1494 guint8 *data; |
|
1495 gchar *oggbuffer; |
|
1496 GstFlowReturn ret = GST_FLOW_OK; |
|
1497 |
|
1498 size = GST_BUFFER_SIZE (buffer); |
|
1499 data = GST_BUFFER_DATA (buffer); |
|
1500 |
|
1501 GST_DEBUG_OBJECT (ogg, "submitting %u bytes", size); |
|
1502 if (G_UNLIKELY (size == 0)) |
|
1503 goto done; |
|
1504 |
|
1505 oggbuffer = ogg_sync_buffer (&ogg->sync, size); |
|
1506 if (G_UNLIKELY (oggbuffer == NULL)) |
|
1507 goto no_buffer; |
|
1508 |
|
1509 memcpy (oggbuffer, data, size); |
|
1510 if (G_UNLIKELY (ogg_sync_wrote (&ogg->sync, size) < 0)) |
|
1511 goto write_failed; |
|
1512 |
|
1513 done: |
|
1514 gst_buffer_unref (buffer); |
|
1515 |
|
1516 return ret; |
|
1517 |
|
1518 /* ERRORS */ |
|
1519 no_buffer: |
|
1520 { |
|
1521 GST_ELEMENT_ERROR (ogg, STREAM, DECODE, |
|
1522 (NULL), ("failed to get ogg sync buffer")); |
|
1523 ret = GST_FLOW_ERROR; |
|
1524 goto done; |
|
1525 } |
|
1526 write_failed: |
|
1527 { |
|
1528 GST_ELEMENT_ERROR (ogg, STREAM, DECODE, |
|
1529 (NULL), ("failed to write %d bytes to the sync buffer", size)); |
|
1530 ret = GST_FLOW_ERROR; |
|
1531 goto done; |
|
1532 } |
|
1533 } |
|
1534 |
|
1535 /* in random access mode this code updates the current read position |
|
1536 * and resets the ogg sync buffer so that the next read will happen |
|
1537 * from this new location. |
|
1538 */ |
|
1539 static void |
|
1540 gst_ogg_demux_seek (GstOggDemux * ogg, gint64 offset) |
|
1541 { |
|
1542 GST_LOG_OBJECT (ogg, "seeking to %" G_GINT64_FORMAT, offset); |
|
1543 |
|
1544 ogg->offset = offset; |
|
1545 ogg_sync_reset (&ogg->sync); |
|
1546 } |
|
1547 |
|
1548 /* read more data from the current offset and submit to |
|
1549 * the ogg sync layer. |
|
1550 */ |
|
1551 static GstFlowReturn |
|
1552 gst_ogg_demux_get_data (GstOggDemux * ogg) |
|
1553 { |
|
1554 GstFlowReturn ret; |
|
1555 GstBuffer *buffer; |
|
1556 |
|
1557 GST_LOG_OBJECT (ogg, "get data %" G_GINT64_FORMAT " %" G_GINT64_FORMAT, |
|
1558 ogg->offset, ogg->length); |
|
1559 if (ogg->offset == ogg->length) |
|
1560 goto eos; |
|
1561 |
|
1562 ret = gst_pad_pull_range (ogg->sinkpad, ogg->offset, CHUNKSIZE, &buffer); |
|
1563 if (ret != GST_FLOW_OK) |
|
1564 goto error; |
|
1565 |
|
1566 ret = gst_ogg_demux_submit_buffer (ogg, buffer); |
|
1567 |
|
1568 return ret; |
|
1569 |
|
1570 /* ERROR */ |
|
1571 eos: |
|
1572 { |
|
1573 GST_LOG_OBJECT (ogg, "reached EOS"); |
|
1574 return GST_FLOW_UNEXPECTED; |
|
1575 } |
|
1576 error: |
|
1577 { |
|
1578 GST_WARNING_OBJECT (ogg, "got %d (%s) from pull range", ret, |
|
1579 gst_flow_get_name (ret)); |
|
1580 return ret; |
|
1581 } |
|
1582 } |
|
1583 |
|
1584 /* Read the next page from the current offset. |
|
1585 * boundary: number of bytes ahead we allow looking for; |
|
1586 * -1 if no boundary |
|
1587 * |
|
1588 * @offset will contain the offset the next page starts at when this function |
|
1589 * returns GST_FLOW_OK. |
|
1590 * |
|
1591 * GST_FLOW_UNEXPECTED is returned on EOS. |
|
1592 * |
|
1593 * GST_FLOW_LIMIT is returned when we did not find a page before the |
|
1594 * boundary. If @boundary is -1, this is never returned. |
|
1595 * |
|
1596 * Any other error returned while retrieving data from the peer is returned as |
|
1597 * is. |
|
1598 */ |
|
1599 static GstFlowReturn |
|
1600 gst_ogg_demux_get_next_page (GstOggDemux * ogg, ogg_page * og, gint64 boundary, |
|
1601 gint64 * offset) |
|
1602 { |
|
1603 gint64 end_offset = 0; |
|
1604 GstFlowReturn ret; |
|
1605 |
|
1606 GST_LOG_OBJECT (ogg, |
|
1607 "get next page, current offset %" G_GINT64_FORMAT ", bytes boundary %" |
|
1608 G_GINT64_FORMAT, ogg->offset, boundary); |
|
1609 |
|
1610 if (boundary > 0) |
|
1611 end_offset = ogg->offset + boundary; |
|
1612 |
|
1613 while (TRUE) { |
|
1614 glong more; |
|
1615 |
|
1616 if (boundary > 0 && ogg->offset >= end_offset) |
|
1617 goto boundary_reached; |
|
1618 |
|
1619 more = ogg_sync_pageseek (&ogg->sync, og); |
|
1620 |
|
1621 GST_LOG_OBJECT (ogg, "pageseek gave %ld", more); |
|
1622 |
|
1623 if (more < 0) { |
|
1624 /* skipped n bytes */ |
|
1625 GST_LOG_OBJECT (ogg, "skipped %ld bytes", more); |
|
1626 ogg->offset -= more; |
|
1627 } else if (more == 0) { |
|
1628 /* we need more data */ |
|
1629 if (boundary == 0) |
|
1630 goto boundary_reached; |
|
1631 |
|
1632 GST_LOG_OBJECT (ogg, "need more data"); |
|
1633 ret = gst_ogg_demux_get_data (ogg); |
|
1634 if (ret != GST_FLOW_OK) |
|
1635 break; |
|
1636 } else { |
|
1637 gint64 res_offset = ogg->offset; |
|
1638 |
|
1639 /* got a page. Return the offset at the page beginning, |
|
1640 advance the internal offset past the page end */ |
|
1641 if (offset) |
|
1642 *offset = res_offset; |
|
1643 ret = GST_FLOW_OK; |
|
1644 |
|
1645 ogg->offset += more; |
|
1646 /* need to reset as we do not keep track of the bytes we |
|
1647 * sent to the sync layer */ |
|
1648 ogg_sync_reset (&ogg->sync); |
|
1649 |
|
1650 GST_LOG_OBJECT (ogg, |
|
1651 "got page at %" G_GINT64_FORMAT ", serial %08x, end at %" |
|
1652 G_GINT64_FORMAT ", granule %" G_GINT64_FORMAT, res_offset, |
|
1653 ogg_page_serialno (og), ogg->offset, ogg_page_granulepos (og)); |
|
1654 break; |
|
1655 } |
|
1656 } |
|
1657 GST_LOG_OBJECT (ogg, "returning %d", ret); |
|
1658 |
|
1659 return ret; |
|
1660 |
|
1661 /* ERRORS */ |
|
1662 boundary_reached: |
|
1663 { |
|
1664 GST_LOG_OBJECT (ogg, |
|
1665 "offset %" G_GINT64_FORMAT " >= end_offset %" G_GINT64_FORMAT, |
|
1666 ogg->offset, end_offset); |
|
1667 return GST_FLOW_LIMIT; |
|
1668 } |
|
1669 } |
|
1670 |
|
1671 /* from the current offset, find the previous page, seeking backwards |
|
1672 * until we find the page. |
|
1673 */ |
|
1674 static GstFlowReturn |
|
1675 gst_ogg_demux_get_prev_page (GstOggDemux * ogg, ogg_page * og, gint64 * offset) |
|
1676 { |
|
1677 GstFlowReturn ret; |
|
1678 gint64 begin = ogg->offset; |
|
1679 gint64 end = begin; |
|
1680 gint64 cur_offset = -1; |
|
1681 |
|
1682 while (cur_offset == -1) { |
|
1683 begin -= CHUNKSIZE; |
|
1684 if (begin < 0) |
|
1685 begin = 0; |
|
1686 |
|
1687 /* seek CHUNKSIZE back */ |
|
1688 gst_ogg_demux_seek (ogg, begin); |
|
1689 |
|
1690 /* now continue reading until we run out of data, if we find a page |
|
1691 * start, we save it. It might not be the final page as there could be |
|
1692 * another page after this one. */ |
|
1693 while (ogg->offset < end) { |
|
1694 gint64 new_offset; |
|
1695 |
|
1696 ret = |
|
1697 gst_ogg_demux_get_next_page (ogg, og, end - ogg->offset, &new_offset); |
|
1698 /* we hit the upper limit, offset contains the last page start */ |
|
1699 if (ret == GST_FLOW_LIMIT) |
|
1700 break; |
|
1701 /* something went wrong */ |
|
1702 if (ret == GST_FLOW_UNEXPECTED) |
|
1703 new_offset = 0; |
|
1704 else if (ret != GST_FLOW_OK) |
|
1705 return ret; |
|
1706 |
|
1707 /* offset is next page start */ |
|
1708 cur_offset = new_offset; |
|
1709 } |
|
1710 } |
|
1711 |
|
1712 /* we have the offset. Actually snork and hold the page now */ |
|
1713 gst_ogg_demux_seek (ogg, cur_offset); |
|
1714 ret = gst_ogg_demux_get_next_page (ogg, og, -1, NULL); |
|
1715 if (ret != GST_FLOW_OK) |
|
1716 /* this shouldn't be possible */ |
|
1717 return ret; |
|
1718 |
|
1719 if (offset) |
|
1720 *offset = cur_offset; |
|
1721 |
|
1722 return ret; |
|
1723 } |
|
1724 |
|
1725 static gboolean |
|
1726 gst_ogg_demux_deactivate_current_chain (GstOggDemux * ogg) |
|
1727 { |
|
1728 gint i; |
|
1729 GstOggChain *chain = ogg->current_chain; |
|
1730 |
|
1731 if (chain == NULL) |
|
1732 return TRUE; |
|
1733 |
|
1734 GST_DEBUG_OBJECT (ogg, "deactivating chain %p", chain); |
|
1735 |
|
1736 /* send EOS on all the pads */ |
|
1737 for (i = 0; i < chain->streams->len; i++) { |
|
1738 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
|
1739 |
|
1740 gst_pad_push_event (GST_PAD_CAST (pad), gst_event_new_eos ()); |
|
1741 |
|
1742 GST_DEBUG_OBJECT (ogg, "removing pad %" GST_PTR_FORMAT, pad); |
|
1743 |
|
1744 /* deactivate first */ |
|
1745 gst_pad_set_active (GST_PAD_CAST (pad), FALSE); |
|
1746 |
|
1747 gst_element_remove_pad (GST_ELEMENT (ogg), GST_PAD_CAST (pad)); |
|
1748 } |
|
1749 /* if we cannot seek back to the chain, we can destroy the chain |
|
1750 * completely */ |
|
1751 if (!ogg->seekable) { |
|
1752 gst_ogg_chain_free (chain); |
|
1753 } |
|
1754 ogg->current_chain = NULL; |
|
1755 |
|
1756 return TRUE; |
|
1757 } |
|
1758 |
|
1759 static gboolean |
|
1760 gst_ogg_demux_activate_chain (GstOggDemux * ogg, GstOggChain * chain, |
|
1761 GstEvent * event) |
|
1762 { |
|
1763 gint i; |
|
1764 |
|
1765 if (chain == ogg->current_chain) { |
|
1766 if (event) |
|
1767 gst_event_unref (event); |
|
1768 return TRUE; |
|
1769 } |
|
1770 |
|
1771 gst_ogg_demux_deactivate_current_chain (ogg); |
|
1772 |
|
1773 /* FIXME, should not be called with NULL */ |
|
1774 if (chain == NULL) { |
|
1775 ogg->current_chain = chain; |
|
1776 return TRUE; |
|
1777 } |
|
1778 |
|
1779 GST_DEBUG_OBJECT (ogg, "activating chain %p", chain); |
|
1780 |
|
1781 /* first add the pads */ |
|
1782 for (i = 0; i < chain->streams->len; i++) { |
|
1783 GstOggPad *pad; |
|
1784 |
|
1785 pad = g_array_index (chain->streams, GstOggPad *, i); |
|
1786 GST_DEBUG_OBJECT (ogg, "adding pad %" GST_PTR_FORMAT, pad); |
|
1787 |
|
1788 /* mark discont */ |
|
1789 pad->discont = TRUE; |
|
1790 pad->last_ret = GST_FLOW_OK; |
|
1791 |
|
1792 /* activate first */ |
|
1793 gst_pad_set_active (GST_PAD_CAST (pad), TRUE); |
|
1794 |
|
1795 gst_element_add_pad (GST_ELEMENT (ogg), GST_PAD_CAST (pad)); |
|
1796 } |
|
1797 |
|
1798 gst_element_no_more_pads (GST_ELEMENT (ogg)); |
|
1799 ogg->current_chain = chain; |
|
1800 |
|
1801 /* FIXME, must be sent from the streaming thread */ |
|
1802 if (event) |
|
1803 gst_ogg_demux_send_event (ogg, event); |
|
1804 |
|
1805 GST_DEBUG_OBJECT (ogg, "starting chain"); |
|
1806 |
|
1807 /* then send out any queued buffers */ |
|
1808 for (i = 0; i < chain->streams->len; i++) { |
|
1809 GList *headers; |
|
1810 GstOggPad *pad; |
|
1811 |
|
1812 pad = g_array_index (chain->streams, GstOggPad *, i); |
|
1813 |
|
1814 for (headers = pad->headers; headers; headers = g_list_next (headers)) { |
|
1815 GstBuffer *buffer = GST_BUFFER (headers->data); |
|
1816 |
|
1817 if (pad->discont) { |
|
1818 GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT); |
|
1819 pad->discont = FALSE; |
|
1820 } |
|
1821 |
|
1822 /* we don't care about the return value here */ |
|
1823 gst_pad_push (GST_PAD_CAST (pad), buffer); |
|
1824 } |
|
1825 /* and free the headers */ |
|
1826 g_list_free (pad->headers); |
|
1827 pad->headers = NULL; |
|
1828 } |
|
1829 return TRUE; |
|
1830 } |
|
1831 |
|
1832 /* |
|
1833 * do seek to time @position, return FALSE or chain and TRUE |
|
1834 */ |
|
1835 static gboolean |
|
1836 gst_ogg_demux_do_seek (GstOggDemux * ogg, gint64 position, gboolean accurate, |
|
1837 GstOggChain ** rchain) |
|
1838 { |
|
1839 GstOggChain *chain = NULL; |
|
1840 gint64 begin, end; |
|
1841 gint64 begintime, endtime; |
|
1842 gint64 target; |
|
1843 gint64 best; |
|
1844 gint64 total; |
|
1845 gint64 result = 0; |
|
1846 GstFlowReturn ret; |
|
1847 gint i; |
|
1848 |
|
1849 /* first find the chain to search in */ |
|
1850 total = ogg->total_time; |
|
1851 if (ogg->chains->len == 0) |
|
1852 goto no_chains; |
|
1853 |
|
1854 for (i = ogg->chains->len - 1; i >= 0; i--) { |
|
1855 chain = g_array_index (ogg->chains, GstOggChain *, i); |
|
1856 total -= chain->total_time; |
|
1857 if (position >= total) |
|
1858 break; |
|
1859 } |
|
1860 |
|
1861 begin = chain->offset; |
|
1862 end = chain->end_offset; |
|
1863 begintime = chain->begin_time; |
|
1864 endtime = chain->begin_time + chain->total_time; |
|
1865 target = position - total + begintime; |
|
1866 if (accurate) { |
|
1867 /* FIXME, seek 4 seconds early to catch keyframes, better implement |
|
1868 * keyframe detection. */ |
|
1869 target = target - (gint64) 4 *GST_SECOND; |
|
1870 } |
|
1871 target = MAX (target, 0); |
|
1872 best = begin; |
|
1873 |
|
1874 GST_DEBUG_OBJECT (ogg, |
|
1875 "seeking to %" GST_TIME_FORMAT " in chain %p", |
|
1876 GST_TIME_ARGS (position), chain); |
|
1877 GST_DEBUG_OBJECT (ogg, |
|
1878 "chain offset %" G_GINT64_FORMAT ", end offset %" G_GINT64_FORMAT, begin, |
|
1879 end); |
|
1880 GST_DEBUG_OBJECT (ogg, |
|
1881 "chain begin time %" GST_TIME_FORMAT ", end time %" GST_TIME_FORMAT, |
|
1882 GST_TIME_ARGS (begintime), GST_TIME_ARGS (endtime)); |
|
1883 GST_DEBUG_OBJECT (ogg, "target %" GST_TIME_FORMAT, GST_TIME_ARGS (target)); |
|
1884 |
|
1885 /* perform the seek */ |
|
1886 while (begin < end) { |
|
1887 gint64 bisect; |
|
1888 |
|
1889 if ((end - begin < CHUNKSIZE) || (endtime == begintime)) { |
|
1890 bisect = begin; |
|
1891 } else { |
|
1892 /* take a (pretty decent) guess, avoiding overflow */ |
|
1893 gint64 rate = (end - begin) * GST_MSECOND / (endtime - begintime); |
|
1894 |
|
1895 bisect = (target - begintime) / GST_MSECOND * rate + begin - CHUNKSIZE; |
|
1896 |
|
1897 if (bisect <= begin) |
|
1898 bisect = begin; |
|
1899 GST_DEBUG_OBJECT (ogg, "Initial guess: %" G_GINT64_FORMAT, bisect); |
|
1900 } |
|
1901 gst_ogg_demux_seek (ogg, bisect); |
|
1902 |
|
1903 while (begin < end) { |
|
1904 ogg_page og; |
|
1905 |
|
1906 GST_DEBUG_OBJECT (ogg, |
|
1907 "after seek, bisect %" G_GINT64_FORMAT ", begin %" G_GINT64_FORMAT |
|
1908 ", end %" G_GINT64_FORMAT, bisect, begin, end); |
|
1909 |
|
1910 ret = gst_ogg_demux_get_next_page (ogg, &og, end - ogg->offset, &result); |
|
1911 GST_LOG_OBJECT (ogg, "looking for next page returned %" G_GINT64_FORMAT, |
|
1912 result); |
|
1913 |
|
1914 if (ret == GST_FLOW_LIMIT) { |
|
1915 /* we hit the upper limit, go back a bit */ |
|
1916 if (bisect <= begin + 1) { |
|
1917 end = begin; /* found it */ |
|
1918 } else { |
|
1919 if (bisect == 0) |
|
1920 goto seek_error; |
|
1921 |
|
1922 bisect -= CHUNKSIZE; |
|
1923 if (bisect <= begin) |
|
1924 bisect = begin + 1; |
|
1925 |
|
1926 gst_ogg_demux_seek (ogg, bisect); |
|
1927 } |
|
1928 } else if (ret == GST_FLOW_OK) { |
|
1929 /* found offset of next ogg page */ |
|
1930 gint64 granulepos; |
|
1931 GstClockTime granuletime; |
|
1932 GstFormat format; |
|
1933 GstOggPad *pad; |
|
1934 |
|
1935 GST_LOG_OBJECT (ogg, "found next ogg page at %" G_GINT64_FORMAT, |
|
1936 result); |
|
1937 granulepos = ogg_page_granulepos (&og); |
|
1938 if (granulepos == -1) { |
|
1939 GST_LOG_OBJECT (ogg, "granulepos of next page is -1"); |
|
1940 continue; |
|
1941 } |
|
1942 |
|
1943 pad = gst_ogg_chain_get_stream (chain, ogg_page_serialno (&og)); |
|
1944 if (pad == NULL || pad->is_skeleton) |
|
1945 continue; |
|
1946 |
|
1947 format = GST_FORMAT_TIME; |
|
1948 if (!gst_ogg_pad_query_convert (pad, |
|
1949 GST_FORMAT_DEFAULT, granulepos, &format, |
|
1950 (gint64 *) & granuletime)) { |
|
1951 GST_WARNING_OBJECT (ogg, "could not convert granulepos to time"); |
|
1952 granuletime = target; |
|
1953 } else { |
|
1954 if (granuletime < pad->start_time) |
|
1955 continue; |
|
1956 GST_LOG_OBJECT (ogg, "granulepos %" G_GINT64_FORMAT " maps to time %" |
|
1957 GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (granuletime)); |
|
1958 |
|
1959 granuletime -= pad->start_time; |
|
1960 } |
|
1961 |
|
1962 GST_DEBUG_OBJECT (ogg, |
|
1963 "found page with granule %" G_GINT64_FORMAT " and time %" |
|
1964 GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (granuletime)); |
|
1965 |
|
1966 if (granuletime < target) { |
|
1967 best = result; /* raw offset of packet with granulepos */ |
|
1968 begin = ogg->offset; /* raw offset of next page */ |
|
1969 begintime = granuletime; |
|
1970 |
|
1971 if (target - begintime > GST_SECOND) |
|
1972 break; |
|
1973 |
|
1974 bisect = begin; /* *not* begin + 1 */ |
|
1975 } else { |
|
1976 if (bisect <= begin + 1) { |
|
1977 end = begin; /* found it */ |
|
1978 } else { |
|
1979 if (end == ogg->offset) { /* we're pretty close - we'd be stuck in */ |
|
1980 end = result; |
|
1981 bisect -= CHUNKSIZE; /* an endless loop otherwise. */ |
|
1982 if (bisect <= begin) |
|
1983 bisect = begin + 1; |
|
1984 gst_ogg_demux_seek (ogg, bisect); |
|
1985 } else { |
|
1986 end = result; |
|
1987 endtime = granuletime; |
|
1988 break; |
|
1989 } |
|
1990 } |
|
1991 } |
|
1992 } else |
|
1993 goto seek_error; |
|
1994 } |
|
1995 } |
|
1996 |
|
1997 ogg->offset = best; |
|
1998 *rchain = chain; |
|
1999 |
|
2000 return TRUE; |
|
2001 |
|
2002 no_chains: |
|
2003 { |
|
2004 GST_DEBUG_OBJECT (ogg, "no chains"); |
|
2005 return FALSE; |
|
2006 } |
|
2007 seek_error: |
|
2008 { |
|
2009 GST_DEBUG_OBJECT (ogg, "got a seek error"); |
|
2010 return FALSE; |
|
2011 } |
|
2012 } |
|
2013 |
|
2014 /* does not take ownership of the event */ |
|
2015 static gboolean |
|
2016 gst_ogg_demux_perform_seek (GstOggDemux * ogg, GstEvent * event) |
|
2017 { |
|
2018 GstOggChain *chain = NULL; |
|
2019 gboolean res; |
|
2020 gboolean flush, accurate; |
|
2021 GstFormat format; |
|
2022 gdouble rate; |
|
2023 GstSeekFlags flags; |
|
2024 GstSeekType cur_type, stop_type; |
|
2025 gint64 cur, stop; |
|
2026 gboolean update; |
|
2027 |
|
2028 if (event) { |
|
2029 GST_DEBUG_OBJECT (ogg, "seek with event"); |
|
2030 |
|
2031 gst_event_parse_seek (event, &rate, &format, &flags, |
|
2032 &cur_type, &cur, &stop_type, &stop); |
|
2033 |
|
2034 /* we can only seek on time */ |
|
2035 if (format != GST_FORMAT_TIME) { |
|
2036 GST_DEBUG_OBJECT (ogg, "can only seek on TIME"); |
|
2037 goto error; |
|
2038 } |
|
2039 } else { |
|
2040 GST_DEBUG_OBJECT (ogg, "seek without event"); |
|
2041 |
|
2042 flags = 0; |
|
2043 rate = 1.0; |
|
2044 } |
|
2045 |
|
2046 GST_DEBUG_OBJECT (ogg, "seek, rate %g", rate); |
|
2047 |
|
2048 flush = flags & GST_SEEK_FLAG_FLUSH; |
|
2049 accurate = flags & GST_SEEK_FLAG_ACCURATE; |
|
2050 |
|
2051 /* first step is to unlock the streaming thread if it is |
|
2052 * blocked in a chain call, we do this by starting the flush. because |
|
2053 * we cannot yet hold any streaming lock, we have to protect the chains |
|
2054 * with their own lock. */ |
|
2055 if (flush) { |
|
2056 gint i; |
|
2057 |
|
2058 gst_pad_push_event (ogg->sinkpad, gst_event_new_flush_start ()); |
|
2059 |
|
2060 GST_CHAIN_LOCK (ogg); |
|
2061 for (i = 0; i < ogg->chains->len; i++) { |
|
2062 GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i); |
|
2063 gint j; |
|
2064 |
|
2065 for (j = 0; j < chain->streams->len; j++) { |
|
2066 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, j); |
|
2067 |
|
2068 gst_pad_push_event (GST_PAD (pad), gst_event_new_flush_start ()); |
|
2069 } |
|
2070 } |
|
2071 GST_CHAIN_UNLOCK (ogg); |
|
2072 } else { |
|
2073 gst_pad_pause_task (ogg->sinkpad); |
|
2074 } |
|
2075 |
|
2076 /* now grab the stream lock so that streaming cannot continue, for |
|
2077 * non flushing seeks when the element is in PAUSED this could block |
|
2078 * forever. */ |
|
2079 GST_PAD_STREAM_LOCK (ogg->sinkpad); |
|
2080 |
|
2081 if (ogg->segment_running && !flush) { |
|
2082 /* create the segment event to close the current segment */ |
|
2083 if ((chain = ogg->current_chain)) { |
|
2084 GstEvent *newseg; |
|
2085 |
|
2086 newseg = gst_event_new_new_segment (TRUE, ogg->segment.rate, |
|
2087 GST_FORMAT_TIME, ogg->segment.start + chain->segment_start, |
|
2088 ogg->segment.last_stop + chain->segment_start, ogg->segment.time); |
|
2089 |
|
2090 /* send segment on old chain, FIXME, must be sent from streaming thread. */ |
|
2091 gst_ogg_demux_send_event (ogg, newseg); |
|
2092 } |
|
2093 } |
|
2094 |
|
2095 if (event) { |
|
2096 gst_segment_set_seek (&ogg->segment, rate, format, flags, |
|
2097 cur_type, cur, stop_type, stop, &update); |
|
2098 } |
|
2099 |
|
2100 GST_DEBUG_OBJECT (ogg, "segment positions set to %" GST_TIME_FORMAT "-%" |
|
2101 GST_TIME_FORMAT, GST_TIME_ARGS (ogg->segment.start), |
|
2102 GST_TIME_ARGS (ogg->segment.stop)); |
|
2103 |
|
2104 /* we need to stop flushing on the srcpad as we're going to use it |
|
2105 * next. We can do this as we have the STREAM lock now. */ |
|
2106 gst_pad_push_event (ogg->sinkpad, gst_event_new_flush_stop ()); |
|
2107 |
|
2108 { |
|
2109 gint i; |
|
2110 |
|
2111 /* reset all ogg streams now, need to do this from within the lock to |
|
2112 * make sure the streaming thread is not messing with the stream */ |
|
2113 for (i = 0; i < ogg->chains->len; i++) { |
|
2114 GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i); |
|
2115 |
|
2116 gst_ogg_chain_reset (chain); |
|
2117 } |
|
2118 } |
|
2119 |
|
2120 res = gst_ogg_demux_do_seek (ogg, ogg->segment.last_stop, accurate, &chain); |
|
2121 |
|
2122 /* seek failed, make sure we continue the current chain */ |
|
2123 if (!res) { |
|
2124 chain = ogg->current_chain; |
|
2125 } |
|
2126 |
|
2127 if (!chain) |
|
2128 goto no_chain; |
|
2129 |
|
2130 /* now we have a new position, prepare for streaming again */ |
|
2131 { |
|
2132 GstEvent *event; |
|
2133 gint64 stop; |
|
2134 gint64 start; |
|
2135 gint64 last_stop, begin_time; |
|
2136 |
|
2137 /* we have to send the flush to the old chain, not the new one */ |
|
2138 if (flush) |
|
2139 gst_ogg_demux_send_event (ogg, gst_event_new_flush_stop ()); |
|
2140 |
|
2141 /* we need this to see how far inside the chain we need to start */ |
|
2142 if (chain->begin_time != GST_CLOCK_TIME_NONE) |
|
2143 begin_time = chain->begin_time; |
|
2144 else |
|
2145 begin_time = 0; |
|
2146 |
|
2147 /* segment.start gives the start over all chains, we calculate the amount |
|
2148 * of time into this chain we need to start */ |
|
2149 start = ogg->segment.start - begin_time; |
|
2150 if (chain->segment_start != GST_CLOCK_TIME_NONE) |
|
2151 start += chain->segment_start; |
|
2152 |
|
2153 if ((stop = ogg->segment.stop) == -1) |
|
2154 stop = ogg->segment.duration; |
|
2155 |
|
2156 /* segment.stop gives the stop time over all chains, calculate the amount of |
|
2157 * time we need to stop in this chain */ |
|
2158 if (stop != -1) { |
|
2159 if (stop > begin_time) |
|
2160 stop -= begin_time; |
|
2161 else |
|
2162 stop = 0; |
|
2163 stop += chain->segment_start; |
|
2164 /* we must stop when this chain ends and switch to the next chain to play |
|
2165 * the remainder of the segment. */ |
|
2166 stop = MIN (stop, chain->segment_stop); |
|
2167 } |
|
2168 |
|
2169 last_stop = ogg->segment.last_stop - begin_time; |
|
2170 if (chain->segment_start != GST_CLOCK_TIME_NONE) |
|
2171 last_stop += chain->segment_start; |
|
2172 |
|
2173 /* create the segment event we are going to send out */ |
|
2174 if (ogg->segment.rate >= 0.0) |
|
2175 event = gst_event_new_new_segment (FALSE, ogg->segment.rate, |
|
2176 ogg->segment.format, last_stop, stop, ogg->segment.time); |
|
2177 else |
|
2178 event = gst_event_new_new_segment (FALSE, ogg->segment.rate, |
|
2179 ogg->segment.format, start, last_stop, ogg->segment.time); |
|
2180 |
|
2181 if (chain != ogg->current_chain) { |
|
2182 /* switch to different chain, send segment on new chain */ |
|
2183 gst_ogg_demux_activate_chain (ogg, chain, event); |
|
2184 } else { |
|
2185 /* mark discont and send segment on current chain */ |
|
2186 gst_ogg_chain_mark_discont (chain); |
|
2187 /* This event should be sent from the streaming thread (sink pad task) */ |
|
2188 if (ogg->newsegment) |
|
2189 gst_event_unref (ogg->newsegment); |
|
2190 ogg->newsegment = event; |
|
2191 } |
|
2192 |
|
2193 /* notify start of new segment */ |
|
2194 if (ogg->segment.flags & GST_SEEK_FLAG_SEGMENT) { |
|
2195 gst_element_post_message (GST_ELEMENT (ogg), |
|
2196 gst_message_new_segment_start (GST_OBJECT (ogg), |
|
2197 GST_FORMAT_TIME, ogg->segment.last_stop)); |
|
2198 } |
|
2199 |
|
2200 ogg->segment_running = TRUE; |
|
2201 /* restart our task since it might have been stopped when we did the |
|
2202 * flush. */ |
|
2203 gst_pad_start_task (ogg->sinkpad, (GstTaskFunction) gst_ogg_demux_loop, |
|
2204 ogg->sinkpad); |
|
2205 } |
|
2206 |
|
2207 /* streaming can continue now */ |
|
2208 GST_PAD_STREAM_UNLOCK (ogg->sinkpad); |
|
2209 |
|
2210 return res; |
|
2211 |
|
2212 error: |
|
2213 { |
|
2214 GST_DEBUG_OBJECT (ogg, "seek failed"); |
|
2215 return FALSE; |
|
2216 } |
|
2217 no_chain: |
|
2218 { |
|
2219 GST_DEBUG_OBJECT (ogg, "no chain to seek in"); |
|
2220 GST_PAD_STREAM_UNLOCK (ogg->sinkpad); |
|
2221 return FALSE; |
|
2222 } |
|
2223 } |
|
2224 |
|
2225 /* finds each bitstream link one at a time using a bisection search |
|
2226 * (has to begin by knowing the offset of the lb's initial page). |
|
2227 * Recurses for each link so it can alloc the link storage after |
|
2228 * finding them all, then unroll and fill the cache at the same time |
|
2229 */ |
|
2230 static GstFlowReturn |
|
2231 gst_ogg_demux_bisect_forward_serialno (GstOggDemux * ogg, |
|
2232 gint64 begin, gint64 searched, gint64 end, GstOggChain * chain, glong m) |
|
2233 { |
|
2234 gint64 endsearched = end; |
|
2235 gint64 next = end; |
|
2236 ogg_page og; |
|
2237 GstFlowReturn ret; |
|
2238 gint64 offset; |
|
2239 GstOggChain *nextchain; |
|
2240 |
|
2241 GST_LOG_OBJECT (ogg, |
|
2242 "bisect begin: %" G_GINT64_FORMAT ", searched: %" G_GINT64_FORMAT |
|
2243 ", end %" G_GINT64_FORMAT ", chain: %p", begin, searched, end, chain); |
|
2244 |
|
2245 /* the below guards against garbage seperating the last and |
|
2246 * first pages of two links. */ |
|
2247 while (searched < endsearched) { |
|
2248 gint64 bisect; |
|
2249 |
|
2250 if (endsearched - searched < CHUNKSIZE) { |
|
2251 bisect = searched; |
|
2252 } else { |
|
2253 bisect = (searched + endsearched) / 2; |
|
2254 } |
|
2255 |
|
2256 gst_ogg_demux_seek (ogg, bisect); |
|
2257 ret = gst_ogg_demux_get_next_page (ogg, &og, -1, &offset); |
|
2258 |
|
2259 if (ret == GST_FLOW_UNEXPECTED) { |
|
2260 endsearched = bisect; |
|
2261 } else if (ret == GST_FLOW_OK) { |
|
2262 glong serial = ogg_page_serialno (&og); |
|
2263 |
|
2264 if (!gst_ogg_chain_has_stream (chain, serial)) { |
|
2265 endsearched = bisect; |
|
2266 next = offset; |
|
2267 } else { |
|
2268 searched = offset + og.header_len + og.body_len; |
|
2269 } |
|
2270 } else |
|
2271 return ret; |
|
2272 } |
|
2273 |
|
2274 GST_LOG_OBJECT (ogg, "current chain ends at %" G_GINT64_FORMAT, searched); |
|
2275 |
|
2276 chain->end_offset = searched; |
|
2277 ret = gst_ogg_demux_read_end_chain (ogg, chain); |
|
2278 if (ret != GST_FLOW_OK) |
|
2279 return ret; |
|
2280 |
|
2281 GST_LOG_OBJECT (ogg, "found begin at %" G_GINT64_FORMAT, next); |
|
2282 |
|
2283 gst_ogg_demux_seek (ogg, next); |
|
2284 ret = gst_ogg_demux_read_chain (ogg, &nextchain); |
|
2285 if (ret == GST_FLOW_UNEXPECTED) { |
|
2286 nextchain = NULL; |
|
2287 ret = GST_FLOW_OK; |
|
2288 GST_LOG_OBJECT (ogg, "no next chain"); |
|
2289 } else if (ret != GST_FLOW_OK) |
|
2290 goto done; |
|
2291 |
|
2292 if (searched < end && nextchain != NULL) { |
|
2293 ret = gst_ogg_demux_bisect_forward_serialno (ogg, next, ogg->offset, |
|
2294 end, nextchain, m + 1); |
|
2295 if (ret != GST_FLOW_OK) |
|
2296 goto done; |
|
2297 } |
|
2298 GST_LOG_OBJECT (ogg, "adding chain %p", chain); |
|
2299 |
|
2300 g_array_insert_val (ogg->chains, 0, chain); |
|
2301 |
|
2302 done: |
|
2303 return ret; |
|
2304 } |
|
2305 |
|
2306 /* read a chain from the ogg file. This code will |
|
2307 * read all BOS pages and will create and return a GstOggChain |
|
2308 * structure with the results. |
|
2309 * |
|
2310 * This function will also read N pages from each stream in the |
|
2311 * chain and submit them to the decoders. When the decoder has |
|
2312 * decoded the first buffer, we know the timestamp of the first |
|
2313 * page in the chain. |
|
2314 */ |
|
2315 static GstFlowReturn |
|
2316 gst_ogg_demux_read_chain (GstOggDemux * ogg, GstOggChain ** res_chain) |
|
2317 { |
|
2318 GstFlowReturn ret; |
|
2319 GstOggChain *chain = NULL; |
|
2320 gint64 offset = ogg->offset; |
|
2321 ogg_page op; |
|
2322 gboolean done; |
|
2323 gint i; |
|
2324 |
|
2325 GST_LOG_OBJECT (ogg, "reading chain at %" G_GINT64_FORMAT, offset); |
|
2326 |
|
2327 /* first read the BOS pages, do typefind on them, create |
|
2328 * the decoders, send data to the decoders. */ |
|
2329 while (TRUE) { |
|
2330 GstOggPad *pad; |
|
2331 glong serial; |
|
2332 |
|
2333 ret = gst_ogg_demux_get_next_page (ogg, &op, -1, NULL); |
|
2334 if (ret != GST_FLOW_OK) { |
|
2335 GST_WARNING_OBJECT (ogg, "problem reading BOS page: ret=%d", ret); |
|
2336 break; |
|
2337 } |
|
2338 if (!ogg_page_bos (&op)) { |
|
2339 GST_WARNING_OBJECT (ogg, "page is not BOS page"); |
|
2340 break; |
|
2341 } |
|
2342 |
|
2343 if (chain == NULL) { |
|
2344 chain = gst_ogg_chain_new (ogg); |
|
2345 chain->offset = offset; |
|
2346 } |
|
2347 |
|
2348 serial = ogg_page_serialno (&op); |
|
2349 if (gst_ogg_chain_get_stream (chain, serial) != NULL) { |
|
2350 GST_WARNING_OBJECT (ogg, "found serial %08lx BOS page twice, ignoring", |
|
2351 serial); |
|
2352 continue; |
|
2353 } |
|
2354 |
|
2355 pad = gst_ogg_chain_new_stream (chain, serial); |
|
2356 gst_ogg_pad_submit_page (pad, &op); |
|
2357 } |
|
2358 |
|
2359 if (ret != GST_FLOW_OK || chain == NULL) { |
|
2360 if (ret != GST_FLOW_UNEXPECTED) { |
|
2361 GST_WARNING_OBJECT (ogg, "failed to read chain"); |
|
2362 } else { |
|
2363 GST_DEBUG_OBJECT (ogg, "done reading chains"); |
|
2364 } |
|
2365 if (chain) { |
|
2366 gst_ogg_chain_free (chain); |
|
2367 } |
|
2368 if (res_chain) |
|
2369 *res_chain = NULL; |
|
2370 return ret; |
|
2371 } |
|
2372 |
|
2373 chain->have_bos = TRUE; |
|
2374 GST_LOG_OBJECT (ogg, "read bos pages, init decoder now"); |
|
2375 |
|
2376 /* now read pages until we receive a buffer from each of the |
|
2377 * stream decoders, this will tell us the timestamp of the |
|
2378 * first packet in the chain then */ |
|
2379 |
|
2380 /* save the offset to the first non bos page in the chain: if searching for |
|
2381 * pad->first_time we read past the end of the chain, we'll seek back to this |
|
2382 * position |
|
2383 */ |
|
2384 offset = ogg->offset; |
|
2385 |
|
2386 done = FALSE; |
|
2387 while (!done) { |
|
2388 glong serial; |
|
2389 gboolean known_serial = FALSE; |
|
2390 GstFlowReturn ret; |
|
2391 |
|
2392 serial = ogg_page_serialno (&op); |
|
2393 done = TRUE; |
|
2394 for (i = 0; i < chain->streams->len; i++) { |
|
2395 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
|
2396 |
|
2397 GST_LOG_OBJECT (ogg, "serial %08x time %" GST_TIME_FORMAT, pad->serialno, |
|
2398 GST_TIME_ARGS (pad->start_time)); |
|
2399 |
|
2400 if (pad->serialno == serial) { |
|
2401 known_serial = TRUE; |
|
2402 |
|
2403 /* submit the page now, this will fill in the start_time when the |
|
2404 * internal decoder finds it */ |
|
2405 gst_ogg_pad_submit_page (pad, &op); |
|
2406 |
|
2407 if (!pad->is_skeleton && pad->start_time == -1 && ogg_page_eos (&op)) { |
|
2408 /* got EOS on a pad before we could find its start_time. |
|
2409 * We have no chance of finding a start_time for every pad so |
|
2410 * stop searching for the other start_time(s). |
|
2411 */ |
|
2412 done = TRUE; |
|
2413 break; |
|
2414 } |
|
2415 } |
|
2416 /* the timestamp will be filled in when we submit the pages */ |
|
2417 if (!pad->is_skeleton) |
|
2418 done &= (pad->start_time != GST_CLOCK_TIME_NONE); |
|
2419 |
|
2420 GST_LOG_OBJECT (ogg, "done %08x now %d", pad->serialno, done); |
|
2421 } |
|
2422 |
|
2423 /* we read a page not belonging to the current chain: seek back to the |
|
2424 * beginning of the chain |
|
2425 */ |
|
2426 if (!known_serial) { |
|
2427 GST_LOG_OBJECT (ogg, "unknown serial %08lx", serial); |
|
2428 gst_ogg_demux_seek (ogg, offset); |
|
2429 break; |
|
2430 } |
|
2431 |
|
2432 if (!done) { |
|
2433 ret = gst_ogg_demux_get_next_page (ogg, &op, -1, NULL); |
|
2434 if (ret != GST_FLOW_OK) |
|
2435 break; |
|
2436 } |
|
2437 } |
|
2438 GST_LOG_OBJECT (ogg, "done reading chain"); |
|
2439 /* now we can fill in the missing info using queries */ |
|
2440 for (i = 0; i < chain->streams->len; i++) { |
|
2441 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
|
2442 GstFormat target; |
|
2443 |
|
2444 if (pad->is_skeleton) |
|
2445 continue; |
|
2446 |
|
2447 GST_LOG_OBJECT (ogg, "convert first granule %" G_GUINT64_FORMAT " to time ", |
|
2448 pad->first_granule); |
|
2449 |
|
2450 target = GST_FORMAT_TIME; |
|
2451 if (!gst_ogg_pad_query_convert (pad, |
|
2452 GST_FORMAT_DEFAULT, pad->first_granule, &target, |
|
2453 (gint64 *) & pad->first_time)) { |
|
2454 GST_WARNING_OBJECT (ogg, "could not convert granulepos to time"); |
|
2455 } else { |
|
2456 GST_LOG_OBJECT (ogg, "converted to first time %" GST_TIME_FORMAT, |
|
2457 GST_TIME_ARGS (pad->first_time)); |
|
2458 } |
|
2459 |
|
2460 pad->mode = GST_OGG_PAD_MODE_STREAMING; |
|
2461 } |
|
2462 |
|
2463 if (res_chain) |
|
2464 *res_chain = chain; |
|
2465 |
|
2466 return GST_FLOW_OK; |
|
2467 } |
|
2468 |
|
2469 /* read the last pages from the ogg stream to get the final |
|
2470 * page end_offsets. |
|
2471 */ |
|
2472 static GstFlowReturn |
|
2473 gst_ogg_demux_read_end_chain (GstOggDemux * ogg, GstOggChain * chain) |
|
2474 { |
|
2475 gint64 begin = chain->end_offset; |
|
2476 gint64 end = begin; |
|
2477 gint64 last_granule = -1; |
|
2478 GstOggPad *last_pad = NULL; |
|
2479 GstFormat target; |
|
2480 GstFlowReturn ret; |
|
2481 gboolean done = FALSE; |
|
2482 ogg_page og; |
|
2483 gint i; |
|
2484 |
|
2485 while (!done) { |
|
2486 begin -= CHUNKSIZE; |
|
2487 if (begin < 0) |
|
2488 begin = 0; |
|
2489 |
|
2490 gst_ogg_demux_seek (ogg, begin); |
|
2491 |
|
2492 /* now continue reading until we run out of data, if we find a page |
|
2493 * start, we save it. It might not be the final page as there could be |
|
2494 * another page after this one. */ |
|
2495 while (ogg->offset < end) { |
|
2496 ret = gst_ogg_demux_get_next_page (ogg, &og, end - ogg->offset, NULL); |
|
2497 |
|
2498 if (ret == GST_FLOW_LIMIT) |
|
2499 break; |
|
2500 if (ret != GST_FLOW_OK) |
|
2501 return ret; |
|
2502 |
|
2503 for (i = 0; i < chain->streams->len; i++) { |
|
2504 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
|
2505 |
|
2506 if (pad->is_skeleton) |
|
2507 continue; |
|
2508 |
|
2509 if (pad->serialno == ogg_page_serialno (&og)) { |
|
2510 gint64 granulepos = ogg_page_granulepos (&og); |
|
2511 |
|
2512 if (last_granule < granulepos) { |
|
2513 last_granule = granulepos; |
|
2514 last_pad = pad; |
|
2515 } |
|
2516 done = TRUE; |
|
2517 break; |
|
2518 } |
|
2519 } |
|
2520 } |
|
2521 } |
|
2522 |
|
2523 target = GST_FORMAT_TIME; |
|
2524 if (last_granule == -1 || !gst_ogg_pad_query_convert (last_pad, |
|
2525 GST_FORMAT_DEFAULT, last_granule, &target, |
|
2526 (gint64 *) & chain->segment_stop)) { |
|
2527 GST_WARNING_OBJECT (ogg, "could not convert granulepos to time"); |
|
2528 chain->segment_stop = GST_CLOCK_TIME_NONE; |
|
2529 } |
|
2530 |
|
2531 return GST_FLOW_OK; |
|
2532 } |
|
2533 |
|
2534 /* find a pad with a given serial number |
|
2535 */ |
|
2536 static GstOggPad * |
|
2537 gst_ogg_demux_find_pad (GstOggDemux * ogg, int serialno) |
|
2538 { |
|
2539 GstOggPad *pad; |
|
2540 gint i; |
|
2541 |
|
2542 /* first look in building chain if any */ |
|
2543 if (ogg->building_chain) { |
|
2544 pad = gst_ogg_chain_get_stream (ogg->building_chain, serialno); |
|
2545 if (pad) |
|
2546 return pad; |
|
2547 } |
|
2548 |
|
2549 /* then look in current chain if any */ |
|
2550 if (ogg->current_chain) { |
|
2551 pad = gst_ogg_chain_get_stream (ogg->current_chain, serialno); |
|
2552 if (pad) |
|
2553 return pad; |
|
2554 } |
|
2555 |
|
2556 for (i = 0; i < ogg->chains->len; i++) { |
|
2557 GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i); |
|
2558 |
|
2559 pad = gst_ogg_chain_get_stream (chain, serialno); |
|
2560 if (pad) |
|
2561 return pad; |
|
2562 } |
|
2563 return NULL; |
|
2564 } |
|
2565 |
|
2566 /* find a chain with a given serial number |
|
2567 */ |
|
2568 static GstOggChain * |
|
2569 gst_ogg_demux_find_chain (GstOggDemux * ogg, int serialno) |
|
2570 { |
|
2571 GstOggPad *pad; |
|
2572 |
|
2573 pad = gst_ogg_demux_find_pad (ogg, serialno); |
|
2574 if (pad) { |
|
2575 return pad->chain; |
|
2576 } |
|
2577 return NULL; |
|
2578 } |
|
2579 |
|
2580 /* returns TRUE if all streams have valid start time */ |
|
2581 static gboolean |
|
2582 gst_ogg_demux_collect_chain_info (GstOggDemux * ogg, GstOggChain * chain) |
|
2583 { |
|
2584 gint i; |
|
2585 gboolean res = TRUE; |
|
2586 |
|
2587 chain->total_time = GST_CLOCK_TIME_NONE; |
|
2588 chain->segment_start = G_MAXUINT64; |
|
2589 |
|
2590 GST_DEBUG_OBJECT (ogg, "trying to collect chain info"); |
|
2591 |
|
2592 for (i = 0; i < chain->streams->len; i++) { |
|
2593 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
|
2594 |
|
2595 if (pad->is_skeleton) |
|
2596 continue; |
|
2597 |
|
2598 /* can do this if the pad start time is not defined */ |
|
2599 if (pad->start_time == GST_CLOCK_TIME_NONE) |
|
2600 res = FALSE; |
|
2601 else |
|
2602 chain->segment_start = MIN (chain->segment_start, pad->start_time); |
|
2603 } |
|
2604 |
|
2605 if (chain->segment_stop != GST_CLOCK_TIME_NONE |
|
2606 && chain->segment_start != G_MAXUINT64) |
|
2607 chain->total_time = chain->segment_stop - chain->segment_start; |
|
2608 |
|
2609 GST_DEBUG_OBJECT (ogg, "return %d", res); |
|
2610 |
|
2611 return res; |
|
2612 } |
|
2613 |
|
2614 static void |
|
2615 gst_ogg_demux_collect_info (GstOggDemux * ogg) |
|
2616 { |
|
2617 gint i; |
|
2618 |
|
2619 /* collect all info */ |
|
2620 ogg->total_time = 0; |
|
2621 |
|
2622 for (i = 0; i < ogg->chains->len; i++) { |
|
2623 GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i); |
|
2624 |
|
2625 chain->begin_time = ogg->total_time; |
|
2626 |
|
2627 gst_ogg_demux_collect_chain_info (ogg, chain); |
|
2628 |
|
2629 ogg->total_time += chain->total_time; |
|
2630 } |
|
2631 gst_segment_set_duration (&ogg->segment, GST_FORMAT_TIME, ogg->total_time); |
|
2632 } |
|
2633 |
|
2634 /* find all the chains in the ogg file, this reads the first and |
|
2635 * last page of the ogg stream, if they match then the ogg file has |
|
2636 * just one chain, else we do a binary search for all chains. |
|
2637 */ |
|
2638 static GstFlowReturn |
|
2639 gst_ogg_demux_find_chains (GstOggDemux * ogg) |
|
2640 { |
|
2641 ogg_page og; |
|
2642 GstPad *peer; |
|
2643 GstFormat format; |
|
2644 gboolean res; |
|
2645 gulong serialno; |
|
2646 GstOggChain *chain; |
|
2647 GstFlowReturn ret; |
|
2648 |
|
2649 /* get peer to figure out length */ |
|
2650 if ((peer = gst_pad_get_peer (ogg->sinkpad)) == NULL) |
|
2651 goto no_peer; |
|
2652 |
|
2653 /* find length to read last page, we store this for later use. */ |
|
2654 format = GST_FORMAT_BYTES; |
|
2655 res = gst_pad_query_duration (peer, &format, &ogg->length); |
|
2656 gst_object_unref (peer); |
|
2657 if (!res || ogg->length <= 0) |
|
2658 goto no_length; |
|
2659 |
|
2660 GST_DEBUG_OBJECT (ogg, "file length %" G_GINT64_FORMAT, ogg->length); |
|
2661 |
|
2662 /* read chain from offset 0, this is the first chain of the |
|
2663 * ogg file. */ |
|
2664 gst_ogg_demux_seek (ogg, 0); |
|
2665 ret = gst_ogg_demux_read_chain (ogg, &chain); |
|
2666 if (ret != GST_FLOW_OK) |
|
2667 goto no_first_chain; |
|
2668 |
|
2669 /* read page from end offset, we use this page to check if its serial |
|
2670 * number is contained in the first chain. If this is the case then |
|
2671 * this ogg is not a chained ogg and we can skip the scanning. */ |
|
2672 gst_ogg_demux_seek (ogg, ogg->length); |
|
2673 ret = gst_ogg_demux_get_prev_page (ogg, &og, NULL); |
|
2674 if (ret != GST_FLOW_OK) |
|
2675 goto no_last_page; |
|
2676 |
|
2677 serialno = ogg_page_serialno (&og); |
|
2678 |
|
2679 if (!gst_ogg_chain_has_stream (chain, serialno)) { |
|
2680 /* the last page is not in the first stream, this means we should |
|
2681 * find all the chains in this chained ogg. */ |
|
2682 ret = |
|
2683 gst_ogg_demux_bisect_forward_serialno (ogg, 0, 0, ogg->length, chain, |
|
2684 0); |
|
2685 } else { |
|
2686 /* we still call this function here but with an empty range so that |
|
2687 * we can reuse the setup code in this routine. */ |
|
2688 ret = |
|
2689 gst_ogg_demux_bisect_forward_serialno (ogg, 0, ogg->length, ogg->length, |
|
2690 chain, 0); |
|
2691 } |
|
2692 if (ret != GST_FLOW_OK) |
|
2693 goto done; |
|
2694 |
|
2695 /* all fine, collect and print */ |
|
2696 gst_ogg_demux_collect_info (ogg); |
|
2697 |
|
2698 /* dump our chains and streams */ |
|
2699 gst_ogg_print (ogg); |
|
2700 |
|
2701 done: |
|
2702 return ret; |
|
2703 |
|
2704 /*** error cases ***/ |
|
2705 no_peer: |
|
2706 { |
|
2707 GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL), ("we don't have a peer")); |
|
2708 return GST_FLOW_NOT_LINKED; |
|
2709 } |
|
2710 no_length: |
|
2711 { |
|
2712 GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL), ("can't get file length")); |
|
2713 return GST_FLOW_NOT_SUPPORTED; |
|
2714 } |
|
2715 no_first_chain: |
|
2716 { |
|
2717 GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL), ("can't get first chain")); |
|
2718 return GST_FLOW_ERROR; |
|
2719 } |
|
2720 no_last_page: |
|
2721 { |
|
2722 GST_DEBUG_OBJECT (ogg, "can't get last page"); |
|
2723 if (chain) |
|
2724 gst_ogg_chain_free (chain); |
|
2725 return ret; |
|
2726 } |
|
2727 } |
|
2728 |
|
2729 static GstFlowReturn |
|
2730 gst_ogg_demux_handle_page (GstOggDemux * ogg, ogg_page * page) |
|
2731 { |
|
2732 GstOggPad *pad; |
|
2733 gint64 granule; |
|
2734 guint serialno; |
|
2735 GstFlowReturn result = GST_FLOW_OK; |
|
2736 |
|
2737 serialno = ogg_page_serialno (page); |
|
2738 granule = ogg_page_granulepos (page); |
|
2739 |
|
2740 GST_LOG_OBJECT (ogg, |
|
2741 "processing ogg page (serial %08x, pageno %ld, granulepos %" |
|
2742 G_GINT64_FORMAT ", bos %d)", |
|
2743 serialno, ogg_page_pageno (page), granule, ogg_page_bos (page)); |
|
2744 |
|
2745 if (ogg_page_bos (page)) { |
|
2746 GstOggChain *chain; |
|
2747 |
|
2748 /* first page */ |
|
2749 /* see if we know about the chain already */ |
|
2750 chain = gst_ogg_demux_find_chain (ogg, serialno); |
|
2751 if (chain) { |
|
2752 GstEvent *event; |
|
2753 |
|
2754 /* create the newsegment event we are going to send out */ |
|
2755 event = gst_event_new_new_segment (FALSE, ogg->segment.rate, |
|
2756 GST_FORMAT_TIME, chain->segment_start, chain->segment_stop, |
|
2757 chain->begin_time); |
|
2758 |
|
2759 GST_DEBUG_OBJECT (ogg, |
|
2760 "segment: start %" GST_TIME_FORMAT ", stop %" GST_TIME_FORMAT |
|
2761 ", time %" GST_TIME_FORMAT, GST_TIME_ARGS (chain->segment_start), |
|
2762 GST_TIME_ARGS (chain->segment_stop), |
|
2763 GST_TIME_ARGS (chain->begin_time)); |
|
2764 |
|
2765 /* activate it as it means we have a non-header */ |
|
2766 gst_ogg_demux_activate_chain (ogg, chain, event); |
|
2767 pad = gst_ogg_demux_find_pad (ogg, serialno); |
|
2768 } else { |
|
2769 GstClockTime chain_time; |
|
2770 GstOggChain *current_chain; |
|
2771 gint64 current_time; |
|
2772 |
|
2773 /* this can only happen in non-seekabe mode */ |
|
2774 if (ogg->seekable) |
|
2775 goto unknown_chain; |
|
2776 |
|
2777 current_chain = ogg->current_chain; |
|
2778 current_time = ogg->segment.last_stop; |
|
2779 |
|
2780 if (current_chain) { |
|
2781 /* remove existing pads */ |
|
2782 gst_ogg_demux_deactivate_current_chain (ogg); |
|
2783 } |
|
2784 /* time of new chain is current time */ |
|
2785 chain_time = current_time; |
|
2786 |
|
2787 if (ogg->building_chain == NULL) { |
|
2788 GstOggChain *newchain; |
|
2789 |
|
2790 newchain = gst_ogg_chain_new (ogg); |
|
2791 newchain->offset = 0; |
|
2792 /* set new chain begin time aligned with end time of old chain */ |
|
2793 newchain->begin_time = chain_time; |
|
2794 GST_DEBUG_OBJECT (ogg, "new chain, begin time %" GST_TIME_FORMAT, |
|
2795 GST_TIME_ARGS (chain_time)); |
|
2796 |
|
2797 /* and this is the one we are building now */ |
|
2798 ogg->building_chain = newchain; |
|
2799 } |
|
2800 pad = gst_ogg_chain_new_stream (ogg->building_chain, serialno); |
|
2801 } |
|
2802 } else { |
|
2803 pad = gst_ogg_demux_find_pad (ogg, serialno); |
|
2804 } |
|
2805 if (pad) { |
|
2806 result = gst_ogg_pad_submit_page (pad, page); |
|
2807 } else { |
|
2808 /* no pad, this is pretty fatal. This means an ogg page without bos |
|
2809 * has been seen for this serialno. could just ignore it too... */ |
|
2810 goto unknown_pad; |
|
2811 } |
|
2812 return result; |
|
2813 |
|
2814 /* ERRORS */ |
|
2815 unknown_chain: |
|
2816 { |
|
2817 GST_ELEMENT_ERROR (ogg, STREAM, DECODE, |
|
2818 (NULL), ("unknown ogg chain for serial %08x detected", serialno)); |
|
2819 return GST_FLOW_ERROR; |
|
2820 } |
|
2821 unknown_pad: |
|
2822 { |
|
2823 GST_ELEMENT_ERROR (ogg, STREAM, DECODE, |
|
2824 (NULL), ("unknown ogg pad for serial %08x detected", serialno)); |
|
2825 return GST_FLOW_ERROR; |
|
2826 } |
|
2827 } |
|
2828 |
|
2829 /* streaming mode, receive a buffer, parse it, create pads for |
|
2830 * the serialno, submit pages and packets to the oggpads |
|
2831 */ |
|
2832 static GstFlowReturn |
|
2833 gst_ogg_demux_chain (GstPad * pad, GstBuffer * buffer) |
|
2834 { |
|
2835 GstOggDemux *ogg; |
|
2836 gint ret; |
|
2837 GstFlowReturn result = GST_FLOW_OK; |
|
2838 |
|
2839 ogg = GST_OGG_DEMUX (GST_OBJECT_PARENT (pad)); |
|
2840 |
|
2841 GST_DEBUG_OBJECT (ogg, "chain"); |
|
2842 result = gst_ogg_demux_submit_buffer (ogg, buffer); |
|
2843 |
|
2844 while (result == GST_FLOW_OK) { |
|
2845 ogg_page page; |
|
2846 |
|
2847 ret = ogg_sync_pageout (&ogg->sync, &page); |
|
2848 if (ret == 0) |
|
2849 /* need more data */ |
|
2850 break; |
|
2851 if (ret == -1) { |
|
2852 /* discontinuity in the pages */ |
|
2853 GST_DEBUG_OBJECT (ogg, "discont in page found, continuing"); |
|
2854 } else { |
|
2855 result = gst_ogg_demux_handle_page (ogg, &page); |
|
2856 } |
|
2857 } |
|
2858 return result; |
|
2859 } |
|
2860 |
|
2861 static void |
|
2862 gst_ogg_demux_send_event (GstOggDemux * ogg, GstEvent * event) |
|
2863 { |
|
2864 GstOggChain *chain = ogg->current_chain; |
|
2865 |
|
2866 if (chain) { |
|
2867 gint i; |
|
2868 |
|
2869 for (i = 0; i < chain->streams->len; i++) { |
|
2870 GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
|
2871 |
|
2872 gst_event_ref (event); |
|
2873 GST_DEBUG_OBJECT (ogg, "Pushing event on pad %p", pad); |
|
2874 gst_pad_push_event (GST_PAD (pad), event); |
|
2875 } |
|
2876 } |
|
2877 gst_event_unref (event); |
|
2878 } |
|
2879 |
|
2880 static GstFlowReturn |
|
2881 gst_ogg_demux_combine_flows (GstOggDemux * ogg, GstOggPad * pad, |
|
2882 GstFlowReturn ret) |
|
2883 { |
|
2884 GstOggChain *chain; |
|
2885 |
|
2886 /* store the value */ |
|
2887 pad->last_ret = ret; |
|
2888 |
|
2889 /* any other error that is not-linked can be returned right |
|
2890 * away */ |
|
2891 if (ret != GST_FLOW_NOT_LINKED) |
|
2892 goto done; |
|
2893 |
|
2894 /* only return NOT_LINKED if all other pads returned NOT_LINKED */ |
|
2895 chain = ogg->current_chain; |
|
2896 if (chain) { |
|
2897 gint i; |
|
2898 |
|
2899 for (i = 0; i < chain->streams->len; i++) { |
|
2900 GstOggPad *opad = g_array_index (chain->streams, GstOggPad *, i); |
|
2901 |
|
2902 ret = opad->last_ret; |
|
2903 /* some other return value (must be SUCCESS but we can return |
|
2904 * other values as well) */ |
|
2905 if (ret != GST_FLOW_NOT_LINKED) |
|
2906 goto done; |
|
2907 } |
|
2908 /* if we get here, all other pads were unlinked and we return |
|
2909 * NOT_LINKED then */ |
|
2910 } |
|
2911 done: |
|
2912 return ret; |
|
2913 } |
|
2914 |
|
2915 static GstFlowReturn |
|
2916 gst_ogg_demux_loop_forward (GstOggDemux * ogg) |
|
2917 { |
|
2918 GstFlowReturn ret; |
|
2919 GstBuffer *buffer; |
|
2920 |
|
2921 if (ogg->offset == ogg->length) { |
|
2922 GST_LOG_OBJECT (ogg, "no more data to pull %" G_GINT64_FORMAT |
|
2923 " == %" G_GINT64_FORMAT, ogg->offset, ogg->length); |
|
2924 ret = GST_FLOW_UNEXPECTED; |
|
2925 goto done; |
|
2926 } |
|
2927 |
|
2928 GST_LOG_OBJECT (ogg, "pull data %" G_GINT64_FORMAT, ogg->offset); |
|
2929 ret = gst_pad_pull_range (ogg->sinkpad, ogg->offset, CHUNKSIZE, &buffer); |
|
2930 if (ret != GST_FLOW_OK) { |
|
2931 GST_LOG_OBJECT (ogg, "Failed pull_range"); |
|
2932 goto done; |
|
2933 } |
|
2934 |
|
2935 ogg->offset += GST_BUFFER_SIZE (buffer); |
|
2936 |
|
2937 if (G_UNLIKELY (ogg->newsegment)) { |
|
2938 gst_ogg_demux_send_event (ogg, ogg->newsegment); |
|
2939 ogg->newsegment = NULL; |
|
2940 } |
|
2941 |
|
2942 ret = gst_ogg_demux_chain (ogg->sinkpad, buffer); |
|
2943 if (ret != GST_FLOW_OK) { |
|
2944 GST_LOG_OBJECT (ogg, "Failed demux_chain"); |
|
2945 goto done; |
|
2946 } |
|
2947 |
|
2948 /* check for the end of the segment */ |
|
2949 if (ogg->segment.stop != -1 && ogg->segment.last_stop != -1) { |
|
2950 if (ogg->segment.last_stop > ogg->segment.stop) { |
|
2951 ret = GST_FLOW_UNEXPECTED; |
|
2952 goto done; |
|
2953 } |
|
2954 } |
|
2955 done: |
|
2956 return ret; |
|
2957 } |
|
2958 |
|
2959 /* reverse mode. |
|
2960 * |
|
2961 * We read the pages backwards and send the packets forwards. The first packet |
|
2962 * in the page will be pushed with the DISCONT flag set. |
|
2963 * |
|
2964 * Special care has to be taken for continued pages, which we can only decode |
|
2965 * when we have the previous page(s). |
|
2966 */ |
|
2967 static GstFlowReturn |
|
2968 gst_ogg_demux_loop_reverse (GstOggDemux * ogg) |
|
2969 { |
|
2970 GstFlowReturn ret; |
|
2971 ogg_page page; |
|
2972 gint64 offset; |
|
2973 |
|
2974 if (ogg->offset == 0) { |
|
2975 GST_LOG_OBJECT (ogg, "no more data to pull %" G_GINT64_FORMAT |
|
2976 " == 0", ogg->offset); |
|
2977 ret = GST_FLOW_UNEXPECTED; |
|
2978 goto done; |
|
2979 } |
|
2980 |
|
2981 GST_LOG_OBJECT (ogg, "read page from %" G_GINT64_FORMAT, ogg->offset); |
|
2982 ret = gst_ogg_demux_get_prev_page (ogg, &page, &offset); |
|
2983 if (ret != GST_FLOW_OK) |
|
2984 goto done; |
|
2985 |
|
2986 ogg->offset = offset; |
|
2987 |
|
2988 if (G_UNLIKELY (ogg->newsegment)) { |
|
2989 gst_ogg_demux_send_event (ogg, ogg->newsegment); |
|
2990 ogg->newsegment = NULL; |
|
2991 } |
|
2992 |
|
2993 ret = gst_ogg_demux_handle_page (ogg, &page); |
|
2994 if (ret != GST_FLOW_OK) |
|
2995 goto done; |
|
2996 |
|
2997 /* check for the end of the segment */ |
|
2998 if (ogg->segment.start != -1 && ogg->segment.last_stop != -1) { |
|
2999 if (ogg->segment.last_stop <= ogg->segment.start) { |
|
3000 ret = GST_FLOW_UNEXPECTED; |
|
3001 goto done; |
|
3002 } |
|
3003 } |
|
3004 done: |
|
3005 return ret; |
|
3006 } |
|
3007 |
|
3008 /* random access code |
|
3009 * |
|
3010 * - first find all the chains and streams by scanning the file. |
|
3011 * - then get and chain buffers, just like the streaming case. |
|
3012 * - when seeking, we can use the chain info to perform the seek. |
|
3013 */ |
|
3014 static void |
|
3015 gst_ogg_demux_loop (GstOggPad * pad) |
|
3016 { |
|
3017 GstOggDemux *ogg; |
|
3018 GstFlowReturn ret; |
|
3019 GstEvent *event; |
|
3020 |
|
3021 ogg = GST_OGG_DEMUX (GST_OBJECT_PARENT (pad)); |
|
3022 |
|
3023 if (ogg->need_chains) { |
|
3024 gboolean res; |
|
3025 |
|
3026 /* this is the only place where we write chains and thus need to lock. */ |
|
3027 GST_CHAIN_LOCK (ogg); |
|
3028 ret = gst_ogg_demux_find_chains (ogg); |
|
3029 GST_CHAIN_UNLOCK (ogg); |
|
3030 if (ret != GST_FLOW_OK) |
|
3031 goto chain_read_failed; |
|
3032 |
|
3033 ogg->need_chains = FALSE; |
|
3034 |
|
3035 GST_OBJECT_LOCK (ogg); |
|
3036 ogg->running = TRUE; |
|
3037 event = ogg->event; |
|
3038 ogg->event = NULL; |
|
3039 GST_OBJECT_UNLOCK (ogg); |
|
3040 |
|
3041 /* and seek to configured positions without FLUSH */ |
|
3042 res = gst_ogg_demux_perform_seek (ogg, event); |
|
3043 if (event) |
|
3044 gst_event_unref (event); |
|
3045 |
|
3046 if (!res) |
|
3047 goto seek_failed; |
|
3048 } |
|
3049 |
|
3050 if (ogg->segment.rate >= 0.0) |
|
3051 ret = gst_ogg_demux_loop_forward (ogg); |
|
3052 else |
|
3053 ret = gst_ogg_demux_loop_reverse (ogg); |
|
3054 |
|
3055 if (ret != GST_FLOW_OK) |
|
3056 goto pause; |
|
3057 |
|
3058 return; |
|
3059 |
|
3060 /* ERRORS */ |
|
3061 chain_read_failed: |
|
3062 { |
|
3063 /* error was posted */ |
|
3064 goto pause; |
|
3065 } |
|
3066 seek_failed: |
|
3067 { |
|
3068 GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL), |
|
3069 ("failed to start demuxing ogg")); |
|
3070 ret = GST_FLOW_ERROR; |
|
3071 goto pause; |
|
3072 } |
|
3073 pause: |
|
3074 { |
|
3075 const gchar *reason = gst_flow_get_name (ret); |
|
3076 |
|
3077 GST_LOG_OBJECT (ogg, "pausing task, reason %s", reason); |
|
3078 ogg->segment_running = FALSE; |
|
3079 gst_pad_pause_task (ogg->sinkpad); |
|
3080 |
|
3081 if (GST_FLOW_IS_FATAL (ret) || ret == GST_FLOW_NOT_LINKED) { |
|
3082 if (ret == GST_FLOW_UNEXPECTED) { |
|
3083 /* perform EOS logic */ |
|
3084 if (ogg->segment.flags & GST_SEEK_FLAG_SEGMENT) { |
|
3085 gint64 stop; |
|
3086 |
|
3087 /* for segment playback we need to post when (in stream time) |
|
3088 * we stopped, this is either stop (when set) or the duration. */ |
|
3089 if ((stop = ogg->segment.stop) == -1) |
|
3090 stop = ogg->segment.duration; |
|
3091 |
|
3092 GST_LOG_OBJECT (ogg, "Sending segment done, at end of segment"); |
|
3093 gst_element_post_message (GST_ELEMENT (ogg), |
|
3094 gst_message_new_segment_done (GST_OBJECT (ogg), GST_FORMAT_TIME, |
|
3095 stop)); |
|
3096 } else { |
|
3097 /* normal playback, send EOS to all linked pads */ |
|
3098 GST_LOG_OBJECT (ogg, "Sending EOS, at end of stream"); |
|
3099 gst_ogg_demux_send_event (ogg, gst_event_new_eos ()); |
|
3100 } |
|
3101 } else { |
|
3102 GST_ELEMENT_ERROR (ogg, STREAM, FAILED, |
|
3103 (_("Internal data stream error.")), |
|
3104 ("stream stopped, reason %s", reason)); |
|
3105 gst_ogg_demux_send_event (ogg, gst_event_new_eos ()); |
|
3106 } |
|
3107 } |
|
3108 return; |
|
3109 } |
|
3110 } |
|
3111 |
|
3112 static void |
|
3113 gst_ogg_demux_clear_chains (GstOggDemux * ogg) |
|
3114 { |
|
3115 gint i; |
|
3116 |
|
3117 gst_ogg_demux_deactivate_current_chain (ogg); |
|
3118 |
|
3119 GST_CHAIN_LOCK (ogg); |
|
3120 for (i = 0; i < ogg->chains->len; i++) { |
|
3121 GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i); |
|
3122 |
|
3123 gst_ogg_chain_free (chain); |
|
3124 } |
|
3125 ogg->chains = g_array_set_size (ogg->chains, 0); |
|
3126 GST_CHAIN_UNLOCK (ogg); |
|
3127 } |
|
3128 |
|
3129 /* this function is called when the pad is activated and should start |
|
3130 * processing data. |
|
3131 * |
|
3132 * We check if we can do random access to decide if we work push or |
|
3133 * pull based. |
|
3134 */ |
|
3135 static gboolean |
|
3136 gst_ogg_demux_sink_activate (GstPad * sinkpad) |
|
3137 { |
|
3138 if (gst_pad_check_pull_range (sinkpad)) { |
|
3139 GST_DEBUG_OBJECT (sinkpad, "activating pull"); |
|
3140 return gst_pad_activate_pull (sinkpad, TRUE); |
|
3141 } else { |
|
3142 GST_DEBUG_OBJECT (sinkpad, "activating push"); |
|
3143 return gst_pad_activate_push (sinkpad, TRUE); |
|
3144 } |
|
3145 } |
|
3146 |
|
3147 /* this function gets called when we activate ourselves in push mode. |
|
3148 * We cannot seek (ourselves) in the stream */ |
|
3149 static gboolean |
|
3150 gst_ogg_demux_sink_activate_push (GstPad * sinkpad, gboolean active) |
|
3151 { |
|
3152 GstOggDemux *ogg; |
|
3153 |
|
3154 ogg = GST_OGG_DEMUX (GST_OBJECT_PARENT (sinkpad)); |
|
3155 |
|
3156 ogg->seekable = FALSE; |
|
3157 |
|
3158 return TRUE; |
|
3159 } |
|
3160 |
|
3161 /* this function gets called when we activate ourselves in pull mode. |
|
3162 * We can perform random access to the resource and we start a task |
|
3163 * to start reading */ |
|
3164 static gboolean |
|
3165 gst_ogg_demux_sink_activate_pull (GstPad * sinkpad, gboolean active) |
|
3166 { |
|
3167 GstOggDemux *ogg; |
|
3168 |
|
3169 ogg = GST_OGG_DEMUX (GST_OBJECT_PARENT (sinkpad)); |
|
3170 |
|
3171 if (active) { |
|
3172 ogg->need_chains = TRUE; |
|
3173 ogg->seekable = TRUE; |
|
3174 |
|
3175 return gst_pad_start_task (sinkpad, (GstTaskFunction) gst_ogg_demux_loop, |
|
3176 sinkpad); |
|
3177 } else { |
|
3178 return gst_pad_stop_task (sinkpad); |
|
3179 } |
|
3180 } |
|
3181 |
|
3182 static GstStateChangeReturn |
|
3183 gst_ogg_demux_change_state (GstElement * element, GstStateChange transition) |
|
3184 { |
|
3185 GstOggDemux *ogg; |
|
3186 GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE; |
|
3187 |
|
3188 ogg = GST_OGG_DEMUX (element); |
|
3189 |
|
3190 switch (transition) { |
|
3191 case GST_STATE_CHANGE_NULL_TO_READY: |
|
3192 ogg->basetime = 0; |
|
3193 ogg->have_fishead = FALSE; |
|
3194 ogg_sync_init (&ogg->sync); |
|
3195 break; |
|
3196 case GST_STATE_CHANGE_READY_TO_PAUSED: |
|
3197 ogg_sync_reset (&ogg->sync); |
|
3198 ogg->current_granule = -1; |
|
3199 ogg->running = FALSE; |
|
3200 ogg->segment_running = FALSE; |
|
3201 gst_segment_init (&ogg->segment, GST_FORMAT_TIME); |
|
3202 break; |
|
3203 case GST_STATE_CHANGE_PAUSED_TO_PLAYING: |
|
3204 break; |
|
3205 default: |
|
3206 break; |
|
3207 } |
|
3208 |
|
3209 result = parent_class->change_state (element, transition); |
|
3210 |
|
3211 switch (transition) { |
|
3212 case GST_STATE_CHANGE_PLAYING_TO_PAUSED: |
|
3213 break; |
|
3214 case GST_STATE_CHANGE_PAUSED_TO_READY: |
|
3215 gst_ogg_demux_clear_chains (ogg); |
|
3216 GST_OBJECT_LOCK (ogg); |
|
3217 ogg->running = FALSE; |
|
3218 ogg->segment_running = FALSE; |
|
3219 ogg->have_fishead = FALSE; |
|
3220 GST_OBJECT_UNLOCK (ogg); |
|
3221 break; |
|
3222 case GST_STATE_CHANGE_READY_TO_NULL: |
|
3223 ogg_sync_clear (&ogg->sync); |
|
3224 break; |
|
3225 default: |
|
3226 break; |
|
3227 } |
|
3228 return result; |
|
3229 } |
|
3230 |
|
3231 static GstClockTime |
|
3232 gst_annodex_granule_to_time (gint64 granulepos, gint64 granulerate_n, |
|
3233 gint64 granulerate_d, guint8 granuleshift) |
|
3234 { |
|
3235 gint64 keyindex, keyoffset; |
|
3236 gint64 granulerate; |
|
3237 GstClockTime res; |
|
3238 |
|
3239 if (granulepos == 0 || granulerate_n == 0 || granulerate_d == 0) |
|
3240 return 0; |
|
3241 |
|
3242 if (granuleshift != 0) { |
|
3243 keyindex = granulepos >> granuleshift; |
|
3244 keyoffset = granulepos - (keyindex << granuleshift); |
|
3245 granulepos = keyindex + keyoffset; |
|
3246 } |
|
3247 |
|
3248 /* GST_SECOND / (granulerate_n / granulerate_d) */ |
|
3249 granulerate = gst_util_uint64_scale (GST_SECOND, |
|
3250 granulerate_d, granulerate_n); |
|
3251 res = gst_util_uint64_scale (granulepos, granulerate, 1); |
|
3252 return res; |
|
3253 } |
|
3254 |
|
3255 gboolean |
|
3256 gst_ogg_demux_plugin_init (GstPlugin * plugin) |
|
3257 { |
|
3258 GST_DEBUG_CATEGORY_INIT (gst_ogg_demux_debug, "oggdemux", 0, "ogg demuxer"); |
|
3259 GST_DEBUG_CATEGORY_INIT (gst_ogg_demux_setup_debug, "oggdemux_setup", 0, |
|
3260 "ogg demuxer setup stage when parsing pipeline"); |
|
3261 |
|
3262 #if ENABLE_NLS |
|
3263 GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE, |
|
3264 LOCALEDIR); |
|
3265 bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); |
|
3266 #endif |
|
3267 |
|
3268 return gst_element_register (plugin, "oggdemux", GST_RANK_PRIMARY, |
|
3269 GST_TYPE_OGG_DEMUX); |
|
3270 } |
|
3271 |
|
3272 /* prints all info about the element */ |
|
3273 #undef GST_CAT_DEFAULT |
|
3274 #define GST_CAT_DEFAULT gst_ogg_demux_setup_debug |
|
3275 |
|
3276 #ifdef GST_DISABLE_GST_DEBUG |
|
3277 |
|
3278 static void |
|
3279 gst_ogg_print (GstOggDemux * ogg) |
|
3280 { |
|
3281 /* NOP */ |
|
3282 } |
|
3283 |
|
3284 #else /* !GST_DISABLE_GST_DEBUG */ |
|
3285 |
|
3286 static void |
|
3287 gst_ogg_print (GstOggDemux * ogg) |
|
3288 { |
|
3289 guint j, i; |
|
3290 |
|
3291 GST_INFO_OBJECT (ogg, "%u chains", ogg->chains->len); |
|
3292 GST_INFO_OBJECT (ogg, " total time: %" GST_TIME_FORMAT, |
|
3293 GST_TIME_ARGS (ogg->total_time)); |
|
3294 |
|
3295 for (i = 0; i < ogg->chains->len; i++) { |
|
3296 GstOggChain *chain = g_array_index (ogg->chains, GstOggChain *, i); |
|
3297 |
|
3298 GST_INFO_OBJECT (ogg, " chain %d (%u streams):", i, chain->streams->len); |
|
3299 GST_INFO_OBJECT (ogg, " offset: %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT, |
|
3300 chain->offset, chain->end_offset); |
|
3301 GST_INFO_OBJECT (ogg, " begin time: %" GST_TIME_FORMAT, |
|
3302 GST_TIME_ARGS (chain->begin_time)); |
|
3303 GST_INFO_OBJECT (ogg, " total time: %" GST_TIME_FORMAT, |
|
3304 GST_TIME_ARGS (chain->total_time)); |
|
3305 GST_INFO_OBJECT (ogg, " segment start: %" GST_TIME_FORMAT, |
|
3306 GST_TIME_ARGS (chain->segment_start)); |
|
3307 GST_INFO_OBJECT (ogg, " segment stop: %" GST_TIME_FORMAT, |
|
3308 GST_TIME_ARGS (chain->segment_stop)); |
|
3309 |
|
3310 for (j = 0; j < chain->streams->len; j++) { |
|
3311 GstOggPad *stream = g_array_index (chain->streams, GstOggPad *, j); |
|
3312 |
|
3313 GST_INFO_OBJECT (ogg, " stream %08x:", stream->serialno); |
|
3314 GST_INFO_OBJECT (ogg, " start time: %" GST_TIME_FORMAT, |
|
3315 GST_TIME_ARGS (stream->start_time)); |
|
3316 GST_INFO_OBJECT (ogg, " first granulepos: %" G_GINT64_FORMAT, |
|
3317 stream->first_granule); |
|
3318 GST_INFO_OBJECT (ogg, " first time: %" GST_TIME_FORMAT, |
|
3319 GST_TIME_ARGS (stream->first_time)); |
|
3320 } |
|
3321 } |
|
3322 } |
|
3323 #endif /* GST_DISABLE_GST_DEBUG */ |
|