| /* GStreamer |
| * Copyright (C) 2005 Michael Smith <msmith@fluendo.com> |
| * |
| * gstoggparse.c: ogg stream parser |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| /* This ogg parser is essentially a subset of the ogg demuxer - rather than |
| * fully demuxing into packets, we only parse out the pages, create one |
| * GstBuffer per page, set all the appropriate flags on those pages, set caps |
| * appropriately (particularly the 'streamheader' which gives all the header |
| * pages required for initialing decode). |
| * |
| * It's dramatically simpler than the full demuxer as it does not support |
| * seeking. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| #include <gst/gst.h> |
| #include <ogg/ogg.h> |
| #include <string.h> |
| |
| #include "gstogg.h" |
| #include "gstoggstream.h" |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_ogg_parse_debug); |
| #define GST_CAT_DEFAULT gst_ogg_parse_debug |
| |
| #define GST_TYPE_OGG_PARSE (gst_ogg_parse_get_type()) |
| #define GST_OGG_PARSE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OGG_PARSE, GstOggParse)) |
| #define GST_OGG_PARSE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OGG_PARSE, GstOggParse)) |
| #define GST_IS_OGG_PARSE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OGG_PARSE)) |
| #define GST_IS_OGG_PARSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OGG_PARSE)) |
| |
| static GType gst_ogg_parse_get_type (void); |
| |
| typedef struct _GstOggParse GstOggParse; |
| typedef struct _GstOggParseClass GstOggParseClass; |
| |
| struct _GstOggParse |
| { |
| GstElement element; |
| |
| GstPad *sinkpad; /* Sink pad we're reading data from */ |
| |
| GstPad *srcpad; /* Source pad we're writing to */ |
| |
| GSList *oggstreams; /* list of GstOggStreams for known streams */ |
| |
| gint64 offset; /* Current stream offset */ |
| |
| gboolean in_headers; /* Set if we're reading headers for streams */ |
| |
| gboolean last_page_not_bos; /* Set if we've seen a non-BOS page */ |
| |
| ogg_sync_state sync; /* Ogg page synchronisation */ |
| |
| GstCaps *caps; /* Our src caps */ |
| |
| GstOggStream *video_stream; /* Stream used to construct delta_unit flags */ |
| }; |
| |
| struct _GstOggParseClass |
| { |
| GstElementClass parent_class; |
| }; |
| |
| static void gst_ogg_parse_base_init (gpointer g_class); |
| static void gst_ogg_parse_class_init (GstOggParseClass * klass); |
| static void gst_ogg_parse_init (GstOggParse * ogg); |
| static GstElementClass *parent_class = NULL; |
| |
| static GType |
| gst_ogg_parse_get_type (void) |
| { |
| static GType ogg_parse_type = 0; |
| |
| if (!ogg_parse_type) { |
| static const GTypeInfo ogg_parse_info = { |
| sizeof (GstOggParseClass), |
| gst_ogg_parse_base_init, |
| NULL, |
| (GClassInitFunc) gst_ogg_parse_class_init, |
| NULL, |
| NULL, |
| sizeof (GstOggParse), |
| 0, |
| (GInstanceInitFunc) gst_ogg_parse_init, |
| }; |
| |
| ogg_parse_type = g_type_register_static (GST_TYPE_ELEMENT, "GstOggParse", |
| &ogg_parse_info, 0); |
| } |
| return ogg_parse_type; |
| } |
| |
| static void |
| free_stream (GstOggStream * stream) |
| { |
| g_list_foreach (stream->headers, (GFunc) gst_mini_object_unref, NULL); |
| g_list_foreach (stream->unknown_pages, (GFunc) gst_mini_object_unref, NULL); |
| g_list_foreach (stream->stored_buffers, (GFunc) gst_mini_object_unref, NULL); |
| |
| g_slice_free (GstOggStream, stream); |
| } |
| |
| static void |
| gst_ogg_parse_delete_all_streams (GstOggParse * ogg) |
| { |
| g_slist_foreach (ogg->oggstreams, (GFunc) free_stream, NULL); |
| g_slist_free (ogg->oggstreams); |
| ogg->oggstreams = NULL; |
| } |
| |
| static GstOggStream * |
| gst_ogg_parse_new_stream (GstOggParse * parser, ogg_page * page) |
| { |
| GstOggStream *stream; |
| ogg_packet packet; |
| int ret; |
| guint32 serialno; |
| |
| serialno = ogg_page_serialno (page); |
| |
| GST_DEBUG_OBJECT (parser, "creating new stream %08x", serialno); |
| |
| stream = g_slice_new0 (GstOggStream); |
| |
| stream->serialno = serialno; |
| stream->in_headers = 1; |
| |
| if (ogg_stream_init (&stream->stream, serialno) != 0) { |
| GST_ERROR ("Could not initialize ogg_stream struct for serial %08x.", |
| serialno); |
| goto failure; |
| } |
| |
| if (ogg_stream_pagein (&stream->stream, page) != 0) |
| goto failure; |
| |
| ret = ogg_stream_packetout (&stream->stream, &packet); |
| if (ret == 1) { |
| if (!gst_ogg_stream_setup_map (stream, &packet)) { |
| GST_ERROR ("Could not setup map for ogg packet."); |
| goto failure; |
| } |
| |
| if (stream->is_video) { |
| parser->video_stream = stream; |
| } |
| } |
| |
| parser->oggstreams = g_slist_append (parser->oggstreams, stream); |
| |
| return stream; |
| |
| failure: |
| free_stream (stream); |
| return NULL; |
| } |
| |
| static GstOggStream * |
| gst_ogg_parse_find_stream (GstOggParse * parser, guint32 serialno) |
| { |
| GSList *l; |
| |
| for (l = parser->oggstreams; l != NULL; l = l->next) { |
| GstOggStream *stream = (GstOggStream *) l->data; |
| |
| if (stream->serialno == serialno) |
| return stream; |
| } |
| return NULL; |
| } |
| |
| /* signals and args */ |
| enum |
| { |
| /* FILL ME */ |
| LAST_SIGNAL |
| }; |
| |
| enum |
| { |
| ARG_0 |
| /* FILL ME */ |
| }; |
| |
| static GstStaticPadTemplate ogg_parse_src_template_factory = |
| GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("application/ogg") |
| ); |
| |
| static GstStaticPadTemplate ogg_parse_sink_template_factory = |
| GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("application/ogg") |
| ); |
| |
| static void gst_ogg_parse_dispose (GObject * object); |
| static GstStateChangeReturn gst_ogg_parse_change_state (GstElement * element, |
| GstStateChange transition); |
| static GstFlowReturn gst_ogg_parse_chain (GstPad * pad, GstObject * parent, |
| GstBuffer * buffer); |
| |
| static void |
| gst_ogg_parse_base_init (gpointer g_class) |
| { |
| GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); |
| |
| gst_element_class_set_static_metadata (element_class, |
| "Ogg parser", "Codec/Parser", |
| "parse ogg streams into pages (info about ogg: http://xiph.org)", |
| "Michael Smith <msmith@fluendo.com>"); |
| |
| gst_element_class_add_static_pad_template (element_class, |
| &ogg_parse_sink_template_factory); |
| gst_element_class_add_static_pad_template (element_class, |
| &ogg_parse_src_template_factory); |
| } |
| |
| static void |
| gst_ogg_parse_class_init (GstOggParseClass * klass) |
| { |
| GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| |
| parent_class = g_type_class_peek_parent (klass); |
| |
| gstelement_class->change_state = gst_ogg_parse_change_state; |
| |
| gobject_class->dispose = gst_ogg_parse_dispose; |
| } |
| |
| static void |
| gst_ogg_parse_init (GstOggParse * ogg) |
| { |
| /* create the sink and source pads */ |
| ogg->sinkpad = |
| gst_pad_new_from_static_template (&ogg_parse_sink_template_factory, |
| "sink"); |
| ogg->srcpad = |
| gst_pad_new_from_static_template (&ogg_parse_src_template_factory, "src"); |
| |
| /* TODO: Are there any events we must handle? */ |
| /* gst_pad_set_event_function (ogg->sinkpad, gst_ogg_parse_handle_event); */ |
| gst_pad_set_chain_function (ogg->sinkpad, gst_ogg_parse_chain); |
| |
| gst_element_add_pad (GST_ELEMENT (ogg), ogg->sinkpad); |
| gst_element_add_pad (GST_ELEMENT (ogg), ogg->srcpad); |
| |
| ogg->oggstreams = NULL; |
| } |
| |
| static void |
| gst_ogg_parse_dispose (GObject * object) |
| { |
| GstOggParse *ogg = GST_OGG_PARSE (object); |
| |
| GST_LOG_OBJECT (ogg, "Disposing of object %p", ogg); |
| |
| ogg_sync_clear (&ogg->sync); |
| gst_ogg_parse_delete_all_streams (ogg); |
| |
| if (ogg->caps) { |
| gst_caps_unref (ogg->caps); |
| ogg->caps = NULL; |
| } |
| |
| if (G_OBJECT_CLASS (parent_class)->dispose) |
| G_OBJECT_CLASS (parent_class)->dispose (object); |
| } |
| |
| /* submit the given buffer to the ogg sync */ |
| static GstFlowReturn |
| gst_ogg_parse_submit_buffer (GstOggParse * ogg, GstBuffer * buffer) |
| { |
| gsize size; |
| gchar *oggbuffer; |
| GstFlowReturn ret = GST_FLOW_OK; |
| |
| size = gst_buffer_get_size (buffer); |
| |
| GST_DEBUG_OBJECT (ogg, "submitting %" G_GSIZE_FORMAT " bytes", size); |
| if (G_UNLIKELY (size == 0)) |
| goto done; |
| |
| oggbuffer = ogg_sync_buffer (&ogg->sync, size); |
| if (G_UNLIKELY (oggbuffer == NULL)) { |
| GST_ELEMENT_ERROR (ogg, STREAM, DECODE, |
| (NULL), ("failed to get ogg sync buffer")); |
| ret = GST_FLOW_ERROR; |
| goto done; |
| } |
| |
| size = gst_buffer_extract (buffer, 0, oggbuffer, size); |
| if (G_UNLIKELY (ogg_sync_wrote (&ogg->sync, size) < 0)) { |
| GST_ELEMENT_ERROR (ogg, STREAM, DECODE, (NULL), |
| ("failed to write %" G_GSIZE_FORMAT " bytes to the sync buffer", size)); |
| ret = GST_FLOW_ERROR; |
| } |
| |
| done: |
| gst_buffer_unref (buffer); |
| |
| return ret; |
| } |
| |
| static void |
| gst_ogg_parse_append_header (GValue * array, GstBuffer * buf) |
| { |
| GValue value = { 0 }; |
| /* We require a copy to avoid circular refcounts */ |
| GstBuffer *buffer = gst_buffer_copy (buf); |
| |
| GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER); |
| |
| g_value_init (&value, GST_TYPE_BUFFER); |
| gst_value_set_buffer (&value, buffer); |
| gst_value_array_append_value (array, &value); |
| g_value_unset (&value); |
| |
| } |
| |
| typedef enum |
| { |
| PAGE_HEADER, /* Header page */ |
| PAGE_DATA, /* Data page */ |
| PAGE_PENDING, /* We don't know yet, we'll have to see some future pages */ |
| } page_type; |
| |
| static page_type |
| gst_ogg_parse_is_header (GstOggParse * ogg, GstOggStream * stream, |
| ogg_page * page) |
| { |
| ogg_int64_t gpos = ogg_page_granulepos (page); |
| |
| if (gpos < 0) |
| return PAGE_PENDING; |
| |
| /* This is good enough for now, but technically requires codec-specific |
| * behaviour to be perfect. This is where we need the mooted library for |
| * this stuff, which nobody has written. |
| */ |
| if (gpos > 0) |
| return PAGE_DATA; |
| else |
| return PAGE_HEADER; |
| } |
| |
| static GstBuffer * |
| gst_ogg_parse_buffer_from_page (ogg_page * page, |
| guint64 offset, GstClockTime timestamp) |
| { |
| int size = page->header_len + page->body_len; |
| GstBuffer *buf = gst_buffer_new_and_alloc (size); |
| |
| gst_buffer_fill (buf, 0, page->header, page->header_len); |
| gst_buffer_fill (buf, page->header_len, page->body, page->body_len); |
| |
| GST_BUFFER_TIMESTAMP (buf) = timestamp; |
| GST_BUFFER_OFFSET (buf) = offset; |
| GST_BUFFER_OFFSET_END (buf) = offset + size; |
| |
| return buf; |
| } |
| |
| |
| /* Reads in buffers, parses them, reframes into one-buffer-per-ogg-page, submits |
| * pages to output pad. |
| */ |
| static GstFlowReturn |
| gst_ogg_parse_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) |
| { |
| GstOggParse *ogg; |
| GstFlowReturn result = GST_FLOW_OK; |
| gint ret = -1; |
| guint32 serialno; |
| GstBuffer *pagebuffer; |
| GstClockTime buffertimestamp = GST_BUFFER_TIMESTAMP (buffer); |
| |
| ogg = GST_OGG_PARSE (parent); |
| |
| GST_LOG_OBJECT (ogg, |
| "Chain function received buffer of size %" G_GSIZE_FORMAT, |
| gst_buffer_get_size (buffer)); |
| |
| gst_ogg_parse_submit_buffer (ogg, buffer); |
| |
| while (ret != 0 && result == GST_FLOW_OK) { |
| ogg_page page; |
| |
| /* We use ogg_sync_pageseek() rather than ogg_sync_pageout() so that we can |
| * track how many bytes the ogg layer discarded (in the case of sync errors, |
| * etc.); this allows us to accurately track the current stream offset |
| */ |
| ret = ogg_sync_pageseek (&ogg->sync, &page); |
| if (ret == 0) { |
| /* need more data, that's fine... */ |
| break; |
| } else if (ret < 0) { |
| /* discontinuity; track how many bytes we skipped (-ret) */ |
| ogg->offset -= ret; |
| } else { |
| gint64 granule = ogg_page_granulepos (&page); |
| #ifndef GST_DISABLE_GST_DEBUG |
| int bos = ogg_page_bos (&page); |
| #endif |
| guint64 startoffset = ogg->offset; |
| GstOggStream *stream; |
| gboolean keyframe; |
| |
| serialno = ogg_page_serialno (&page); |
| stream = gst_ogg_parse_find_stream (ogg, serialno); |
| |
| GST_LOG_OBJECT (ogg, "Timestamping outgoing buffer as %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (buffertimestamp)); |
| |
| if (stream) { |
| buffertimestamp = gst_ogg_stream_get_end_time_for_granulepos (stream, |
| granule); |
| if (ogg->video_stream) { |
| if (stream == ogg->video_stream) { |
| keyframe = gst_ogg_stream_granulepos_is_key_frame (stream, granule); |
| } else { |
| keyframe = FALSE; |
| } |
| } else { |
| keyframe = TRUE; |
| } |
| } else { |
| buffertimestamp = GST_CLOCK_TIME_NONE; |
| keyframe = TRUE; |
| } |
| pagebuffer = gst_ogg_parse_buffer_from_page (&page, startoffset, |
| buffertimestamp); |
| |
| /* We read out 'ret' bytes, so we set the next offset appropriately */ |
| ogg->offset += ret; |
| |
| GST_LOG_OBJECT (ogg, |
| "processing ogg page (serial %08x, pageno %ld, " |
| "granule pos %" G_GUINT64_FORMAT ", bos %d, offset %" |
| G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT ") keyframe=%d", |
| serialno, ogg_page_pageno (&page), |
| granule, bos, startoffset, ogg->offset, keyframe); |
| |
| if (ogg_page_bos (&page)) { |
| /* If we've seen this serialno before, this is technically an error, |
| * we log this case but accept it - this one replaces the previous |
| * stream with this serialno. We can do this since we're streaming, and |
| * not supporting seeking... |
| */ |
| GstOggStream *stream = gst_ogg_parse_find_stream (ogg, serialno); |
| |
| if (stream != NULL) { |
| GST_LOG_OBJECT (ogg, "Incorrect stream; repeats serial number %08x " |
| "at offset %" G_GINT64_FORMAT, serialno, ogg->offset); |
| } |
| |
| if (ogg->last_page_not_bos) { |
| GST_LOG_OBJECT (ogg, "Deleting all referenced streams, found a new " |
| "chain starting with serial %u", serialno); |
| gst_ogg_parse_delete_all_streams (ogg); |
| } |
| |
| stream = gst_ogg_parse_new_stream (ogg, &page); |
| if (!stream) { |
| GST_LOG_OBJECT (ogg, "Incorrect page"); |
| goto failure; |
| } |
| |
| ogg->last_page_not_bos = FALSE; |
| |
| gst_buffer_ref (pagebuffer); |
| stream->headers = g_list_append (stream->headers, pagebuffer); |
| |
| if (!ogg->in_headers) { |
| GST_LOG_OBJECT (ogg, |
| "Found start of new chain at offset %" G_GUINT64_FORMAT, |
| startoffset); |
| ogg->in_headers = 1; |
| } |
| |
| /* For now, we just keep the header buffer in the stream->headers list; |
| * it actually gets output once we've collected the entire set |
| */ |
| } else { |
| /* Non-BOS page. Either: we're outside headers, and this isn't a |
| * header (normal data), outside headers and this is (error!), inside |
| * headers, this is (append header), or inside headers and this isn't |
| * (we've found the end of headers; flush the lot!) |
| * |
| * Before that, we flag that the last page seen (this one) was not a |
| * BOS page; that way we know that when we next see a BOS page it's a |
| * new chain, and we can flush all existing streams. |
| */ |
| page_type type; |
| GstOggStream *stream = gst_ogg_parse_find_stream (ogg, serialno); |
| |
| if (!stream) { |
| GST_LOG_OBJECT (ogg, |
| "Non-BOS page unexpectedly found at %" G_GINT64_FORMAT, |
| ogg->offset); |
| goto failure; |
| } |
| |
| ogg->last_page_not_bos = TRUE; |
| |
| type = gst_ogg_parse_is_header (ogg, stream, &page); |
| |
| if (type == PAGE_PENDING && ogg->in_headers) { |
| gst_buffer_ref (pagebuffer); |
| |
| stream->unknown_pages = g_list_append (stream->unknown_pages, |
| pagebuffer); |
| } else if (type == PAGE_HEADER) { |
| if (!ogg->in_headers) { |
| GST_LOG_OBJECT (ogg, "Header page unexpectedly found outside " |
| "headers at offset %" G_GINT64_FORMAT, ogg->offset); |
| goto failure; |
| } else { |
| /* Append the header to the buffer list, after any unknown previous |
| * pages |
| */ |
| stream->headers = g_list_concat (stream->headers, |
| stream->unknown_pages); |
| g_list_free (stream->unknown_pages); |
| gst_buffer_ref (pagebuffer); |
| stream->headers = g_list_append (stream->headers, pagebuffer); |
| } |
| } else { /* PAGE_DATA, or PAGE_PENDING but outside headers */ |
| if (ogg->in_headers) { |
| /* First non-header page... set caps, flush headers. |
| * |
| * First up, we build a single GValue list of all the pagebuffers |
| * we're using for the headers, in order. |
| * Then we set this on the caps structure. Then we can start pushing |
| * buffers for the headers, and finally we send this non-header |
| * page. |
| */ |
| GstCaps *caps; |
| GstStructure *structure; |
| GValue array = { 0 }; |
| gint count = 0; |
| gboolean found_pending_headers = FALSE; |
| GSList *l; |
| |
| g_value_init (&array, GST_TYPE_ARRAY); |
| |
| for (l = ogg->oggstreams; l != NULL; l = l->next) { |
| GstOggStream *stream = (GstOggStream *) l->data; |
| |
| if (g_list_length (stream->headers) == 0) { |
| GST_LOG_OBJECT (ogg, "No primary header found for stream %08x", |
| stream->serialno); |
| goto failure; |
| } |
| |
| gst_ogg_parse_append_header (&array, |
| GST_BUFFER (stream->headers->data)); |
| count++; |
| } |
| |
| for (l = ogg->oggstreams; l != NULL; l = l->next) { |
| GstOggStream *stream = (GstOggStream *) l->data; |
| GList *j; |
| |
| /* already appended the first header, now do headers 2-N */ |
| for (j = stream->headers->next; j != NULL; j = j->next) { |
| gst_ogg_parse_append_header (&array, GST_BUFFER (j->data)); |
| count++; |
| } |
| } |
| |
| caps = gst_pad_query_caps (ogg->srcpad, NULL); |
| caps = gst_caps_make_writable (caps); |
| |
| structure = gst_caps_get_structure (caps, 0); |
| gst_structure_take_value (structure, "streamheader", &array); |
| |
| gst_pad_set_caps (ogg->srcpad, caps); |
| |
| if (ogg->caps) |
| gst_caps_unref (ogg->caps); |
| ogg->caps = caps; |
| |
| GST_LOG_OBJECT (ogg, "Set \"streamheader\" caps with %d buffers " |
| "(one per page)", count); |
| |
| /* Now, we do the same thing, but push buffers... */ |
| for (l = ogg->oggstreams; l != NULL; l = l->next) { |
| GstOggStream *stream = (GstOggStream *) l->data; |
| GstBuffer *buf = GST_BUFFER (stream->headers->data); |
| |
| result = gst_pad_push (ogg->srcpad, buf); |
| if (result != GST_FLOW_OK) |
| return result; |
| } |
| for (l = ogg->oggstreams; l != NULL; l = l->next) { |
| GstOggStream *stream = (GstOggStream *) l->data; |
| GList *j; |
| |
| /* pushed the first one for each stream already, now do 2-N */ |
| for (j = stream->headers->next; j != NULL; j = j->next) { |
| GstBuffer *buf = GST_BUFFER (j->data); |
| |
| result = gst_pad_push (ogg->srcpad, buf); |
| if (result != GST_FLOW_OK) |
| return result; |
| } |
| } |
| |
| ogg->in_headers = 0; |
| |
| /* And finally the pending data pages */ |
| for (l = ogg->oggstreams; l != NULL; l = l->next) { |
| GstOggStream *stream = (GstOggStream *) l->data; |
| GList *k; |
| |
| if (stream->unknown_pages == NULL) |
| continue; |
| |
| if (found_pending_headers) { |
| GST_WARNING_OBJECT (ogg, "Incorrectly muxed headers found at " |
| "approximate offset %" G_GINT64_FORMAT, ogg->offset); |
| } |
| found_pending_headers = TRUE; |
| |
| GST_LOG_OBJECT (ogg, "Pushing %d pending pages after headers", |
| g_list_length (stream->unknown_pages) + 1); |
| |
| for (k = stream->unknown_pages; k != NULL; k = k->next) { |
| GstBuffer *buf = GST_BUFFER (k->data); |
| |
| result = gst_pad_push (ogg->srcpad, buf); |
| if (result != GST_FLOW_OK) |
| return result; |
| } |
| g_list_foreach (stream->unknown_pages, |
| (GFunc) gst_mini_object_unref, NULL); |
| g_list_free (stream->unknown_pages); |
| stream->unknown_pages = NULL; |
| } |
| } |
| |
| if (granule == -1) { |
| stream->stored_buffers = g_list_append (stream->stored_buffers, |
| pagebuffer); |
| } else { |
| while (stream->stored_buffers) { |
| GstBuffer *buf = stream->stored_buffers->data; |
| |
| buf = gst_buffer_make_writable (buf); |
| |
| GST_BUFFER_TIMESTAMP (buf) = buffertimestamp; |
| if (!keyframe) { |
| GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); |
| } else { |
| keyframe = FALSE; |
| } |
| |
| result = gst_pad_push (ogg->srcpad, buf); |
| if (result != GST_FLOW_OK) |
| return result; |
| |
| stream->stored_buffers = |
| g_list_delete_link (stream->stored_buffers, |
| stream->stored_buffers); |
| } |
| |
| pagebuffer = gst_buffer_make_writable (pagebuffer); |
| if (!keyframe) { |
| GST_BUFFER_FLAG_SET (pagebuffer, GST_BUFFER_FLAG_DELTA_UNIT); |
| } else { |
| keyframe = FALSE; |
| } |
| |
| result = gst_pad_push (ogg->srcpad, pagebuffer); |
| if (result != GST_FLOW_OK) |
| return result; |
| } |
| } |
| } |
| } |
| } |
| |
| return result; |
| |
| failure: |
| gst_pad_push_event (GST_PAD (ogg->srcpad), gst_event_new_eos ()); |
| return GST_FLOW_ERROR; |
| } |
| |
| static GstStateChangeReturn |
| gst_ogg_parse_change_state (GstElement * element, GstStateChange transition) |
| { |
| GstOggParse *ogg; |
| GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE; |
| |
| ogg = GST_OGG_PARSE (element); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_NULL_TO_READY: |
| ogg_sync_init (&ogg->sync); |
| break; |
| case GST_STATE_CHANGE_READY_TO_PAUSED: |
| ogg_sync_reset (&ogg->sync); |
| break; |
| case GST_STATE_CHANGE_PAUSED_TO_PLAYING: |
| break; |
| default: |
| break; |
| } |
| |
| result = parent_class->change_state (element, transition); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_PLAYING_TO_PAUSED: |
| break; |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| break; |
| case GST_STATE_CHANGE_READY_TO_NULL: |
| ogg_sync_clear (&ogg->sync); |
| break; |
| default: |
| break; |
| } |
| return result; |
| } |
| |
| gboolean |
| gst_ogg_parse_plugin_init (GstPlugin * plugin) |
| { |
| GST_DEBUG_CATEGORY_INIT (gst_ogg_parse_debug, "oggparse", 0, "ogg parser"); |
| |
| return gst_element_register (plugin, "oggparse", GST_RANK_NONE, |
| GST_TYPE_OGG_PARSE); |
| } |