2 * Copyright (C) 2005 Michael Smith <msmith@fluendo.com>
4 * gstoggparse.c: ogg stream parser
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
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).
28 * It's dramatically simpler than the full demuxer as it does not support
40 #include "gstoggstream.h"
42 GST_DEBUG_CATEGORY_STATIC (gst_ogg_parse_debug);
43 #define GST_CAT_DEFAULT gst_ogg_parse_debug
45 #define GST_TYPE_OGG_PARSE (gst_ogg_parse_get_type())
46 #define GST_OGG_PARSE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OGG_PARSE, GstOggParse))
47 #define GST_OGG_PARSE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OGG_PARSE, GstOggParse))
48 #define GST_IS_OGG_PARSE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OGG_PARSE))
49 #define GST_IS_OGG_PARSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OGG_PARSE))
51 static GType gst_ogg_parse_get_type (void);
53 typedef struct _GstOggParse GstOggParse;
54 typedef struct _GstOggParseClass GstOggParseClass;
60 GstPad *sinkpad; /* Sink pad we're reading data from */
62 GstPad *srcpad; /* Source pad we're writing to */
64 GSList *oggstreams; /* list of GstOggStreams for known streams */
66 gint64 offset; /* Current stream offset */
68 gboolean in_headers; /* Set if we're reading headers for streams */
70 gboolean last_page_not_bos; /* Set if we've seen a non-BOS page */
72 ogg_sync_state sync; /* Ogg page synchronisation */
74 GstCaps *caps; /* Our src caps */
76 GstOggStream *video_stream; /* Stream used to construct delta_unit flags */
79 struct _GstOggParseClass
81 GstElementClass parent_class;
84 static void gst_ogg_parse_base_init (gpointer g_class);
85 static void gst_ogg_parse_class_init (GstOggParseClass * klass);
86 static void gst_ogg_parse_init (GstOggParse * ogg);
87 static GstElementClass *parent_class = NULL;
90 gst_ogg_parse_get_type (void)
92 static GType ogg_parse_type = 0;
94 if (!ogg_parse_type) {
95 static const GTypeInfo ogg_parse_info = {
96 sizeof (GstOggParseClass),
97 gst_ogg_parse_base_init,
99 (GClassInitFunc) gst_ogg_parse_class_init,
102 sizeof (GstOggParse),
104 (GInstanceInitFunc) gst_ogg_parse_init,
107 ogg_parse_type = g_type_register_static (GST_TYPE_ELEMENT, "GstOggParse",
110 return ogg_parse_type;
114 free_stream (GstOggStream * stream)
116 g_list_foreach (stream->headers, (GFunc) gst_mini_object_unref, NULL);
117 g_list_foreach (stream->unknown_pages, (GFunc) gst_mini_object_unref, NULL);
118 g_list_foreach (stream->stored_buffers, (GFunc) gst_mini_object_unref, NULL);
120 g_slice_free (GstOggStream, stream);
124 gst_ogg_parse_delete_all_streams (GstOggParse * ogg)
126 g_slist_foreach (ogg->oggstreams, (GFunc) free_stream, NULL);
127 g_slist_free (ogg->oggstreams);
128 ogg->oggstreams = NULL;
131 static GstOggStream *
132 gst_ogg_parse_new_stream (GstOggParse * parser, ogg_page * page)
134 GstOggStream *stream;
139 serialno = ogg_page_serialno (page);
141 GST_DEBUG_OBJECT (parser, "creating new stream %08x", serialno);
143 stream = g_slice_new0 (GstOggStream);
145 stream->serialno = serialno;
146 stream->in_headers = 1;
148 if (ogg_stream_init (&stream->stream, serialno) != 0) {
149 GST_ERROR ("Could not initialize ogg_stream struct for serial %08x.",
154 /* FIXME check return */
155 ogg_stream_pagein (&stream->stream, page);
157 /* FIXME check return */
158 ret = ogg_stream_packetout (&stream->stream, &packet);
160 gst_ogg_stream_setup_map (stream, &packet);
161 if (stream->is_video) {
162 parser->video_stream = stream;
166 parser->oggstreams = g_slist_append (parser->oggstreams, stream);
171 static GstOggStream *
172 gst_ogg_parse_find_stream (GstOggParse * parser, guint32 serialno)
176 for (l = parser->oggstreams; l != NULL; l = l->next) {
177 GstOggStream *stream = (GstOggStream *) l->data;
179 if (stream->serialno == serialno)
185 /* signals and args */
198 static GstStaticPadTemplate ogg_parse_src_template_factory =
199 GST_STATIC_PAD_TEMPLATE ("src",
202 GST_STATIC_CAPS ("application/ogg")
205 static GstStaticPadTemplate ogg_parse_sink_template_factory =
206 GST_STATIC_PAD_TEMPLATE ("sink",
209 GST_STATIC_CAPS ("application/ogg")
212 static void gst_ogg_parse_dispose (GObject * object);
213 static GstStateChangeReturn gst_ogg_parse_change_state (GstElement * element,
214 GstStateChange transition);
215 static GstFlowReturn gst_ogg_parse_chain (GstPad * pad, GstBuffer * buffer);
218 gst_ogg_parse_base_init (gpointer g_class)
220 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
222 gst_element_class_set_details_simple (element_class,
223 "Ogg parser", "Codec/Parser",
224 "parse ogg streams into pages (info about ogg: http://xiph.org)",
225 "Michael Smith <msmith@fluendo.com>");
227 gst_element_class_add_pad_template (element_class,
228 gst_static_pad_template_get (&ogg_parse_sink_template_factory));
229 gst_element_class_add_pad_template (element_class,
230 gst_static_pad_template_get (&ogg_parse_src_template_factory));
234 gst_ogg_parse_class_init (GstOggParseClass * klass)
236 GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
237 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
239 parent_class = g_type_class_peek_parent (klass);
241 gstelement_class->change_state = gst_ogg_parse_change_state;
243 gobject_class->dispose = gst_ogg_parse_dispose;
247 gst_ogg_parse_init (GstOggParse * ogg)
249 /* create the sink and source pads */
251 gst_pad_new_from_static_template (&ogg_parse_sink_template_factory,
254 gst_pad_new_from_static_template (&ogg_parse_src_template_factory, "src");
256 /* TODO: Are there any events we must handle? */
257 /* gst_pad_set_event_function (ogg->sinkpad, gst_ogg_parse_handle_event); */
258 gst_pad_set_chain_function (ogg->sinkpad, gst_ogg_parse_chain);
260 gst_element_add_pad (GST_ELEMENT (ogg), ogg->sinkpad);
261 gst_element_add_pad (GST_ELEMENT (ogg), ogg->srcpad);
263 ogg->oggstreams = NULL;
267 gst_ogg_parse_dispose (GObject * object)
269 GstOggParse *ogg = GST_OGG_PARSE (object);
271 GST_LOG_OBJECT (ogg, "Disposing of object %p", ogg);
273 ogg_sync_clear (&ogg->sync);
274 gst_ogg_parse_delete_all_streams (ogg);
277 gst_caps_unref (ogg->caps);
281 if (G_OBJECT_CLASS (parent_class)->dispose)
282 G_OBJECT_CLASS (parent_class)->dispose (object);
285 /* submit the given buffer to the ogg sync */
287 gst_ogg_parse_submit_buffer (GstOggParse * ogg, GstBuffer * buffer)
292 GstFlowReturn ret = GST_FLOW_OK;
294 data = gst_buffer_map (buffer, &size, NULL, GST_MAP_READ);
296 GST_DEBUG_OBJECT (ogg, "submitting %" G_GSIZE_FORMAT " bytes", size);
297 if (G_UNLIKELY (size == 0))
300 oggbuffer = ogg_sync_buffer (&ogg->sync, size);
301 if (G_UNLIKELY (oggbuffer == NULL)) {
302 GST_ELEMENT_ERROR (ogg, STREAM, DECODE,
303 (NULL), ("failed to get ogg sync buffer"));
304 ret = GST_FLOW_ERROR;
308 memcpy (oggbuffer, data, size);
309 if (G_UNLIKELY (ogg_sync_wrote (&ogg->sync, size) < 0)) {
310 GST_ELEMENT_ERROR (ogg, STREAM, DECODE, (NULL),
311 ("failed to write %" G_GSIZE_FORMAT " bytes to the sync buffer", size));
312 ret = GST_FLOW_ERROR;
316 gst_buffer_unmap (buffer, data, size);
317 gst_buffer_unref (buffer);
323 gst_ogg_parse_append_header (GValue * array, GstBuffer * buf)
325 GValue value = { 0 };
326 /* We require a copy to avoid circular refcounts */
327 GstBuffer *buffer = gst_buffer_copy (buf);
329 GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS);
331 g_value_init (&value, GST_TYPE_BUFFER);
332 gst_value_set_buffer (&value, buffer);
333 gst_value_array_append_value (array, &value);
334 g_value_unset (&value);
340 PAGE_HEADER, /* Header page */
341 PAGE_DATA, /* Data page */
342 PAGE_PENDING, /* We don't know yet, we'll have to see some future pages */
346 gst_ogg_parse_is_header (GstOggParse * ogg, GstOggStream * stream,
349 ogg_int64_t gpos = ogg_page_granulepos (page);
354 /* This is good enough for now, but technically requires codec-specific
355 * behaviour to be perfect. This is where we need the mooted library for
356 * this stuff, which nobody has written.
365 gst_ogg_parse_buffer_from_page (ogg_page * page,
366 guint64 offset, GstClockTime timestamp)
368 int size = page->header_len + page->body_len;
369 GstBuffer *buf = gst_buffer_new_and_alloc (size);
371 gst_buffer_fill (buf, 0, page->header, page->header_len);
372 gst_buffer_fill (buf, page->header_len, page->body, page->body_len);
374 GST_BUFFER_TIMESTAMP (buf) = timestamp;
375 GST_BUFFER_OFFSET (buf) = offset;
376 GST_BUFFER_OFFSET_END (buf) = offset + size;
382 /* Reads in buffers, parses them, reframes into one-buffer-per-ogg-page, submits
383 * pages to output pad.
386 gst_ogg_parse_chain (GstPad * pad, GstBuffer * buffer)
389 GstFlowReturn result = GST_FLOW_OK;
392 GstBuffer *pagebuffer;
393 GstClockTime buffertimestamp = GST_BUFFER_TIMESTAMP (buffer);
395 ogg = GST_OGG_PARSE (GST_OBJECT_PARENT (pad));
398 "Chain function received buffer of size %" G_GSIZE_FORMAT,
399 gst_buffer_get_size (buffer));
401 gst_ogg_parse_submit_buffer (ogg, buffer);
403 while (ret != 0 && result == GST_FLOW_OK) {
406 /* We use ogg_sync_pageseek() rather than ogg_sync_pageout() so that we can
407 * track how many bytes the ogg layer discarded (in the case of sync errors,
408 * etc.); this allows us to accurately track the current stream offset
410 ret = ogg_sync_pageseek (&ogg->sync, &page);
412 /* need more data, that's fine... */
414 } else if (ret < 0) {
415 /* discontinuity; track how many bytes we skipped (-ret) */
418 gint64 granule = ogg_page_granulepos (&page);
419 #ifndef GST_DISABLE_GST_DEBUG
420 int bos = ogg_page_bos (&page);
422 guint64 startoffset = ogg->offset;
423 GstOggStream *stream;
426 serialno = ogg_page_serialno (&page);
427 stream = gst_ogg_parse_find_stream (ogg, serialno);
429 GST_LOG_OBJECT (ogg, "Timestamping outgoing buffer as %" GST_TIME_FORMAT,
430 GST_TIME_ARGS (buffertimestamp));
433 buffertimestamp = gst_ogg_stream_get_end_time_for_granulepos (stream,
435 if (ogg->video_stream) {
436 if (stream == ogg->video_stream) {
437 keyframe = gst_ogg_stream_granulepos_is_key_frame (stream, granule);
445 buffertimestamp = GST_CLOCK_TIME_NONE;
448 pagebuffer = gst_ogg_parse_buffer_from_page (&page, startoffset,
451 /* We read out 'ret' bytes, so we set the next offset appropriately */
455 "processing ogg page (serial %08x, pageno %ld, "
456 "granule pos %" G_GUINT64_FORMAT ", bos %d, offset %"
457 G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT ") keyframe=%d",
458 serialno, ogg_page_pageno (&page),
459 granule, bos, startoffset, ogg->offset, keyframe);
461 if (ogg_page_bos (&page)) {
462 /* If we've seen this serialno before, this is technically an error,
463 * we log this case but accept it - this one replaces the previous
464 * stream with this serialno. We can do this since we're streaming, and
465 * not supporting seeking...
467 GstOggStream *stream = gst_ogg_parse_find_stream (ogg, serialno);
469 if (stream != NULL) {
470 GST_LOG_OBJECT (ogg, "Incorrect stream; repeats serial number %08x "
471 "at offset %" G_GINT64_FORMAT, serialno, ogg->offset);
474 if (ogg->last_page_not_bos) {
475 GST_LOG_OBJECT (ogg, "Deleting all referenced streams, found a new "
476 "chain starting with serial %u", serialno);
477 gst_ogg_parse_delete_all_streams (ogg);
480 stream = gst_ogg_parse_new_stream (ogg, &page);
482 ogg->last_page_not_bos = FALSE;
484 gst_buffer_ref (pagebuffer);
485 stream->headers = g_list_append (stream->headers, pagebuffer);
487 if (!ogg->in_headers) {
489 "Found start of new chain at offset %" G_GUINT64_FORMAT,
494 /* For now, we just keep the header buffer in the stream->headers list;
495 * it actually gets output once we've collected the entire set
498 /* Non-BOS page. Either: we're outside headers, and this isn't a
499 * header (normal data), outside headers and this is (error!), inside
500 * headers, this is (append header), or inside headers and this isn't
501 * (we've found the end of headers; flush the lot!)
503 * Before that, we flag that the last page seen (this one) was not a
504 * BOS page; that way we know that when we next see a BOS page it's a
505 * new chain, and we can flush all existing streams.
508 GstOggStream *stream = gst_ogg_parse_find_stream (ogg, serialno);
512 "Non-BOS page unexpectedly found at %" G_GINT64_FORMAT,
517 ogg->last_page_not_bos = TRUE;
519 type = gst_ogg_parse_is_header (ogg, stream, &page);
521 if (type == PAGE_PENDING && ogg->in_headers) {
522 gst_buffer_ref (pagebuffer);
524 stream->unknown_pages = g_list_append (stream->unknown_pages,
526 } else if (type == PAGE_HEADER) {
527 if (!ogg->in_headers) {
528 GST_LOG_OBJECT (ogg, "Header page unexpectedly found outside "
529 "headers at offset %" G_GINT64_FORMAT, ogg->offset);
532 /* Append the header to the buffer list, after any unknown previous
535 stream->headers = g_list_concat (stream->headers,
536 stream->unknown_pages);
537 g_list_free (stream->unknown_pages);
538 gst_buffer_ref (pagebuffer);
539 stream->headers = g_list_append (stream->headers, pagebuffer);
541 } else { /* PAGE_DATA, or PAGE_PENDING but outside headers */
542 if (ogg->in_headers) {
543 /* First non-header page... set caps, flush headers.
545 * First up, we build a single GValue list of all the pagebuffers
546 * we're using for the headers, in order.
547 * Then we set this on the caps structure. Then we can start pushing
548 * buffers for the headers, and finally we send this non-header
552 GstStructure *structure;
553 GValue array = { 0 };
555 gboolean found_pending_headers = FALSE;
558 g_value_init (&array, GST_TYPE_ARRAY);
560 for (l = ogg->oggstreams; l != NULL; l = l->next) {
561 GstOggStream *stream = (GstOggStream *) l->data;
563 if (g_list_length (stream->headers) == 0) {
564 GST_LOG_OBJECT (ogg, "No primary header found for stream %08x",
569 gst_ogg_parse_append_header (&array,
570 GST_BUFFER (stream->headers->data));
574 for (l = ogg->oggstreams; l != NULL; l = l->next) {
575 GstOggStream *stream = (GstOggStream *) l->data;
578 /* already appended the first header, now do headers 2-N */
579 for (j = stream->headers->next; j != NULL; j = j->next) {
580 gst_ogg_parse_append_header (&array, GST_BUFFER (j->data));
585 caps = gst_pad_get_caps (ogg->srcpad, NULL);
586 caps = gst_caps_make_writable (caps);
588 structure = gst_caps_get_structure (caps, 0);
589 gst_structure_set_value (structure, "streamheader", &array);
591 gst_pad_set_caps (ogg->srcpad, caps);
593 g_value_unset (&array);
596 gst_caps_unref (ogg->caps);
599 GST_LOG_OBJECT (ogg, "Set \"streamheader\" caps with %d buffers "
600 "(one per page)", count);
602 /* Now, we do the same thing, but push buffers... */
603 for (l = ogg->oggstreams; l != NULL; l = l->next) {
604 GstOggStream *stream = (GstOggStream *) l->data;
605 GstBuffer *buf = GST_BUFFER (stream->headers->data);
607 result = gst_pad_push (ogg->srcpad, buf);
608 if (result != GST_FLOW_OK)
611 for (l = ogg->oggstreams; l != NULL; l = l->next) {
612 GstOggStream *stream = (GstOggStream *) l->data;
615 /* pushed the first one for each stream already, now do 2-N */
616 for (j = stream->headers->next; j != NULL; j = j->next) {
617 GstBuffer *buf = GST_BUFFER (j->data);
619 result = gst_pad_push (ogg->srcpad, buf);
620 if (result != GST_FLOW_OK)
627 /* And finally the pending data pages */
628 for (l = ogg->oggstreams; l != NULL; l = l->next) {
629 GstOggStream *stream = (GstOggStream *) l->data;
632 if (stream->unknown_pages == NULL)
635 if (found_pending_headers) {
636 GST_WARNING_OBJECT (ogg, "Incorrectly muxed headers found at "
637 "approximate offset %" G_GINT64_FORMAT, ogg->offset);
639 found_pending_headers = TRUE;
641 GST_LOG_OBJECT (ogg, "Pushing %d pending pages after headers",
642 g_list_length (stream->unknown_pages) + 1);
644 for (k = stream->unknown_pages; k != NULL; k = k->next) {
645 GstBuffer *buf = GST_BUFFER (k->data);
647 result = gst_pad_push (ogg->srcpad, buf);
648 if (result != GST_FLOW_OK)
651 g_list_foreach (stream->unknown_pages,
652 (GFunc) gst_mini_object_unref, NULL);
653 g_list_free (stream->unknown_pages);
654 stream->unknown_pages = NULL;
659 stream->stored_buffers = g_list_append (stream->stored_buffers,
662 while (stream->stored_buffers) {
663 GstBuffer *buf = stream->stored_buffers->data;
665 buf = gst_buffer_make_writable (buf);
667 GST_BUFFER_TIMESTAMP (buf) = buffertimestamp;
669 GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
674 result = gst_pad_push (ogg->srcpad, buf);
675 if (result != GST_FLOW_OK)
678 stream->stored_buffers =
679 g_list_delete_link (stream->stored_buffers,
680 stream->stored_buffers);
683 pagebuffer = gst_buffer_make_writable (pagebuffer);
685 GST_BUFFER_FLAG_SET (pagebuffer, GST_BUFFER_FLAG_DELTA_UNIT);
690 result = gst_pad_push (ogg->srcpad, pagebuffer);
691 if (result != GST_FLOW_OK)
702 gst_pad_push_event (GST_PAD (ogg->srcpad), gst_event_new_eos ());
703 return GST_FLOW_ERROR;
706 static GstStateChangeReturn
707 gst_ogg_parse_change_state (GstElement * element, GstStateChange transition)
710 GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE;
712 ogg = GST_OGG_PARSE (element);
714 switch (transition) {
715 case GST_STATE_CHANGE_NULL_TO_READY:
716 ogg_sync_init (&ogg->sync);
718 case GST_STATE_CHANGE_READY_TO_PAUSED:
719 ogg_sync_reset (&ogg->sync);
721 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
727 result = parent_class->change_state (element, transition);
729 switch (transition) {
730 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
732 case GST_STATE_CHANGE_PAUSED_TO_READY:
734 case GST_STATE_CHANGE_READY_TO_NULL:
735 ogg_sync_clear (&ogg->sync);
744 gst_ogg_parse_plugin_init (GstPlugin * plugin)
746 GST_DEBUG_CATEGORY_INIT (gst_ogg_parse_debug, "oggparse", 0, "ogg parser");
748 return gst_element_register (plugin, "oggparse", GST_RANK_NONE,