| /* GStreamer |
| * Copyright (C) 2004 Wim Taymans <wim@fluendo.com> |
| * |
| * gstoggdemux.c: ogg stream demuxer |
| * |
| * 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. |
| */ |
| |
| /** |
| * SECTION:element-oggdemux |
| * @title: oggdemux |
| * @see_also: <link linkend="gst-plugins-base-plugins-oggmux">oggmux</link> |
| * |
| * This element demuxes ogg files into their encoded audio and video components. |
| * |
| * ## Example pipelines |
| * |[ |
| * gst-launch-1.0 -v filesrc location=test.ogg ! oggdemux ! vorbisdec ! audioconvert ! audioresample ! autoaudiosink |
| * ]| |
| * Decodes a vorbis audio stream stored inside an ogg container and plays it. |
| * |
| */ |
| |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <string.h> |
| #include <gst/gst-i18n-plugin.h> |
| #include <gst/tag/tag.h> |
| #include <gst/audio/audio.h> |
| |
| #include "gstoggdemux.h" |
| |
| #define CHUNKSIZE (8500) /* this is out of vorbisfile */ |
| |
| /* we hope we get a granpos within this many bytes off the end */ |
| #define DURATION_CHUNK_OFFSET (128*1024) |
| |
| /* An Ogg page can not be larger than 255 segments of 255 bytes, plus |
| 26 bytes of header */ |
| #define MAX_OGG_PAGE_SIZE (255 * 255 + 26) |
| |
| #define GST_FLOW_LIMIT GST_FLOW_CUSTOM_ERROR |
| #define GST_FLOW_SKIP_PUSH GST_FLOW_CUSTOM_SUCCESS_1 |
| |
| #define SEEK_GIVE_UP_THRESHOLD (3*GST_SECOND) |
| |
| #define GST_CHAIN_LOCK(ogg) g_mutex_lock(&(ogg)->chain_lock) |
| #define GST_CHAIN_UNLOCK(ogg) g_mutex_unlock(&(ogg)->chain_lock) |
| |
| #define GST_PUSH_LOCK(ogg) \ |
| do { \ |
| GST_TRACE_OBJECT(ogg, "Push lock"); \ |
| g_mutex_lock(&(ogg)->push_lock); \ |
| } while(0) |
| |
| #define GST_PUSH_UNLOCK(ogg) \ |
| do { \ |
| GST_TRACE_OBJECT(ogg, "Push unlock"); \ |
| g_mutex_unlock(&(ogg)->push_lock); \ |
| } while(0) |
| |
| GST_DEBUG_CATEGORY (gst_ogg_demux_debug); |
| GST_DEBUG_CATEGORY (gst_ogg_demux_setup_debug); |
| #define GST_CAT_DEFAULT gst_ogg_demux_debug |
| |
| |
| static ogg_packet * |
| _ogg_packet_copy (const ogg_packet * packet) |
| { |
| ogg_packet *ret = g_slice_new (ogg_packet); |
| |
| *ret = *packet; |
| ret->packet = g_memdup (packet->packet, packet->bytes); |
| |
| return ret; |
| } |
| |
| static void |
| _ogg_packet_free (ogg_packet * packet) |
| { |
| g_free (packet->packet); |
| g_slice_free (ogg_packet, packet); |
| } |
| |
| static ogg_page * |
| gst_ogg_page_copy (ogg_page * page) |
| { |
| ogg_page *p = g_slice_new (ogg_page); |
| |
| /* make a copy of the page */ |
| p->header = g_memdup (page->header, page->header_len); |
| p->header_len = page->header_len; |
| p->body = g_memdup (page->body, page->body_len); |
| p->body_len = page->body_len; |
| |
| return p; |
| } |
| |
| static void |
| gst_ogg_page_free (ogg_page * page) |
| { |
| g_free (page->header); |
| g_free (page->body); |
| g_slice_free (ogg_page, page); |
| } |
| |
| static gboolean gst_ogg_demux_collect_chain_info (GstOggDemux * ogg, |
| GstOggChain * chain); |
| static gboolean gst_ogg_demux_activate_chain (GstOggDemux * ogg, |
| GstOggChain * chain, GstEvent * event); |
| static void gst_ogg_pad_mark_discont (GstOggPad * pad); |
| static void gst_ogg_chain_mark_discont (GstOggChain * chain); |
| |
| static gboolean gst_ogg_demux_perform_seek (GstOggDemux * ogg, |
| GstEvent * event); |
| static gboolean gst_ogg_demux_receive_event (GstElement * element, |
| GstEvent * event); |
| |
| static void gst_ogg_pad_dispose (GObject * object); |
| static void gst_ogg_pad_finalize (GObject * object); |
| |
| static gboolean gst_ogg_pad_src_query (GstPad * pad, GstObject * parent, |
| GstQuery * query); |
| static gboolean gst_ogg_pad_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| static GstOggPad *gst_ogg_chain_get_stream (GstOggChain * chain, |
| guint32 serialno); |
| |
| static GstFlowReturn gst_ogg_demux_combine_flows (GstOggDemux * ogg, |
| GstOggPad * pad, GstFlowReturn ret); |
| static void gst_ogg_demux_sync_streams (GstOggDemux * ogg); |
| |
| static GstCaps *gst_ogg_demux_set_header_on_caps (GstOggDemux * ogg, |
| GstCaps * caps, GList * headers); |
| static gboolean gst_ogg_demux_send_event (GstOggDemux * ogg, GstEvent * event); |
| static gboolean gst_ogg_demux_perform_seek_push (GstOggDemux * ogg, |
| GstEvent * event); |
| static gboolean gst_ogg_demux_check_duration_push (GstOggDemux * ogg, |
| GstSeekFlags flags, GstEvent * event); |
| |
| GType gst_ogg_pad_get_type (void); |
| G_DEFINE_TYPE (GstOggPad, gst_ogg_pad, GST_TYPE_PAD); |
| |
| static void |
| gst_ogg_pad_class_init (GstOggPadClass * klass) |
| { |
| GObjectClass *gobject_class; |
| |
| gobject_class = (GObjectClass *) klass; |
| |
| gobject_class->dispose = gst_ogg_pad_dispose; |
| gobject_class->finalize = gst_ogg_pad_finalize; |
| } |
| |
| static void |
| gst_ogg_pad_init (GstOggPad * pad) |
| { |
| gst_pad_set_event_function (GST_PAD (pad), |
| GST_DEBUG_FUNCPTR (gst_ogg_pad_event)); |
| gst_pad_set_query_function (GST_PAD (pad), |
| GST_DEBUG_FUNCPTR (gst_ogg_pad_src_query)); |
| gst_pad_use_fixed_caps (GST_PAD (pad)); |
| |
| pad->current_granule = -1; |
| pad->prev_granule = -1; |
| pad->keyframe_granule = -1; |
| |
| pad->start_time = GST_CLOCK_TIME_NONE; |
| |
| pad->position = GST_CLOCK_TIME_NONE; |
| |
| pad->have_type = FALSE; |
| pad->continued = NULL; |
| pad->map.headers = NULL; |
| pad->map.queued = NULL; |
| |
| pad->map.granulerate_n = 0; |
| pad->map.granulerate_d = 0; |
| pad->map.granuleshift = -1; |
| } |
| |
| static void |
| gst_ogg_pad_dispose (GObject * object) |
| { |
| GstOggPad *pad = GST_OGG_PAD (object); |
| |
| pad->chain = NULL; |
| pad->ogg = NULL; |
| |
| g_list_foreach (pad->map.headers, (GFunc) _ogg_packet_free, NULL); |
| g_list_free (pad->map.headers); |
| pad->map.headers = NULL; |
| g_list_foreach (pad->map.queued, (GFunc) _ogg_packet_free, NULL); |
| g_list_free (pad->map.queued); |
| pad->map.queued = NULL; |
| |
| g_free (pad->map.index); |
| pad->map.index = NULL; |
| |
| /* clear continued pages */ |
| g_list_foreach (pad->continued, (GFunc) gst_ogg_page_free, NULL); |
| g_list_free (pad->continued); |
| pad->continued = NULL; |
| |
| if (pad->map.caps) { |
| gst_caps_unref (pad->map.caps); |
| pad->map.caps = NULL; |
| } |
| |
| if (pad->map.taglist) { |
| gst_tag_list_unref (pad->map.taglist); |
| pad->map.taglist = NULL; |
| } |
| |
| ogg_stream_reset (&pad->map.stream); |
| |
| G_OBJECT_CLASS (gst_ogg_pad_parent_class)->dispose (object); |
| } |
| |
| static void |
| gst_ogg_pad_finalize (GObject * object) |
| { |
| GstOggPad *pad = GST_OGG_PAD (object); |
| |
| ogg_stream_clear (&pad->map.stream); |
| |
| G_OBJECT_CLASS (gst_ogg_pad_parent_class)->finalize (object); |
| } |
| |
| static gboolean |
| gst_ogg_pad_src_query (GstPad * pad, GstObject * parent, GstQuery * query) |
| { |
| gboolean res = TRUE; |
| GstOggDemux *ogg; |
| |
| ogg = GST_OGG_DEMUX (parent); |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_DURATION: |
| { |
| GstFormat format; |
| gint64 total_time = -1; |
| |
| gst_query_parse_duration (query, &format, NULL); |
| /* can only get duration in time */ |
| if (format != GST_FORMAT_TIME) |
| goto wrong_format; |
| |
| if (ogg->total_time != -1) { |
| /* we can return the total length */ |
| total_time = ogg->total_time; |
| } else { |
| gint bitrate = ogg->bitrate; |
| |
| /* try with length and bitrate */ |
| if (bitrate > 0) { |
| GstQuery *uquery; |
| |
| /* ask upstream for total length in bytes */ |
| uquery = gst_query_new_duration (GST_FORMAT_BYTES); |
| if (gst_pad_peer_query (ogg->sinkpad, uquery)) { |
| gint64 length; |
| |
| gst_query_parse_duration (uquery, NULL, &length); |
| |
| /* estimate using the bitrate */ |
| total_time = |
| gst_util_uint64_scale (length, 8 * GST_SECOND, bitrate); |
| |
| GST_LOG_OBJECT (ogg, |
| "length: %" G_GINT64_FORMAT ", bitrate %d, total_time %" |
| GST_TIME_FORMAT, length, bitrate, GST_TIME_ARGS (total_time)); |
| } |
| gst_query_unref (uquery); |
| } |
| } |
| |
| gst_query_set_duration (query, GST_FORMAT_TIME, total_time); |
| break; |
| } |
| case GST_QUERY_SEEKING: |
| { |
| GstFormat format; |
| |
| gst_query_parse_seeking (query, &format, NULL, NULL, NULL); |
| if (format == GST_FORMAT_TIME) { |
| gboolean seekable = FALSE; |
| gint64 stop = -1; |
| |
| GST_CHAIN_LOCK (ogg); |
| if (ogg->pullmode) { |
| seekable = TRUE; |
| stop = ogg->total_time; |
| } else if (ogg->push_disable_seeking) { |
| seekable = FALSE; |
| } else if (ogg->current_chain == NULL) { |
| GstQuery *squery; |
| |
| /* assume we can seek if upstream is seekable in BYTES format */ |
| GST_LOG_OBJECT (ogg, "no current chain, check upstream seekability"); |
| squery = gst_query_new_seeking (GST_FORMAT_BYTES); |
| if (gst_pad_peer_query (ogg->sinkpad, squery)) |
| gst_query_parse_seeking (squery, NULL, &seekable, NULL, NULL); |
| else |
| seekable = FALSE; |
| gst_query_unref (squery); |
| } else if (ogg->current_chain->streams->len) { |
| gint i; |
| |
| seekable = FALSE; |
| for (i = 0; i < ogg->current_chain->streams->len; i++) { |
| GstOggPad *pad = |
| g_array_index (ogg->current_chain->streams, GstOggPad *, i); |
| |
| seekable = TRUE; |
| if (pad->map.index != NULL && pad->map.n_index != 0) { |
| GstOggIndex *idx; |
| GstClockTime idx_time; |
| |
| idx = &pad->map.index[pad->map.n_index - 1]; |
| idx_time = |
| gst_util_uint64_scale (idx->timestamp, GST_SECOND, |
| pad->map.kp_denom); |
| if (stop == -1) |
| stop = idx_time; |
| else |
| stop = MAX (idx_time, stop); |
| } else { |
| stop = ogg->push_time_length; |
| if (stop == -1) |
| stop = ogg->total_time; |
| } |
| } |
| } |
| |
| gst_query_set_seeking (query, GST_FORMAT_TIME, seekable, 0, stop); |
| GST_CHAIN_UNLOCK (ogg); |
| } else { |
| res = FALSE; |
| } |
| break; |
| } |
| case GST_QUERY_SEGMENT:{ |
| GstFormat format; |
| gint64 start, stop; |
| |
| format = ogg->segment.format; |
| |
| start = |
| gst_segment_to_stream_time (&ogg->segment, format, |
| ogg->segment.start); |
| if ((stop = ogg->segment.stop) == -1) |
| stop = ogg->segment.duration; |
| else |
| stop = gst_segment_to_stream_time (&ogg->segment, format, stop); |
| |
| gst_query_set_segment (query, ogg->segment.rate, format, start, stop); |
| res = TRUE; |
| break; |
| } |
| default: |
| res = gst_pad_query_default (pad, parent, query); |
| break; |
| } |
| done: |
| |
| return res; |
| |
| /* ERRORS */ |
| wrong_format: |
| { |
| GST_DEBUG_OBJECT (ogg, "only query duration on TIME is supported"); |
| res = FALSE; |
| goto done; |
| } |
| } |
| |
| static gboolean |
| gst_ogg_demux_receive_event (GstElement * element, GstEvent * event) |
| { |
| gboolean res; |
| GstOggDemux *ogg; |
| |
| ogg = GST_OGG_DEMUX (element); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_SEEK: |
| /* now do the seek */ |
| res = gst_ogg_demux_perform_seek (ogg, event); |
| gst_event_unref (event); |
| break; |
| default: |
| GST_DEBUG_OBJECT (ogg, "We only handle seek events here"); |
| goto error; |
| } |
| |
| return res; |
| |
| /* ERRORS */ |
| error: |
| { |
| GST_DEBUG_OBJECT (ogg, "error handling event"); |
| gst_event_unref (event); |
| return FALSE; |
| } |
| } |
| |
| static gboolean |
| gst_ogg_pad_event (GstPad * pad, GstObject * parent, GstEvent * event) |
| { |
| gboolean res; |
| GstOggDemux *ogg; |
| |
| ogg = GST_OGG_DEMUX (parent); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_SEEK: |
| /* now do the seek */ |
| res = gst_ogg_demux_perform_seek (ogg, event); |
| gst_event_unref (event); |
| break; |
| case GST_EVENT_RECONFIGURE: |
| GST_OGG_PAD (pad)->last_ret = GST_FLOW_OK; |
| res = gst_pad_event_default (pad, parent, event); |
| break; |
| default: |
| res = gst_pad_event_default (pad, parent, event); |
| break; |
| } |
| |
| return res; |
| } |
| |
| static void |
| gst_ogg_pad_reset (GstOggPad * pad) |
| { |
| ogg_stream_reset (&pad->map.stream); |
| |
| GST_DEBUG_OBJECT (pad, "doing reset"); |
| |
| /* clear continued pages */ |
| g_list_foreach (pad->continued, (GFunc) gst_ogg_page_free, NULL); |
| g_list_free (pad->continued); |
| pad->continued = NULL; |
| |
| pad->last_ret = GST_FLOW_OK; |
| pad->position = GST_CLOCK_TIME_NONE; |
| pad->current_granule = -1; |
| pad->prev_granule = -1; |
| pad->keyframe_granule = -1; |
| pad->is_eos = FALSE; |
| } |
| |
| /* queue data, basically takes the packet, puts it in a buffer and store the |
| * buffer in the queued list. */ |
| static GstFlowReturn |
| gst_ogg_demux_queue_data (GstOggPad * pad, ogg_packet * packet) |
| { |
| #ifndef GST_DISABLE_GST_DEBUG |
| GstOggDemux *ogg = pad->ogg; |
| #endif |
| |
| GST_DEBUG_OBJECT (ogg, "%p queueing data serial %08x", |
| pad, pad->map.serialno); |
| |
| pad->map.queued = g_list_append (pad->map.queued, _ogg_packet_copy (packet)); |
| |
| /* we are ok now */ |
| return GST_FLOW_OK; |
| } |
| |
| static GstFlowReturn |
| gst_ogg_demux_chain_peer (GstOggPad * pad, ogg_packet * packet, |
| gboolean push_headers) |
| { |
| GstBuffer *buf = NULL; |
| GstFlowReturn ret, cret; |
| GstOggDemux *ogg = pad->ogg; |
| gint64 current_time; |
| GstOggChain *chain; |
| gint64 duration; |
| gint offset; |
| gint trim; |
| GstClockTime out_timestamp, out_duration; |
| guint64 out_offset, out_offset_end; |
| gboolean delta_unit = FALSE; |
| gboolean is_header; |
| guint64 clip_start = 0, clip_end = 0; |
| |
| ret = cret = GST_FLOW_OK; |
| GST_DEBUG_OBJECT (pad, "Chaining %d %d %" GST_TIME_FORMAT " %d %p", |
| ogg->pullmode, ogg->push_state, GST_TIME_ARGS (ogg->push_time_length), |
| ogg->push_disable_seeking, ogg->building_chain); |
| |
| if (G_UNLIKELY (pad->is_eos)) { |
| GST_DEBUG_OBJECT (pad, "Skipping packet on pad that is eos"); |
| ret = GST_FLOW_EOS; |
| goto combine; |
| } |
| |
| GST_PUSH_LOCK (ogg); |
| if (!ogg->pullmode && ogg->push_state == PUSH_PLAYING |
| && ogg->push_time_length == GST_CLOCK_TIME_NONE |
| && !ogg->push_disable_seeking) { |
| if (!ogg->building_chain) { |
| /* we got all headers, now try to get duration */ |
| if (!gst_ogg_demux_check_duration_push (ogg, GST_SEEK_FLAG_FLUSH, NULL)) { |
| GST_PUSH_UNLOCK (ogg); |
| return GST_FLOW_OK; |
| } |
| } |
| GST_PUSH_UNLOCK (ogg); |
| return GST_FLOW_OK; |
| } |
| GST_PUSH_UNLOCK (ogg); |
| |
| GST_DEBUG_OBJECT (ogg, |
| "%p streaming to peer serial %08x", pad, pad->map.serialno); |
| |
| gst_ogg_stream_update_stats (&pad->map, packet); |
| |
| if (pad->map.is_ogm) { |
| const guint8 *data; |
| long bytes; |
| |
| data = packet->packet; |
| bytes = packet->bytes; |
| |
| if (bytes < 1) |
| goto empty_packet; |
| |
| if ((data[0] & 1) || (data[0] & 3 && pad->map.is_ogm_text)) { |
| /* We don't push header packets for OGM */ |
| goto done; |
| } |
| |
| offset = 1 + (((data[0] & 0xc0) >> 6) | ((data[0] & 0x02) << 1)); |
| delta_unit = (((data[0] & 0x08) >> 3) == 0); |
| |
| trim = 0; |
| |
| /* Strip trailing \0 for subtitles */ |
| if (pad->map.is_ogm_text) { |
| while (bytes && data[bytes - 1] == 0) { |
| trim++; |
| bytes--; |
| } |
| } |
| } else if (pad->map.is_vp8) { |
| if ((packet->bytes >= 7 && memcmp (packet->packet, "OVP80\2 ", 7) == 0) || |
| packet->b_o_s || |
| (packet->bytes >= 5 && memcmp (packet->packet, "OVP80", 5) == 0)) { |
| /* We don't push header packets for VP8 */ |
| goto done; |
| } |
| offset = 0; |
| trim = 0; |
| delta_unit = !gst_ogg_stream_packet_is_key_frame (&pad->map, packet); |
| } else { |
| offset = 0; |
| trim = 0; |
| delta_unit = !gst_ogg_stream_packet_is_key_frame (&pad->map, packet); |
| } |
| |
| /* get timing info for the packet */ |
| is_header = gst_ogg_stream_packet_is_header (&pad->map, packet); |
| if (is_header) { |
| duration = 0; |
| GST_DEBUG_OBJECT (ogg, "packet is header"); |
| } else { |
| duration = gst_ogg_stream_get_packet_duration (&pad->map, packet); |
| GST_DEBUG_OBJECT (ogg, "packet duration %" G_GUINT64_FORMAT, duration); |
| } |
| |
| |
| /* If we get a hole at start, it might be we're catching a stream |
| * partway through. In that case, if the stream has an index, the |
| * index might be mooted. However, as it's totally valid to index |
| * a stream with a hole at start (ie, capturing a live stream and |
| * then index it), we now check whether the index references some |
| * offset beyond the byte length (if known). If this is the case, |
| * we can be reasonably sure we're getting a stream partway, with |
| * its index being now useless since we don't know how many bytes |
| * were skipped, preventing us from patching the index offsets to |
| * match the hole size. */ |
| if (!is_header && ogg->check_index_overflow) { |
| GstQuery *query; |
| GstFormat format; |
| int i; |
| gint64 length; |
| gboolean beyond; |
| |
| if (ogg->current_chain) { |
| query = gst_query_new_duration (GST_FORMAT_BYTES); |
| if (gst_pad_peer_query (ogg->sinkpad, query)) { |
| gst_query_parse_duration (query, &format, &length); |
| if (format == GST_FORMAT_BYTES && length >= 0) { |
| for (i = 0; i < ogg->current_chain->streams->len; i++) { |
| GstOggPad *ipad = |
| g_array_index (ogg->current_chain->streams, GstOggPad *, i); |
| if (!ipad->map.index) |
| continue; |
| beyond = ipad->map.n_index |
| && ipad->map.index[ipad->map.n_index - 1].offset >= length; |
| if (beyond) { |
| GST_WARNING_OBJECT (pad, "Index offsets beyong byte length"); |
| if (ipad->discont) { |
| /* hole - the index is most likely screwed up */ |
| GST_WARNING_OBJECT (ogg, "Discarding entire index"); |
| g_free (ipad->map.index); |
| ipad->map.index = NULL; |
| ipad->map.n_index = 0; |
| } else { |
| /* no hole - we can just clip the index if needed */ |
| GST_WARNING_OBJECT (ogg, "Clipping index"); |
| while (ipad->map.n_index > 0 |
| && ipad->map.index[ipad->map.n_index - 1].offset >= length) |
| ipad->map.n_index--; |
| if (ipad->map.n_index == 0) { |
| GST_WARNING_OBJECT (ogg, "The entire index was clipped"); |
| g_free (ipad->map.index); |
| ipad->map.index = NULL; |
| } |
| } |
| /* We can't trust the total time if the index goes beyond */ |
| ipad->map.total_time = -1; |
| } else { |
| /* use total time to update the total ogg time */ |
| if (ogg->total_time == -1) { |
| ogg->total_time = ipad->map.total_time; |
| } else if (ipad->map.total_time > 0) { |
| ogg->total_time = MAX (ogg->total_time, ipad->map.total_time); |
| } |
| } |
| } |
| } |
| } |
| gst_query_unref (query); |
| } |
| ogg->check_index_overflow = FALSE; |
| } |
| |
| if (packet->b_o_s) { |
| out_timestamp = GST_CLOCK_TIME_NONE; |
| out_duration = GST_CLOCK_TIME_NONE; |
| out_offset = 0; |
| out_offset_end = -1; |
| } else { |
| if (packet->granulepos > -1) { |
| gint64 granule = gst_ogg_stream_granulepos_to_granule (&pad->map, |
| packet->granulepos); |
| if (granule < 0) { |
| GST_ERROR_OBJECT (ogg, |
| "granulepos %" G_GINT64_FORMAT " yielded granule %" G_GINT64_FORMAT, |
| (gint64) packet->granulepos, (gint64) granule); |
| return GST_FLOW_ERROR; |
| } |
| pad->current_granule = granule; |
| pad->keyframe_granule = |
| gst_ogg_stream_granulepos_to_key_granule (&pad->map, |
| packet->granulepos); |
| GST_DEBUG_OBJECT (ogg, "new granule %" G_GUINT64_FORMAT, |
| pad->current_granule); |
| } else if (pad->current_granule != -1) { |
| pad->current_granule += duration; |
| if (!delta_unit) { |
| pad->keyframe_granule = pad->current_granule; |
| } |
| GST_DEBUG_OBJECT (ogg, "interpolating granule %" G_GUINT64_FORMAT, |
| pad->current_granule); |
| } |
| |
| if (ogg->segment.rate < 0.0 && pad->current_granule == -1) { |
| /* negative rates, allow output of packets with no timestamp, let downstream reconstruct */ |
| out_timestamp = -1; |
| out_duration = -1; |
| out_offset = -1; |
| out_offset_end = -1; |
| pad->prev_granule = -1; |
| } else { |
| /* we only push buffers after we have a valid granule. This is done so that |
| * we nicely skip packets without a timestamp after a seek. This is ok |
| * because we base our seek on the packet after the page with the smaller |
| * timestamp. */ |
| if (pad->current_granule == -1) { |
| pad->prev_granule = -1; |
| goto no_timestamp; |
| } |
| |
| if (pad->map.is_ogm) { |
| out_timestamp = gst_ogg_stream_granule_to_time (&pad->map, |
| pad->current_granule); |
| out_duration = gst_util_uint64_scale (duration, |
| GST_SECOND * pad->map.granulerate_d, pad->map.granulerate_n); |
| } else if (pad->map.is_sparse) { |
| out_timestamp = gst_ogg_stream_granule_to_time (&pad->map, |
| pad->current_granule); |
| if (duration == GST_CLOCK_TIME_NONE) { |
| out_duration = GST_CLOCK_TIME_NONE; |
| } else { |
| out_duration = gst_util_uint64_scale (duration, |
| GST_SECOND * pad->map.granulerate_d, pad->map.granulerate_n); |
| } |
| } else { |
| /* The last packet may be clipped. This will be represented |
| by the last granule being smaller than what it would otherwise |
| have been, had no content been clipped. In that case, we |
| cannot calculate the PTS of the audio from the packet length |
| and granule. */ |
| if (packet->e_o_s) { |
| if (pad->prev_granule >= 0) |
| out_timestamp = gst_ogg_stream_granule_to_time (&pad->map, |
| pad->prev_granule); |
| else |
| out_timestamp = GST_CLOCK_TIME_NONE; |
| |
| if (pad->map.audio_clipping |
| && pad->current_granule < pad->prev_granule + duration) { |
| clip_end = pad->prev_granule + duration - pad->current_granule; |
| } |
| if (pad->map.audio_clipping |
| && pad->current_granule - duration < -pad->map.granule_offset) { |
| if (pad->current_granule >= -pad->map.granule_offset) { |
| guint64 already_removed = |
| pad->current_granule > |
| duration ? pad->current_granule - duration : 0; |
| clip_start = |
| already_removed > |
| -pad->map.granule_offset ? 0 : -pad->map.granule_offset - |
| already_removed; |
| } else |
| clip_start = pad->current_granule; |
| } |
| } else { |
| out_timestamp = gst_ogg_stream_granule_to_time (&pad->map, |
| pad->current_granule - duration); |
| |
| if (pad->map.audio_clipping |
| && pad->current_granule - duration < -pad->map.granule_offset) { |
| if (pad->current_granule >= -pad->map.granule_offset) { |
| guint64 already_removed = |
| pad->current_granule > |
| duration ? pad->current_granule - duration : 0; |
| clip_start = |
| already_removed > |
| -pad->map.granule_offset ? 0 : -pad->map.granule_offset - |
| already_removed; |
| } else |
| clip_start = pad->current_granule; |
| } |
| } |
| out_duration = |
| gst_ogg_stream_granule_to_time (&pad->map, |
| pad->current_granule) - out_timestamp; |
| } |
| out_offset_end = |
| gst_ogg_stream_granule_to_granulepos (&pad->map, |
| pad->current_granule, pad->keyframe_granule); |
| out_offset = |
| gst_ogg_stream_granule_to_time (&pad->map, pad->current_granule); |
| } |
| pad->prev_granule = pad->current_granule; |
| } |
| |
| if (G_UNLIKELY (offset + trim > packet->bytes)) |
| goto invalid_packet; |
| else if (pad->map.is_ogm_text) { |
| /* check for invalid buffer sizes */ |
| if (G_UNLIKELY (offset + trim >= packet->bytes)) |
| goto empty_packet; |
| } |
| |
| if (!pad->added) |
| goto not_added; |
| |
| buf = gst_buffer_new_and_alloc (packet->bytes - offset - trim); |
| |
| if (pad->map.audio_clipping && (clip_start || clip_end)) { |
| GST_DEBUG_OBJECT (pad, |
| "Clipping %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT " (%" |
| G_GUINT64_FORMAT " / %" G_GUINT64_FORMAT ")", clip_start, clip_end, |
| clip_start + clip_end, duration); |
| gst_buffer_add_audio_clipping_meta (buf, GST_FORMAT_DEFAULT, clip_start, |
| clip_end); |
| } |
| |
| /* set delta flag for OGM content */ |
| if (delta_unit) |
| GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); |
| |
| /* set header flag for buffers that are also in the streamheaders */ |
| if (is_header) |
| GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER); |
| |
| if (packet->packet != NULL) { |
| /* copy packet in buffer */ |
| gst_buffer_fill (buf, 0, packet->packet + offset, |
| packet->bytes - offset - trim); |
| } |
| |
| GST_BUFFER_TIMESTAMP (buf) = out_timestamp; |
| GST_BUFFER_DURATION (buf) = out_duration; |
| GST_BUFFER_OFFSET (buf) = out_offset; |
| GST_BUFFER_OFFSET_END (buf) = out_offset_end; |
| |
| /* Mark discont on the buffer */ |
| if (pad->discont) { |
| GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); |
| if (ogg->segment.rate < 0.0 || GST_BUFFER_TIMESTAMP_IS_VALID (buf)) |
| pad->discont = FALSE; |
| } |
| |
| /* don't push the header packets when we are asked to skip them */ |
| if (!packet->b_o_s || push_headers) { |
| if (pad->last_ret == GST_FLOW_OK) { |
| GST_LOG_OBJECT (ogg, "Pushing buf %" GST_PTR_FORMAT, buf); |
| ret = gst_pad_push (GST_PAD_CAST (pad), buf); |
| } else { |
| GST_DEBUG_OBJECT (ogg, "not pushing buffer on error pad"); |
| ret = pad->last_ret; |
| gst_buffer_unref (buf); |
| } |
| buf = NULL; |
| } |
| |
| /* we're done with skeleton stuff */ |
| if (pad->map.is_skeleton) |
| goto combine; |
| |
| /* check if valid granulepos, then we can calculate the current |
| * position. We know the granule for each packet but we only want to update |
| * the position when we have a valid granulepos on the packet because else |
| * our time jumps around for the different streams. */ |
| if (packet->granulepos < 0) |
| goto combine; |
| |
| /* convert to time */ |
| current_time = gst_ogg_stream_get_end_time_for_granulepos (&pad->map, |
| packet->granulepos); |
| |
| /* convert to stream time */ |
| if ((chain = pad->chain)) { |
| gint64 chain_start = 0; |
| |
| if (chain->segment_start != GST_CLOCK_TIME_NONE) |
| chain_start = chain->segment_start; |
| |
| current_time = current_time - chain_start + chain->begin_time; |
| } |
| |
| /* and store as the current position */ |
| ogg->segment.position = current_time; |
| |
| GST_DEBUG_OBJECT (ogg, "ogg current time %" GST_TIME_FORMAT |
| " (%" G_GINT64_FORMAT ")", GST_TIME_ARGS (current_time), current_time); |
| |
| pad->position = ogg->segment.position; |
| |
| /* check stream eos */ |
| if (!pad->is_eos && !delta_unit && |
| ((ogg->segment.rate > 0.0 && |
| ogg->segment.stop != GST_CLOCK_TIME_NONE && |
| current_time >= ogg->segment.stop) || |
| (ogg->segment.rate < 0.0 && current_time <= ogg->segment.start))) { |
| GST_DEBUG_OBJECT (ogg, "marking pad %p EOS", pad); |
| pad->is_eos = TRUE; |
| |
| if (ret == GST_FLOW_OK) { |
| ret = GST_FLOW_EOS; |
| } |
| } |
| |
| combine: |
| /* combine flows */ |
| cret = gst_ogg_demux_combine_flows (ogg, pad, ret); |
| |
| done: |
| if (buf) |
| gst_buffer_unref (buf); |
| /* return combined flow result */ |
| return cret; |
| |
| /* special cases */ |
| empty_packet: |
| { |
| GST_DEBUG_OBJECT (ogg, "Skipping empty packet"); |
| goto done; |
| } |
| |
| invalid_packet: |
| { |
| GST_DEBUG_OBJECT (ogg, "Skipping invalid packet"); |
| goto done; |
| } |
| |
| no_timestamp: |
| { |
| GST_DEBUG_OBJECT (ogg, "skipping packet: no valid granule found yet"); |
| goto done; |
| } |
| not_added: |
| { |
| GST_DEBUG_OBJECT (ogg, "pad not added yet"); |
| goto done; |
| } |
| } |
| |
| static guint64 |
| gst_ogg_demux_collect_start_time (GstOggDemux * ogg, GstOggChain * chain) |
| { |
| gint i; |
| guint64 start_time = G_MAXUINT64; |
| |
| for (i = 0; i < chain->streams->len; i++) { |
| GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
| |
| if (pad->map.is_skeleton) |
| continue; |
| |
| /* can do this if the pad start time is not defined */ |
| GST_DEBUG_OBJECT (ogg, "Pad %08x (%s) start time is %" GST_TIME_FORMAT, |
| pad->map.serialno, gst_ogg_stream_get_media_type (&pad->map), |
| GST_TIME_ARGS (pad->start_time)); |
| if (pad->start_time == GST_CLOCK_TIME_NONE) { |
| if (!pad->map.is_sparse) { |
| start_time = G_MAXUINT64; |
| break; |
| } |
| } else { |
| start_time = MIN (start_time, pad->start_time); |
| } |
| } |
| return start_time; |
| } |
| |
| static GstClockTime |
| gst_ogg_demux_collect_sync_time (GstOggDemux * ogg, GstOggChain * chain) |
| { |
| gint i; |
| GstClockTime sync_time = GST_CLOCK_TIME_NONE; |
| |
| if (!chain) { |
| GST_WARNING_OBJECT (ogg, "No chain!"); |
| return GST_CLOCK_TIME_NONE; |
| } |
| |
| for (i = 0; i < chain->streams->len; i++) { |
| GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
| |
| if (pad->map.is_sparse) |
| continue; |
| |
| if (pad->push_sync_time == GST_CLOCK_TIME_NONE) { |
| sync_time = GST_CLOCK_TIME_NONE; |
| break; |
| } else { |
| if (sync_time == GST_CLOCK_TIME_NONE) |
| sync_time = pad->push_sync_time; |
| else |
| sync_time = MAX (sync_time, pad->push_sync_time); |
| } |
| } |
| return sync_time; |
| } |
| |
| /* submit a packet to the oggpad, this function will run the type detection |
| * code for the pad if this is the first packet for this stream |
| */ |
| static GstFlowReturn |
| gst_ogg_pad_submit_packet (GstOggPad * pad, ogg_packet * packet) |
| { |
| gint64 granule; |
| GstFlowReturn ret = GST_FLOW_OK; |
| |
| GstOggDemux *ogg = pad->ogg; |
| |
| GST_DEBUG_OBJECT (ogg, "%p submit packet serial %08x", |
| pad, pad->map.serialno); |
| |
| if (!pad->have_type) { |
| pad->have_type = gst_ogg_stream_setup_map (&pad->map, packet); |
| if (!pad->have_type && !pad->map.caps) { |
| pad->map.caps = gst_caps_new_empty_simple ("application/x-unknown"); |
| } |
| if (pad->map.is_skeleton) { |
| GST_DEBUG_OBJECT (ogg, "we have a fishead"); |
| /* copy values over to global ogg level */ |
| ogg->basetime = pad->map.basetime; |
| ogg->prestime = pad->map.prestime; |
| |
| /* use total time to update the total ogg time */ |
| if (ogg->total_time == -1) { |
| ogg->total_time = pad->map.total_time; |
| } else if (pad->map.total_time > 0) { |
| ogg->total_time = MAX (ogg->total_time, pad->map.total_time); |
| } |
| } |
| if (!pad->map.caps) { |
| GST_WARNING_OBJECT (ogg, "stream parser didn't create src pad caps"); |
| } |
| } |
| |
| if (pad->map.is_skeleton) { |
| guint32 serialno; |
| GstOggPad *skel_pad; |
| GstOggSkeleton type; |
| |
| /* try to parse the serialno first */ |
| if (gst_ogg_map_parse_fisbone (&pad->map, packet->packet, packet->bytes, |
| &serialno, &type)) { |
| |
| GST_DEBUG_OBJECT (pad->ogg, |
| "got skeleton packet for stream 0x%08x", serialno); |
| |
| skel_pad = gst_ogg_chain_get_stream (pad->chain, serialno); |
| if (skel_pad) { |
| switch (type) { |
| case GST_OGG_SKELETON_FISBONE: |
| /* parse the remainder of the fisbone in the pad with the serialno, |
| * note that we ignore the start_time as this is usually wrong for |
| * live streams */ |
| gst_ogg_map_add_fisbone (&skel_pad->map, &pad->map, packet->packet, |
| packet->bytes, NULL); |
| break; |
| case GST_OGG_SKELETON_INDEX: |
| gst_ogg_map_add_index (&skel_pad->map, &pad->map, packet->packet, |
| packet->bytes); |
| ogg->check_index_overflow = TRUE; |
| break; |
| default: |
| break; |
| } |
| |
| } else { |
| GST_WARNING_OBJECT (pad->ogg, |
| "found skeleton fisbone for an unknown stream 0x%08x", serialno); |
| } |
| } |
| } |
| |
| GST_DEBUG_OBJECT (ogg, "%p packet has granulepos %" G_GINT64_FORMAT, pad, |
| packet->granulepos); |
| granule = |
| gst_ogg_stream_granulepos_to_granule (&pad->map, packet->granulepos); |
| if (granule > 0) { |
| GST_DEBUG_OBJECT (ogg, "%p has granule %" G_GINT64_FORMAT, pad, granule); |
| pad->current_granule = granule; |
| } else if (granule == 0) { |
| /* headers */ |
| } else if (granule != -1) { |
| GST_ERROR_OBJECT (ogg, |
| "granulepos %" G_GINT64_FORMAT " yielded granule %" G_GINT64_FORMAT, |
| (gint64) packet->granulepos, (gint64) granule); |
| return GST_FLOW_ERROR; |
| } |
| |
| /* restart header packet count when seeing a b_o_s page; |
| * particularly useful following a seek or even following chain finding */ |
| if (packet->b_o_s) { |
| GST_DEBUG_OBJECT (ogg, "b_o_s packet, resetting header packet count"); |
| pad->map.n_header_packets_seen = 0; |
| if (!pad->map.have_headers) { |
| GST_DEBUG_OBJECT (ogg, "clearing header packets"); |
| g_list_foreach (pad->map.headers, (GFunc) _ogg_packet_free, NULL); |
| g_list_free (pad->map.headers); |
| pad->map.headers = NULL; |
| } |
| } |
| |
| /* Overload the value of b_o_s in ogg_packet with a flag whether or |
| * not this is a header packet. Maybe some day this could be cleaned |
| * up. */ |
| packet->b_o_s = gst_ogg_stream_packet_is_header (&pad->map, packet); |
| if (!packet->b_o_s) { |
| GST_DEBUG ("found non-header packet"); |
| pad->map.have_headers = TRUE; |
| if (pad->start_time == GST_CLOCK_TIME_NONE) { |
| gint64 duration = gst_ogg_stream_get_packet_duration (&pad->map, packet); |
| GST_DEBUG ("duration %" G_GINT64_FORMAT, duration); |
| if (duration != -1) { |
| pad->map.accumulated_granule += duration; |
| GST_DEBUG ("accumulated granule %" G_GINT64_FORMAT, |
| pad->map.accumulated_granule); |
| } |
| |
| if (packet->granulepos != -1) { |
| ogg_int64_t start_granule; |
| gint64 granule; |
| |
| granule = gst_ogg_stream_granulepos_to_granule (&pad->map, |
| packet->granulepos); |
| if (granule < 0) { |
| GST_ERROR_OBJECT (ogg, |
| "granulepos %" G_GINT64_FORMAT " yielded granule %" |
| G_GINT64_FORMAT, (gint64) packet->granulepos, (gint64) granule); |
| return GST_FLOW_ERROR; |
| } |
| |
| if (granule >= pad->map.accumulated_granule) |
| start_granule = granule - pad->map.accumulated_granule; |
| else |
| start_granule = 0; |
| |
| pad->start_time = gst_ogg_stream_granule_to_time (&pad->map, |
| start_granule); |
| GST_DEBUG_OBJECT (ogg, |
| "start time %" GST_TIME_FORMAT " (%" GST_TIME_FORMAT ") for %s " |
| "from granpos %" G_GINT64_FORMAT " (granule %" G_GINT64_FORMAT ", " |
| "accumulated granule %" G_GINT64_FORMAT, |
| GST_TIME_ARGS (pad->start_time), GST_TIME_ARGS (pad->start_time), |
| gst_ogg_stream_get_media_type (&pad->map), |
| (gint64) packet->granulepos, granule, pad->map.accumulated_granule); |
| } else { |
| packet->granulepos = gst_ogg_stream_granule_to_granulepos (&pad->map, |
| pad->map.accumulated_granule + pad->current_granule, |
| pad->keyframe_granule); |
| } |
| } |
| } else { |
| /* look for tags in header packet (before inc header count) */ |
| gst_ogg_stream_extract_tags (&pad->map, packet); |
| pad->map.n_header_packets_seen++; |
| if (!pad->map.have_headers) { |
| pad->map.headers = |
| g_list_append (pad->map.headers, _ogg_packet_copy (packet)); |
| GST_DEBUG ("keeping header packet %d", pad->map.n_header_packets_seen); |
| } |
| } |
| |
| /* we know the start_time of the pad data, see if we |
| * can activate the complete chain if this is a dynamic |
| * chain. We need all the headers too for this. */ |
| if (pad->start_time != GST_CLOCK_TIME_NONE && pad->map.have_headers) { |
| GstOggChain *chain = pad->chain; |
| |
| /* check if complete chain has start time */ |
| if (chain == ogg->building_chain) { |
| GstEvent *event = NULL; |
| |
| if (ogg->resync) { |
| guint64 start_time; |
| |
| GST_DEBUG_OBJECT (ogg, "need to resync"); |
| |
| /* when we need to resync after a seek, we wait until we have received |
| * timestamps on all streams */ |
| start_time = gst_ogg_demux_collect_start_time (ogg, chain); |
| |
| if (start_time != G_MAXUINT64) { |
| gint64 segment_time; |
| GstSegment segment; |
| |
| GST_DEBUG_OBJECT (ogg, "start_time: %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (start_time)); |
| |
| if (chain->segment_start < start_time) |
| segment_time = |
| (start_time - chain->segment_start) + chain->begin_time; |
| else |
| segment_time = chain->begin_time; |
| |
| /* create the newsegment event we are going to send out */ |
| gst_segment_init (&segment, GST_FORMAT_TIME); |
| |
| GST_PUSH_LOCK (ogg); |
| if (!ogg->pullmode && ogg->push_state == PUSH_LINEAR2) { |
| /* if we are fast forwarding to the actual seek target, |
| ensure previous frames are clipped */ |
| GST_DEBUG_OBJECT (ogg, |
| "Resynced, starting segment at %" GST_TIME_FORMAT |
| ", start_time %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (ogg->push_seek_time_original_target), |
| GST_TIME_ARGS (start_time)); |
| segment.rate = ogg->push_seek_rate; |
| segment.start = ogg->push_seek_time_original_target; |
| segment.position = ogg->push_seek_time_original_target; |
| segment.stop = ogg->push_seek_time_original_stop; |
| segment.time = ogg->push_seek_time_original_target; |
| segment.base = ogg->segment.base; |
| event = gst_event_new_segment (&segment); |
| ogg->push_state = PUSH_PLAYING; |
| } else { |
| segment.rate = ogg->segment.rate; |
| segment.applied_rate = ogg->segment.applied_rate; |
| segment.start = start_time; |
| segment.position = start_time; |
| segment.stop = chain->segment_stop; |
| segment.time = segment_time; |
| segment.base = ogg->segment.base; |
| event = gst_event_new_segment (&segment); |
| } |
| GST_PUSH_UNLOCK (ogg); |
| |
| ogg->resync = FALSE; |
| } |
| } else { |
| /* see if we have enough info to activate the chain, we have enough info |
| * when all streams have a valid start time. */ |
| if (gst_ogg_demux_collect_chain_info (ogg, chain)) { |
| GstSegment segment; |
| |
| GST_DEBUG_OBJECT (ogg, "segment_start: %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (chain->segment_start)); |
| GST_DEBUG_OBJECT (ogg, "segment_stop: %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (chain->segment_stop)); |
| GST_DEBUG_OBJECT (ogg, "segment_time: %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (chain->begin_time)); |
| |
| /* create the newsegment event we are going to send out */ |
| gst_segment_init (&segment, GST_FORMAT_TIME); |
| segment.rate = ogg->segment.rate; |
| segment.applied_rate = ogg->segment.applied_rate; |
| segment.start = chain->segment_start; |
| segment.position = chain->segment_start; |
| segment.stop = chain->segment_stop; |
| segment.time = chain->begin_time; |
| segment.base = ogg->segment.base + segment.time; |
| event = gst_event_new_segment (&segment); |
| } |
| } |
| |
| if (event) { |
| gst_event_set_seqnum (event, ogg->seqnum); |
| |
| gst_ogg_demux_activate_chain (ogg, chain, event); |
| |
| ogg->building_chain = NULL; |
| } |
| } |
| } |
| |
| /* if we are building a chain, store buffer for when we activate |
| * it. This path is taken if we operate in streaming mode. */ |
| if (ogg->building_chain) { |
| /* bos packets where stored in the header list so we can discard |
| * them here*/ |
| if (!packet->b_o_s) |
| ret = gst_ogg_demux_queue_data (pad, packet); |
| } |
| /* else we are completely streaming to the peer */ |
| else { |
| ret = gst_ogg_demux_chain_peer (pad, packet, !ogg->pullmode); |
| } |
| return ret; |
| } |
| |
| /* flush at most @npackets from the stream layer. All packets if |
| * @npackets is 0; |
| */ |
| static GstFlowReturn |
| gst_ogg_pad_stream_out (GstOggPad * pad, gint npackets) |
| { |
| GstFlowReturn result = GST_FLOW_OK; |
| gboolean done = FALSE; |
| GstOggDemux *ogg; |
| |
| ogg = pad->ogg; |
| |
| while (!done) { |
| int ret; |
| ogg_packet packet; |
| |
| ret = ogg_stream_packetout (&pad->map.stream, &packet); |
| switch (ret) { |
| case 0: |
| GST_LOG_OBJECT (ogg, "packetout done"); |
| done = TRUE; |
| break; |
| case -1: |
| GST_LOG_OBJECT (ogg, "packetout discont"); |
| if (!pad->map.is_sparse) { |
| gst_ogg_chain_mark_discont (pad->chain); |
| } else { |
| gst_ogg_pad_mark_discont (pad); |
| } |
| break; |
| case 1: |
| GST_LOG_OBJECT (ogg, "packetout gave packet of size %ld", packet.bytes); |
| |
| if (packet.granulepos < -1) { |
| GST_WARNING_OBJECT (ogg, |
| "Invalid granulepos (%" G_GINT64_FORMAT "), resetting stream", |
| packet.granulepos); |
| gst_ogg_pad_reset (pad); |
| break; |
| } |
| |
| if (packet.bytes > ogg->max_packet_size) |
| ogg->max_packet_size = packet.bytes; |
| result = gst_ogg_pad_submit_packet (pad, &packet); |
| /* not linked is not a problem, it's possible that we are still |
| * collecting headers and that we don't have exposed the pads yet */ |
| if (result == GST_FLOW_NOT_LINKED) |
| break; |
| else if (result <= GST_FLOW_EOS) |
| goto could_not_submit; |
| break; |
| default: |
| GST_WARNING_OBJECT (ogg, |
| "invalid return value %d for ogg_stream_packetout, resetting stream", |
| ret); |
| gst_ogg_pad_reset (pad); |
| break; |
| } |
| if (npackets > 0) { |
| npackets--; |
| done = (npackets == 0); |
| } |
| } |
| return result; |
| |
| /* ERRORS */ |
| could_not_submit: |
| { |
| GST_WARNING_OBJECT (ogg, |
| "could not submit packet for stream %08x, " |
| "error: %d", pad->map.serialno, result); |
| gst_ogg_pad_reset (pad); |
| return result; |
| } |
| } |
| |
| static void |
| gst_ogg_demux_setup_first_granule (GstOggDemux * ogg, GstOggPad * pad, |
| ogg_page * page) |
| { |
| /* When we submit a page, we check if we have started tracking granules. |
| * If not, we calculate the granule corresponding to the first packet |
| * on the page. */ |
| gboolean valid_granule = TRUE; |
| |
| if (pad->current_granule == -1) { |
| ogg_int64_t granpos = ogg_page_granulepos (page); |
| if (granpos > 0) { |
| gint64 granule = |
| (gint64) gst_ogg_stream_granulepos_to_granule (&pad->map, granpos); |
| gint64 duration; |
| int packets = ogg_page_packets (page), n; |
| GST_DEBUG_OBJECT (pad, |
| "This page completes %d packets, granule %" G_GINT64_FORMAT, packets, |
| granule); |
| |
| if (packets > 0) { |
| ogg_stream_state os; |
| ogg_packet op; |
| int last_size = pad->map.last_size; |
| |
| memcpy (&os, &pad->map.stream, sizeof (os)); |
| for (n = 0; valid_granule && n < packets; ++n) { |
| int ret = ogg_stream_packetout (&os, &op); |
| if (ret < 0) { |
| /* This usually means a continued packet after a seek and we can't calc the first granule, |
| * but sometimes not - so if it's ret == -1 and first packet, try again */ |
| if (ret == -1 && n == 0) { |
| n--; |
| continue; |
| } |
| GST_DEBUG_OBJECT (pad, "Failed to read packet off first page"); |
| valid_granule = FALSE; |
| break; |
| } |
| if (ret == 0) { |
| GST_WARNING_OBJECT (pad, |
| "Short read getting %d packets off first page", packets); |
| valid_granule = FALSE; |
| break; |
| } |
| duration = gst_ogg_stream_get_packet_duration (&pad->map, &op); |
| GST_DEBUG_OBJECT (pad, "Packet %d has duration %" G_GINT64_FORMAT, |
| n, duration); |
| granule -= duration; |
| } |
| pad->map.last_size = last_size; |
| if (valid_granule) { |
| if (granule >= 0) { |
| pad->current_granule = granule; |
| GST_INFO_OBJECT (pad, |
| "Starting with first granule %" G_GINT64_FORMAT, granule); |
| } else { |
| pad->current_granule = 0; |
| GST_INFO_OBJECT (pad, "Extrapolated first granule is negative, " |
| "used to clip samples at start"); |
| } |
| } |
| } else { |
| GST_WARNING_OBJECT (pad, |
| "Ogg page finishing no packets, but a valid granule"); |
| } |
| } |
| } |
| } |
| |
| static void |
| gst_ogg_demux_setup_bisection_bounds (GstOggDemux * ogg) |
| { |
| if (ogg->push_last_seek_time >= ogg->push_seek_time_target) { |
| GST_DEBUG_OBJECT (ogg, "We overshot by %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (ogg->push_last_seek_time - ogg->push_seek_time_target)); |
| ogg->push_offset1 = ogg->push_last_seek_offset; |
| ogg->push_time1 = ogg->push_last_seek_time; |
| ogg->seek_undershot = FALSE; |
| } else { |
| GST_DEBUG_OBJECT (ogg, "We undershot by %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (ogg->push_seek_time_target - ogg->push_last_seek_time)); |
| ogg->push_offset0 = ogg->push_last_seek_offset; |
| ogg->push_time0 = ogg->push_last_seek_time; |
| ogg->seek_undershot = TRUE; |
| } |
| } |
| |
| static gint64 |
| gst_ogg_demux_estimate_bisection_target (GstOggDemux * ogg, float seek_quality) |
| { |
| gint64 best; |
| gint64 segment_bitrate; |
| gint64 skew; |
| |
| /* we might not know the length of the stream in time, |
| so push_time1 might not be set */ |
| GST_DEBUG_OBJECT (ogg, |
| "push time 1: %" GST_TIME_FORMAT ", dbytes %" G_GINT64_FORMAT, |
| GST_TIME_ARGS (ogg->push_time1), ogg->push_offset1 - ogg->push_offset0); |
| if (ogg->push_time1 == GST_CLOCK_TIME_NONE) { |
| GST_DEBUG_OBJECT (ogg, |
| "New segment to consider: bytes %" G_GINT64_FORMAT " %" G_GINT64_FORMAT |
| ", time %" GST_TIME_FORMAT " (open ended)", ogg->push_offset0, |
| ogg->push_offset1, GST_TIME_ARGS (ogg->push_time0)); |
| if (ogg->push_last_seek_time == ogg->push_start_time) { |
| /* if we're at start and don't know the end time, we can't estimate |
| bitrate, so get the nominal declared bitrate as a failsafe, or some |
| random constant which will be discarded after we made a (probably |
| dire) first guess */ |
| segment_bitrate = (ogg->bitrate > 0 ? ogg->bitrate : 1000); |
| } else { |
| segment_bitrate = |
| gst_util_uint64_scale (ogg->push_last_seek_offset - 0, |
| 8 * GST_SECOND, ogg->push_last_seek_time - ogg->push_start_time); |
| } |
| best = |
| ogg->push_offset0 + |
| gst_util_uint64_scale (ogg->push_seek_time_target - ogg->push_time0, |
| segment_bitrate, 8 * GST_SECOND); |
| ogg->seek_secant = TRUE; |
| } else { |
| GST_DEBUG_OBJECT (ogg, |
| "New segment to consider: bytes %" G_GINT64_FORMAT " %" G_GINT64_FORMAT |
| ", time %" GST_TIME_FORMAT " %" GST_TIME_FORMAT, ogg->push_offset0, |
| ogg->push_offset1, GST_TIME_ARGS (ogg->push_time0), |
| GST_TIME_ARGS (ogg->push_time1)); |
| if (ogg->push_time0 == ogg->push_time1) { |
| best = ogg->push_offset0; |
| } else { |
| segment_bitrate = |
| gst_util_uint64_scale (ogg->push_offset1 - ogg->push_offset0, |
| 8 * GST_SECOND, ogg->push_time1 - ogg->push_time0); |
| GST_DEBUG_OBJECT (ogg, |
| "Local bitrate on the %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT |
| " segment: %" G_GINT64_FORMAT, GST_TIME_ARGS (ogg->push_time0), |
| GST_TIME_ARGS (ogg->push_time1), segment_bitrate); |
| |
| best = |
| ogg->push_offset0 + |
| gst_util_uint64_scale (ogg->push_seek_time_target - ogg->push_time0, |
| segment_bitrate, 8 * GST_SECOND); |
| if (seek_quality < 0.5f && ogg->seek_secant) { |
| gint64 new_best, best2 = (ogg->push_offset0 + ogg->push_offset1) / 2; |
| /* if dire result, give as much as 25% weight to a dumb bisection guess */ |
| float secant_weight = 1.0f - ((0.5 - seek_quality) / 0.5f) * 0.25; |
| new_best = (best * secant_weight + best2 * (1.0f - secant_weight)); |
| GST_DEBUG_OBJECT (ogg, |
| "Secant says %" G_GINT64_FORMAT ", straight is %" G_GINT64_FORMAT |
| ", new best %" G_GINT64_FORMAT " with secant_weight %f", best, |
| best2, new_best, secant_weight); |
| best = new_best; |
| ogg->seek_secant = FALSE; |
| } else { |
| ogg->seek_secant = TRUE; |
| } |
| } |
| } |
| |
| GST_DEBUG_OBJECT (ogg, "Raw best guess: %" G_GINT64_FORMAT, best); |
| |
| /* offset the guess down as we need to capture the start of the |
| page we are targetting - but only do so if we did not undershoot |
| last time, as we're likely to still do this time */ |
| if (!ogg->seek_undershot) { |
| /* very small packets are packed on pages, so offset by at least |
| a value which is likely to get us at least one page where the |
| packet starts */ |
| skew = |
| ogg->max_packet_size > |
| ogg->max_page_size ? ogg->max_packet_size : ogg->max_page_size; |
| GST_DEBUG_OBJECT (ogg, "Offsetting by %" G_GINT64_FORMAT, skew); |
| best -= skew; |
| } |
| |
| /* do not seek too close to the bounds, as we stop seeking |
| when we get to within max_packet_size before the target */ |
| if (best > ogg->push_offset1 - ogg->max_packet_size) { |
| best = ogg->push_offset1 - ogg->max_packet_size; |
| GST_DEBUG_OBJECT (ogg, |
| "Too close to high bound, pushing back to %" G_GINT64_FORMAT, best); |
| } else if (best < ogg->push_offset0 + ogg->max_packet_size) { |
| best = ogg->push_offset0 + ogg->max_packet_size; |
| GST_DEBUG_OBJECT (ogg, |
| "Too close to low bound, pushing forth to %" G_GINT64_FORMAT, best); |
| } |
| |
| /* keep within bounds */ |
| if (best > ogg->push_offset1) |
| best = ogg->push_offset1; |
| if (best < ogg->push_offset0) |
| best = ogg->push_offset0; |
| |
| GST_DEBUG_OBJECT (ogg, "Choosing target %" G_GINT64_FORMAT, best); |
| return best; |
| } |
| |
| static void |
| gst_ogg_demux_record_keyframe_time (GstOggDemux * ogg, GstOggPad * pad, |
| ogg_int64_t granpos) |
| { |
| gint64 kf_granule; |
| GstClockTime kf_time; |
| |
| kf_granule = gst_ogg_stream_granulepos_to_key_granule (&pad->map, granpos); |
| kf_time = gst_ogg_stream_granule_to_time (&pad->map, kf_granule); |
| |
| pad->push_kf_time = kf_time; |
| } |
| |
| /* returns the earliest keyframe time for all non sparse pads in the chain, |
| * if known, and GST_CLOCK_TIME_NONE if not */ |
| static GstClockTime |
| gst_ogg_demux_get_earliest_keyframe_time (GstOggDemux * ogg) |
| { |
| GstClockTime t = GST_CLOCK_TIME_NONE; |
| GstOggChain *chain = ogg->building_chain; |
| int i; |
| |
| if (!chain) { |
| GST_WARNING_OBJECT (ogg, "No chain!"); |
| return GST_CLOCK_TIME_NONE; |
| } |
| for (i = 0; i < chain->streams->len; i++) { |
| GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
| |
| if (pad->map.is_sparse) |
| continue; |
| if (pad->push_kf_time == GST_CLOCK_TIME_NONE) |
| return GST_CLOCK_TIME_NONE; |
| if (t == GST_CLOCK_TIME_NONE || pad->push_kf_time < t) |
| t = pad->push_kf_time; |
| } |
| |
| return t; |
| } |
| |
| /* MUST be called with the push lock locked, and will unlock it |
| regardless of return value. */ |
| static GstFlowReturn |
| gst_ogg_demux_seek_back_after_push_duration_check_unlock (GstOggDemux * ogg) |
| { |
| GstEvent *event; |
| |
| /* Get the delayed event, if any */ |
| event = ogg->push_mode_seek_delayed_event; |
| ogg->push_mode_seek_delayed_event = NULL; |
| |
| /* if we haven't learnt about the total time yet, disable seeking */ |
| if (ogg->total_time == -1) |
| ogg->push_disable_seeking = TRUE; |
| |
| ogg->push_state = PUSH_PLAYING; |
| |
| /* If there is one, perform it. Otherwise, seek back at start to start |
| * normal playback */ |
| if (!event) { |
| GST_INFO_OBJECT (ogg, "Seeking back to 0 after duration check"); |
| event = gst_event_new_seek (1.0, GST_FORMAT_BYTES, |
| GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH, |
| GST_SEEK_TYPE_SET, 1, GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE); |
| } |
| gst_event_replace (&ogg->seek_event, event); |
| gst_event_unref (event); |
| GST_PUSH_UNLOCK (ogg); |
| g_mutex_lock (&ogg->seek_event_mutex); |
| g_cond_broadcast (&ogg->seek_event_cond); |
| g_mutex_unlock (&ogg->seek_event_mutex); |
| |
| return GST_FLOW_OK; |
| } |
| |
| static float |
| gst_ogg_demux_estimate_seek_quality (GstOggDemux * ogg) |
| { |
| GstClockTimeDiff diff; /* how far from the goal we ended up */ |
| GstClockTimeDiff dist; /* how far we moved this iteration */ |
| float seek_quality; |
| |
| if (ogg->push_prev_seek_time == GST_CLOCK_TIME_NONE) { |
| /* for the first seek, we pretend we got a good seek, |
| as we don't have a previous seek yet */ |
| return 1.0f; |
| } |
| |
| /* We take a guess at how good the last seek was at guessing |
| the byte target by comparing the amplitude of the last |
| seek to the error */ |
| diff = GST_CLOCK_DIFF (ogg->push_seek_time_target, ogg->push_last_seek_time); |
| if (diff < 0) |
| diff = -diff; |
| dist = GST_CLOCK_DIFF (ogg->push_last_seek_time, ogg->push_prev_seek_time); |
| if (dist < 0) |
| dist = -dist; |
| |
| seek_quality = (dist == 0) ? 0.0f : 1.0f / (1.0f + diff / (float) dist); |
| |
| GST_DEBUG_OBJECT (ogg, |
| "We moved %" GST_STIME_FORMAT ", we're off by %" GST_STIME_FORMAT |
| ", seek quality %f", GST_STIME_ARGS (dist), GST_STIME_ARGS (diff), |
| seek_quality); |
| return seek_quality; |
| } |
| |
| static void |
| gst_ogg_demux_update_bisection_stats (GstOggDemux * ogg) |
| { |
| int n; |
| |
| GST_INFO_OBJECT (ogg, "Bisection needed %d + %d steps", |
| ogg->push_bisection_steps[0], ogg->push_bisection_steps[1]); |
| |
| for (n = 0; n < 2; ++n) { |
| ogg->stats_bisection_steps[n] += ogg->push_bisection_steps[n]; |
| if (ogg->stats_bisection_max_steps[n] < ogg->push_bisection_steps[n]) |
| ogg->stats_bisection_max_steps[n] = ogg->push_bisection_steps[n]; |
| } |
| ogg->stats_nbisections++; |
| |
| GST_INFO_OBJECT (ogg, |
| "So far, %.2f + %.2f bisections needed per seek (max %d + %d)", |
| ogg->stats_bisection_steps[0] / (float) ogg->stats_nbisections, |
| ogg->stats_bisection_steps[1] / (float) ogg->stats_nbisections, |
| ogg->stats_bisection_max_steps[0], ogg->stats_bisection_max_steps[1]); |
| } |
| |
| static gboolean |
| gst_ogg_pad_handle_push_mode_state (GstOggPad * pad, ogg_page * page) |
| { |
| GstOggDemux *ogg = pad->ogg; |
| ogg_int64_t granpos = ogg_page_granulepos (page); |
| |
| GST_PUSH_LOCK (ogg); |
| if (granpos >= 0 && pad->have_type) { |
| if (ogg->push_start_time == GST_CLOCK_TIME_NONE) { |
| ogg->push_start_time = |
| gst_ogg_stream_get_start_time_for_granulepos (&pad->map, granpos); |
| GST_DEBUG_OBJECT (ogg, "Stream start time: %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (ogg->push_start_time)); |
| } |
| ogg->push_time_offset = |
| gst_ogg_stream_get_end_time_for_granulepos (&pad->map, granpos); |
| if (ogg->push_time_offset > 0) { |
| GST_DEBUG_OBJECT (ogg, "Bitrate since start: %" G_GUINT64_FORMAT, |
| gst_util_uint64_scale (ogg->push_byte_offset, 8 * GST_SECOND, |
| ogg->push_time_offset)); |
| } |
| |
| if (ogg->push_state == PUSH_DURATION) { |
| GstClockTime t = |
| gst_ogg_stream_get_end_time_for_granulepos (&pad->map, granpos); |
| |
| if (ogg->total_time == GST_CLOCK_TIME_NONE || t > ogg->total_time) { |
| GST_DEBUG_OBJECT (ogg, "New total time: %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (t)); |
| ogg->total_time = t; |
| ogg->push_time_length = t; |
| } |
| |
| /* If we're still receiving data from before the seek segment, drop it */ |
| if (ogg->seek_event_drop_till != 0) { |
| GST_PUSH_UNLOCK (ogg); |
| return GST_FLOW_SKIP_PUSH; |
| } |
| |
| /* If we were determining the duration of the stream, we're now done, |
| and can get back to sending the original event we delayed. |
| We stop a bit before the end of the stream, as if we get a EOS |
| event and there is a queue2 upstream (such as when using playbin), |
| it will pause the task *after* we come back from the EOS handler, |
| so we cannot prevent the pausing by issuing a seek. */ |
| if (ogg->push_byte_offset >= ogg->push_byte_length) { |
| GstMessage *message; |
| GstFlowReturn res; |
| |
| /* tell the pipeline we've just found out the duration */ |
| ogg->push_time_length = ogg->total_time; |
| GST_INFO_OBJECT (ogg, "New duration found: %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (ogg->total_time)); |
| message = gst_message_new_duration_changed (GST_OBJECT (ogg)); |
| gst_element_post_message (GST_ELEMENT (ogg), message); |
| |
| GST_DEBUG_OBJECT (ogg, |
| "We're close enough to the end, and we're scared " |
| "to get too close, seeking back to start"); |
| |
| res = gst_ogg_demux_seek_back_after_push_duration_check_unlock (ogg); |
| if (res != GST_FLOW_OK) |
| return res; |
| return GST_FLOW_SKIP_PUSH; |
| } else { |
| GST_PUSH_UNLOCK (ogg); |
| } |
| return GST_FLOW_SKIP_PUSH; |
| } |
| } |
| |
| /* if we're seeking, look at time, and decide what to do */ |
| if (ogg->push_state != PUSH_PLAYING && ogg->push_state != PUSH_LINEAR2) { |
| GstClockTime t; |
| gint64 best = -1; |
| GstEvent *sevent; |
| gboolean close_enough; |
| float seek_quality; |
| |
| /* ignore -1 granpos when seeking, we want to sync on a real granpos */ |
| if (granpos < 0) { |
| GST_PUSH_UNLOCK (ogg); |
| if (ogg_stream_pagein (&pad->map.stream, page) != 0) |
| goto choked; |
| if (pad->current_granule == -1) |
| gst_ogg_demux_setup_first_granule (ogg, pad, page); |
| return GST_FLOW_SKIP_PUSH; |
| } |
| |
| t = gst_ogg_stream_get_end_time_for_granulepos (&pad->map, granpos); |
| |
| if (ogg->push_state == PUSH_BISECT1 || ogg->push_state == PUSH_BISECT2) { |
| GstClockTime sync_time; |
| |
| if (pad->push_sync_time == GST_CLOCK_TIME_NONE) |
| pad->push_sync_time = t; |
| GST_DEBUG_OBJECT (ogg, "Got PTS %" GST_TIME_FORMAT " for %s", |
| GST_TIME_ARGS (t), gst_ogg_stream_get_media_type (&pad->map)); |
| sync_time = gst_ogg_demux_collect_sync_time (ogg, ogg->building_chain); |
| if (sync_time == GST_CLOCK_TIME_NONE) { |
| GST_PUSH_UNLOCK (ogg); |
| GST_DEBUG_OBJECT (ogg, |
| "Not enough timing info collected for sync, waiting for more"); |
| if (ogg_stream_pagein (&pad->map.stream, page) != 0) |
| goto choked; |
| if (pad->current_granule == -1) |
| gst_ogg_demux_setup_first_granule (ogg, pad, page); |
| return GST_FLOW_SKIP_PUSH; |
| } |
| ogg->push_last_seek_time = sync_time; |
| |
| GST_DEBUG_OBJECT (ogg, |
| "Bisection just seeked at %" G_GINT64_FORMAT ", time %" |
| GST_TIME_FORMAT ", target was %" GST_TIME_FORMAT, |
| ogg->push_last_seek_offset, |
| GST_TIME_ARGS (ogg->push_last_seek_time), |
| GST_TIME_ARGS (ogg->push_seek_time_target)); |
| |
| if (ogg->push_time1 != GST_CLOCK_TIME_NONE) { |
| seek_quality = gst_ogg_demux_estimate_seek_quality (ogg); |
| GST_DEBUG_OBJECT (ogg, |
| "Interval was %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT " (%" |
| G_GINT64_FORMAT "), time %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT |
| " (%" GST_TIME_FORMAT "), seek quality %f", ogg->push_offset0, |
| ogg->push_offset1, ogg->push_offset1 - ogg->push_offset0, |
| GST_TIME_ARGS (ogg->push_time0), GST_TIME_ARGS (ogg->push_time1), |
| GST_TIME_ARGS (ogg->push_time1 - ogg->push_time0), seek_quality); |
| } else { |
| /* in a open ended seek, we can't do bisection, so we pretend |
| we like our result so far */ |
| seek_quality = 1.0f; |
| GST_DEBUG_OBJECT (ogg, |
| "Interval was %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT " (%" |
| G_GINT64_FORMAT "), time %" GST_TIME_FORMAT " - unknown", |
| ogg->push_offset0, ogg->push_offset1, |
| ogg->push_offset1 - ogg->push_offset0, |
| GST_TIME_ARGS (ogg->push_time0)); |
| } |
| ogg->push_prev_seek_time = ogg->push_last_seek_time; |
| |
| gst_ogg_demux_setup_bisection_bounds (ogg); |
| |
| best = gst_ogg_demux_estimate_bisection_target (ogg, seek_quality); |
| |
| if (ogg->push_seek_time_target == 0) { |
| GST_DEBUG_OBJECT (ogg, "Seeking to 0, deemed close enough"); |
| close_enough = (ogg->push_last_seek_time == 0); |
| } else { |
| /* TODO: make this dependent on framerate ? */ |
| GstClockTime time_threshold = GST_SECOND / 2; |
| guint64 byte_threshold = |
| (ogg->max_packet_size > |
| 64 * 1024 ? ogg->max_packet_size : 64 * 1024); |
| |
| /* We want to be within half a second before the target, |
| or before the target and half less or equal to the max |
| packet size left to search in */ |
| if (time_threshold > ogg->push_seek_time_target) |
| time_threshold = ogg->push_seek_time_target; |
| close_enough = ogg->push_last_seek_time < ogg->push_seek_time_target |
| && (ogg->push_last_seek_time >= |
| ogg->push_seek_time_target - time_threshold |
| || ogg->push_offset1 <= ogg->push_offset0 + byte_threshold); |
| GST_DEBUG_OBJECT (ogg, |
| "testing if we're close enough: %" GST_TIME_FORMAT " <= %" |
| GST_TIME_FORMAT " < %" GST_TIME_FORMAT ", or %" G_GUINT64_FORMAT |
| " <= %" G_GUINT64_FORMAT " ? %s", |
| GST_TIME_ARGS (ogg->push_seek_time_target - time_threshold), |
| GST_TIME_ARGS (ogg->push_last_seek_time), |
| GST_TIME_ARGS (ogg->push_seek_time_target), |
| ogg->push_offset1 - ogg->push_offset0, byte_threshold, |
| close_enough ? "Yes" : "No"); |
| } |
| |
| if (close_enough || best == ogg->push_last_seek_offset) { |
| if (ogg->push_state == PUSH_BISECT1) { |
| /* we now know the time segment we'll have to search for |
| the second bisection */ |
| ogg->push_time0 = ogg->push_start_time; |
| ogg->push_offset0 = 0; |
| |
| GST_DEBUG_OBJECT (ogg, |
| "Seek to %" GST_TIME_FORMAT |
| " (%lx) done, now gathering pages for all non-sparse streams", |
| GST_TIME_ARGS (ogg->push_seek_time_target), (long) granpos); |
| ogg->push_state = PUSH_LINEAR1; |
| } else { |
| /* If we're asked for an accurate seek, we'll go forward till |
| we get to the original seek target time, else we'll just drop |
| here at the keyframe */ |
| if (ogg->push_seek_flags & GST_SEEK_FLAG_ACCURATE) { |
| GST_INFO_OBJECT (ogg, |
| "Seek to keyframe at %" GST_TIME_FORMAT " done (we're at %" |
| GST_TIME_FORMAT "), skipping to original target (%" |
| GST_TIME_FORMAT ")", |
| GST_TIME_ARGS (ogg->push_seek_time_target), |
| GST_TIME_ARGS (sync_time), |
| GST_TIME_ARGS (ogg->push_seek_time_original_target)); |
| ogg->push_state = PUSH_LINEAR2; |
| } else { |
| GST_INFO_OBJECT (ogg, "Seek to keyframe done, playing"); |
| |
| /* we're synced to the seek target, so flush stream and stuff |
| any queued pages into the stream so we start decoding there */ |
| ogg->push_state = PUSH_PLAYING; |
| } |
| gst_ogg_demux_update_bisection_stats (ogg); |
| } |
| } |
| } else if (ogg->push_state == PUSH_LINEAR1) { |
| if (pad->push_kf_time == GST_CLOCK_TIME_NONE) { |
| GstClockTime earliest_keyframe_time; |
| |
| gst_ogg_demux_record_keyframe_time (ogg, pad, granpos); |
| GST_DEBUG_OBJECT (ogg, |
| "Previous keyframe for %s stream at %" GST_TIME_FORMAT, |
| gst_ogg_stream_get_media_type (&pad->map), |
| GST_TIME_ARGS (pad->push_kf_time)); |
| earliest_keyframe_time = gst_ogg_demux_get_earliest_keyframe_time (ogg); |
| if (earliest_keyframe_time != GST_CLOCK_TIME_NONE) { |
| if (earliest_keyframe_time > ogg->push_last_seek_time) { |
| GST_INFO_OBJECT (ogg, |
| "All non sparse streams now have a previous keyframe time, " |
| "and we already decoded it, switching to playing"); |
| ogg->push_state = PUSH_PLAYING; |
| gst_ogg_demux_update_bisection_stats (ogg); |
| } else { |
| GST_INFO_OBJECT (ogg, |
| "All non sparse streams now have a previous keyframe time, " |
| "bisecting again to %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (earliest_keyframe_time)); |
| |
| ogg->push_seek_time_target = earliest_keyframe_time; |
| ogg->push_offset0 = 0; |
| ogg->push_time0 = ogg->push_start_time; |
| ogg->push_offset1 = ogg->push_last_seek_offset; |
| ogg->push_time1 = ogg->push_last_seek_time; |
| ogg->push_prev_seek_time = GST_CLOCK_TIME_NONE; |
| ogg->seek_secant = FALSE; |
| ogg->seek_undershot = FALSE; |
| |
| ogg->push_state = PUSH_BISECT2; |
| best = gst_ogg_demux_estimate_bisection_target (ogg, 1.0f); |
| } |
| } |
| } |
| } |
| |
| if (ogg->push_state == PUSH_BISECT1 || ogg->push_state == PUSH_BISECT2) { |
| gint i; |
| |
| ogg_sync_reset (&ogg->sync); |
| for (i = 0; i < ogg->building_chain->streams->len; i++) { |
| GstOggPad *pad = |
| g_array_index (ogg->building_chain->streams, GstOggPad *, i); |
| |
| pad->push_sync_time = GST_CLOCK_TIME_NONE; |
| ogg_stream_reset (&pad->map.stream); |
| } |
| |
| GST_DEBUG_OBJECT (ogg, |
| "seeking to %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT, best, |
| (gint64) - 1); |
| /* do seek */ |
| g_assert (best != -1); |
| ogg->push_bisection_steps[ogg->push_state == PUSH_BISECT2 ? 1 : 0]++; |
| sevent = |
| gst_event_new_seek (ogg->push_seek_rate, GST_FORMAT_BYTES, |
| ogg->push_seek_flags, GST_SEEK_TYPE_SET, best, |
| GST_SEEK_TYPE_NONE, -1); |
| gst_event_set_seqnum (sevent, ogg->seqnum); |
| |
| gst_event_replace (&ogg->seek_event, sevent); |
| gst_event_unref (sevent); |
| GST_PUSH_UNLOCK (ogg); |
| g_mutex_lock (&ogg->seek_event_mutex); |
| g_cond_broadcast (&ogg->seek_event_cond); |
| g_mutex_unlock (&ogg->seek_event_mutex); |
| return GST_FLOW_SKIP_PUSH; |
| } |
| |
| if (ogg->push_state != PUSH_PLAYING) { |
| GST_PUSH_UNLOCK (ogg); |
| return GST_FLOW_SKIP_PUSH; |
| } |
| } |
| GST_PUSH_UNLOCK (ogg); |
| |
| return GST_FLOW_OK; |
| |
| choked: |
| { |
| GST_WARNING_OBJECT (ogg, |
| "ogg stream choked on page (serial %08x), " |
| "resetting stream", pad->map.serialno); |
| gst_ogg_pad_reset (pad); |
| /* we continue to recover */ |
| return GST_FLOW_SKIP_PUSH; |
| } |
| } |
| |
| static void |
| gst_ogg_demux_query_duration_push (GstOggDemux * ogg) |
| { |
| if (!ogg->pullmode && ogg->push_byte_length == -1) { |
| GstQuery *query; |
| gboolean seekable = FALSE; |
| |
| query = gst_query_new_seeking (GST_FORMAT_BYTES); |
| if (gst_pad_peer_query (ogg->sinkpad, query)) |
| gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL); |
| gst_query_unref (query); |
| |
| if (seekable) { |
| gint64 length = -1; |
| if (!gst_element_query_duration (GST_ELEMENT (ogg), GST_FORMAT_BYTES, |
| &length) |
| || length <= 0) { |
| GST_DEBUG_OBJECT (ogg, |
| "Unable to determine stream size, assuming live, seeking disabled"); |
| ogg->push_disable_seeking = TRUE; |
| } else { |
| ogg->push_disable_seeking = FALSE; |
| } |
| } else { |
| GST_DEBUG_OBJECT (ogg, "Stream is not seekable, seeking disabled"); |
| ogg->push_disable_seeking = TRUE; |
| } |
| } |
| } |
| |
| /* submit a page to an oggpad, this function will then submit all |
| * the packets in the page. |
| */ |
| static GstFlowReturn |
| gst_ogg_pad_submit_page (GstOggPad * pad, ogg_page * page) |
| { |
| GstFlowReturn result = GST_FLOW_OK; |
| GstOggDemux *ogg; |
| gboolean continued = FALSE; |
| |
| ogg = pad->ogg; |
| |
| /* for negative rates we read pages backwards and must therefore be careful |
| * with continued pages */ |
| if (ogg->segment.rate < 0.0) { |
| gint npackets; |
| |
| continued = ogg_page_continued (page); |
| |
| /* number of completed packets in the page */ |
| npackets = ogg_page_packets (page); |
| if (!continued) { |
| /* page is not continued so it contains at least one packet start. It's |
| * possible that no packet ends on this page (npackets == 0). In that |
| * case, the next (continued) page(s) we kept contain the remainder of the |
| * packets. We mark npackets=1 to make us start decoding the pages in the |
| * remainder of the algorithm. */ |
| if (npackets == 0) |
| npackets = 1; |
| } |
| GST_LOG_OBJECT (ogg, "continued: %d, %d packets", continued, npackets); |
| |
| if (npackets == 0) { |
| GST_LOG_OBJECT (ogg, "no decodable packets, we need a previous page"); |
| goto done; |
| } |
| } |
| |
| gst_ogg_demux_query_duration_push (ogg); |
| |
| /* keep track of time in push mode */ |
| if (!ogg->pullmode) { |
| result = gst_ogg_pad_handle_push_mode_state (pad, page); |
| if (result == GST_FLOW_SKIP_PUSH) |
| return GST_FLOW_OK; |
| if (result != GST_FLOW_OK) |
| return result; |
| } |
| |
| if (page->header_len + page->body_len > ogg->max_page_size) |
| ogg->max_page_size = page->header_len + page->body_len; |
| |
| if (ogg_stream_pagein (&pad->map.stream, page) != 0) |
| goto choked; |
| if (pad->current_granule == -1) |
| gst_ogg_demux_setup_first_granule (ogg, pad, page); |
| |
| /* flush all packets in the stream layer, this might not give a packet if |
| * the page had no packets finishing on the page (npackets == 0). */ |
| result = gst_ogg_pad_stream_out (pad, 0); |
| |
| if (pad->continued) { |
| ogg_packet packet; |
| |
| /* now send the continued pages to the stream layer */ |
| while (pad->continued) { |
| ogg_page *p = (ogg_page *) pad->continued->data; |
| |
| GST_LOG_OBJECT (ogg, "submitting continued page %p", p); |
| if (ogg_stream_pagein (&pad->map.stream, p) != 0) |
| goto choked; |
| |
| pad->continued = g_list_delete_link (pad->continued, pad->continued); |
| |
| /* free the page */ |
| gst_ogg_page_free (p); |
| } |
| |
| GST_LOG_OBJECT (ogg, "flushing last continued packet"); |
| /* flush 1 continued packet in the stream layer */ |
| result = gst_ogg_pad_stream_out (pad, 1); |
| |
| /* flush all remaining packets, we pushed them in the previous round. |
| * We don't use _reset() because we still want to get the discont when |
| * we submit a next page. */ |
| while (ogg_stream_packetout (&pad->map.stream, &packet) != 0); |
| } |
| |
| done: |
| /* keep continued pages (only in reverse mode) */ |
| if (continued) { |
| ogg_page *p = gst_ogg_page_copy (page); |
| |
| GST_LOG_OBJECT (ogg, "keeping continued page %p", p); |
| pad->continued = g_list_prepend (pad->continued, p); |
| } |
| |
| return result; |
| |
| choked: |
| { |
| GST_WARNING_OBJECT (ogg, |
| "ogg stream choked on page (serial %08x), " |
| "resetting stream", pad->map.serialno); |
| gst_ogg_pad_reset (pad); |
| /* we continue to recover */ |
| return GST_FLOW_OK; |
| } |
| } |
| |
| |
| static GstOggChain * |
| gst_ogg_chain_new (GstOggDemux * ogg) |
| { |
| GstOggChain *chain = g_slice_new0 (GstOggChain); |
| |
| GST_DEBUG_OBJECT (ogg, "creating new chain %p", chain); |
| chain->ogg = ogg; |
| chain->offset = -1; |
| chain->bytes = -1; |
| chain->have_bos = FALSE; |
| chain->streams = g_array_new (FALSE, TRUE, sizeof (GstOggPad *)); |
| chain->begin_time = GST_CLOCK_TIME_NONE; |
| chain->segment_start = GST_CLOCK_TIME_NONE; |
| chain->segment_stop = GST_CLOCK_TIME_NONE; |
| chain->total_time = GST_CLOCK_TIME_NONE; |
| |
| return chain; |
| } |
| |
| static void |
| gst_ogg_chain_free (GstOggChain * chain) |
| { |
| gint i; |
| |
| for (i = 0; i < chain->streams->len; i++) { |
| GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
| |
| gst_object_unref (pad); |
| } |
| g_array_free (chain->streams, TRUE); |
| g_slice_free (GstOggChain, chain); |
| } |
| |
| static void |
| gst_ogg_pad_mark_discont (GstOggPad * pad) |
| { |
| GST_LOG_OBJECT (pad, "Marking discont on pad"); |
| pad->discont = TRUE; |
| pad->map.last_size = 0; |
| } |
| |
| static void |
| gst_ogg_chain_mark_discont (GstOggChain * chain) |
| { |
| gint i; |
| |
| for (i = 0; i < chain->streams->len; i++) { |
| GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
| |
| gst_ogg_pad_mark_discont (pad); |
| } |
| } |
| |
| static void |
| gst_ogg_chain_reset (GstOggChain * chain) |
| { |
| gint i; |
| |
| for (i = 0; i < chain->streams->len; i++) { |
| GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
| |
| gst_ogg_pad_reset (pad); |
| } |
| } |
| |
| static GstOggPad * |
| gst_ogg_chain_new_stream (GstOggChain * chain, guint32 serialno) |
| { |
| GstOggPad *ret; |
| gchar *name; |
| |
| GST_DEBUG_OBJECT (chain->ogg, |
| "creating new stream %08x in chain %p", serialno, chain); |
| |
| name = g_strdup_printf ("src_%08x", serialno); |
| ret = g_object_new (GST_TYPE_OGG_PAD, "name", name, NULL); |
| g_free (name); |
| /* we own this one */ |
| gst_object_ref_sink (ret); |
| |
| GST_PAD_DIRECTION (ret) = GST_PAD_SRC; |
| gst_ogg_pad_mark_discont (ret); |
| |
| ret->chain = chain; |
| ret->ogg = chain->ogg; |
| |
| ret->map.serialno = serialno; |
| if (ogg_stream_init (&ret->map.stream, serialno) != 0) |
| goto init_failed; |
| |
| GST_DEBUG_OBJECT (chain->ogg, |
| "created new ogg src %p for stream with serial %08x", ret, serialno); |
| |
| g_array_append_val (chain->streams, ret); |
| gst_pad_set_active (GST_PAD_CAST (ret), TRUE); |
| |
| return ret; |
| |
| /* ERRORS */ |
| init_failed: |
| { |
| GST_ERROR ("Could not initialize ogg_stream struct for serial %08x", |
| serialno); |
| gst_object_unref (ret); |
| return NULL; |
| } |
| } |
| |
| static GstOggPad * |
| gst_ogg_chain_get_stream (GstOggChain * chain, guint32 serialno) |
| { |
| gint i; |
| |
| for (i = 0; i < chain->streams->len; i++) { |
| GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
| |
| if (pad->map.serialno == serialno) |
| return pad; |
| } |
| return NULL; |
| } |
| |
| static gboolean |
| gst_ogg_chain_has_stream (GstOggChain * chain, guint32 serialno) |
| { |
| return gst_ogg_chain_get_stream (chain, serialno) != NULL; |
| } |
| |
| /* signals and args */ |
| enum |
| { |
| /* FILL ME */ |
| LAST_SIGNAL |
| }; |
| |
| enum |
| { |
| ARG_0 |
| /* FILL ME */ |
| }; |
| |
| static GstStaticPadTemplate ogg_demux_src_template_factory = |
| GST_STATIC_PAD_TEMPLATE ("src_%08x", |
| GST_PAD_SRC, |
| GST_PAD_SOMETIMES, |
| GST_STATIC_CAPS_ANY); |
| |
| static GstStaticPadTemplate ogg_demux_sink_template_factory = |
| GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("application/ogg; audio/ogg; video/ogg; application/kate") |
| ); |
| |
| static void gst_ogg_demux_finalize (GObject * object); |
| |
| static GstFlowReturn gst_ogg_demux_read_chain (GstOggDemux * ogg, |
| GstOggChain ** chain); |
| static GstFlowReturn gst_ogg_demux_read_end_chain (GstOggDemux * ogg, |
| GstOggChain * chain); |
| |
| static gboolean gst_ogg_demux_sink_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| static void gst_ogg_demux_loop (GstOggPad * pad); |
| static GstFlowReturn gst_ogg_demux_chain (GstPad * pad, GstObject * parent, |
| GstBuffer * buffer); |
| static gboolean gst_ogg_demux_sink_activate (GstPad * sinkpad, |
| GstObject * parent); |
| static gboolean gst_ogg_demux_sink_activate_mode (GstPad * sinkpad, |
| GstObject * parent, GstPadMode mode, gboolean active); |
| static GstStateChangeReturn gst_ogg_demux_change_state (GstElement * element, |
| GstStateChange transition); |
| |
| static void gst_ogg_print (GstOggDemux * demux); |
| |
| #define gst_ogg_demux_parent_class parent_class |
| G_DEFINE_TYPE (GstOggDemux, gst_ogg_demux, GST_TYPE_ELEMENT); |
| |
| static void |
| gst_ogg_demux_class_init (GstOggDemuxClass * klass) |
| { |
| GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| |
| gst_element_class_set_static_metadata (gstelement_class, |
| "Ogg demuxer", "Codec/Demuxer", |
| "demux ogg streams (info about ogg: http://xiph.org)", |
| "Wim Taymans <wim@fluendo.com>"); |
| |
| gst_element_class_add_static_pad_template (gstelement_class, |
| &ogg_demux_sink_template_factory); |
| gst_element_class_add_static_pad_template (gstelement_class, |
| &ogg_demux_src_template_factory); |
| |
| gstelement_class->change_state = gst_ogg_demux_change_state; |
| gstelement_class->send_event = gst_ogg_demux_receive_event; |
| |
| gobject_class->finalize = gst_ogg_demux_finalize; |
| } |
| |
| static void |
| gst_ogg_demux_init (GstOggDemux * ogg) |
| { |
| /* create the sink pad */ |
| ogg->sinkpad = |
| gst_pad_new_from_static_template (&ogg_demux_sink_template_factory, |
| "sink"); |
| |
| gst_pad_set_event_function (ogg->sinkpad, gst_ogg_demux_sink_event); |
| gst_pad_set_chain_function (ogg->sinkpad, gst_ogg_demux_chain); |
| gst_pad_set_activate_function (ogg->sinkpad, gst_ogg_demux_sink_activate); |
| gst_pad_set_activatemode_function (ogg->sinkpad, |
| gst_ogg_demux_sink_activate_mode); |
| gst_element_add_pad (GST_ELEMENT (ogg), ogg->sinkpad); |
| |
| g_mutex_init (&ogg->chain_lock); |
| g_mutex_init (&ogg->push_lock); |
| g_mutex_init (&ogg->seek_event_mutex); |
| g_cond_init (&ogg->seek_event_cond); |
| g_cond_init (&ogg->thread_started_cond); |
| |
| ogg->chains = g_array_new (FALSE, TRUE, sizeof (GstOggChain *)); |
| |
| ogg->stats_nbisections = 0; |
| ogg->stats_bisection_steps[0] = 0; |
| ogg->stats_bisection_steps[1] = 0; |
| ogg->stats_bisection_max_steps[0] = 0; |
| ogg->stats_bisection_max_steps[1] = 0; |
| |
| ogg->newsegment = NULL; |
| |
| ogg->chunk_size = CHUNKSIZE; |
| ogg->flowcombiner = gst_flow_combiner_new (); |
| } |
| |
| static void |
| gst_ogg_demux_finalize (GObject * object) |
| { |
| GstOggDemux *ogg; |
| |
| ogg = GST_OGG_DEMUX (object); |
| |
| g_array_free (ogg->chains, TRUE); |
| g_mutex_clear (&ogg->chain_lock); |
| g_mutex_clear (&ogg->push_lock); |
| g_cond_clear (&ogg->seek_event_cond); |
| g_cond_clear (&ogg->thread_started_cond); |
| g_mutex_clear (&ogg->seek_event_mutex); |
| |
| ogg_sync_clear (&ogg->sync); |
| |
| if (ogg->newsegment) |
| gst_event_unref (ogg->newsegment); |
| |
| gst_flow_combiner_free (ogg->flowcombiner); |
| |
| if (ogg->building_chain) |
| gst_ogg_chain_free (ogg->building_chain); |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static void |
| gst_ogg_demux_reset_streams (GstOggDemux * ogg) |
| { |
| GstOggChain *chain; |
| guint i; |
| |
| chain = ogg->current_chain; |
| if (chain == NULL) |
| return; |
| |
| for (i = 0; i < chain->streams->len; i++) { |
| GstOggPad *stream = g_array_index (chain->streams, GstOggPad *, i); |
| |
| stream->start_time = -1; |
| stream->map.accumulated_granule = 0; |
| stream->current_granule = -1; |
| stream->keyframe_granule = -1; |
| } |
| ogg->building_chain = chain; |
| GST_DEBUG_OBJECT (ogg, "Resetting current chain"); |
| ogg->current_chain = NULL; |
| ogg->resync = TRUE; |
| gst_ogg_chain_mark_discont (chain); |
| |
| ogg->chunk_size = CHUNKSIZE; |
| } |
| |
| static gboolean |
| gst_ogg_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) |
| { |
| gboolean res; |
| GstOggDemux *ogg; |
| |
| ogg = GST_OGG_DEMUX (parent); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_FLUSH_START: |
| res = gst_ogg_demux_send_event (ogg, event); |
| break; |
| case GST_EVENT_FLUSH_STOP: |
| GST_DEBUG_OBJECT (ogg, "got a flush stop event"); |
| ogg_sync_reset (&ogg->sync); |
| res = gst_ogg_demux_send_event (ogg, event); |
| if (ogg->pullmode || ogg->push_state != PUSH_DURATION) { |
| /* it's starting to feel reaaaally dirty :( |
| if we're on a spliced seek to get duration, don't reset streams, |
| we'll need them for the delayed seek */ |
| gst_ogg_demux_reset_streams (ogg); |
| } |
| break; |
| case GST_EVENT_SEGMENT: |
| GST_DEBUG_OBJECT (ogg, "got a new segment event"); |
| { |
| GstSegment segment; |
| gboolean update; |
| |
| gst_event_copy_segment (event, &segment); |
| |
| if (segment.format == GST_FORMAT_BYTES) { |
| GST_PUSH_LOCK (ogg); |
| ogg->push_byte_offset = segment.start; |
| ogg->push_last_seek_offset = segment.start; |
| |
| if (gst_event_get_seqnum (event) == ogg->seqnum) { |
| GstSeekType stop_type = GST_SEEK_TYPE_NONE; |
| if (ogg->push_seek_time_original_stop != -1) |
| stop_type = GST_SEEK_TYPE_SET; |
| gst_segment_do_seek (&ogg->segment, ogg->push_seek_rate, |
| GST_FORMAT_TIME, ogg->push_seek_flags, GST_SEEK_TYPE_SET, |
| ogg->push_seek_time_original_target, stop_type, |
| ogg->push_seek_time_original_stop, &update); |
| } |
| |
| if (!ogg->pullmode && !(ogg->push_seek_flags & GST_SEEK_FLAG_FLUSH)) { |
| int i; |
| GstOggChain *chain = ogg->current_chain; |
| |
| ogg->push_seek_flags = 0; |
| if (!chain) { |
| /* This will happen when we bisect, as we clear the chain when |
| we do the first seek. On subsequent ones, we just reset the |
| ogg sync object as we already reset the chain */ |
| GST_DEBUG_OBJECT (ogg, "No chain, just resetting ogg sync"); |
| ogg_sync_reset (&ogg->sync); |
| } else { |
| /* reset pad push mode seeking state */ |
| for (i = 0; i < chain->streams->len; i++) { |
| GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
| pad->push_kf_time = GST_CLOCK_TIME_NONE; |
| pad->push_sync_time = GST_CLOCK_TIME_NONE; |
| } |
| ogg_sync_reset (&ogg->sync); |
| gst_ogg_demux_reset_streams (ogg); |
| } |
| } |
| |
| if (!ogg->pullmode) { |
| if (ogg->seek_event_drop_till == gst_event_get_seqnum (event)) { |
| GST_DEBUG_OBJECT (ogg, "Got event seqnum %u, stopping dropping", |
| ogg->seek_event_drop_till); |
| ogg->seek_event_drop_till = 0; |
| } |
| } |
| GST_PUSH_UNLOCK (ogg); |
| } else { |
| GST_WARNING_OBJECT (ogg, "unexpected segment format: %s", |
| gst_format_get_name (segment.format)); |
| } |
| } |
| |
| gst_event_unref (event); |
| res = TRUE; |
| break; |
| case GST_EVENT_EOS: |
| { |
| GST_DEBUG_OBJECT (ogg, "got an EOS event"); |
| GST_PUSH_LOCK (ogg); |
| if (ogg->push_state == PUSH_DURATION) { |
| GST_DEBUG_OBJECT (ogg, "Got EOS while determining length"); |
| res = gst_ogg_demux_seek_back_after_push_duration_check_unlock (ogg); |
| if (res != GST_FLOW_OK) { |
| GST_DEBUG_OBJECT (ogg, "Error seeking back after duration check: %d", |
| res); |
| } |
| break; |
| } else |
| GST_PUSH_UNLOCK (ogg); |
| res = gst_ogg_demux_send_event (ogg, event); |
| if (ogg->current_chain == NULL) { |
| GST_WARNING_OBJECT (ogg, |
| "EOS while trying to retrieve chain, seeking disabled"); |
| ogg->push_disable_seeking = TRUE; |
| res = TRUE; |
| } |
| break; |
| } |
| default: |
| res = gst_pad_event_default (pad, parent, event); |
| break; |
| } |
| |
| return res; |
| } |
| |
| /* submit the given buffer to the ogg sync */ |
| static GstFlowReturn |
| gst_ogg_demux_submit_buffer (GstOggDemux * 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)) |
| goto no_buffer; |
| |
| gst_buffer_extract (buffer, 0, oggbuffer, size); |
| |
| if (G_UNLIKELY (ogg_sync_wrote (&ogg->sync, size) < 0)) |
| goto write_failed; |
| |
| if (!ogg->pullmode) { |
| GST_PUSH_LOCK (ogg); |
| ogg->push_byte_offset += size; |
| GST_PUSH_UNLOCK (ogg); |
| } |
| |
| done: |
| gst_buffer_unref (buffer); |
| |
| return ret; |
| |
| /* ERRORS */ |
| no_buffer: |
| { |
| GST_ELEMENT_ERROR (ogg, STREAM, DECODE, |
| (NULL), ("failed to get ogg sync buffer")); |
| ret = GST_FLOW_ERROR; |
| goto done; |
| } |
| write_failed: |
| { |
| GST_ELEMENT_ERROR (ogg, STREAM, DECODE, (NULL), |
| ("failed to write %" G_GSIZE_FORMAT " bytes to the sync buffer", size)); |
| ret = GST_FLOW_ERROR; |
| goto done; |
| } |
| } |
| |
| /* in random access mode this code updates the current read position |
| * and resets the ogg sync buffer so that the next read will happen |
| * from this new location. |
| */ |
| static void |
| gst_ogg_demux_seek (GstOggDemux * ogg, gint64 offset) |
| { |
| GST_LOG_OBJECT (ogg, "seeking to %" G_GINT64_FORMAT, offset); |
| |
| ogg->offset = offset; |
| ogg->read_offset = offset; |
| ogg_sync_reset (&ogg->sync); |
| } |
| |
| /* read more data from the current offset and submit to |
| * the ogg sync layer. |
| */ |
| static GstFlowReturn |
| gst_ogg_demux_get_data (GstOggDemux * ogg, gint64 end_offset) |
| { |
| GstFlowReturn ret; |
| GstBuffer *buffer; |
| gchar *oggbuffer; |
| gsize size; |
| |
| GST_LOG_OBJECT (ogg, |
| "get data %" G_GINT64_FORMAT " %" G_GINT64_FORMAT " %" G_GINT64_FORMAT, |
| ogg->read_offset, ogg->length, end_offset); |
| |
| if (end_offset > 0 && ogg->read_offset >= end_offset) |
| goto boundary_reached; |
| |
| if (ogg->read_offset == ogg->length) |
| goto eos; |
| |
| oggbuffer = ogg_sync_buffer (&ogg->sync, ogg->chunk_size); |
| if (G_UNLIKELY (oggbuffer == NULL)) |
| goto no_buffer; |
| |
| buffer = |
| gst_buffer_new_wrapped_full (0, oggbuffer, ogg->chunk_size, 0, |
| ogg->chunk_size, NULL, NULL); |
| |
| ret = |
| gst_pad_pull_range (ogg->sinkpad, ogg->read_offset, ogg->chunk_size, |
| &buffer); |
| if (ret != GST_FLOW_OK) |
| goto error; |
| |
| size = gst_buffer_get_size (buffer); |
| |
| if (G_UNLIKELY (ogg_sync_wrote (&ogg->sync, size) < 0)) |
| goto write_failed; |
| |
| ogg->read_offset += size; |
| gst_buffer_unref (buffer); |
| |
| return ret; |
| |
| /* ERROR */ |
| boundary_reached: |
| { |
| GST_LOG_OBJECT (ogg, "reached boundary"); |
| return GST_FLOW_LIMIT; |
| } |
| eos: |
| { |
| GST_LOG_OBJECT (ogg, "reached EOS"); |
| return GST_FLOW_EOS; |
| } |
| no_buffer: |
| { |
| GST_ELEMENT_ERROR (ogg, STREAM, DECODE, |
| (NULL), ("failed to get ogg sync buffer")); |
| return GST_FLOW_ERROR; |
| } |
| error: |
| { |
| GST_WARNING_OBJECT (ogg, "got %d (%s) from pull range", ret, |
| gst_flow_get_name (ret)); |
| gst_buffer_unref (buffer); |
| return ret; |
| } |
| write_failed: |
| { |
| GST_ELEMENT_ERROR (ogg, STREAM, DECODE, (NULL), |
| ("failed to write %" G_GSIZE_FORMAT " bytes to the sync buffer", size)); |
| gst_buffer_unref (buffer); |
| return GST_FLOW_ERROR; |
| } |
| } |
| |
| /* Read the next page from the current offset. |
| * boundary: number of bytes ahead we allow looking for; |
| * -1 if no boundary |
| * |
| * @offset will contain the offset the next page starts at when this function |
| * returns GST_FLOW_OK. |
| * |
| * GST_FLOW_EOS is returned on EOS. |
| * |
| * GST_FLOW_LIMIT is returned when we did not find a page before the |
| * boundary. If @boundary is -1, this is never returned. |
| * |
| * Any other error returned while retrieving data from the peer is returned as |
| * is. |
| */ |
| static GstFlowReturn |
| gst_ogg_demux_get_next_page (GstOggDemux * ogg, ogg_page * og, |
| gint64 boundary, gint64 * offset) |
| { |
| gint64 end_offset = -1; |
| GstFlowReturn ret; |
| |
| GST_LOG_OBJECT (ogg, |
| "get next page, current offset %" G_GINT64_FORMAT ", bytes boundary %" |
| G_GINT64_FORMAT, ogg->offset, boundary); |
| |
| if (boundary >= 0) |
| end_offset = ogg->offset + boundary; |
| |
| while (TRUE) { |
| glong more; |
| |
| if (end_offset > 0 && ogg->offset >= end_offset) |
| goto boundary_reached; |
| |
| more = ogg_sync_pageseek (&ogg->sync, og); |
| |
| GST_LOG_OBJECT (ogg, "pageseek gave %ld", more); |
| |
| if (more < 0) { |
| /* skipped n bytes */ |
| ogg->offset -= more; |
| GST_LOG_OBJECT (ogg, "skipped %ld bytes, offset %" G_GINT64_FORMAT, |
| more, ogg->offset); |
| } else if (more == 0) { |
| /* we need more data */ |
| if (boundary == 0) |
| goto boundary_reached; |
| |
| GST_LOG_OBJECT (ogg, "need more data"); |
| ret = gst_ogg_demux_get_data (ogg, end_offset); |
| if (ret != GST_FLOW_OK) |
| break; |
| } else { |
| gint64 res_offset = ogg->offset; |
| |
| /* got a page. Return the offset at the page beginning, |
| advance the internal offset past the page end */ |
| if (offset) |
| *offset = res_offset; |
| ret = GST_FLOW_OK; |
| |
| ogg->offset += more; |
| |
| GST_LOG_OBJECT (ogg, |
| "got page at %" G_GINT64_FORMAT ", serial %08x, end at %" |
| G_GINT64_FORMAT ", granule %" G_GINT64_FORMAT, res_offset, |
| ogg_page_serialno (og), ogg->offset, |
| (gint64) ogg_page_granulepos (og)); |
| break; |
| } |
| } |
| GST_LOG_OBJECT (ogg, "returning %d", ret); |
| |
| return ret; |
| |
| /* ERRORS */ |
| boundary_reached: |
| { |
| GST_LOG_OBJECT (ogg, |
| "offset %" G_GINT64_FORMAT " >= end_offset %" G_GINT64_FORMAT, |
| ogg->offset, end_offset); |
| return GST_FLOW_LIMIT; |
| } |
| } |
| |
| /* from the current offset, find the previous page, seeking backwards |
| * until we find the page. |
| */ |
| static GstFlowReturn |
| gst_ogg_demux_get_prev_page (GstOggDemux * ogg, ogg_page * og, gint64 * offset) |
| { |
| GstFlowReturn ret; |
| gint64 begin = ogg->offset; |
| gint64 end = begin; |
| gint64 cur_offset = -1; |
| |
| GST_LOG_OBJECT (ogg, "getting page before %" G_GINT64_FORMAT, begin); |
| |
| while (cur_offset == -1) { |
| begin -= ogg->chunk_size; |
| if (begin < 0) |
| begin = 0; |
| |
| /* seek ogg->chunk_size back */ |
| GST_LOG_OBJECT (ogg, "seeking back to %" G_GINT64_FORMAT, begin); |
| gst_ogg_demux_seek (ogg, begin); |
| |
| /* now continue reading until we run out of data, if we find a page |
| * start, we save it. It might not be the final page as there could be |
| * another page after this one. */ |
| while (ogg->offset < end) { |
| gint64 new_offset, boundary; |
| |
| /* An Ogg page cannot be more than a bit less than 64 KB, so we can |
| bound the boundary to that size when searching backwards if we |
| haven't found a page yet. So the most we have to look at is twice |
| the max page size, which is the worst case if we start scanning |
| just after a large page, after which also lies a large page. */ |
| boundary = end - ogg->offset; |
| if (boundary > 2 * MAX_OGG_PAGE_SIZE) |
| boundary = 2 * MAX_OGG_PAGE_SIZE; |
| |
| ret = gst_ogg_demux_get_next_page (ogg, og, boundary, &new_offset); |
| /* we hit the upper limit, offset contains the last page start */ |
| if (ret == GST_FLOW_LIMIT) { |
| GST_LOG_OBJECT (ogg, "hit limit"); |
| break; |
| } |
| /* something went wrong */ |
| if (ret == GST_FLOW_EOS) { |
| new_offset = 0; |
| GST_LOG_OBJECT (ogg, "got unexpected"); |
| /* We hit EOS. */ |
| goto beach; |
| } else if (ret != GST_FLOW_OK) { |
| GST_LOG_OBJECT (ogg, "got error %d", ret); |
| return ret; |
| } |
| |
| GST_LOG_OBJECT (ogg, "found page at %" G_GINT64_FORMAT, new_offset); |
| |
| /* offset is next page start */ |
| cur_offset = new_offset; |
| } |
| } |
| |
| GST_LOG_OBJECT (ogg, "found previous page at %" G_GINT64_FORMAT, cur_offset); |
| |
| /* we have the offset. Actually snork and hold the page now */ |
| gst_ogg_demux_seek (ogg, cur_offset); |
| ret = gst_ogg_demux_get_next_page (ogg, og, -1, NULL); |
| if (ret != GST_FLOW_OK) { |
| GST_WARNING_OBJECT (ogg, "can't get last page at %" G_GINT64_FORMAT, |
| cur_offset); |
| /* this shouldn't be possible */ |
| return ret; |
| } |
| |
| if (offset) |
| *offset = cur_offset; |
| |
| beach: |
| return ret; |
| } |
| |
| static gboolean |
| gst_ogg_demux_deactivate_current_chain (GstOggDemux * ogg) |
| { |
| gint i; |
| GstOggChain *chain = ogg->current_chain; |
| |
| if (chain == NULL) |
| return TRUE; |
| |
| GST_DEBUG_OBJECT (ogg, "deactivating chain %p", chain); |
| |
| /* send EOS on all the pads */ |
| for (i = 0; i < chain->streams->len; i++) { |
| GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
| GstEvent *event; |
| |
| if (!pad->added) |
| continue; |
| |
| event = gst_event_new_eos (); |
| gst_event_set_seqnum (event, ogg->seqnum); |
| gst_pad_push_event (GST_PAD_CAST (pad), event); |
| |
| GST_DEBUG_OBJECT (ogg, "removing pad %" GST_PTR_FORMAT, pad); |
| |
| /* deactivate first */ |
| gst_pad_set_active (GST_PAD_CAST (pad), FALSE); |
| |
| gst_flow_combiner_remove_pad (ogg->flowcombiner, GST_PAD_CAST (pad)); |
| |
| gst_element_remove_pad (GST_ELEMENT (ogg), GST_PAD_CAST (pad)); |
| |
| pad->added = FALSE; |
| } |
| |
| /* if we cannot seek back to the chain, we can destroy the chain |
| * completely */ |
| if (!ogg->pullmode) { |
| if (ogg->building_chain == chain) |
| ogg->building_chain = NULL; |
| ogg->current_chain = NULL; |
| gst_ogg_chain_free (chain); |
| } |
| |
| return TRUE; |
| } |
| |
| static GstCaps * |
| gst_ogg_demux_set_header_on_caps (GstOggDemux * ogg, GstCaps * caps, |
| GList * headers) |
| { |
| GstStructure *structure; |
| GValue array = { 0 }; |
| |
| GST_LOG_OBJECT (ogg, "caps: %" GST_PTR_FORMAT, caps); |
| |
| if (G_UNLIKELY (!caps)) |
| return NULL; |
| if (G_UNLIKELY (!headers)) |
| return caps; |
| |
| caps = gst_caps_make_writable (caps); |
| structure = gst_caps_get_structure (caps, 0); |
| |
| g_value_init (&array, GST_TYPE_ARRAY); |
| |
| while (headers) { |
| GValue value = { 0 }; |
| GstBuffer *buffer; |
| ogg_packet *op = headers->data; |
| g_assert (op); |
| buffer = gst_buffer_new_and_alloc (op->bytes); |
| if (op->bytes) |
| gst_buffer_fill (buffer, 0, op->packet, op->bytes); |
| GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_HEADER); |
| g_value_init (&value, GST_TYPE_BUFFER); |
| gst_value_take_buffer (&value, buffer); |
| gst_value_array_append_value (&array, &value); |
| g_value_unset (&value); |
| headers = headers->next; |
| } |
| |
| gst_structure_take_value (structure, "streamheader", &array); |
| GST_LOG_OBJECT (ogg, "here are the newly set caps: %" GST_PTR_FORMAT, caps); |
| |
| return caps; |
| } |
| |
| static void |
| gst_ogg_demux_push_queued_buffers (GstOggDemux * ogg, GstOggPad * pad) |
| { |
| GList *walk; |
| |
| /* push queued packets */ |
| for (walk = pad->map.queued; walk; walk = g_list_next (walk)) { |
| ogg_packet *p = walk->data; |
| |
| gst_ogg_demux_chain_peer (pad, p, TRUE); |
| _ogg_packet_free (p); |
| } |
| /* and free the queued buffers */ |
| g_list_free (pad->map.queued); |
| pad->map.queued = NULL; |
| } |
| |
| static gboolean |
| gst_ogg_demux_activate_chain (GstOggDemux * ogg, GstOggChain * chain, |
| GstEvent * event) |
| { |
| gint i; |
| gint bitrate, idx_bitrate; |
| |
| g_return_val_if_fail (chain != NULL, FALSE); |
| |
| if (chain == ogg->current_chain) { |
| if (event) |
| gst_event_unref (event); |
| |
| for (i = 0; i < chain->streams->len; i++) { |
| GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); |
| gst_ogg_demux_push_queued_buffers (ogg, pad); |
| } |
| return TRUE; |
| } |
| |
| |
| GST_DEBUG_OBJECT (ogg, "activating chain %p", chain); |
| |
| bitrate = idx_bitrate = 0; |
| |
| /* first add the pads */ |
| for (i = 0; i < chain->streams->len; i++) { |
| GstOggPad *pad; |
| GstEvent *ss_event; |
| gchar *stream_id; |
| |
| pad = g_array_index (chain->streams, GstOggPad *, i); |
| |
| if (pad->map.idx_bitrate) |
| idx_bitrate = MAX (idx_bitrate, pad->map.idx_bitrate); |
| |
| bitrate += pad->map.bitrate; |
| |
| /* mark discont */ |
| gst_ogg_pad_mark_discont (pad); |
| pad->last_ret = GST_FLOW_OK; |
| |
| if (pad->map.is_skeleton || pad->map.is_cmml || pad->added |
| || !pad->map.caps) |
| continue; |
| |
| GST_DEBUG_OBJECT (ogg, "adding pad %" GST_PTR_FORMAT, pad); |
| |
| /* activate first */ |
| gst_pad_set_active (GST_PAD_CAST (pad), TRUE); |
| |
| stream_id = |
| gst_pad_create_stream_id_printf (GST_PAD (pad), GST_ELEMENT_CAST (ogg), |
| "%08x", pad->map.serialno); |
| ss_event = |
| gst_pad_get_sticky_event (ogg->sinkpad, GST_EVENT_STREAM_START, 0); |
| if (ss_event) { |
| if (gst_event_parse_group_id (ss_event, &ogg->group_id)) |
| ogg->have_group_id = TRUE; |
| else |
| ogg->have_group_id = FALSE; |
| gst_event_unref (ss_event); |
| } else if (!ogg->have_group_id) { |
| ogg->have_group_id = TRUE; |
| ogg->group_id = gst_util_group_id_next (); |
| } |
| ss_event = gst_event_new_stream_start (stream_id); |
| if (ogg->have_group_id) |
| gst_event_set_group_id (ss_event, ogg->group_id); |
| |
| gst_pad_push_event (GST_PAD (pad), ss_event); |
| g_free (stream_id); |
| |
| /* Set headers on caps */ |
| pad->map.caps = |
| gst_ogg_demux_set_header_on_caps (ogg, pad->map.caps, pad->map.headers); |
| gst_pad_set_caps |