1 /* GStreamer |
|
2 * Copyright (C) 2005 Michael Smith <msmith@fluendo.com> |
|
3 * |
|
4 * gstoggparse.c: ogg stream parser |
|
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 /* This ogg parser is essentially a subset of the ogg demuxer - rather than |
|
23 * fully demuxing into packets, we only parse out the pages, create one |
|
24 * GstBuffer per page, set all the appropriate flags on those pages, set caps |
|
25 * appropriately (particularly the 'streamheader' which gives all the header |
|
26 * pages required for initialing decode). |
|
27 * |
|
28 * It's dramatically simpler than the full demuxer as it does not support |
|
29 * seeking. |
|
30 */ |
|
31 |
|
32 #ifdef HAVE_CONFIG_H |
|
33 #include "config.h" |
|
34 #endif |
|
35 #include <gst/gst.h> |
|
36 #include <ogg/ogg.h> |
|
37 #include <string.h> |
|
38 |
|
39 static const GstElementDetails gst_ogg_parse_details = |
|
40 GST_ELEMENT_DETAILS ("Ogg parser", |
|
41 "Codec/Parser", |
|
42 "parse ogg streams into pages (info about ogg: http://xiph.org)", |
|
43 "Michael Smith <msmith@fluendo.com>"); |
|
44 |
|
45 GST_DEBUG_CATEGORY_STATIC (gst_ogg_parse_debug); |
|
46 #define GST_CAT_DEFAULT gst_ogg_parse_debug |
|
47 |
|
48 #define GST_TYPE_OGG_PARSE (gst_ogg_parse_get_type()) |
|
49 #define GST_OGG_PARSE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OGG_PARSE, GstOggParse)) |
|
50 #define GST_OGG_PARSE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OGG_PARSE, GstOggParse)) |
|
51 #define GST_IS_OGG_PARSE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OGG_PARSE)) |
|
52 #define GST_IS_OGG_PARSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OGG_PARSE)) |
|
53 |
|
54 static GType gst_ogg_parse_get_type (void); |
|
55 |
|
56 typedef struct _GstOggParse GstOggParse; |
|
57 typedef struct _GstOggParseClass GstOggParseClass; |
|
58 |
|
59 /* Each ogg logical stream has a GstOggStream associated with it */ |
|
60 typedef struct |
|
61 { |
|
62 /*ogg_stream_state stream; *//* We need this to get the packets out in order |
|
63 to do codec identification, for various |
|
64 codec-specific tasks */ |
|
65 |
|
66 gboolean in_headers; /* Initially true, false once we've read all the |
|
67 headers for this logical stream */ |
|
68 |
|
69 guint32 serialno; /* Unique serial number of this stream */ |
|
70 |
|
71 GSList *headers; /* List of ogg pages that we'll set on caps */ |
|
72 GSList *unknown_pages; /* List of pages we haven't yet classified */ |
|
73 } GstOggStream; |
|
74 |
|
75 struct _GstOggParse |
|
76 { |
|
77 GstElement element; |
|
78 |
|
79 GstPad *sinkpad; /* Sink pad we're reading data from */ |
|
80 |
|
81 GstPad *srcpad; /* Source pad we're writing to */ |
|
82 |
|
83 GSList *oggstreams; /* list of GstOggStreams for known streams */ |
|
84 |
|
85 gint64 offset; /* Current stream offset */ |
|
86 |
|
87 gboolean in_headers; /* Set if we're reading headers for streams */ |
|
88 |
|
89 gboolean last_page_not_bos; /* Set if we've seen a non-BOS page */ |
|
90 |
|
91 ogg_sync_state sync; /* Ogg page synchronisation */ |
|
92 |
|
93 GstCaps *caps; /* Our src caps */ |
|
94 }; |
|
95 |
|
96 struct _GstOggParseClass |
|
97 { |
|
98 GstElementClass parent_class; |
|
99 }; |
|
100 |
|
101 static void gst_ogg_parse_base_init (gpointer g_class); |
|
102 static void gst_ogg_parse_class_init (GstOggParseClass * klass); |
|
103 static void gst_ogg_parse_init (GstOggParse * ogg); |
|
104 static GstElementClass *parent_class = NULL; |
|
105 |
|
106 static GType |
|
107 gst_ogg_parse_get_type (void) |
|
108 { |
|
109 static GType ogg_parse_type = 0; |
|
110 |
|
111 if (!ogg_parse_type) { |
|
112 static const GTypeInfo ogg_parse_info = { |
|
113 sizeof (GstOggParseClass), |
|
114 gst_ogg_parse_base_init, |
|
115 NULL, |
|
116 (GClassInitFunc) gst_ogg_parse_class_init, |
|
117 NULL, |
|
118 NULL, |
|
119 sizeof (GstOggParse), |
|
120 0, |
|
121 (GInstanceInitFunc) gst_ogg_parse_init, |
|
122 }; |
|
123 |
|
124 ogg_parse_type = g_type_register_static (GST_TYPE_ELEMENT, "GstOggParse", |
|
125 &ogg_parse_info, 0); |
|
126 } |
|
127 return ogg_parse_type; |
|
128 } |
|
129 |
|
130 static void |
|
131 free_stream (GstOggStream * stream) |
|
132 { |
|
133 g_slist_foreach (stream->headers, (GFunc) gst_mini_object_unref, NULL); |
|
134 g_slist_foreach (stream->unknown_pages, (GFunc) gst_mini_object_unref, NULL); |
|
135 |
|
136 g_free (stream); |
|
137 } |
|
138 |
|
139 static void |
|
140 gst_ogg_parse_delete_all_streams (GstOggParse * ogg) |
|
141 { |
|
142 g_slist_foreach (ogg->oggstreams, (GFunc) free_stream, NULL); |
|
143 g_slist_free (ogg->oggstreams); |
|
144 ogg->oggstreams = NULL; |
|
145 } |
|
146 |
|
147 static GstOggStream * |
|
148 gst_ogg_parse_new_stream (GstOggParse * parser, guint32 serialno) |
|
149 { |
|
150 GstOggStream *ret; |
|
151 |
|
152 GST_DEBUG_OBJECT (parser, "creating new stream %08x", serialno); |
|
153 |
|
154 ret = g_new0 (GstOggStream, 1); |
|
155 |
|
156 ret->serialno = serialno; |
|
157 ret->in_headers = 1; |
|
158 |
|
159 /* |
|
160 if (ogg_stream_init (&ret->stream, serialno) != 0) { |
|
161 GST_ERROR ("Could not initialize ogg_stream struct for serial %08lx.", |
|
162 serialno); |
|
163 return NULL; |
|
164 } |
|
165 */ |
|
166 |
|
167 parser->oggstreams = g_slist_append (parser->oggstreams, ret); |
|
168 |
|
169 return ret; |
|
170 } |
|
171 |
|
172 static GstOggStream * |
|
173 gst_ogg_parse_find_stream (GstOggParse * parser, guint32 serialno) |
|
174 { |
|
175 GSList *l; |
|
176 |
|
177 for (l = parser->oggstreams; l != NULL; l = l->next) { |
|
178 GstOggStream *stream = (GstOggStream *) l->data; |
|
179 |
|
180 if (stream->serialno == serialno) |
|
181 return stream; |
|
182 } |
|
183 return NULL; |
|
184 } |
|
185 |
|
186 /* signals and args */ |
|
187 enum |
|
188 { |
|
189 /* FILL ME */ |
|
190 LAST_SIGNAL |
|
191 }; |
|
192 |
|
193 enum |
|
194 { |
|
195 ARG_0 |
|
196 /* FILL ME */ |
|
197 }; |
|
198 |
|
199 static GstStaticPadTemplate ogg_parse_src_template_factory = |
|
200 GST_STATIC_PAD_TEMPLATE ("src", |
|
201 GST_PAD_SRC, |
|
202 GST_PAD_ALWAYS, |
|
203 GST_STATIC_CAPS ("application/ogg") |
|
204 ); |
|
205 |
|
206 static GstStaticPadTemplate ogg_parse_sink_template_factory = |
|
207 GST_STATIC_PAD_TEMPLATE ("sink", |
|
208 GST_PAD_SINK, |
|
209 GST_PAD_ALWAYS, |
|
210 GST_STATIC_CAPS ("application/ogg") |
|
211 ); |
|
212 |
|
213 static void gst_ogg_parse_dispose (GObject * object); |
|
214 static GstStateChangeReturn gst_ogg_parse_change_state (GstElement * element, |
|
215 GstStateChange transition); |
|
216 static GstFlowReturn gst_ogg_parse_chain (GstPad * pad, GstBuffer * buffer); |
|
217 |
|
218 static void |
|
219 gst_ogg_parse_base_init (gpointer g_class) |
|
220 { |
|
221 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); |
|
222 |
|
223 gst_element_class_set_details (element_class, &gst_ogg_parse_details); |
|
224 |
|
225 gst_element_class_add_pad_template (element_class, |
|
226 gst_static_pad_template_get (&ogg_parse_sink_template_factory)); |
|
227 gst_element_class_add_pad_template (element_class, |
|
228 gst_static_pad_template_get (&ogg_parse_src_template_factory)); |
|
229 } |
|
230 |
|
231 static void |
|
232 gst_ogg_parse_class_init (GstOggParseClass * klass) |
|
233 { |
|
234 GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); |
|
235 GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
|
236 |
|
237 parent_class = g_type_class_peek_parent (klass); |
|
238 |
|
239 gstelement_class->change_state = gst_ogg_parse_change_state; |
|
240 |
|
241 gobject_class->dispose = gst_ogg_parse_dispose; |
|
242 } |
|
243 |
|
244 static void |
|
245 gst_ogg_parse_init (GstOggParse * ogg) |
|
246 { |
|
247 /* create the sink and source pads */ |
|
248 ogg->sinkpad = |
|
249 gst_pad_new_from_static_template (&ogg_parse_sink_template_factory, |
|
250 "sink"); |
|
251 ogg->srcpad = |
|
252 gst_pad_new_from_static_template (&ogg_parse_src_template_factory, "src"); |
|
253 |
|
254 /* TODO: Are there any events we must handle? */ |
|
255 /* gst_pad_set_event_function (ogg->sinkpad, gst_ogg_parse_handle_event); */ |
|
256 gst_pad_set_chain_function (ogg->sinkpad, gst_ogg_parse_chain); |
|
257 |
|
258 gst_element_add_pad (GST_ELEMENT (ogg), ogg->sinkpad); |
|
259 gst_element_add_pad (GST_ELEMENT (ogg), ogg->srcpad); |
|
260 |
|
261 ogg->oggstreams = NULL; |
|
262 } |
|
263 |
|
264 static void |
|
265 gst_ogg_parse_dispose (GObject * object) |
|
266 { |
|
267 GstOggParse *ogg = GST_OGG_PARSE (object); |
|
268 |
|
269 GST_LOG_OBJECT (ogg, "Disposing of object %p", ogg); |
|
270 |
|
271 ogg_sync_clear (&ogg->sync); |
|
272 gst_ogg_parse_delete_all_streams (ogg); |
|
273 |
|
274 if (ogg->caps) { |
|
275 gst_caps_unref (ogg->caps); |
|
276 ogg->caps = NULL; |
|
277 } |
|
278 |
|
279 if (G_OBJECT_CLASS (parent_class)->dispose) |
|
280 G_OBJECT_CLASS (parent_class)->dispose (object); |
|
281 } |
|
282 |
|
283 /* submit the given buffer to the ogg sync. |
|
284 * |
|
285 * Returns the number of bytes submited. |
|
286 */ |
|
287 static gint |
|
288 gst_ogg_parse_submit_buffer (GstOggParse * ogg, GstBuffer * buffer) |
|
289 { |
|
290 guint size; |
|
291 guint8 *data; |
|
292 gchar *oggbuffer; |
|
293 |
|
294 size = GST_BUFFER_SIZE (buffer); |
|
295 data = GST_BUFFER_DATA (buffer); |
|
296 |
|
297 /* We now have a buffer, submit it to the ogg sync layer */ |
|
298 oggbuffer = ogg_sync_buffer (&ogg->sync, size); |
|
299 memcpy (oggbuffer, data, size); |
|
300 ogg_sync_wrote (&ogg->sync, size); |
|
301 |
|
302 /* We've copied all the neccesary data, so we're done with the buffer */ |
|
303 gst_buffer_unref (buffer); |
|
304 |
|
305 return size; |
|
306 } |
|
307 |
|
308 static void |
|
309 gst_ogg_parse_append_header (GValue * array, GstBuffer * buf) |
|
310 { |
|
311 GValue value = { 0 }; |
|
312 /* We require a copy to avoid circular refcounts */ |
|
313 GstBuffer *buffer = gst_buffer_copy (buf); |
|
314 |
|
315 GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS); |
|
316 |
|
317 g_value_init (&value, GST_TYPE_BUFFER); |
|
318 gst_value_set_buffer (&value, buffer); |
|
319 gst_value_array_append_value (array, &value); |
|
320 g_value_unset (&value); |
|
321 |
|
322 } |
|
323 |
|
324 typedef enum |
|
325 { |
|
326 PAGE_HEADER, /* Header page */ |
|
327 PAGE_DATA, /* Data page */ |
|
328 PAGE_PENDING, /* We don't know yet, we'll have to see some future pages */ |
|
329 } page_type; |
|
330 |
|
331 static page_type |
|
332 gst_ogg_parse_is_header (GstOggParse * ogg, GstOggStream * stream, |
|
333 ogg_page * page) |
|
334 { |
|
335 ogg_int64_t gpos = ogg_page_granulepos (page); |
|
336 |
|
337 if (gpos < 0) |
|
338 return PAGE_PENDING; |
|
339 |
|
340 /* This is good enough for now, but technically requires codec-specific |
|
341 * behaviour to be perfect. This is where we need the mooted library for |
|
342 * this stuff, which nobody has written. |
|
343 */ |
|
344 if (gpos > 0) |
|
345 return PAGE_DATA; |
|
346 else |
|
347 return PAGE_HEADER; |
|
348 } |
|
349 |
|
350 static GstBuffer * |
|
351 gst_ogg_parse_buffer_from_page (ogg_page * page, |
|
352 guint64 offset, gboolean delta, GstClockTime timestamp) |
|
353 { |
|
354 int size = page->header_len + page->body_len; |
|
355 GstBuffer *buf = gst_buffer_new_and_alloc (size); |
|
356 |
|
357 memcpy (GST_BUFFER_DATA (buf), page->header, page->header_len); |
|
358 memcpy (GST_BUFFER_DATA (buf) + page->header_len, page->body, page->body_len); |
|
359 |
|
360 GST_BUFFER_TIMESTAMP (buf) = timestamp; |
|
361 GST_BUFFER_OFFSET (buf) = offset; |
|
362 GST_BUFFER_OFFSET_END (buf) = offset + size; |
|
363 |
|
364 return buf; |
|
365 } |
|
366 |
|
367 |
|
368 /* Reads in buffers, parses them, reframes into one-buffer-per-ogg-page, submits |
|
369 * pages to output pad. |
|
370 */ |
|
371 static GstFlowReturn |
|
372 gst_ogg_parse_chain (GstPad * pad, GstBuffer * buffer) |
|
373 { |
|
374 GstOggParse *ogg; |
|
375 GstFlowReturn result = GST_FLOW_OK; |
|
376 gint ret = -1; |
|
377 guint32 serialno; |
|
378 GstBuffer *pagebuffer; |
|
379 GstClockTime buffertimestamp = GST_BUFFER_TIMESTAMP (buffer); |
|
380 |
|
381 ogg = GST_OGG_PARSE (GST_OBJECT_PARENT (pad)); |
|
382 |
|
383 GST_LOG_OBJECT (ogg, "Chain function received buffer of size %d", |
|
384 GST_BUFFER_SIZE (buffer)); |
|
385 |
|
386 gst_ogg_parse_submit_buffer (ogg, buffer); |
|
387 |
|
388 while (ret != 0 && result == GST_FLOW_OK) { |
|
389 ogg_page page; |
|
390 |
|
391 /* We use ogg_sync_pageseek() rather than ogg_sync_pageout() so that we can |
|
392 * track how many bytes the ogg layer discarded (in the case of sync errors, |
|
393 * etc.); this allows us to accurately track the current stream offset |
|
394 */ |
|
395 ret = ogg_sync_pageseek (&ogg->sync, &page); |
|
396 if (ret == 0) { |
|
397 /* need more data, that's fine... */ |
|
398 break; |
|
399 } else if (ret < 0) { |
|
400 /* discontinuity; track how many bytes we skipped (-ret) */ |
|
401 ogg->offset -= ret; |
|
402 } else { |
|
403 #ifndef GST_DISABLE_GST_DEBUG |
|
404 gint64 granule = ogg_page_granulepos (&page); |
|
405 int bos = ogg_page_bos (&page); |
|
406 #endif |
|
407 guint64 startoffset = ogg->offset; |
|
408 |
|
409 GST_LOG_OBJECT (ogg, "Timestamping outgoing buffer as %" GST_TIME_FORMAT, |
|
410 GST_TIME_ARGS (buffertimestamp)); |
|
411 /* Turn our page into a GstBuffer TODO: better timestamps? Requires format |
|
412 * parsing. */ |
|
413 pagebuffer = gst_ogg_parse_buffer_from_page (&page, startoffset, FALSE, |
|
414 buffertimestamp); |
|
415 |
|
416 /* We read out 'ret' bytes, so we set the next offset appropriately */ |
|
417 ogg->offset += ret; |
|
418 |
|
419 serialno = ogg_page_serialno (&page); |
|
420 |
|
421 GST_LOG_OBJECT (ogg, |
|
422 "processing ogg page (serial %08x, pageno %ld, " |
|
423 "granule pos %" G_GUINT64_FORMAT ", bos %d, offset %" |
|
424 G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT ")", |
|
425 serialno, ogg_page_pageno (&page), |
|
426 granule, bos, startoffset, ogg->offset); |
|
427 |
|
428 if (ogg_page_bos (&page)) { |
|
429 /* If we've seen this serialno before, this is technically an error, |
|
430 * we log this case but accept it - this one replaces the previous |
|
431 * stream with this serialno. We can do this since we're streaming, and |
|
432 * not supporting seeking... |
|
433 */ |
|
434 GstOggStream *stream = gst_ogg_parse_find_stream (ogg, serialno); |
|
435 |
|
436 if (stream != NULL) { |
|
437 GST_LOG_OBJECT (ogg, "Incorrect stream; repeats serial number %u " |
|
438 "at offset %lld", serialno, ogg->offset); |
|
439 } |
|
440 |
|
441 if (ogg->last_page_not_bos) { |
|
442 GST_LOG_OBJECT (ogg, "Deleting all referenced streams, found a new " |
|
443 "chain starting with serial %u", serialno); |
|
444 gst_ogg_parse_delete_all_streams (ogg); |
|
445 } |
|
446 |
|
447 stream = gst_ogg_parse_new_stream (ogg, serialno); |
|
448 |
|
449 ogg->last_page_not_bos = FALSE; |
|
450 |
|
451 gst_buffer_ref (pagebuffer); |
|
452 stream->headers = g_slist_append (stream->headers, pagebuffer); |
|
453 |
|
454 if (!ogg->in_headers) { |
|
455 GST_LOG_OBJECT (ogg, "Found start of new chain at offset %llu", |
|
456 startoffset); |
|
457 ogg->in_headers = 1; |
|
458 } |
|
459 |
|
460 /* For now, we just keep the header buffer in the stream->headers list; |
|
461 * it actually gets output once we've collected the entire set |
|
462 */ |
|
463 } else { |
|
464 /* Non-BOS page. Either: we're outside headers, and this isn't a |
|
465 * header (normal data), outside headers and this is (error!), inside |
|
466 * headers, this is (append header), or inside headers and this isn't |
|
467 * (we've found the end of headers; flush the lot!) |
|
468 * |
|
469 * Before that, we flag that the last page seen (this one) was not a |
|
470 * BOS page; that way we know that when we next see a BOS page it's a |
|
471 * new chain, and we can flush all existing streams. |
|
472 */ |
|
473 page_type type; |
|
474 GstOggStream *stream = gst_ogg_parse_find_stream (ogg, serialno); |
|
475 |
|
476 if (!stream) { |
|
477 GST_LOG_OBJECT (ogg, "Non-BOS page unexpectedly found at %lld", |
|
478 ogg->offset); |
|
479 goto failure; |
|
480 } |
|
481 |
|
482 ogg->last_page_not_bos = TRUE; |
|
483 |
|
484 type = gst_ogg_parse_is_header (ogg, stream, &page); |
|
485 |
|
486 if (type == PAGE_PENDING && ogg->in_headers) { |
|
487 gst_buffer_ref (pagebuffer); |
|
488 |
|
489 stream->unknown_pages = g_slist_append (stream->unknown_pages, |
|
490 pagebuffer); |
|
491 } else if (type == PAGE_HEADER) { |
|
492 if (!ogg->in_headers) { |
|
493 GST_LOG_OBJECT (ogg, "Header page unexpectedly found outside " |
|
494 "headers at offset %lld", ogg->offset); |
|
495 goto failure; |
|
496 } else { |
|
497 /* Append the header to the buffer list, after any unknown previous |
|
498 * pages |
|
499 */ |
|
500 stream->headers = g_slist_concat (stream->headers, |
|
501 stream->unknown_pages); |
|
502 g_slist_free (stream->unknown_pages); |
|
503 gst_buffer_ref (pagebuffer); |
|
504 stream->headers = g_slist_append (stream->headers, pagebuffer); |
|
505 } |
|
506 } else { /* PAGE_DATA, or PAGE_PENDING but outside headers */ |
|
507 if (ogg->in_headers) { |
|
508 /* First non-header page... set caps, flush headers. |
|
509 * |
|
510 * First up, we build a single GValue list of all the pagebuffers |
|
511 * we're using for the headers, in order. |
|
512 * Then we set this on the caps structure. Then we can start pushing |
|
513 * buffers for the headers, and finally we send this non-header |
|
514 * page. |
|
515 */ |
|
516 GstCaps *caps; |
|
517 GstStructure *structure; |
|
518 GValue array = { 0 }; |
|
519 gint count = 0; |
|
520 gboolean found_pending_headers = FALSE; |
|
521 GSList *l; |
|
522 |
|
523 g_value_init (&array, GST_TYPE_ARRAY); |
|
524 |
|
525 for (l = ogg->oggstreams; l != NULL; l = l->next) { |
|
526 GstOggStream *stream = (GstOggStream *) l->data; |
|
527 |
|
528 if (g_slist_length (stream->headers) == 0) { |
|
529 GST_LOG_OBJECT (ogg, "No primary header found for stream %u", |
|
530 stream->serialno); |
|
531 goto failure; |
|
532 } |
|
533 |
|
534 gst_ogg_parse_append_header (&array, |
|
535 GST_BUFFER (stream->headers->data)); |
|
536 count++; |
|
537 } |
|
538 |
|
539 for (l = ogg->oggstreams; l != NULL; l = l->next) { |
|
540 GstOggStream *stream = (GstOggStream *) l->data; |
|
541 int j; |
|
542 |
|
543 for (j = 1; j < g_slist_length (stream->headers); j++) { |
|
544 gst_ogg_parse_append_header (&array, |
|
545 GST_BUFFER (g_slist_nth_data (stream->headers, j))); |
|
546 count++; |
|
547 } |
|
548 } |
|
549 |
|
550 caps = gst_pad_get_caps (ogg->srcpad); |
|
551 caps = gst_caps_make_writable (caps); |
|
552 |
|
553 structure = gst_caps_get_structure (caps, 0); |
|
554 gst_structure_set_value (structure, "streamheader", &array); |
|
555 |
|
556 gst_pad_set_caps (ogg->srcpad, caps); |
|
557 |
|
558 g_value_unset (&array); |
|
559 |
|
560 if (ogg->caps) |
|
561 gst_caps_unref (ogg->caps); |
|
562 ogg->caps = caps; |
|
563 |
|
564 GST_LOG_OBJECT (ogg, "Set \"streamheader\" caps with %d buffers " |
|
565 "(one per page)", count); |
|
566 |
|
567 /* Now, we do the same thing, but push buffers... */ |
|
568 for (l = ogg->oggstreams; l != NULL; l = l->next) { |
|
569 GstOggStream *stream = (GstOggStream *) l->data; |
|
570 GstBuffer *buf = GST_BUFFER (stream->headers->data); |
|
571 |
|
572 gst_buffer_set_caps (buf, caps); |
|
573 |
|
574 result = gst_pad_push (ogg->srcpad, buf); |
|
575 if (result != GST_FLOW_OK) |
|
576 return result; |
|
577 } |
|
578 for (l = ogg->oggstreams; l != NULL; l = l->next) { |
|
579 GstOggStream *stream = (GstOggStream *) l->data; |
|
580 int j; |
|
581 |
|
582 for (j = 1; j < g_slist_length (stream->headers); j++) { |
|
583 GstBuffer *buf = |
|
584 GST_BUFFER (g_slist_nth_data (stream->headers, j)); |
|
585 gst_buffer_set_caps (buf, caps); |
|
586 |
|
587 result = gst_pad_push (ogg->srcpad, buf); |
|
588 if (result != GST_FLOW_OK) |
|
589 return result; |
|
590 } |
|
591 } |
|
592 |
|
593 ogg->in_headers = 0; |
|
594 |
|
595 /* And finally the pending data pages */ |
|
596 for (l = ogg->oggstreams; l != NULL; l = l->next) { |
|
597 GstOggStream *stream = (GstOggStream *) l->data; |
|
598 GSList *k; |
|
599 |
|
600 if (stream->unknown_pages == NULL) |
|
601 continue; |
|
602 |
|
603 if (found_pending_headers) { |
|
604 GST_WARNING_OBJECT (ogg, "Incorrectly muxed headers found at " |
|
605 "approximate offset %lld", ogg->offset); |
|
606 } |
|
607 found_pending_headers = TRUE; |
|
608 |
|
609 GST_LOG_OBJECT (ogg, "Pushing %d pending pages after headers", |
|
610 g_slist_length (stream->unknown_pages) + 1); |
|
611 |
|
612 for (k = stream->unknown_pages; k != NULL; k = k->next) { |
|
613 GstBuffer *buf; |
|
614 |
|
615 buf = GST_BUFFER (k->data); |
|
616 gst_buffer_set_caps (buf, caps); |
|
617 result = gst_pad_push (ogg->srcpad, buf); |
|
618 if (result != GST_FLOW_OK) |
|
619 return result; |
|
620 } |
|
621 g_slist_foreach (stream->unknown_pages, |
|
622 (GFunc) gst_mini_object_unref, NULL); |
|
623 g_slist_free (stream->unknown_pages); |
|
624 stream->unknown_pages = NULL; |
|
625 } |
|
626 |
|
627 gst_buffer_set_caps (pagebuffer, caps); |
|
628 |
|
629 result = gst_pad_push (ogg->srcpad, GST_BUFFER (pagebuffer)); |
|
630 if (result != GST_FLOW_OK) |
|
631 return result; |
|
632 } else { |
|
633 /* Normal data page, submit buffer */ |
|
634 gst_buffer_set_caps (pagebuffer, ogg->caps); |
|
635 result = gst_pad_push (ogg->srcpad, GST_BUFFER (pagebuffer)); |
|
636 if (result != GST_FLOW_OK) |
|
637 return result; |
|
638 } |
|
639 } |
|
640 } |
|
641 } |
|
642 } |
|
643 |
|
644 return result; |
|
645 |
|
646 failure: |
|
647 gst_pad_push_event (GST_PAD (ogg->srcpad), gst_event_new_eos ()); |
|
648 return GST_FLOW_ERROR; |
|
649 } |
|
650 |
|
651 static GstStateChangeReturn |
|
652 gst_ogg_parse_change_state (GstElement * element, GstStateChange transition) |
|
653 { |
|
654 GstOggParse *ogg; |
|
655 GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE; |
|
656 |
|
657 ogg = GST_OGG_PARSE (element); |
|
658 |
|
659 switch (transition) { |
|
660 case GST_STATE_CHANGE_NULL_TO_READY: |
|
661 ogg_sync_init (&ogg->sync); |
|
662 break; |
|
663 case GST_STATE_CHANGE_READY_TO_PAUSED: |
|
664 ogg_sync_reset (&ogg->sync); |
|
665 break; |
|
666 case GST_STATE_CHANGE_PAUSED_TO_PLAYING: |
|
667 break; |
|
668 default: |
|
669 break; |
|
670 } |
|
671 |
|
672 result = parent_class->change_state (element, transition); |
|
673 |
|
674 switch (transition) { |
|
675 case GST_STATE_CHANGE_PLAYING_TO_PAUSED: |
|
676 break; |
|
677 case GST_STATE_CHANGE_PAUSED_TO_READY: |
|
678 break; |
|
679 case GST_STATE_CHANGE_READY_TO_NULL: |
|
680 ogg_sync_clear (&ogg->sync); |
|
681 break; |
|
682 default: |
|
683 break; |
|
684 } |
|
685 return result; |
|
686 } |
|
687 |
|
688 gboolean |
|
689 gst_ogg_parse_plugin_init (GstPlugin * plugin) |
|
690 { |
|
691 GST_DEBUG_CATEGORY_INIT (gst_ogg_parse_debug, "oggparse", 0, "ogg parser"); |
|
692 |
|
693 return gst_element_register (plugin, "oggparse", GST_RANK_NONE, |
|
694 GST_TYPE_OGG_PARSE); |
|
695 } |
|