#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
+
+/* FIXME 0.11: suppress warnings for deprecated API such as GStaticRecMutex
+ * with newer GLib versions (>= 2.31.0) */
+#define GLIB_DISABLE_DEPRECATION_WARNINGS
+
#include <string.h>
#include <gst/gst-i18n-plugin.h>
#include <gst/tag/tag.h>
#include "gstoggdemux.h"
+#include "gst/glib-compat-private.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 (64*1024)
+
+/* stop duration checks within this much of EOS */
+#define EOS_AVOIDANCE_THRESHOLD 8192
+
#define GST_FLOW_LIMIT GST_FLOW_CUSTOM_ERROR
+#define GST_FLOW_SKIP_PUSH GST_FLOW_CUSTOM_SUCCESS_1
#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_new0 (ogg_packet, 1);
+ ogg_packet *ret = g_slice_new (ogg_packet);
*ret = *packet;
ret->packet = g_memdup (packet->packet, packet->bytes);
_ogg_packet_free (ogg_packet * packet)
{
g_free (packet->packet);
- g_free (packet);
+ g_slice_free (ogg_packet, packet);
}
static ogg_page *
gst_ogg_page_copy (ogg_page * page)
{
- ogg_page *p = g_new0 (ogg_page, 1);
+ ogg_page *p = g_slice_new (ogg_page);
/* make a copy of the page */
p->header = g_memdup (page->header, page->header_len);
{
g_free (page->header);
g_free (page->body);
- g_free (page);
+ 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,
static gboolean gst_ogg_pad_event (GstPad * pad, GstEvent * event);
static GstCaps *gst_ogg_pad_getcaps (GstPad * pad);
static GstOggPad *gst_ogg_chain_get_stream (GstOggChain * chain,
- glong serialno);
+ guint32 serialno);
static GstFlowReturn gst_ogg_demux_combine_flows (GstOggDemux * ogg,
GstOggPad * pad, GstFlowReturn ret);
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);
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
if (ogg->pullmode) {
seekable = TRUE;
stop = ogg->total_time;
+ } else if (ogg->push_disable_seeking) {
+ seekable = FALSE;
} else if (ogg->current_chain->streams->len) {
gint i;
GstOggPad *pad =
g_array_index (ogg->current_chain->streams, GstOggPad *, i);
- seekable |= (pad->map.index != NULL && pad->map.n_index != 0);
-
+ seekable = TRUE;
if (pad->map.index != NULL && pad->map.n_index != 0) {
GstOggIndex *idx;
GstClockTime idx_time;
stop = idx_time;
else
stop = MAX (idx_time, stop);
+ } else {
+ stop = -1; /* we've no clue, sadly, without seeking */
}
}
}
GstOggDemux *ogg = pad->ogg;
#endif
- GST_DEBUG_OBJECT (ogg, "%p queueing data serial %08lx", pad,
- pad->map.serialno);
+ 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));
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);
+ 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 %08lx", pad, pad->map.serialno);
+ "%p streaming to peer serial %08x", pad, pad->map.serialno);
if (pad->map.is_ogm) {
const guint8 *data;
}
/* get timing info for the packet */
- duration = gst_ogg_stream_get_packet_duration (&pad->map, packet);
- GST_DEBUG_OBJECT (ogg, "packet duration %" G_GUINT64_FORMAT, duration);
+ if (gst_ogg_stream_packet_is_header (&pad->map, packet)) {
+ 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 (packet->b_o_s) {
out_timestamp = GST_CLOCK_TIME_NONE;
pad->current_granule);
} else if (ogg->segment.rate > 0.0 && pad->current_granule != -1) {
pad->current_granule += duration;
- GST_DEBUG_OBJECT (ogg, "interpollating granule %" G_GUINT64_FORMAT,
+ if (gst_ogg_stream_packet_is_key_frame (&pad->map, packet)) {
+ pad->keyframe_granule = pad->current_granule;
+ }
+ GST_DEBUG_OBJECT (ogg, "interpolating granule %" G_GUINT64_FORMAT,
pad->current_granule);
}
if (ogg->segment.rate < 0.0 && packet->granulepos == -1) {
for (i = 0; i < chain->streams->len; i++) {
GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);
- if (pad->map.is_sparse)
+ 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) {
- start_time = G_MAXUINT64;
- break;
+ 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
* typefind code for the pad if this is the first packet for this
* stream
GstOggDemux *ogg = pad->ogg;
- GST_DEBUG_OBJECT (ogg, "%p submit packet serial %08lx", pad,
- pad->map.serialno);
+ 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 (gst_ogg_map_parse_fisbone (&pad->map, packet->packet, packet->bytes,
&serialno, &type)) {
- GST_WARNING_OBJECT (pad->ogg,
+ GST_DEBUG_OBJECT (pad->ogg,
"got skeleton packet for stream 0x%08x", serialno);
skel_pad = gst_ogg_chain_get_stream (pad->chain, serialno);
pad->start_time = gst_ogg_stream_granule_to_time (&pad->map,
start_granule);
- GST_DEBUG ("start time %" G_GINT64_FORMAT, pad->start_time);
+ 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->keyframe_granule);
segment_time = chain->begin_time;
/* create the newsegment event we are going to send out */
- event = gst_event_new_new_segment (FALSE, ogg->segment.rate,
- GST_FORMAT_TIME, start_time, chain->segment_stop, segment_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));
+ event =
+ gst_event_new_new_segment (FALSE, ogg->push_seek_rate,
+ GST_FORMAT_TIME, ogg->push_seek_time_original_target, -1,
+ ogg->push_seek_time_original_target);
+ ogg->push_state = PUSH_PLAYING;
+ } else {
+ event = gst_event_new_new_segment (FALSE, ogg->segment.rate,
+ GST_FORMAT_TIME, start_time, chain->segment_stop, segment_time);
+ }
+ GST_PUSH_UNLOCK (ogg);
ogg->resync = FALSE;
}
break;
case -1:
GST_LOG_OBJECT (ogg, "packetout discont");
- gst_ogg_chain_mark_discont (pad->chain);
+ 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.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 */
could_not_submit:
{
GST_WARNING_OBJECT (ogg,
- "could not submit packet for stream %08lx, error: %d",
- pad->map.serialno, result);
+ "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_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;
+
+ ogg->push_state = PUSH_PLAYING;
+
+ GST_PUSH_UNLOCK (ogg);
+
+ if (event) {
+ /* If there is one, perform it */
+ gst_ogg_demux_perform_seek_push (ogg, event);
+ } else {
+ /* If there wasn't, seek back at start to start normal playback */
+ 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_NONE, GST_CLOCK_TIME_NONE);
+ if (!gst_pad_push_event (ogg->sinkpad, event)) {
+ GST_WARNING_OBJECT (ogg, "Failed seeking back to start");
+ return GST_FLOW_ERROR;
+ }
+ }
+
+ return GST_FLOW_OK;
+}
+
+static float
+gst_ogg_demux_estimate_seek_quality (GstOggDemux * ogg)
+{
+ gint64 diff; /* how far from the goal we ended up */
+ gint64 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 = ogg->push_seek_time_target - ogg->push_last_seek_time;
+ if (diff < 0)
+ diff = -diff;
+ dist = 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_TIME_FORMAT ", we're off by %" GST_TIME_FORMAT
+ ", seek quality %f", GST_TIME_ARGS (dist), GST_TIME_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) {
+ 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 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 playbin2),
+ 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 + EOS_AVOIDANCE_THRESHOLD >=
+ 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 (GST_OBJECT (ogg), GST_FORMAT_TIME,
+ ogg->total_time);
+ 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;
+ int res;
+ 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;
+ 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 timestamp %" 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;
+ 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_PUSH_UNLOCK (ogg);
+ res = gst_pad_push_event (ogg->sinkpad, sevent);
+ if (!res) {
+ /* We failed to send the seek event, notify the pipeline */
+ GST_ELEMENT_ERROR (ogg, RESOURCE, SEEK, (NULL), ("Failed to seek"));
+ return GST_FLOW_ERROR;
+ }
+ 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;
+ }
+}
+
/* submit a page to an oggpad, this function will then submit all
* the packets in the page.
*/
ogg = pad->ogg;
- /* for negative rates we read pages backwards and must therefore be carefull
+ /* for negative rates we read pages backwards and must therefore be careful
* with continued pages */
if (ogg->segment.rate < 0.0) {
gint npackets;
}
}
+ /* 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;
choked:
{
GST_WARNING_OBJECT (ogg,
- "ogg stream choked on page (serial %08lx), resetting stream",
- pad->map.serialno);
+ "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_new0 (GstOggChain, 1);
+ GstOggChain *chain = g_slice_new0 (GstOggChain);
GST_DEBUG_OBJECT (ogg, "creating new chain %p", chain);
chain->ogg = ogg;
gst_object_unref (pad);
}
g_array_free (chain->streams, TRUE);
- g_free (chain);
+ g_slice_free (GstOggChain, chain);
}
static void
}
static GstOggPad *
-gst_ogg_chain_new_stream (GstOggChain * chain, glong serialno)
+gst_ogg_chain_new_stream (GstOggChain * chain, guint32 serialno)
{
GstOggPad *ret;
GstTagList *list;
gchar *name;
- GST_DEBUG_OBJECT (chain->ogg, "creating new stream %08lx in chain %p",
- serialno, chain);
+ GST_DEBUG_OBJECT (chain->ogg,
+ "creating new stream %08x in chain %p", serialno, chain);
- ret = g_object_new (GST_TYPE_OGG_PAD, NULL);
+ name = g_strdup_printf ("serial_%08x", serialno);
+ ret = g_object_new (GST_TYPE_OGG_PAD, "name", name, NULL);
+ g_free (name);
/* we own this one */
gst_object_ref (ret);
gst_object_sink (ret);
if (ogg_stream_init (&ret->map.stream, serialno) != 0)
goto init_failed;
- name = g_strdup_printf ("serial_%08lx", serialno);
- gst_object_set_name (GST_OBJECT (ret), name);
- g_free (name);
-
/* FIXME: either do something with it or remove it */
list = gst_tag_list_new ();
gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_SERIAL, serialno,
gst_tag_list_free (list);
GST_DEBUG_OBJECT (chain->ogg,
- "created new ogg src %p for stream with serial %08lx", ret, serialno);
+ "created new ogg src %p for stream with serial %08x", ret, serialno);
g_array_append_val (chain->streams, ret);
/* ERRORS */
init_failed:
{
- GST_ERROR ("Could not initialize ogg_stream struct for serial %08lx.",
+ 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, glong serialno)
+gst_ogg_chain_get_stream (GstOggChain * chain, guint32 serialno)
{
gint i;
}
static gboolean
-gst_ogg_chain_has_stream (GstOggChain * chain, glong serialno)
+gst_ogg_chain_has_stream (GstOggChain * chain, guint32 serialno)
{
return gst_ogg_chain_get_stream (chain, serialno) != NULL;
}
gboolean active);
static GstStateChangeReturn gst_ogg_demux_change_state (GstElement * element,
GstStateChange transition);
-static gboolean gst_ogg_demux_send_event (GstOggDemux * ogg, GstEvent * event);
static void gst_ogg_print (GstOggDemux * demux);
"demux ogg streams (info about ogg: http://xiph.org)",
"Wim Taymans <wim@fluendo.com>");
- gst_element_class_add_pad_template (element_class,
- gst_static_pad_template_get (&ogg_demux_sink_template_factory));
- gst_element_class_add_pad_template (element_class,
- gst_static_pad_template_get (&ogg_demux_src_template_factory));
+ gst_element_class_add_static_pad_template (element_class,
+ &ogg_demux_sink_template_factory);
+ gst_element_class_add_static_pad_template (element_class,
+ &ogg_demux_src_template_factory);
}
static void
gst_element_add_pad (GST_ELEMENT (ogg), ogg->sinkpad);
ogg->chain_lock = g_mutex_new ();
+ ogg->push_lock = g_mutex_new ();
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;
}
g_array_free (ogg->chains, TRUE);
g_mutex_free (ogg->chain_lock);
+ g_mutex_free (ogg->push_lock);
ogg_sync_clear (&ogg->sync);
if (ogg->newsegment)
stream->map.accumulated_granule = 0;
}
ogg->building_chain = chain;
+ GST_DEBUG_OBJECT (ogg, "Resetting current chain");
ogg->current_chain = NULL;
ogg->resync = TRUE;
}
GST_DEBUG_OBJECT (ogg, "got a flush stop event");
ogg_sync_reset (&ogg->sync);
res = gst_ogg_demux_send_event (ogg, event);
- gst_ogg_demux_reset_streams (ogg);
+ 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_NEWSEGMENT:
GST_DEBUG_OBJECT (ogg, "got a new segment event");
+ {
+ gboolean update;
+ GstFormat format;
+ gdouble rate, arate;
+ gint64 start, stop, time;
+
+ gst_event_parse_new_segment_full (event, &update, &rate, &arate,
+ &format, &start, &stop, &time);
+ if (format == GST_FORMAT_BYTES) {
+ GST_PUSH_LOCK (ogg);
+ ogg->push_byte_offset = start;
+ ogg->push_last_seek_offset = start;
+ GST_PUSH_UNLOCK (ogg);
+ } else {
+ GST_WARNING_OBJECT (ogg, "unexpected segment format: %s",
+ gst_format_get_name (format));
+ }
+ }
gst_event_unref (event);
res = TRUE;
break;
case GST_EVENT_EOS:
{
GST_DEBUG_OBJECT (ogg, "got an EOS event");
+#if 0
+ /* This would be what is needed (recover from EOS by going on to
+ the next step (issue the delayed seek)), but it does not work
+ if there is a queue2 upstream - see more details comment in
+ gst_ogg_pad_submit_page.
+ If I could find a way to bypass queue2 behavior, this should
+ be enabled. */
+ 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;
+ }
+ GST_PUSH_UNLOCK (ogg);
+#endif
res = gst_ogg_demux_send_event (ogg, event);
if (ogg->current_chain == NULL) {
GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL),
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);
if (ret == GST_FLOW_UNEXPECTED) {
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;
if (offset)
*offset = cur_offset;
+beach:
return ret;
}
pad->added = FALSE;
}
+
/* if we cannot seek back to the chain, we can destroy the chain
* completely */
if (!ogg->pullmode) {
gst_ogg_chain_free (chain);
}
- ogg->current_chain = NULL;
return TRUE;
}
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)
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;
}
/* after adding the new pads, remove the old pads */
gst_ogg_demux_deactivate_current_chain (ogg);
+ GST_DEBUG_OBJECT (ogg, "Setting current chain to %p", chain);
ogg->current_chain = chain;
/* we are finished now */
}
GST_DEBUG_OBJECT (ogg, "pushing queued buffers");
- /* 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;
+ gst_ogg_demux_push_queued_buffers (ogg, pad);
}
return TRUE;
}
/* get time of the keyframe */
keyframe_time =
gst_ogg_stream_granule_to_time (&pad->map, pad->keyframe_granule);
- GST_LOG_OBJECT (ogg, "stream %08lx granule time %" GST_TIME_FORMAT,
+ GST_LOG_OBJECT (ogg,
+ "stream %08x granule time %" GST_TIME_FORMAT,
pad->map.serialno, GST_TIME_ARGS (keyframe_time));
/* collect smallest value */
}
static gboolean
+gst_ogg_demux_get_duration_push (GstOggDemux * ogg, int flags)
+{
+ /* In push mode, we get to the end of the stream to get the duration */
+ gint64 position;
+ GstEvent *sevent;
+ gboolean res;
+
+ /* A full Ogg page can be almost 64 KB. There's no guarantee that there'll be a
+ granpos there, but it's fairly likely */
+ position =
+ ogg->push_byte_length - DURATION_CHUNK_OFFSET - EOS_AVOIDANCE_THRESHOLD;
+ if (position < 0)
+ position = 0;
+
+ GST_DEBUG_OBJECT (ogg,
+ "Getting duration, seeking near the end, to %" G_GINT64_FORMAT, position);
+ ogg->push_state = PUSH_DURATION;
+ /* do not read the last byte */
+ sevent = gst_event_new_seek (1.0, GST_FORMAT_BYTES, flags, GST_SEEK_TYPE_SET,
+ position, GST_SEEK_TYPE_SET, ogg->push_byte_length - 1);
+ res = gst_pad_push_event (ogg->sinkpad, sevent);
+ if (res) {
+ GST_DEBUG_OBJECT (ogg, "Seek succesful");
+ return TRUE;
+ } else {
+ GST_INFO_OBJECT (ogg, "Seek failed, duration will stay unknown");
+ ogg->push_state = PUSH_PLAYING;
+ ogg->push_disable_seeking = TRUE;
+ return FALSE;
+ }
+}
+
+static gboolean
+gst_ogg_demux_check_duration_push (GstOggDemux * ogg, GstSeekFlags flags,
+ GstEvent * event)
+{
+ if (ogg->push_byte_length < 0) {
+ GstPad *peer;
+
+ GST_DEBUG_OBJECT (ogg, "Trying to find byte/time length");
+ if ((peer = gst_pad_get_peer (ogg->sinkpad)) != NULL) {
+ GstFormat format = GST_FORMAT_BYTES;
+ gint64 length;
+ int res;
+
+ res = gst_pad_query_duration (peer, &format, &length);
+ if (res && length > 0) {
+ ogg->push_byte_length = length;
+ GST_DEBUG_OBJECT (ogg,
+ "File byte length %" G_GINT64_FORMAT, ogg->push_byte_length);
+ } else {
+ GST_DEBUG_OBJECT (ogg, "File byte length unknown, assuming live");
+ ogg->push_disable_seeking = TRUE;
+ return TRUE;
+ }
+ format = GST_FORMAT_TIME;
+ res = gst_pad_query_duration (peer, &format, &length);
+ gst_object_unref (peer);
+ if (res && length >= 0) {
+ ogg->push_time_length = length;
+ GST_DEBUG_OBJECT (ogg, "File time length %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (ogg->push_time_length));
+ } else if (!ogg->push_disable_seeking) {
+ gboolean res;
+
+ res = gst_ogg_demux_get_duration_push (ogg, flags);
+ if (res) {
+ GST_DEBUG_OBJECT (ogg,
+ "File time length unknown, trying to determine");
+ ogg->push_mode_seek_delayed_event = NULL;
+ if (event) {
+ GST_DEBUG_OBJECT (ogg,
+ "Let me intercept this innocent looking seek request");
+ ogg->push_mode_seek_delayed_event = gst_event_copy (event);
+ }
+ return FALSE;
+ }
+ }
+ }
+ }
+ return TRUE;
+}
+
+static gboolean
gst_ogg_demux_perform_seek_push (GstOggDemux * ogg, GstEvent * event)
{
gint bitrate;
GstEvent *sevent;
GstOggChain *chain;
gint64 best, best_time;
+ gint i;
+
+ GST_DEBUG_OBJECT (ogg, "Push mode seek request received");
gst_event_parse_seek (event, &rate, &format, &flags,
&start_type, &start, &stop_type, &stop);
goto error;
}
+ if (start_type != GST_SEEK_TYPE_SET) {
+ GST_DEBUG_OBJECT (ogg, "can only seek to a SET target");
+ goto error;
+ }
+
+ if (!(flags & GST_SEEK_FLAG_FLUSH)) {
+ GST_DEBUG_OBJECT (ogg, "can only do flushing seeks");
+ goto error;
+ }
+
+ GST_DEBUG_OBJECT (ogg, "Push mode seek request: %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (start));
+
chain = ogg->current_chain;
- if (!chain)
+ if (!chain) {
+ GST_WARNING_OBJECT (ogg, "No chain to seek on");
+ goto error;
+ }
+
+ /* start accessing push_* members */
+ GST_PUSH_LOCK (ogg);
+
+ /* not if we disabled seeking (chained streams) */
+ if (ogg->push_disable_seeking) {
+ GST_DEBUG_OBJECT (ogg, "Seeking disabled");
+ goto error_locked;
+ }
+
+ /* not when we're trying to work out duration */
+ if (ogg->push_state == PUSH_DURATION) {
+ GST_DEBUG_OBJECT (ogg, "Busy working out duration, try again later");
+ goto error_locked;
+ }
+
+ /* actually, not if we're doing any seeking already */
+ if (ogg->push_state != PUSH_PLAYING) {
+ GST_DEBUG_OBJECT (ogg, "Already doing some seeking, try again later");
+ goto error_locked;
+ }
+
+ /* on the first seek, get length if we can */
+ if (!gst_ogg_demux_check_duration_push (ogg, flags, event)) {
+ GST_PUSH_UNLOCK (ogg);
return FALSE;
+ }
if (do_index_search (ogg, chain, 0, -1, 0, -1, start, &best, &best_time)) {
/* the index gave some result */
GST_DEBUG_OBJECT (ogg,
"found offset %" G_GINT64_FORMAT " with time %" G_GUINT64_FORMAT,
best, best_time);
- start = best;
- } else if ((bitrate = ogg->bitrate) > 0) {
- /* try with bitrate convert the seek positions to bytes */
- if (start_type != GST_SEEK_TYPE_NONE) {
- start = gst_util_uint64_scale (start, bitrate, 8 * GST_SECOND);
- }
- if (stop_type != GST_SEEK_TYPE_NONE) {
- stop = gst_util_uint64_scale (stop, bitrate, 8 * GST_SECOND);
- }
} else {
- /* we don't know */
- res = FALSE;
+ if (ogg->push_time_length > 0) {
+ /* if we know the time length, we know the full segment bitrate */
+ GST_DEBUG_OBJECT (ogg, "Using real file bitrate");
+ bitrate =
+ gst_util_uint64_scale (ogg->push_byte_length, 8 * GST_SECOND,
+ ogg->push_time_length);
+ } else if (ogg->push_time_offset > 0) {
+ /* get a first approximation using known bitrate to the current position */
+ GST_DEBUG_OBJECT (ogg, "Using file bitrate so far");
+ bitrate =
+ gst_util_uint64_scale (ogg->push_byte_offset, 8 * GST_SECOND,
+ ogg->push_time_offset);
+ } else if (ogg->bitrate > 0) {
+ /* nominal bitrate is better than nothing, even if it lies often */
+ GST_DEBUG_OBJECT (ogg, "Using nominal bitrate");
+ bitrate = ogg->bitrate;
+ } else {
+ /* meh */
+ GST_DEBUG_OBJECT (ogg,
+ "At stream start, and no nominal bitrate, using some random magic "
+ "number to seed");
+ /* the bisection, once started, should give us a better approximation */
+ bitrate = 1000;
+ }
+ best = gst_util_uint64_scale (start, bitrate, 8 * GST_SECOND);
}
- if (res) {
- GST_DEBUG_OBJECT (ogg,
- "seeking to %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT, start, stop);
- /* do seek */
- sevent = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags,
- start_type, start, stop_type, stop);
-
- res = gst_pad_push_event (ogg->sinkpad, sevent);
+ /* offset by typical page length, and ensure our best guess is within
+ reasonable bounds */
+ best -= CHUNKSIZE;
+ if (best < 0)
+ best = 0;
+ if (ogg->push_byte_length > 0 && best >= ogg->push_byte_length)
+ best = ogg->push_byte_length - 1;
+
+ /* set up bisection search */
+ ogg->push_offset0 = 0;
+ ogg->push_offset1 = ogg->push_byte_length - 1;
+ ogg->push_time0 = ogg->push_start_time;
+ ogg->push_time1 = ogg->push_time_length;
+ ogg->push_seek_time_target = start;
+ ogg->push_prev_seek_time = GST_CLOCK_TIME_NONE;
+ ogg->push_seek_time_original_target = start;
+ ogg->push_state = PUSH_BISECT1;
+ ogg->seek_secant = FALSE;
+ ogg->seek_undershot = FALSE;
+
+ /* 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;
}
+ GST_DEBUG_OBJECT (ogg,
+ "Setting up bisection search for %" 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));
+ GST_DEBUG_OBJECT (ogg,
+ "Target time is %" GST_TIME_FORMAT ", best first guess is %"
+ G_GINT64_FORMAT, GST_TIME_ARGS (ogg->push_seek_time_target), best);
+
+ ogg->push_seek_rate = rate;
+ ogg->push_seek_flags = flags;
+ ogg->push_mode_seek_delayed_event = NULL;
+ ogg->push_bisection_steps[0] = 1;
+ ogg->push_bisection_steps[1] = 0;
+ sevent = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags,
+ start_type, best, GST_SEEK_TYPE_NONE, -1);
+
+ GST_PUSH_UNLOCK (ogg);
+ res = gst_pad_push_event (ogg->sinkpad, sevent);
+
return res;
/* ERRORS */
GST_DEBUG_OBJECT (ogg, "seek failed");
return FALSE;
}
+
+error_locked:
+ GST_PUSH_UNLOCK (ogg);
+ goto error;
}
static gboolean
if (ret == GST_FLOW_UNEXPECTED) {
endsearched = bisect;
} else if (ret == GST_FLOW_OK) {
- glong serial = ogg_page_serialno (&og);
+ guint32 serial = ogg_page_serialno (&og);
if (!gst_ogg_chain_has_stream (chain, serial)) {
endsearched = bisect;
GstFlowReturn ret;
GstOggChain *chain = NULL;
gint64 offset = ogg->offset;
- ogg_page op;
+ ogg_page og;
gboolean done;
gint i;
* the decoders, send data to the decoders. */
while (TRUE) {
GstOggPad *pad;
- glong serial;
+ guint32 serial;
- ret = gst_ogg_demux_get_next_page (ogg, &op, -1, NULL);
+ ret = gst_ogg_demux_get_next_page (ogg, &og, -1, NULL);
if (ret != GST_FLOW_OK) {
- GST_WARNING_OBJECT (ogg, "problem reading BOS page: ret=%d", ret);
+ if (ret == GST_FLOW_UNEXPECTED) {
+ GST_DEBUG_OBJECT (ogg, "Reached EOS, done reading end chain");
+ } else {
+ GST_WARNING_OBJECT (ogg, "problem reading BOS page: ret=%d", ret);
+ }
break;
}
- if (!ogg_page_bos (&op)) {
- GST_WARNING_OBJECT (ogg, "page is not BOS page");
+ if (!ogg_page_bos (&og)) {
+ GST_INFO_OBJECT (ogg, "page is not BOS page, all streams identified");
/* if we did not find a chain yet, assume this is a bogus stream and
* ignore it */
- if (!chain)
+ if (!chain) {
+ GST_WARNING_OBJECT (ogg, "No chain found, no Ogg data in stream ?");
ret = GST_FLOW_UNEXPECTED;
+ }
break;
}
chain->offset = offset;
}
- serial = ogg_page_serialno (&op);
+ serial = ogg_page_serialno (&og);
if (gst_ogg_chain_get_stream (chain, serial) != NULL) {
- GST_WARNING_OBJECT (ogg, "found serial %08lx BOS page twice, ignoring",
- serial);
+ GST_WARNING_OBJECT (ogg,
+ "found serial %08x BOS page twice, ignoring", serial);
continue;
}
pad = gst_ogg_chain_new_stream (chain, serial);
- gst_ogg_pad_submit_page (pad, &op);
+ gst_ogg_pad_submit_page (pad, &og);
}
if (ret != GST_FLOW_OK || chain == NULL) {
done = FALSE;
while (!done) {
- glong serial;
+ guint32 serial;
gboolean known_serial = FALSE;
GstFlowReturn ret;
- serial = ogg_page_serialno (&op);
+ serial = ogg_page_serialno (&og);
done = TRUE;
for (i = 0; i < chain->streams->len; i++) {
GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);
- GST_LOG_OBJECT (ogg, "serial %08lx time %" GST_TIME_FORMAT,
+ GST_LOG_OBJECT (ogg,
+ "serial %08x time %" GST_TIME_FORMAT,
pad->map.serialno, GST_TIME_ARGS (pad->start_time));
if (pad->map.serialno == serial) {
/* submit the page now, this will fill in the start_time when the
* internal decoder finds it */
- gst_ogg_pad_submit_page (pad, &op);
+ gst_ogg_pad_submit_page (pad, &og);
if (!pad->map.is_skeleton && pad->start_time == -1
- && ogg_page_eos (&op)) {
+ && ogg_page_eos (&og)) {
/* got EOS on a pad before we could find its start_time.
* We have no chance of finding a start_time for every pad so
* stop searching for the other start_time(s).
if (!pad->map.is_sparse)
done &= (pad->start_time != GST_CLOCK_TIME_NONE);
- GST_LOG_OBJECT (ogg, "done %08lx now %d", pad->map.serialno, done);
+ GST_LOG_OBJECT (ogg, "done %08x now %d", pad->map.serialno, done);
}
/* we read a page not belonging to the current chain: seek back to the
* beginning of the chain
*/
if (!known_serial) {
- GST_LOG_OBJECT (ogg, "unknown serial %08lx", serial);
+ GST_LOG_OBJECT (ogg, "unknown serial %08x", serial);
gst_ogg_demux_seek (ogg, offset);
break;
}
if (!done) {
- ret = gst_ogg_demux_get_next_page (ogg, &op, -1, NULL);
+ ret = gst_ogg_demux_get_next_page (ogg, &og, -1, NULL);
if (ret != GST_FLOW_OK)
break;
}
for (i = 0; i < chain->streams->len; i++) {
GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i);
- if (pad->map.is_sparse)
+ if (pad->map.is_skeleton)
continue;
if (pad->map.serialno == ogg_page_serialno (&og)) {
/* find a pad with a given serial number
*/
static GstOggPad *
-gst_ogg_demux_find_pad (GstOggDemux * ogg, glong serialno)
+gst_ogg_demux_find_pad (GstOggDemux * ogg, guint32 serialno)
{
GstOggPad *pad;
gint i;
/* find a chain with a given serial number
*/
static GstOggChain *
-gst_ogg_demux_find_chain (GstOggDemux * ogg, glong serialno)
+gst_ogg_demux_find_chain (GstOggDemux * ogg, guint32 serialno)
{
GstOggPad *pad;
GstPad *peer;
GstFormat format;
gboolean res;
- gulong serialno;
+ guint32 serialno;
GstOggChain *chain;
GstFlowReturn ret;
{
GstOggPad *pad;
gint64 granule;
- glong serialno;
+ guint32 serialno;
GstFlowReturn result = GST_FLOW_OK;
serialno = ogg_page_serialno (page);
granule = ogg_page_granulepos (page);
GST_LOG_OBJECT (ogg,
- "processing ogg page (serial %08lx, pageno %ld, granulepos %"
- G_GINT64_FORMAT ", bos %d)",
- serialno, ogg_page_pageno (page), granule, ogg_page_bos (page));
+ "processing ogg page (serial %08x, "
+ "pageno %ld, granulepos %" G_GINT64_FORMAT ", bos %d)", serialno,
+ ogg_page_pageno (page), granule, ogg_page_bos (page));
if (ogg_page_bos (page)) {
GstOggChain *chain;
if (pad) {
result = gst_ogg_pad_submit_page (pad, page);
} else {
- /* no pad. This means an ogg page without bos has been seen for this
- * serialno. we just ignore it but post a warning... */
- GST_ELEMENT_WARNING (ogg, STREAM, DECODE,
- (NULL), ("unknown ogg pad for serial %08lx detected", serialno));
- return GST_FLOW_OK;
+ GST_PUSH_LOCK (ogg);
+ if (!ogg->pullmode && !ogg->push_disable_seeking) {
+ /* no pad while probing for duration, we must have a chained stream,
+ and we don't support them, so back off */
+ GST_INFO_OBJECT (ogg, "We seem to have a chained stream, we won't seek");
+ if (ogg->push_state == PUSH_DURATION) {
+ GstFlowReturn res;
+
+ res = gst_ogg_demux_seek_back_after_push_duration_check_unlock (ogg);
+ if (res != GST_FLOW_OK)
+ return res;
+ }
+
+ /* only once we seeked back */
+ GST_PUSH_LOCK (ogg);
+ ogg->push_disable_seeking = TRUE;
+ } else {
+ GST_PUSH_UNLOCK (ogg);
+ /* no pad. This means an ogg page without bos has been seen for this
+ * serialno. we just ignore it but post a warning... */
+ GST_ELEMENT_WARNING (ogg, STREAM, DECODE,
+ (NULL), ("unknown ogg pad for serial %08x detected", serialno));
+ return GST_FLOW_OK;
+ }
+ GST_PUSH_UNLOCK (ogg);
}
return result;
unknown_chain:
{
GST_ELEMENT_ERROR (ogg, STREAM, DECODE,
- (NULL), ("unknown ogg chain for serial %08lx detected", serialno));
+ (NULL), ("unknown ogg chain for serial %08x detected", serialno));
return GST_FLOW_ERROR;
}
}
ogg = GST_OGG_DEMUX (GST_OBJECT_PARENT (pad));
- GST_DEBUG_OBJECT (ogg, "chain");
+ GST_DEBUG_OBJECT (ogg, "enter");
result = gst_ogg_demux_submit_buffer (ogg, buffer);
+ if (result < 0) {
+ GST_DEBUG_OBJECT (ogg, "gst_ogg_demux_submit_buffer returned %d", result);
+ }
while (result == GST_FLOW_OK) {
ogg_page page;
GST_DEBUG_OBJECT (ogg, "discont in page found, continuing");
} else {
result = gst_ogg_demux_handle_page (ogg, &page);
+ if (result < 0) {
+ GST_DEBUG_OBJECT (ogg, "gst_ogg_demux_handle_page returned %d", result);
+ }
}
}
if (ret == 0 || result == GST_FLOW_OK) {
gst_ogg_demux_sync_streams (ogg);
}
+ GST_DEBUG_OBJECT (ogg, "leave with %d", result);
return result;
}
GstOggChain *chain = ogg->current_chain;
gboolean res = TRUE;
+ if (!chain)
+ chain = ogg->building_chain;
+
if (chain) {
gint i;
GST_DEBUG_OBJECT (pad, "Pushing event %" GST_PTR_FORMAT, event);
res &= gst_pad_push_event (GST_PAD (pad), event);
}
+ } else {
+ GST_WARNING_OBJECT (ogg, "No chain to forward event to");
}
gst_event_unref (event);
* e.g. because of a flushing seek.
*/
if (event) {
- gst_event_set_seqnum (event, ogg->seqnum);
- gst_ogg_demux_send_event (ogg, event);
+ /* guard against corrupt/truncated files, where one can hit EOS
+ before prerolling is done and a chain created. If we have no
+ chain to send the event to, error out. */
+ if (ogg->current_chain || ogg->building_chain) {
+ gst_event_set_seqnum (event, ogg->seqnum);
+ gst_ogg_demux_send_event (ogg, event);
+ } else {
+ gst_event_unref (event);
+ GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL),
+ ("EOS before finding a chain"));
+ }
}
return;
}
ogg->bitrate = 0;
ogg->segment_running = FALSE;
ogg->total_time = -1;
+ GST_PUSH_LOCK (ogg);
+ ogg->push_byte_offset = 0;
+ ogg->push_byte_length = -1;
+ ogg->push_time_length = GST_CLOCK_TIME_NONE;
+ ogg->push_time_offset = GST_CLOCK_TIME_NONE;
+ ogg->push_state = PUSH_PLAYING;
+
+ ogg->push_disable_seeking = FALSE;
+ if (!ogg->pullmode) {
+ GstPad *peer;
+ if ((peer = gst_pad_get_peer (ogg->sinkpad)) != NULL) {
+ GstFormat format = GST_FORMAT_BYTES;
+ gint64 length = -1;
+ if (!gst_pad_query_duration (peer, &format, &length) || length <= 0) {
+ GST_DEBUG_OBJECT (ogg,
+ "Unable to determine stream size, assuming live, seeking disabled");
+ ogg->push_disable_seeking = TRUE;
+ }
+ gst_object_unref (peer);
+ }
+ }
+
+ GST_PUSH_UNLOCK (ogg);
gst_segment_init (&ogg->segment, GST_FORMAT_TIME);
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
for (j = 0; j < chain->streams->len; j++) {
GstOggPad *stream = g_array_index (chain->streams, GstOggPad *, j);
- GST_INFO_OBJECT (ogg, " stream %08lx:", stream->map.serialno);
+ GST_INFO_OBJECT (ogg, " stream %08x: %s", stream->map.serialno,
+ gst_ogg_stream_get_media_type (&stream->map));
GST_INFO_OBJECT (ogg, " start time: %" GST_TIME_FORMAT,
GST_TIME_ARGS (stream->start_time));
}