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