Bug 469408. Make seeking in bufferred ranges fast by trying a 'bounded seek' in each buffered data range before falling back to a seek over the whole resource. r=doublec,sr=roc

This commit is contained in:
Chris Pearce 2009-05-18 10:15:57 +12:00
parent c8a063a105
commit 0719504584
10 changed files with 618 additions and 77 deletions

View File

@ -296,7 +296,18 @@ public:
// Returns the end of the bytes starting at the given offset
// which are in cache.
PRInt64 GetCachedDataEnd(PRInt64 aOffset);
// XXX we may need to add GetUncachedDataEnd at some point.
// Returns the offset of the first byte of cached data at or after aOffset,
// or -1 if there is no such cached data.
PRInt64 GetNextCachedData(PRInt64 aOffset);
// Reads from buffered data only. Will fail if not all data to be read is
// in the cache. Will not mark blocks as read. Can be called from the main
// thread. It's the caller's responsibility to wrap the call in a pin/unpin,
// and also to check that the range they want is cached before calling this.
nsresult ReadFromCache(char* aBuffer,
PRInt64 aOffset,
PRInt64 aCount);
// IsDataCachedToEndOfStream returns true if all the data from
// aOffset to the end of the stream (the server-reported end, if the
// real end is not known) is in cache. If we know nothing about the
@ -371,6 +382,11 @@ private:
// This method assumes that the cache monitor is held and can be called on
// any thread.
PRInt64 GetCachedDataEndInternal(PRInt64 aOffset);
// Returns the offset of the first byte of cached data at or after aOffset,
// or -1 if there is no such cached data.
// This method assumes that the cache monitor is held and can be called on
// any thread.
PRInt64 GetNextCachedDataInternal(PRInt64 aOffset);
// A helper function to do the work of closing the stream. Assumes
// that the cache monitor is held. Main thread only.
// aMonitor is the nsAutoMonitor wrapper holding the cache monitor.

View File

@ -228,6 +228,9 @@ public:
// header and give us more or less data than it reported. We will adjust
// the result of GetLength to reflect the data that's actually arriving.
virtual PRInt64 GetLength() = 0;
// Returns the offset of the first byte of cached data at or after aOffset,
// or -1 if there is no such cached data.
virtual PRInt64 GetNextCachedData(PRInt64 aOffset) = 0;
// Returns the end of the bytes starting at the given offset
// which are in cache.
virtual PRInt64 GetCachedDataEnd(PRInt64 aOffset) = 0;
@ -335,6 +338,7 @@ public:
virtual void Unpin();
virtual double GetDownloadRate(PRPackedBool* aIsReliable);
virtual PRInt64 GetLength();
virtual PRInt64 GetNextCachedData(PRInt64 aOffset);
virtual PRInt64 GetCachedDataEnd(PRInt64 aOffset);
virtual PRBool IsDataCachedToEndOfStream(PRInt64 aOffset);
virtual PRBool IsSuspendedByCache();

View File

@ -43,6 +43,12 @@
#include "nsChannelReader.h"
#include "nsIScriptSecurityManager.h"
// The minimum size buffered byte range inside which we'll consider
// trying a bounded-seek. When we seek, we first try to seek inside all
// buffered ranges larger than this, and if they all fail we fall back to
// an unbounded seek over the whole media. 64K is approximately 16 pages.
#define MIN_BOUNDED_SEEK_SIZE (64 * 1024)
OggPlayErrorCode nsChannelReader::initialise(int aBlock)
{
return E_OGGPLAY_OK;
@ -131,6 +137,70 @@ static ogg_int64_t oggplay_channel_reader_duration(struct _OggPlayReader *aReade
return me->duration();
}
class ByteRange {
public:
ByteRange() : mStart(-1), mEnd(-1) {}
ByteRange(PRInt64 aStart, PRInt64 aEnd) : mStart(aStart), mEnd(aEnd) {}
PRInt64 mStart, mEnd;
};
static void GetBufferedBytes(nsMediaStream* aStream, nsTArray<ByteRange>& aRanges)
{
PRInt64 startOffset = 0;
while (PR_TRUE) {
PRInt64 endOffset = aStream->GetCachedDataEnd(startOffset);
if (endOffset == startOffset) {
// Uncached at startOffset.
endOffset = aStream->GetNextCachedData(startOffset);
if (endOffset == -1) {
// Uncached at startOffset until endOffset of stream, or we're at
// the end of stream.
break;
}
} else {
// Bytes [startOffset..endOffset] are cached.
PRInt64 cachedLength = endOffset - startOffset;
// Only bother trying to seek inside ranges greater than
// MIN_BOUNDED_SEEK_SIZE, so that the bounded seek is unlikely to
// read outside of the range when finding Ogg page boundaries.
if (cachedLength > MIN_BOUNDED_SEEK_SIZE) {
aRanges.AppendElement(ByteRange(startOffset, endOffset));
}
}
startOffset = endOffset;
}
}
OggPlayErrorCode oggplay_channel_reader_seek(struct _OggPlayReader *me,
OGGZ *oggz,
ogg_int64_t aTargetMs)
{
nsChannelReader* reader = static_cast<nsChannelReader*>(me);
nsMediaStream* stream = reader->Stream();
nsAutoTArray<ByteRange, 16> ranges;
stream->Pin();
GetBufferedBytes(stream, ranges);
PRInt64 rv = -1;
for (PRUint32 i = 0; rv == -1 && i < ranges.Length(); i++) {
rv = oggz_bounded_seek_set(oggz,
aTargetMs,
ranges[i].mStart,
ranges[i].mEnd);
}
stream->Unpin();
if (rv == -1) {
// Could not seek in a buffered range, fall back to seeking over the
// entire media.
rv = oggz_bounded_seek_set(oggz,
aTargetMs,
0,
stream->GetLength());
}
return (rv == -1) ? E_OGGPLAY_CANT_SEEK : E_OGGPLAY_OK;
}
nsresult nsChannelReader::Init(nsMediaDecoder* aDecoder, nsIURI* aURI,
nsIChannel* aChannel,
nsIStreamListener** aStreamListener)
@ -151,7 +221,7 @@ nsChannelReader::nsChannelReader() :
OggPlayReader* reader = this;
reader->initialise = &oggplay_channel_reader_initialise;
reader->destroy = &oggplay_channel_reader_destroy;
reader->seek = nsnull;
reader->seek = &oggplay_channel_reader_seek;
reader->io_read = &oggplay_channel_reader_io_read;
reader->io_seek = &oggplay_channel_reader_io_seek;
reader->io_tell = &oggplay_channel_reader_io_tell;

View File

@ -1588,6 +1588,13 @@ nsMediaCacheStream::GetLength()
return mStreamLength;
}
PRInt64
nsMediaCacheStream::GetNextCachedData(PRInt64 aOffset)
{
nsAutoMonitor mon(gMediaCache->Monitor());
return GetNextCachedDataInternal(aOffset);
}
PRInt64
nsMediaCacheStream::GetCachedDataEnd(PRInt64 aOffset)
{
@ -1607,6 +1614,7 @@ nsMediaCacheStream::IsDataCachedToEndOfStream(PRInt64 aOffset)
PRInt64
nsMediaCacheStream::GetCachedDataEndInternal(PRInt64 aOffset)
{
PR_ASSERT_CURRENT_THREAD_IN_MONITOR(gMediaCache->Monitor());
PRUint32 startBlockIndex = aOffset/BLOCK_SIZE;
PRUint32 blockIndex = startBlockIndex;
while (blockIndex < mBlocks.Length() && mBlocks[blockIndex] != -1) {
@ -1626,6 +1634,53 @@ nsMediaCacheStream::GetCachedDataEndInternal(PRInt64 aOffset)
return PR_MAX(result, aOffset);
}
PRInt64
nsMediaCacheStream::GetNextCachedDataInternal(PRInt64 aOffset)
{
PR_ASSERT_CURRENT_THREAD_IN_MONITOR(gMediaCache->Monitor());
if (aOffset == mStreamLength)
return -1;
PRUint32 startBlockIndex = aOffset/BLOCK_SIZE;
PRUint32 channelBlockIndex = mChannelOffset/BLOCK_SIZE;
if (startBlockIndex == channelBlockIndex &&
aOffset < mChannelOffset) {
// The block containing mChannelOffset is partially read, but not
// yet committed to the main cache. aOffset lies in the partially
// read portion, thus it is effectively cached.
return aOffset;
}
if (startBlockIndex >= mBlocks.Length())
return -1;
// Is the current block cached?
if (mBlocks[startBlockIndex] != -1)
return aOffset;
// Count the number of uncached blocks
PRBool hasPartialBlock = (mChannelOffset % BLOCK_SIZE) != 0;
PRUint32 blockIndex = startBlockIndex + 1;
while (PR_TRUE) {
if ((hasPartialBlock && blockIndex == channelBlockIndex) ||
(blockIndex < mBlocks.Length() && mBlocks[blockIndex] != -1)) {
// We at the incoming channel block, which has has data in it,
// or are we at a cached block. Return index of block start.
return blockIndex * BLOCK_SIZE;
}
// No more cached blocks?
if (blockIndex >= mBlocks.Length())
return -1;
++blockIndex;
}
NS_NOTREACHED("Should return in loop");
return -1;
}
void
nsMediaCacheStream::SetReadMode(ReadMode aMode)
{
@ -1721,7 +1776,7 @@ nsMediaCacheStream::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
PRUint32 channelBlock = PRUint32(mChannelOffset/BLOCK_SIZE);
PRInt32 cacheBlock = streamBlock < mBlocks.Length() ? mBlocks[streamBlock] : -1;
if (channelBlock == streamBlock && mStreamOffset < mChannelOffset) {
// We can just use the data in mPartialBuffer. In fact we should
// We can just use the data in mPartialBlockBuffer. In fact we should
// use it rather than waiting for the block to fill and land in
// the cache.
bytes = PR_MIN(size, mChannelOffset - mStreamOffset);
@ -1775,6 +1830,61 @@ nsMediaCacheStream::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
return NS_OK;
}
nsresult
nsMediaCacheStream::ReadFromCache(char* aBuffer,
PRInt64 aOffset,
PRInt64 aCount)
{
nsAutoMonitor mon(gMediaCache->Monitor());
if (mClosed)
return NS_ERROR_FAILURE;
// Read one block (or part of a block) at a time
PRUint32 count = 0;
PRInt64 streamOffset = aOffset;
while (count < aCount) {
PRUint32 streamBlock = PRUint32(streamOffset/BLOCK_SIZE);
PRUint32 offsetInStreamBlock =
PRUint32(streamOffset - streamBlock*BLOCK_SIZE);
PRInt32 size = PR_MIN(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
if (mStreamLength >= 0) {
// Don't try to read beyond the end of the stream
PRInt64 bytesRemaining = mStreamLength - streamOffset;
if (bytesRemaining <= 0) {
return NS_ERROR_FAILURE;
}
size = PR_MIN(size, PRInt32(bytesRemaining));
}
PRInt32 bytes;
PRUint32 channelBlock = PRUint32(mChannelOffset/BLOCK_SIZE);
PRInt32 cacheBlock = streamBlock < mBlocks.Length() ? mBlocks[streamBlock] : -1;
if (channelBlock == streamBlock && streamOffset < mChannelOffset) {
// We can just use the data in mPartialBlockBuffer. In fact we should
// use it rather than waiting for the block to fill and land in
// the cache.
bytes = PR_MIN(size, mChannelOffset - streamOffset);
memcpy(aBuffer + count,
reinterpret_cast<char*>(mPartialBlockBuffer) + offsetInStreamBlock, bytes);
} else {
if (cacheBlock < 0) {
// We expect all blocks to be cached! Fail!
return NS_ERROR_FAILURE;
}
PRInt64 offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
nsresult rv = gMediaCache->ReadCacheFile(offset, aBuffer + count, size, &bytes);
if (NS_FAILED(rv)) {
return rv;
}
}
streamOffset += bytes;
count += bytes;
}
return NS_OK;
}
nsresult
nsMediaCacheStream::Init()
{

View File

@ -596,6 +596,12 @@ nsMediaChannelStream::CacheClientResume()
return NS_OK;
}
PRInt64
nsMediaChannelStream::GetNextCachedData(PRInt64 aOffset)
{
return mCacheStream.GetNextCachedData(aOffset);
}
PRInt64
nsMediaChannelStream::GetCachedDataEnd(PRInt64 aOffset)
{
@ -693,6 +699,10 @@ public:
return 100*1024*1024; // arbitray, use 100MB/s
}
virtual PRInt64 GetLength() { return mSize; }
virtual PRInt64 GetNextCachedData(PRInt64 aOffset)
{
return (aOffset < mSize) ? aOffset : -1;
}
virtual PRInt64 GetCachedDataEnd(PRInt64 aOffset) { return PR_MAX(aOffset, mSize); }
virtual PRBool IsDataCachedToEndOfStream(PRInt64 aOffset) { return PR_TRUE; }
virtual PRBool IsSuspendedByCache() { return PR_FALSE; }

View File

@ -10,3 +10,5 @@ The wince.patch addresses the lack of posix file IO support on windows ce,
see bug 461844 for details.
endian.patch is applied to fix bug 452698.
bounded_seek.patch is applied to fix bug 469408.

View File

@ -0,0 +1,332 @@
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
@@ -470,9 +470,28 @@ long oggz_seek_byorder (OGGZ * oggz, voi
* \param oggz An OGGZ handle previously opened for reading
* \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.
+ * \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
+ * \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);
+
#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
@@ -617,205 +617,182 @@ oggz_offset_end (OGGZ * oggz)
if (oggz_io_seek (oggz, offset_save, SEEK_SET) == -1) {
return -1; /* fubar */
}
}
return offset_end;
}
-static ogg_int64_t
-oggz_seek_set (OGGZ * oggz, ogg_int64_t unit_target)
+ogg_int64_t
+oggz_bounded_seek_set (OGGZ * oggz,
+ ogg_int64_t unit_target,
+ ogg_int64_t offset_begin,
+ ogg_int64_t offset_end)
{
OggzReader * reader;
oggz_off_t offset_orig, offset_at, offset_guess;
- oggz_off_t offset_begin, offset_end = -1, offset_next;
+ oggz_off_t offset_next;
ogg_int64_t granule_at;
- ogg_int64_t unit_at, unit_begin = 0, unit_end = -1, unit_last_iter = -1;
+ 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;
}
if (unit_target > 0 && !oggz_has_metrics (oggz)) {
#ifdef DEBUG
- printf ("oggz_seek_set: No metric defined, FAIL\n");
+ printf ("oggz_bounded_seek_set: No metric defined, FAIL\n");
#endif
return -1;
}
-
- if ((offset_end = oggz_offset_end (oggz)) == -1) {
+
+ if (offset_end == -1 && (offset_end = oggz_offset_end (oggz)) == -1) {
#ifdef DEBUG
- printf ("oggz_seek_set: oggz_offset_end == -1, FAIL\n");
+ printf ("oggz_bounded_seek_set: oggz_offset_end == -1, FAIL\n");
#endif
return -1;
}
reader = &oggz->x.reader;
if (unit_target == reader->current_unit) {
#ifdef DEBUG
- printf ("oggz_seek_set: unit_target == reader->current_unit, SKIP\n");
+ printf ("oggz_bounded_seek_set: unit_target == reader->current_unit, SKIP\n");
#endif
return (long)reader->current_unit;
}
if (unit_target == 0) {
offset_at = oggz_reset (oggz, oggz->offset_data_begin, 0, SEEK_SET);
if (offset_at == -1) return -1;
return 0;
}
offset_at = oggz_tell_raw (oggz);
if (offset_at == -1) return -1;
offset_orig = oggz->offset;
- offset_begin = 0;
-
unit_at = reader->current_unit;
- unit_begin = 0;
og = &oggz->current_page;
- if (oggz_seek_raw (oggz, 0, SEEK_END) >= 0) {
+ if (unit_end == -1 && oggz_seek_raw (oggz, offset_end, SEEK_SET) >= 0) {
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) {
+ 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)
+ 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;
+ offset_begin = offset_at;
+ }
+ }
+
og = &oggz->current_page;
for ( ; ; ) {
unit_last_iter = unit_at;
hit_eof = 0;
#ifdef DEBUG
- printf ("oggz_seek_set: [A] want u%lld: (u%lld - u%lld) [@%" PRI_OGGZ_OFF_T "d - @%" PRI_OGGZ_OFF_T "d]\n",
+ printf ("oggz_bounded_seek_set: [A] want u%lld: (u%lld - u%lld) [@%" PRI_OGGZ_OFF_T "d - @%" PRI_OGGZ_OFF_T "d]\n",
unit_target, unit_begin, unit_end, offset_begin, offset_end);
#endif
offset_guess = oggz_seek_guess (unit_at, unit_target,
unit_begin, unit_end,
offset_at,
offset_begin, offset_end);
if (offset_guess == -1) break;
if (offset_guess == offset_at) {
/* Already there, looping */
break;
}
if (offset_guess > offset_end) {
offset_guess = offset_end;
- }
-
- offset_at = oggz_seek_raw (oggz, offset_guess, SEEK_SET);
- if (offset_at == -1) {
- goto notfound;
- }
-
- offset_next = oggz_get_next_start_page (oggz, og);
-
-#ifdef DEBUG
- printf ("oggz_seek_set: offset_next %" PRI_OGGZ_OFF_T "d\n", offset_next);
-#endif
-
- if (/*unit_end == -1 &&*/ offset_next == -2) { /* reached eof, backtrack */
- hit_eof = 1;
- offset_next = oggz_get_prev_start_page (oggz, og, &granule_at,
- &serialno);
- unit_end = oggz_get_unit (oggz, serialno, granule_at);
-#ifdef DEBUG
- printf ("oggz_seek_set: [C] offset_next @%" PRI_OGGZ_OFF_T "d, g%lld, (s%ld)\n",
- offset_next, granule_at, serialno);
- printf ("oggz_seek_set: [c] u%lld\n",
- oggz_get_unit (oggz, serialno, granule_at));
-#endif
- } else if (offset_next >= 0) {
+ offset_at = oggz_seek_raw (oggz, offset_guess, SEEK_SET);
+ offset_next = oggz_get_prev_start_page (oggz, og, &granule_at, &serialno);
+ } else {
+ 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);
}
- if (offset_next < 0) {
- goto notfound;
- }
-
- if (hit_eof || offset_next > offset_end) {
- offset_next =
- oggz_scan_for_page (oggz, og, unit_target, offset_begin, offset_end);
- if (offset_next < 0) goto notfound;
-
- offset_at = offset_next;
- serialno = ogg_page_serialno (og);
- granule_at = ogg_page_granulepos (og);
-
- unit_at = oggz_get_unit (oggz, serialno, granule_at);
-
- goto found;
- }
-
- offset_at = offset_next;
-
unit_at = oggz_get_unit (oggz, serialno, granule_at);
+#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_seek_set: [D] want u%lld, got page u%lld @%" PRI_OGGZ_OFF_T "d g%lld\n",
+ 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);
#endif
if (unit_at < unit_target) {
offset_begin = offset_at;
unit_begin = unit_at;
if (unit_end == unit_begin) break;
} else if (unit_at > unit_target) {
offset_end = offset_at-1;
unit_end = unit_at;
if (unit_end == unit_begin) break;
} else {
break;
}
}
- found:
do {
offset_at = oggz_get_prev_start_page (oggz, og, &granule_at, &serialno);
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_seek_set: FOUND (%lld)\n", unit_at);
+ printf ("oggz_bounded_seek_set: FOUND (%lld)\n", unit_at);
#endif
return (long)reader->current_unit;
-
- notfound:
-#ifdef DEBUG
- printf ("oggz_seek_set: NOT FOUND\n");
-#endif
-
- oggz_reset (oggz, offset_orig, -1, SEEK_SET);
-
- return -1;
}
static ogg_int64_t
oggz_seek_end (OGGZ * oggz, ogg_int64_t unit_offset)
{
oggz_off_t offset_orig, offset_at, offset_end;
ogg_int64_t granulepos;
ogg_int64_t unit_end;
@@ -838,17 +815,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_seek_set (oggz, unit_end + unit_offset);
+ return oggz_bounded_seek_set (oggz, unit_end + unit_offset, 0, -1);
}
off_t
oggz_seek (OGGZ * oggz, oggz_off_t offset, int whence)
{
OggzReader * reader;
ogg_int64_t units = -1;
@@ -897,21 +874,21 @@ oggz_seek_units (OGGZ * oggz, ogg_int64_
#endif
return -1;
}
reader = &oggz->x.reader;
switch (whence) {
case SEEK_SET:
- r = oggz_seek_set (oggz, units);
+ r = oggz_bounded_seek_set (oggz, units, 0, -1);
break;
case SEEK_CUR:
units += reader->current_unit;
- r = oggz_seek_set (oggz, units);
+ r = oggz_bounded_seek_set (oggz, units, 0, -1);
break;
case SEEK_END:
r = oggz_seek_end (oggz, units);
break;
default:
/*oggz_set_error (oggz, OGGZ_EINVALID);*/
r = -1;
break;

View File

@ -475,4 +475,23 @@ 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.
* \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
* \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);
#endif /* __OGGZ_SEEK_H__ */

View File

@ -622,14 +622,17 @@ oggz_offset_end (OGGZ * oggz)
return offset_end;
}
static ogg_int64_t
oggz_seek_set (OGGZ * oggz, ogg_int64_t unit_target)
ogg_int64_t
oggz_bounded_seek_set (OGGZ * oggz,
ogg_int64_t unit_target,
ogg_int64_t offset_begin,
ogg_int64_t offset_end)
{
OggzReader * reader;
oggz_off_t offset_orig, offset_at, offset_guess;
oggz_off_t offset_begin, offset_end = -1, offset_next;
oggz_off_t offset_next;
ogg_int64_t granule_at;
ogg_int64_t unit_at, unit_begin = 0, unit_end = -1, unit_last_iter = -1;
ogg_int64_t unit_at, unit_begin = -1, unit_end = -1, unit_last_iter = -1;
long serialno;
ogg_page * og;
int hit_eof = 0;
@ -640,14 +643,14 @@ oggz_seek_set (OGGZ * oggz, ogg_int64_t unit_target)
if (unit_target > 0 && !oggz_has_metrics (oggz)) {
#ifdef DEBUG
printf ("oggz_seek_set: No metric defined, FAIL\n");
printf ("oggz_bounded_seek_set: No metric defined, FAIL\n");
#endif
return -1;
}
if ((offset_end = oggz_offset_end (oggz)) == -1) {
if (offset_end == -1 && (offset_end = oggz_offset_end (oggz)) == -1) {
#ifdef DEBUG
printf ("oggz_seek_set: oggz_offset_end == -1, FAIL\n");
printf ("oggz_bounded_seek_set: oggz_offset_end == -1, FAIL\n");
#endif
return -1;
}
@ -656,7 +659,7 @@ oggz_seek_set (OGGZ * oggz, ogg_int64_t unit_target)
if (unit_target == reader->current_unit) {
#ifdef DEBUG
printf ("oggz_seek_set: unit_target == reader->current_unit, SKIP\n");
printf ("oggz_bounded_seek_set: unit_target == reader->current_unit, SKIP\n");
#endif
return (long)reader->current_unit;
}
@ -672,14 +675,11 @@ oggz_seek_set (OGGZ * oggz, ogg_int64_t unit_target)
offset_orig = oggz->offset;
offset_begin = 0;
unit_at = reader->current_unit;
unit_begin = 0;
og = &oggz->current_page;
if (oggz_seek_raw (oggz, 0, SEEK_END) >= 0) {
if (unit_end == -1 && oggz_seek_raw (oggz, offset_end, SEEK_SET) >= 0) {
ogg_int64_t granulepos;
if (oggz_get_prev_start_page (oggz, og, &granulepos, &serialno) >= 0) {
@ -687,6 +687,30 @@ oggz_seek_set (OGGZ * oggz, ogg_int64_t unit_target)
}
}
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) {
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)
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;
offset_begin = offset_at;
}
}
og = &oggz->current_page;
for ( ; ; ) {
@ -695,7 +719,7 @@ oggz_seek_set (OGGZ * oggz, ogg_int64_t unit_target)
hit_eof = 0;
#ifdef DEBUG
printf ("oggz_seek_set: [A] want u%lld: (u%lld - u%lld) [@%" PRI_OGGZ_OFF_T "d - @%" PRI_OGGZ_OFF_T "d]\n",
printf ("oggz_bounded_seek_set: [A] want u%lld: (u%lld - u%lld) [@%" PRI_OGGZ_OFF_T "d - @%" PRI_OGGZ_OFF_T "d]\n",
unit_target, unit_begin, unit_end, offset_begin, offset_end);
#endif
@ -712,61 +736,24 @@ oggz_seek_set (OGGZ * oggz, ogg_int64_t unit_target)
if (offset_guess > offset_end) {
offset_guess = offset_end;
}
offset_at = oggz_seek_raw (oggz, offset_guess, SEEK_SET);
if (offset_at == -1) {
goto notfound;
}
offset_next = oggz_get_next_start_page (oggz, og);
#ifdef DEBUG
printf ("oggz_seek_set: offset_next %" PRI_OGGZ_OFF_T "d\n", offset_next);
#endif
if (/*unit_end == -1 &&*/ offset_next == -2) { /* reached eof, backtrack */
hit_eof = 1;
offset_next = oggz_get_prev_start_page (oggz, og, &granule_at,
&serialno);
unit_end = oggz_get_unit (oggz, serialno, granule_at);
#ifdef DEBUG
printf ("oggz_seek_set: [C] offset_next @%" PRI_OGGZ_OFF_T "d, g%lld, (s%ld)\n",
offset_next, granule_at, serialno);
printf ("oggz_seek_set: [c] u%lld\n",
oggz_get_unit (oggz, serialno, granule_at));
#endif
} else if (offset_next >= 0) {
offset_at = oggz_seek_raw (oggz, offset_guess, SEEK_SET);
offset_next = oggz_get_prev_start_page (oggz, og, &granule_at, &serialno);
} else {
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);
}
if (offset_next < 0) {
goto notfound;
}
if (hit_eof || offset_next > offset_end) {
offset_next =
oggz_scan_for_page (oggz, og, unit_target, offset_begin, offset_end);
if (offset_next < 0) goto notfound;
offset_at = offset_next;
serialno = ogg_page_serialno (og);
granule_at = ogg_page_granulepos (og);
unit_at = oggz_get_unit (oggz, serialno, granule_at);
goto found;
}
offset_at = offset_next;
unit_at = oggz_get_unit (oggz, serialno, granule_at);
#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_seek_set: [D] want u%lld, got page u%lld @%" PRI_OGGZ_OFF_T "d g%lld\n",
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);
#endif
@ -783,7 +770,6 @@ oggz_seek_set (OGGZ * oggz, ogg_int64_t unit_target)
}
}
found:
do {
offset_at = oggz_get_prev_start_page (oggz, og, &granule_at, &serialno);
unit_at = oggz_get_unit (oggz, serialno, granule_at);
@ -798,19 +784,10 @@ oggz_seek_set (OGGZ * oggz, ogg_int64_t unit_target)
if (offset_at == -1) return -1;
#ifdef DEBUG
printf ("oggz_seek_set: FOUND (%lld)\n", unit_at);
printf ("oggz_bounded_seek_set: FOUND (%lld)\n", unit_at);
#endif
return (long)reader->current_unit;
notfound:
#ifdef DEBUG
printf ("oggz_seek_set: NOT FOUND\n");
#endif
oggz_reset (oggz, offset_orig, -1, SEEK_SET);
return -1;
}
static ogg_int64_t
@ -843,7 +820,7 @@ oggz_seek_end (OGGZ * oggz, ogg_int64_t unit_offset)
unit_end, offset_end, granulepos);
#endif
return oggz_seek_set (oggz, unit_end + unit_offset);
return oggz_bounded_seek_set (oggz, unit_end + unit_offset, 0, -1);
}
off_t
@ -902,11 +879,11 @@ oggz_seek_units (OGGZ * oggz, ogg_int64_t units, int whence)
switch (whence) {
case SEEK_SET:
r = oggz_seek_set (oggz, units);
r = oggz_bounded_seek_set (oggz, units, 0, -1);
break;
case SEEK_CUR:
units += reader->current_unit;
r = oggz_seek_set (oggz, units);
r = oggz_bounded_seek_set (oggz, units, 0, -1);
break;
case SEEK_END:
r = oggz_seek_end (oggz, units);

View File

@ -46,3 +46,4 @@ sed s/\#include\ \"config.h\"/\#ifdef\ WIN32\\n\#include\ \"config_win32.h\"\\n\
cp $1/AUTHORS ./AUTHORS
patch -p3 <wince.patch
patch -p3 <endian.patch
patch -p3 <bounded_seek.patch