Bug 501031 - Make media seeking faster over HTTP by adding 'fuzz' factor to liboggz seek, and fixing its bisection search. r=doublec

This commit is contained in:
Chris Pearce 2009-09-08 09:41:56 +12:00
parent 8a38b2de30
commit 8d783e47e7
11 changed files with 660 additions and 205 deletions

View File

@ -1268,19 +1268,6 @@ static void GetBufferedBytes(nsMediaStream* aStream, nsTArray<ByteRange>& aRange
nsresult nsOggDecodeStateMachine::Seek(float aTime, nsChannelReader* aReader)
{
LOG(PR_LOG_DEBUG, ("About to seek OggPlay to %fms", aTime));
// Get active tracks.
PRInt32 numTracks = 0;
PRInt32 tracks[2];
if (mVideoTrack != -1) {
tracks[numTracks] = mVideoTrack;
numTracks++;
}
if (mAudioTrack != -1) {
tracks[numTracks] = mAudioTrack;
numTracks++;
}
nsMediaStream* stream = aReader->Stream();
nsAutoTArray<ByteRange, 16> ranges;
stream->Pin();
@ -1288,8 +1275,6 @@ nsresult nsOggDecodeStateMachine::Seek(float aTime, nsChannelReader* aReader)
PRInt64 rv = -1;
for (PRUint32 i = 0; rv < 0 && i < ranges.Length(); i++) {
rv = oggplay_seek_to_keyframe(mPlayer,
tracks,
numTracks,
ogg_int64_t(aTime * 1000),
ranges[i].mStart,
ranges[i].mEnd);
@ -1300,8 +1285,6 @@ nsresult nsOggDecodeStateMachine::Seek(float aTime, nsChannelReader* aReader)
// Could not seek in a buffered range, fall back to seeking over the
// entire media.
rv = oggplay_seek_to_keyframe(mPlayer,
tracks,
numTracks,
ogg_int64_t(aTime * 1000),
0,
stream->GetLength());

View File

@ -46,4 +46,4 @@ oggplay_os2.patch: Bug 448918 - add OS/2 support (this patch should be
bug498815.patch: Fix for bug 498815.
bug498824.patch: Fix for bug 498824.
bug500311.patch: Fix crash during decoder initialization.
faster_seek.patch: Fix for bug 501031, make seeking faster over HTTP.

View File

@ -0,0 +1,78 @@
diff --git a/media/liboggplay/include/oggplay/oggplay_seek.h b/media/liboggplay/include/oggplay/oggplay_seek.h
--- a/media/liboggplay/include/oggplay/oggplay_seek.h
+++ b/media/liboggplay/include/oggplay/oggplay_seek.h
@@ -42,15 +42,13 @@
OggPlayErrorCode
oggplay_seek(OggPlay *me, ogg_int64_t milliseconds);
/**
* Seeks to key frame before |milliseconds|.
*/
OggPlayErrorCode
oggplay_seek_to_keyframe(OggPlay *me,
- int* tracks,
- int num_tracks,
ogg_int64_t milliseconds,
ogg_int64_t offset_begin,
ogg_int64_t offset_end);
#endif
diff --git a/media/liboggplay/src/liboggplay/oggplay_seek.c b/media/liboggplay/src/liboggplay/oggplay_seek.c
--- a/media/liboggplay/src/liboggplay/oggplay_seek.c
+++ b/media/liboggplay/src/liboggplay/oggplay_seek.c
@@ -75,54 +75,38 @@ oggplay_seek(OggPlay *me, ogg_int64_t mi
oggplay_seek_cleanup(me, milliseconds);
return E_OGGPLAY_OK;
}
OggPlayErrorCode
oggplay_seek_to_keyframe(OggPlay *me,
- int* tracks,
- int num_tracks,
ogg_int64_t milliseconds,
ogg_int64_t offset_begin,
ogg_int64_t offset_end)
{
- long *serial_nos;
- int i;
ogg_int64_t eof, time;
if (me == NULL) {
return E_OGGPLAY_BAD_OGGPLAY;
}
- if (num_tracks > me->num_tracks || milliseconds < 0)
+ if (milliseconds < 0)
return E_OGGPLAY_CANT_SEEK;
eof = oggplay_get_duration(me);
if (eof > -1 && milliseconds > eof) {
return E_OGGPLAY_CANT_SEEK;
}
- // Get the serialnos for the tracks we're seeking.
- serial_nos = (long*)oggplay_malloc(sizeof(long)*num_tracks);
- if (!serial_nos) {
- return E_OGGPLAY_CANT_SEEK;
- }
- for (i=0; i<num_tracks; i++) {
- serial_nos[i] = me->decode_data[tracks[i]]->serialno;
- }
-
time = oggz_keyframe_seek_set(me->oggz,
- serial_nos,
- num_tracks,
milliseconds,
offset_begin,
offset_end);
- oggplay_free(serial_nos);
if (time == -1) {
return E_OGGPLAY_CANT_SEEK;
}
oggplay_seek_cleanup(me, time);
return E_OGGPLAY_OK;

View File

@ -47,8 +47,6 @@ oggplay_seek(OggPlay *me, ogg_int64_t milliseconds);
*/
OggPlayErrorCode
oggplay_seek_to_keyframe(OggPlay *me,
int* tracks,
int num_tracks,
ogg_int64_t milliseconds,
ogg_int64_t offset_begin,
ogg_int64_t offset_end);

View File

@ -80,21 +80,17 @@ oggplay_seek(OggPlay *me, ogg_int64_t milliseconds) {
OggPlayErrorCode
oggplay_seek_to_keyframe(OggPlay *me,
int* tracks,
int num_tracks,
ogg_int64_t milliseconds,
ogg_int64_t offset_begin,
ogg_int64_t offset_end)
{
long *serial_nos;
int i;
ogg_int64_t eof, time;
if (me == NULL) {
return E_OGGPLAY_BAD_OGGPLAY;
}
if (num_tracks > me->num_tracks || milliseconds < 0)
if (milliseconds < 0)
return E_OGGPLAY_CANT_SEEK;
eof = oggplay_get_duration(me);
@ -102,22 +98,10 @@ oggplay_seek_to_keyframe(OggPlay *me,
return E_OGGPLAY_CANT_SEEK;
}
// Get the serialnos for the tracks we're seeking.
serial_nos = (long*)oggplay_malloc(sizeof(long)*num_tracks);
if (!serial_nos) {
return E_OGGPLAY_CANT_SEEK;
}
for (i=0; i<num_tracks; i++) {
serial_nos[i] = me->decode_data[tracks[i]]->serialno;
}
time = oggz_keyframe_seek_set(me->oggz,
serial_nos,
num_tracks,
milliseconds,
offset_begin,
offset_end);
oggplay_free(serial_nos);
if (time == -1) {
return E_OGGPLAY_CANT_SEEK;

View File

@ -65,3 +65,4 @@ patch -p3 < bug498824.patch
patch -p3 < bug496529.patch
patch -p3 < bug499519.patch
patch -p3 < bug500311.patch
patch -p3 < faster_seek.patch

View File

@ -23,3 +23,4 @@ bug496063.patch: Fix for infinite loop during seek while shutting down.
oggz_os2.patch: Bug 448918 - add OS/2 support (this patch should be
removed when OS/2 support is added upstream)
faster_seek.patch: Fix for bug 501031, make seeking faster over HTTP.

View File

@ -0,0 +1,481 @@
diff --git a/media/liboggz/include/oggz/oggz_seek.h b/media/liboggz/include/oggz/oggz_seek.h
--- a/media/liboggz/include/oggz/oggz_seek.h
+++ b/media/liboggz/include/oggz/oggz_seek.h
@@ -471,40 +471,40 @@ long oggz_seek_byorder (OGGZ * oggz, voi
* \param offset The offset of the start of data
* \returns 0 on success, -1 on failure.
*/
int oggz_set_data_start (OGGZ * oggz, oggz_off_t offset);
/** \}
*/
/**
- * Seeks Oggz to time unit_target, but with the bounds of the offset range
- * [offset_begin, offset_end]. This is useful when seeking in network streams
- * where only parts of a media are buffered, and retrieving unbuffered
- * parts is expensive.
+ * Seeks to within fuzz_margin milliseconds of time unit_target, within the
+ * bounds of the offset range [offset_begin, offset_end]. This is useful when
+ * seeking in network streams where only parts of a media are buffered, and
+ * retrieving unbuffered parts is expensive.
* \param oggz An OGGZ handle previously opened for reading
* \param unit_target The seek target, in milliseconds, or custom units
* \param offset_begin Start of offset range to seek inside, in bytes
* \param offset_end End of offset range to seek inside, in bytes,
pass -1 for end of media
+ * \param fuzz_margin The seek stops when it's within this many milliseconds
+ of unit_target
* \returns The new position, in milliseconds or custom units
* \retval -1 on failure (unit_target is not within range)
*/
ogg_int64_t
oggz_bounded_seek_set (OGGZ * oggz,
ogg_int64_t unit_target,
ogg_int64_t offset_begin,
- ogg_int64_t offset_end);
+ ogg_int64_t offset_end,
+ int fuzz_margin);
/**
- * Seeks to the first key frame before unit_target, in the range
- * [offset_begin, offset_end]. serial_nos contains an array of size serial_nos
- * of serialnos of the streams which need to be seeked.
+ * Seeks to before the first key frame before unit_target, in the range
+ * [offset_begin, offset_end].
*/
ogg_int64_t
oggz_keyframe_seek_set(OGGZ * oggz,
- long* serial_nos,
- int num_serialno,
ogg_int64_t unit_target,
ogg_int64_t offset_begin,
ogg_int64_t offset_end);
#endif /* __OGGZ_SEEK_H__ */
diff --git a/media/liboggz/src/liboggz/oggz_seek.c b/media/liboggz/src/liboggz/oggz_seek.c
--- a/media/liboggz/src/liboggz/oggz_seek.c
+++ b/media/liboggz/src/liboggz/oggz_seek.c
@@ -491,65 +491,61 @@ oggz_scan_for_page (OGGZ * oggz, ogg_pag
}
}
return offset_at;
}
#define GUESS_MULTIPLIER (1<<16)
-static oggz_off_t
+static ogg_int64_t
guess (ogg_int64_t unit_at, ogg_int64_t unit_target,
ogg_int64_t unit_begin, ogg_int64_t unit_end,
- oggz_off_t offset_begin, oggz_off_t offset_end)
+ ogg_int64_t offset_begin, ogg_int64_t offset_end)
{
ogg_int64_t guess_ratio;
- oggz_off_t offset_guess;
-
- if (unit_at == unit_begin) return offset_begin;
+ ogg_int64_t offset_guess;
if (unit_end != -1) {
guess_ratio =
GUESS_MULTIPLIER * (unit_target - unit_begin) /
(unit_end - unit_begin);
} else {
guess_ratio =
GUESS_MULTIPLIER * (unit_target - unit_begin) /
(unit_at - unit_begin);
}
+ offset_guess = offset_begin +
+ (((offset_end - offset_begin) * guess_ratio) /
+ GUESS_MULTIPLIER);
+
#ifdef DEBUG
- printf ("oggz_seek::guess: guess_ratio %lld = (%lld - %lld) / (%lld - %lld)\n",
- guess_ratio, unit_target, unit_begin, unit_at, unit_begin);
+ printf("guess: [o=%lld t=%lld]-[o=%lld t=%lld] guess_ratio=%lf offset_guess=%lu\n",
+ offset_begin, unit_begin, offset_end, unit_end,
+ ((double)guess_ratio / (double)GUESS_MULTIPLIER), offset_guess);
#endif
-
- offset_guess = offset_begin +
- (oggz_off_t)(((offset_end - offset_begin) * guess_ratio) /
- GUESS_MULTIPLIER);
-
+
return offset_guess;
}
-static oggz_off_t
-oggz_seek_guess (ogg_int64_t unit_at, ogg_int64_t unit_target,
- ogg_int64_t unit_begin, ogg_int64_t unit_end,
- oggz_off_t offset_at,
- oggz_off_t offset_begin, oggz_off_t offset_end)
+static ogg_int64_t
+oggz_seek_guess (ogg_int64_t unit_at,
+ ogg_int64_t unit_target,
+ ogg_int64_t unit_begin,
+ ogg_int64_t unit_end,
+ oggz_off_t offset_at,
+ oggz_off_t offset_begin,
+ oggz_off_t offset_end)
{
oggz_off_t offset_guess;
-
- if (unit_at == unit_begin) {
- offset_guess = offset_begin + (offset_end - offset_begin)/2;
- } else if (unit_end == -1) {
+ if (unit_end == -1) {
offset_guess = guess (unit_at, unit_target, unit_begin, unit_end,
offset_begin, offset_at);
} else if (unit_end <= unit_begin) {
-#ifdef DEBUG
- printf ("oggz_seek_guess: unit_end <= unit_begin (ERROR)\n");
-#endif
offset_guess = -1;
} else {
offset_guess = guess (unit_at, unit_target, unit_begin, unit_end,
offset_begin, offset_end);
}
#ifdef DEBUG
printf ("oggz_seek_guess: guessed %" PRI_OGGZ_OFF_T "d\n", offset_guess);
@@ -617,21 +613,22 @@ oggz_offset_end (OGGZ * oggz)
return offset_end;
}
ogg_int64_t
oggz_bounded_seek_set (OGGZ * oggz,
ogg_int64_t unit_target,
ogg_int64_t offset_begin,
- ogg_int64_t offset_end)
+ ogg_int64_t offset_end,
+ int fuzz_margin)
{
OggzReader * reader;
- oggz_off_t offset_orig, offset_at, offset_guess;
- oggz_off_t offset_next;
+ ogg_int64_t offset_orig, offset_at, offset_guess;
+ ogg_int64_t offset_next;
ogg_int64_t granule_at;
ogg_int64_t unit_at, unit_begin = -1, unit_end = -1, unit_last_iter = -1;
long serialno;
ogg_page * og;
int hit_eof = 0;
if (oggz == NULL) {
return -1;
@@ -679,27 +676,30 @@ oggz_bounded_seek_set (OGGZ * oggz,
ogg_int64_t granulepos;
if (oggz_get_prev_start_page (oggz, og, &granulepos, &serialno) >= 0) {
unit_end = oggz_get_unit (oggz, serialno, granulepos);
}
}
if (unit_begin == -1 && oggz_seek_raw (oggz, offset_begin, SEEK_SET) >= 0) {
- ogg_int64_t granulepos;
- if (oggz_get_next_start_page (oggz, og) >= 0) {
+ ogg_int64_t granulepos = 0;
+ unit_begin = 0;
+ // Start time needs to be the end time of the first non-header page.
+ while (oggz_get_next_start_page (oggz, og) >= 0 && unit_begin <= 0) {
serialno = ogg_page_serialno (og);
granulepos = ogg_page_granulepos (og);
unit_begin = oggz_get_unit (oggz, serialno, granulepos);
}
}
/* Fail if target isn't in specified range. */
- if (unit_target < unit_begin || unit_target > unit_end)
+ if (unit_target < unit_begin || unit_target > unit_end) {
return -1;
+ }
/* Reduce the search range if possible using read cursor position. */
if (unit_at > unit_begin && unit_at < unit_end) {
if (unit_target < unit_at) {
unit_end = unit_at;
offset_end = offset_at;
} else {
unit_begin = unit_at;
@@ -738,16 +738,21 @@ oggz_bounded_seek_set (OGGZ * oggz,
offset_at = oggz_seek_raw (oggz, offset_guess, SEEK_SET);
offset_next = oggz_get_next_start_page (oggz, og);
serialno = ogg_page_serialno (og);
granule_at = ogg_page_granulepos (og);
}
unit_at = oggz_get_unit (oggz, serialno, granule_at);
+ if (abs(unit_at - unit_target) < fuzz_margin) {
+ // Within fuzz_margin of target, stop.
+ break;
+ }
+
#ifdef DEBUG
printf ("oggz_bounded_seek_set: offset_next %" PRI_OGGZ_OFF_T "d\n", offset_next);
#endif
if (unit_at == unit_last_iter) break;
#ifdef DEBUG
printf ("oggz_bounded_seek_set: [D] want u%lld, got page u%lld @%" PRI_OGGZ_OFF_T "d g%lld\n",
unit_target, unit_at, offset_at, granule_at);
@@ -761,34 +766,17 @@ oggz_bounded_seek_set (OGGZ * oggz,
offset_end = offset_at-1;
unit_end = unit_at;
if (unit_end == unit_begin) break;
} else {
break;
}
}
- do {
- offset_at = oggz_get_prev_start_page (oggz, og, &granule_at, &serialno);
- if (offset_at < 0)
- break;
- unit_at = oggz_get_unit (oggz, serialno, granule_at);
- } while (unit_at > unit_target);
-
- if (offset_at < 0) {
- oggz_reset (oggz, offset_orig, -1, SEEK_SET);
- return -1;
- }
-
- offset_at = oggz_reset (oggz, offset_at, unit_at, SEEK_SET);
- if (offset_at == -1) return -1;
-
-#ifdef DEBUG
- printf ("oggz_bounded_seek_set: FOUND (%lld)\n", unit_at);
-#endif
+ /* Reader is now approximately at the seek target. */
return (long)reader->current_unit;
}
static ogg_int64_t
oggz_seek_end (OGGZ * oggz, ogg_int64_t unit_offset)
{
oggz_off_t offset_orig, offset_at, offset_end;
@@ -813,17 +801,17 @@ oggz_seek_end (OGGZ * oggz, ogg_int64_t
return -1;
}
#ifdef DEBUG
printf ("*** oggz_seek_end: found packet (%lld) at @%" PRI_OGGZ_OFF_T "d [%lld]\n",
unit_end, offset_end, granulepos);
#endif
- return oggz_bounded_seek_set (oggz, unit_end + unit_offset, 0, -1);
+ return oggz_bounded_seek_set (oggz, unit_end + unit_offset, 0, -1, 0);
}
off_t
oggz_seek (OGGZ * oggz, oggz_off_t offset, int whence)
{
OggzReader * reader;
ogg_int64_t units = -1;
@@ -872,21 +860,21 @@ oggz_seek_units (OGGZ * oggz, ogg_int64_
#endif
return -1;
}
reader = &oggz->x.reader;
switch (whence) {
case SEEK_SET:
- r = oggz_bounded_seek_set (oggz, units, 0, -1);
+ r = oggz_bounded_seek_set (oggz, units, 0, -1, 0);
break;
case SEEK_CUR:
units += reader->current_unit;
- r = oggz_bounded_seek_set (oggz, units, 0, -1);
+ r = oggz_bounded_seek_set (oggz, units, 0, -1, 0);
break;
case SEEK_END:
r = oggz_seek_end (oggz, units);
break;
default:
/*oggz_set_error (oggz, OGGZ_EINVALID);*/
r = -1;
break;
@@ -934,130 +922,70 @@ oggz_seek_byorder (OGGZ * oggz, void * t
long
oggz_seek_packets (OGGZ * oggz, long serialno, long packets, int whence)
{
return OGGZ_ERR_DISABLED;
}
#endif
-// Returns 1 if any of the elements of array |a|, which is of length |n|,
-// contain the value |val|. Otherwise returns 0.
-static int
-is_any(ogg_int64_t* a, int n, ogg_int64_t val)
-{
- int i;
- for (i=0; i<n; i++) {
- if (a[i] == val) {
- return 1;
- }
- }
- return 0;
+// Returns the maximum time in milliseconds by which a key frame could be
+// offset for a given stream. Ogg granulepos encode time as:
+// ((key_frame_number << granule_shift) + frame_offset).
+// Therefore the maximum possible time by which any frame could be offset
+// from a keyframe is the duration of (1 << granule_shift) - 1) frames.
+static ogg_int64_t
+get_keyframe_offset(oggz_stream_t* stream) {
+ ogg_int64_t frame_duration;
+ ogg_int64_t keyframe_diff;
+
+ if (stream->granuleshift == 0)
+ return 0;
+
+ // Max number of frames keyframe could possibly be offset.
+ keyframe_diff = (1 << stream->granuleshift) - 1;
+
+ // Length of frame in ms.
+ frame_duration = stream->granulerate_d / stream->granulerate_n;
+
+ return frame_duration * keyframe_diff;
}
-// Returns the index of the element in array |a|, which is of length |n|,
-// which contains the value |val|, or -1 of it's not present.
-static int
-find(long* a, int n, ogg_int64_t val)
-{
- int i;
- for (i=0; i<n; i++) {
- if (a[i] == val) {
- return i;
- }
+// Returns the maximum possible time by which a keyframe could be offset in
+// milliseconds, for all streams in the media.
+static ogg_int64_t
+get_max_keyframe_offset(OGGZ* oggz) {
+ int i=0, size = 0, max_gshift = 0;
+ ogg_int64_t max = 0, x;
+ oggz_stream_t* stream = 0;
+ size = oggz_vector_size (oggz->streams);
+ for (i = 0; i < size; i++) {
+ stream = (oggz_stream_t *)oggz_vector_nth_p (oggz->streams, i);
+ if (!stream) continue;
+ x = get_keyframe_offset(stream);
+ if (x > max)
+ max = x;
}
- return -1;
-}
-
-// Returns the element with the smallest value in array |a|, which is
-// of length |n|.
-static ogg_int64_t
-minimum(ogg_int64_t* a, int n) {
- ogg_int64_t m = 0x7FFFFFFFFFFFFFFF;
- int i;
- for (i=0; i<n; i++) {
- if (a[i] < m) {
- m = a[i];
- }
- }
- return m;
+ return max;
}
ogg_int64_t
oggz_keyframe_seek_set(OGGZ * oggz,
- long* serial_nos,
- int num_serialno,
ogg_int64_t unit_target,
ogg_int64_t offset_begin,
ogg_int64_t offset_end)
{
- oggz_off_t offset_at;
- oggz_off_t offset_next;
- ogg_int64_t granule_at;
ogg_int64_t unit_at;
- ogg_int64_t key_granule_at, key_unit_at;
- long serialno;
- ogg_page * og;
- int granule_shift = 0, idx;
- ogg_int64_t* key_frames = 0;
-
+ ogg_int64_t max_keyframe_offset;
+ const ogg_int64_t fuzz = 500;
+ ogg_int64_t keyframe_unit_target;
+
+ max_keyframe_offset = get_max_keyframe_offset(oggz);
+
+ keyframe_unit_target = MAX(0, unit_target - max_keyframe_offset - fuzz);
+
unit_at = oggz_bounded_seek_set(oggz,
- unit_target,
+ keyframe_unit_target,
offset_begin,
- offset_end);
- // Time isn't in the specified offset range, fail.
- if (unit_at == -1)
- return -1;
-
- // We've seeked to beginning, we're at a key frame.
- if (unit_at == 0)
- return 0;
-
- // Backup this, in case we need to fail.
- offset_at = oggz->offset;
-
- key_frames = oggz_malloc(sizeof(ogg_int64_t) * num_serialno);
- if (!key_frames) {
- // Malloc failure. We can still exit with the seek finishing at a non
- // key frame.
- return unit_at;
- }
- memset(key_frames, -1, sizeof(ogg_int64_t) * num_serialno);
-
- // Find the key frame offset for every stream.
- og = &oggz->current_page;
- while (is_any(key_frames, num_serialno, -1)) {
- do {
- offset_next = oggz_get_prev_start_page (oggz, og, &granule_at, &serialno);
- if (offset_next <= 0 || granule_at == 0) {
- // At beginning of file, or some other failure. Return with
- // non-key frame seek if possible.
- oggz_free(key_frames);
- offset_at = oggz_reset (oggz, offset_at, unit_at, SEEK_SET);
- return (offset_at == -1) ? -1 : unit_at;
- }
- } while (granule_at < 0);
-
- idx = find(serial_nos, num_serialno, serialno);
- if (idx == -1 || key_frames[idx] != -1)
- continue;
-
- granule_shift = oggz_get_granuleshift(oggz, serialno);
- key_granule_at = (granule_at >> granule_shift) << granule_shift;
- key_unit_at = oggz_get_unit(oggz, serialno, key_granule_at);
-
- if (key_unit_at < unit_target)
- key_frames[idx] = key_unit_at;
- }
-
- // Seek to 100ms before the earliest of all the streams' key frames.
- // This is so that after the seek, the decoder will defintately return frames
- // at or before get the key frame. Without this, some decoders will return
- // frames which start after the specified time - after the key frame.
- key_unit_at = minimum(key_frames, num_serialno);
- unit_at = oggz_bounded_seek_set(oggz,
- MAX((key_unit_at - 100), 0),
- offset_begin,
- offset_end);
- oggz_free(key_frames);
-
+ offset_end,
+ fuzz);
return unit_at;
}

View File

@ -476,15 +476,17 @@ int oggz_set_data_start (OGGZ * oggz, oggz_off_t offset);
*/
/**
* Seeks Oggz to time unit_target, but with the bounds of the offset range
* [offset_begin, offset_end]. This is useful when seeking in network streams
* where only parts of a media are buffered, and retrieving unbuffered
* parts is expensive.
* Seeks to within fuzz_margin milliseconds of time unit_target, within the
* bounds of the offset range [offset_begin, offset_end]. This is useful when
* seeking in network streams where only parts of a media are buffered, and
* retrieving unbuffered parts is expensive.
* \param oggz An OGGZ handle previously opened for reading
* \param unit_target The seek target, in milliseconds, or custom units
* \param offset_begin Start of offset range to seek inside, in bytes
* \param offset_end End of offset range to seek inside, in bytes,
pass -1 for end of media
* \param fuzz_margin The seek stops when it's within this many milliseconds
of unit_target
* \returns The new position, in milliseconds or custom units
* \retval -1 on failure (unit_target is not within range)
*/
@ -492,17 +494,15 @@ ogg_int64_t
oggz_bounded_seek_set (OGGZ * oggz,
ogg_int64_t unit_target,
ogg_int64_t offset_begin,
ogg_int64_t offset_end);
ogg_int64_t offset_end,
int fuzz_margin);
/**
* Seeks to the first key frame before unit_target, in the range
* [offset_begin, offset_end]. serial_nos contains an array of size serial_nos
* of serialnos of the streams which need to be seeked.
* Seeks to before the first key frame before unit_target, in the range
* [offset_begin, offset_end].
*/
ogg_int64_t
oggz_keyframe_seek_set(OGGZ * oggz,
long* serial_nos,
int num_serialno,
ogg_int64_t unit_target,
ogg_int64_t offset_begin,
ogg_int64_t offset_end);

View File

@ -496,15 +496,13 @@ oggz_scan_for_page (OGGZ * oggz, ogg_page * og, ogg_int64_t unit_target,
#define GUESS_MULTIPLIER (1<<16)
static oggz_off_t
static ogg_int64_t
guess (ogg_int64_t unit_at, ogg_int64_t unit_target,
ogg_int64_t unit_begin, ogg_int64_t unit_end,
oggz_off_t offset_begin, oggz_off_t offset_end)
ogg_int64_t offset_begin, ogg_int64_t offset_end)
{
ogg_int64_t guess_ratio;
oggz_off_t offset_guess;
if (unit_at == unit_begin) return offset_begin;
ogg_int64_t offset_guess;
if (unit_end != -1) {
guess_ratio =
@ -516,35 +514,33 @@ guess (ogg_int64_t unit_at, ogg_int64_t unit_target,
(unit_at - unit_begin);
}
#ifdef DEBUG
printf ("oggz_seek::guess: guess_ratio %lld = (%lld - %lld) / (%lld - %lld)\n",
guess_ratio, unit_target, unit_begin, unit_at, unit_begin);
#endif
offset_guess = offset_begin +
(oggz_off_t)(((offset_end - offset_begin) * guess_ratio) /
(((offset_end - offset_begin) * guess_ratio) /
GUESS_MULTIPLIER);
#ifdef DEBUG
printf("guess: [o=%lld t=%lld]-[o=%lld t=%lld] guess_ratio=%lf offset_guess=%lu\n",
offset_begin, unit_begin, offset_end, unit_end,
((double)guess_ratio / (double)GUESS_MULTIPLIER), offset_guess);
#endif
return offset_guess;
}
static oggz_off_t
oggz_seek_guess (ogg_int64_t unit_at, ogg_int64_t unit_target,
ogg_int64_t unit_begin, ogg_int64_t unit_end,
oggz_off_t offset_at,
oggz_off_t offset_begin, oggz_off_t offset_end)
static ogg_int64_t
oggz_seek_guess (ogg_int64_t unit_at,
ogg_int64_t unit_target,
ogg_int64_t unit_begin,
ogg_int64_t unit_end,
oggz_off_t offset_at,
oggz_off_t offset_begin,
oggz_off_t offset_end)
{
oggz_off_t offset_guess;
if (unit_at == unit_begin) {
offset_guess = offset_begin + (offset_end - offset_begin)/2;
} else if (unit_end == -1) {
if (unit_end == -1) {
offset_guess = guess (unit_at, unit_target, unit_begin, unit_end,
offset_begin, offset_at);
} else if (unit_end <= unit_begin) {
#ifdef DEBUG
printf ("oggz_seek_guess: unit_end <= unit_begin (ERROR)\n");
#endif
offset_guess = -1;
} else {
offset_guess = guess (unit_at, unit_target, unit_begin, unit_end,
@ -622,11 +618,12 @@ ogg_int64_t
oggz_bounded_seek_set (OGGZ * oggz,
ogg_int64_t unit_target,
ogg_int64_t offset_begin,
ogg_int64_t offset_end)
ogg_int64_t offset_end,
int fuzz_margin)
{
OggzReader * reader;
oggz_off_t offset_orig, offset_at, offset_guess;
oggz_off_t offset_next;
ogg_int64_t offset_orig, offset_at, offset_guess;
ogg_int64_t offset_next;
ogg_int64_t granule_at;
ogg_int64_t unit_at, unit_begin = -1, unit_end = -1, unit_last_iter = -1;
long serialno;
@ -684,8 +681,10 @@ oggz_bounded_seek_set (OGGZ * oggz,
}
if (unit_begin == -1 && oggz_seek_raw (oggz, offset_begin, SEEK_SET) >= 0) {
ogg_int64_t granulepos;
if (oggz_get_next_start_page (oggz, og) >= 0) {
ogg_int64_t granulepos = 0;
unit_begin = 0;
// Start time needs to be the end time of the first non-header page.
while (oggz_get_next_start_page (oggz, og) >= 0 && unit_begin <= 0) {
serialno = ogg_page_serialno (og);
granulepos = ogg_page_granulepos (og);
unit_begin = oggz_get_unit (oggz, serialno, granulepos);
@ -693,8 +692,9 @@ oggz_bounded_seek_set (OGGZ * oggz,
}
/* Fail if target isn't in specified range. */
if (unit_target < unit_begin || unit_target > unit_end)
if (unit_target < unit_begin || unit_target > unit_end) {
return -1;
}
/* Reduce the search range if possible using read cursor position. */
if (unit_at > unit_begin && unit_at < unit_end) {
@ -743,6 +743,11 @@ oggz_bounded_seek_set (OGGZ * oggz,
unit_at = oggz_get_unit (oggz, serialno, granule_at);
if (abs(unit_at - unit_target) < fuzz_margin) {
// Within fuzz_margin of target, stop.
break;
}
#ifdef DEBUG
printf ("oggz_bounded_seek_set: offset_next %" PRI_OGGZ_OFF_T "d\n", offset_next);
#endif
@ -766,24 +771,7 @@ oggz_bounded_seek_set (OGGZ * oggz,
}
}
do {
offset_at = oggz_get_prev_start_page (oggz, og, &granule_at, &serialno);
if (offset_at < 0)
break;
unit_at = oggz_get_unit (oggz, serialno, granule_at);
} while (unit_at > unit_target);
if (offset_at < 0) {
oggz_reset (oggz, offset_orig, -1, SEEK_SET);
return -1;
}
offset_at = oggz_reset (oggz, offset_at, unit_at, SEEK_SET);
if (offset_at == -1) return -1;
#ifdef DEBUG
printf ("oggz_bounded_seek_set: FOUND (%lld)\n", unit_at);
#endif
/* Reader is now approximately at the seek target. */
return (long)reader->current_unit;
}
@ -818,7 +806,7 @@ oggz_seek_end (OGGZ * oggz, ogg_int64_t unit_offset)
unit_end, offset_end, granulepos);
#endif
return oggz_bounded_seek_set (oggz, unit_end + unit_offset, 0, -1);
return oggz_bounded_seek_set (oggz, unit_end + unit_offset, 0, -1, 0);
}
off_t
@ -877,11 +865,11 @@ oggz_seek_units (OGGZ * oggz, ogg_int64_t units, int whence)
switch (whence) {
case SEEK_SET:
r = oggz_bounded_seek_set (oggz, units, 0, -1);
r = oggz_bounded_seek_set (oggz, units, 0, -1, 0);
break;
case SEEK_CUR:
units += reader->current_unit;
r = oggz_bounded_seek_set (oggz, units, 0, -1);
r = oggz_bounded_seek_set (oggz, units, 0, -1, 0);
break;
case SEEK_END:
r = oggz_seek_end (oggz, units);
@ -939,125 +927,65 @@ oggz_seek_packets (OGGZ * oggz, long serialno, long packets, int whence)
#endif
// Returns 1 if any of the elements of array |a|, which is of length |n|,
// contain the value |val|. Otherwise returns 0.
static int
is_any(ogg_int64_t* a, int n, ogg_int64_t val)
{
int i;
for (i=0; i<n; i++) {
if (a[i] == val) {
return 1;
}
}
return 0;
}
// Returns the index of the element in array |a|, which is of length |n|,
// which contains the value |val|, or -1 of it's not present.
static int
find(long* a, int n, ogg_int64_t val)
{
int i;
for (i=0; i<n; i++) {
if (a[i] == val) {
return i;
}
}
return -1;
}
// Returns the element with the smallest value in array |a|, which is
// of length |n|.
// Returns the maximum time in milliseconds by which a key frame could be
// offset for a given stream. Ogg granulepos encode time as:
// ((key_frame_number << granule_shift) + frame_offset).
// Therefore the maximum possible time by which any frame could be offset
// from a keyframe is the duration of (1 << granule_shift) - 1) frames.
static ogg_int64_t
minimum(ogg_int64_t* a, int n) {
ogg_int64_t m = 0x7FFFFFFFFFFFFFFF;
int i;
for (i=0; i<n; i++) {
if (a[i] < m) {
m = a[i];
}
get_keyframe_offset(oggz_stream_t* stream) {
ogg_int64_t frame_duration;
ogg_int64_t keyframe_diff;
if (stream->granuleshift == 0)
return 0;
// Max number of frames keyframe could possibly be offset.
keyframe_diff = (1 << stream->granuleshift) - 1;
// Length of frame in ms.
frame_duration = stream->granulerate_d / stream->granulerate_n;
return frame_duration * keyframe_diff;
}
// Returns the maximum possible time by which a keyframe could be offset in
// milliseconds, for all streams in the media.
static ogg_int64_t
get_max_keyframe_offset(OGGZ* oggz) {
int i=0, size = 0, max_gshift = 0;
ogg_int64_t max = 0, x;
oggz_stream_t* stream = 0;
size = oggz_vector_size (oggz->streams);
for (i = 0; i < size; i++) {
stream = (oggz_stream_t *)oggz_vector_nth_p (oggz->streams, i);
if (!stream) continue;
x = get_keyframe_offset(stream);
if (x > max)
max = x;
}
return m;
return max;
}
ogg_int64_t
oggz_keyframe_seek_set(OGGZ * oggz,
long* serial_nos,
int num_serialno,
ogg_int64_t unit_target,
ogg_int64_t offset_begin,
ogg_int64_t offset_end)
{
oggz_off_t offset_at;
oggz_off_t offset_next;
ogg_int64_t granule_at;
ogg_int64_t unit_at;
ogg_int64_t key_granule_at, key_unit_at;
long serialno;
ogg_page * og;
int granule_shift = 0, idx;
ogg_int64_t* key_frames = 0;
ogg_int64_t max_keyframe_offset;
const ogg_int64_t fuzz = 500;
ogg_int64_t keyframe_unit_target;
max_keyframe_offset = get_max_keyframe_offset(oggz);
keyframe_unit_target = MAX(0, unit_target - max_keyframe_offset - fuzz);
unit_at = oggz_bounded_seek_set(oggz,
unit_target,
keyframe_unit_target,
offset_begin,
offset_end);
// Time isn't in the specified offset range, fail.
if (unit_at == -1)
return -1;
// We've seeked to beginning, we're at a key frame.
if (unit_at == 0)
return 0;
// Backup this, in case we need to fail.
offset_at = oggz->offset;
key_frames = oggz_malloc(sizeof(ogg_int64_t) * num_serialno);
if (!key_frames) {
// Malloc failure. We can still exit with the seek finishing at a non
// key frame.
return unit_at;
}
memset(key_frames, -1, sizeof(ogg_int64_t) * num_serialno);
// Find the key frame offset for every stream.
og = &oggz->current_page;
while (is_any(key_frames, num_serialno, -1)) {
do {
offset_next = oggz_get_prev_start_page (oggz, og, &granule_at, &serialno);
if (offset_next <= 0 || granule_at == 0) {
// At beginning of file, or some other failure. Return with
// non-key frame seek if possible.
oggz_free(key_frames);
offset_at = oggz_reset (oggz, offset_at, unit_at, SEEK_SET);
return (offset_at == -1) ? -1 : unit_at;
}
} while (granule_at < 0);
idx = find(serial_nos, num_serialno, serialno);
if (idx == -1 || key_frames[idx] != -1)
continue;
granule_shift = oggz_get_granuleshift(oggz, serialno);
key_granule_at = (granule_at >> granule_shift) << granule_shift;
key_unit_at = oggz_get_unit(oggz, serialno, key_granule_at);
if (key_unit_at < unit_target)
key_frames[idx] = key_unit_at;
}
// Seek to 100ms before the earliest of all the streams' key frames.
// This is so that after the seek, the decoder will defintately return frames
// at or before get the key frame. Without this, some decoders will return
// frames which start after the specified time - after the key frame.
key_unit_at = minimum(key_frames, num_serialno);
unit_at = oggz_bounded_seek_set(oggz,
MAX((key_unit_at - 100), 0),
offset_begin,
offset_end);
oggz_free(key_frames);
offset_end,
fuzz);
return unit_at;
}

View File

@ -57,3 +57,4 @@ patch -p3 <offset_next.patch
patch -p3 <bug487519.patch
patch -p3 <bug496063.patch
patch -p3 <oggz_os2.patch
patch -p3 <faster_seek.patch