From: Vincent Penquerc'h Date: Sat, 22 Oct 2011 19:20:38 +0000 (+0100) Subject: oggdemux: improve push time seeking X-Git-Tag: 1.19.3~511^2~6555^2~313 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=e7079cd8d5a7752f8052a093ca694f9e5a070fb0;p=platform%2Fupstream%2Fgstreamer.git oggdemux: improve push time seeking Various tweaks to improve convergence, in particular for the worst case, which is now cut in about half. https://bugzilla.gnome.org/show_bug.cgi?id=662475 --- diff --git a/ext/ogg/gstoggdemux.c b/ext/ogg/gstoggdemux.c index 4198f6c..3afbd91 100644 --- a/ext/ogg/gstoggdemux.c +++ b/ext/ogg/gstoggdemux.c @@ -1066,6 +1066,8 @@ gst_ogg_pad_stream_out (GstOggPad * pad, gint npackets) 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 */ @@ -1107,19 +1109,22 @@ gst_ogg_demux_setup_bisection_bounds (GstOggDemux * ogg) 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) +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 */ @@ -1146,6 +1151,7 @@ gst_ogg_demux_estimate_bisection_target (GstOggDemux * ogg) 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 @@ -1162,20 +1168,63 @@ gst_ogg_demux_estimate_bisection_target (GstOggDemux * 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; + } } } - /* offset by typical page size */ - best -= CHUNKSIZE; + 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; - if (best < 0) - best = 0; + GST_DEBUG_OBJECT (ogg, "Choosing target %" G_GINT64_FORMAT, best); return best; } @@ -1252,6 +1301,38 @@ gst_ogg_demux_seek_back_after_push_duration_check_unlock (GstOggDemux * ogg) 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 gboolean gst_ogg_pad_handle_push_mode_state (GstOggPad * pad, ogg_page * page) { @@ -1327,6 +1408,7 @@ gst_ogg_pad_handle_push_mode_state (GstOggPad * pad, ogg_page * page) 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) { @@ -1364,14 +1446,18 @@ gst_ogg_pad_handle_push_mode_state (GstOggPad * pad, ogg_page * page) 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 ")", ogg->push_offset0, ogg->push_offset1, - ogg->push_offset1 - ogg->push_offset0, + " (%" 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)); + 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", @@ -1379,30 +1465,39 @@ gst_ogg_pad_handle_push_mode_state (GstOggPad * pad, ogg_page * page) 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); + 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 threshold = GST_SECOND / 2; - - /* We want to be within half a second before the target */ - if (threshold > ogg->push_seek_time_target) - threshold = ogg->push_seek_time_target; + 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 - threshold; + && (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 " ? %s", - GST_TIME_ARGS (ogg->push_seek_time_target - threshold), + 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"); } @@ -1432,7 +1527,7 @@ gst_ogg_pad_handle_push_mode_state (GstOggPad * pad, ogg_page * page) GST_TIME_ARGS (ogg->push_seek_time_original_target)); ogg->push_state = PUSH_LINEAR2; } else { - GST_DEBUG_OBJECT (ogg, "Seek to keyframe done, playing"); + 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 */ @@ -1466,14 +1561,22 @@ gst_ogg_pad_handle_push_mode_state (GstOggPad * pad, ogg_page * page) 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) { - GST_DEBUG_OBJECT (ogg, + 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); + best = gst_ogg_demux_estimate_bisection_target (ogg, 1.0f); } } } @@ -1578,6 +1681,9 @@ gst_ogg_pad_submit_page (GstOggPad * pad, ogg_page * page) 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; @@ -3289,8 +3395,11 @@ gst_ogg_demux_perform_seek_push (GstOggDemux * ogg, GstEvent * event) 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++) { diff --git a/ext/ogg/gstoggdemux.h b/ext/ogg/gstoggdemux.h index fea4c60..a8ac92b 100644 --- a/ext/ogg/gstoggdemux.h +++ b/ext/ogg/gstoggdemux.h @@ -145,6 +145,10 @@ struct _GstOggDemux gboolean need_chains; gboolean resync; + /* keep track of how large pages and packets are, + useful for skewing when seeking */ + guint64 max_packet_size, max_page_size; + /* state */ GMutex *chain_lock; /* we need the lock to protect the chains */ GArray *chains; /* list of chains we know */ @@ -186,6 +190,9 @@ struct _GstOggDemux GstSeekFlags push_seek_flags; GstEvent *push_mode_seek_delayed_event; gboolean push_disable_seeking; + gboolean seek_secant; + gboolean seek_undershot; + GstClockTime push_prev_seek_time; gint push_bisection_steps[2]; gint stats_bisection_steps[2];