Bug 513144. Allow nsMediaCache blocks to be owned by multiple streams at the same time. r=doublec

--HG--
extra : rebase_source : 8788764ff6c883d2da5347ead39660626deb9d7c
This commit is contained in:
Robert O'Callahan 2009-09-15 14:30:44 +12:00
parent 93031ccc14
commit e6abe07b1b
2 changed files with 248 additions and 184 deletions

View File

@ -1,4 +1,4 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */ /* vim:set ts=2 sw=2 sts=2 et cindent: */
/* ***** BEGIN LICENSE BLOCK ***** /* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
@ -157,8 +157,8 @@ public:
// in-memory mPartialBlockBuffer while the block is only partly full, // in-memory mPartialBlockBuffer while the block is only partly full,
// and thus hasn't yet been committed to the cache. The caller will // and thus hasn't yet been committed to the cache. The caller will
// call QueueUpdate(). // call QueueUpdate().
void NoteBlockUsage(PRInt32 aBlockIndex, nsMediaCacheStream::ReadMode aMode, void NoteBlockUsage(nsMediaCacheStream* aStream, PRInt32 aBlockIndex,
TimeStamp aNow); nsMediaCacheStream::ReadMode aMode, TimeStamp aNow);
// This queues a call to Update() on the main thread. // This queues a call to Update() on the main thread.
void QueueUpdate(); void QueueUpdate();
@ -197,6 +197,7 @@ protected:
nsMediaCacheStream* aForStream, nsMediaCacheStream* aForStream,
PRInt32 aForStreamBlock, PRInt32 aForStreamBlock,
PRInt32 aMaxSearchBlockIndex); PRInt32 aMaxSearchBlockIndex);
PRBool BlockIsReusable(PRInt32 aBlockIndex);
// Given a list of blocks sorted with the most reusable blocks at the // Given a list of blocks sorted with the most reusable blocks at the
// end, find the last block whose stream is not pinned (if any) // end, find the last block whose stream is not pinned (if any)
// and whose cache entry index is less than aBlockIndexLimit // and whose cache entry index is less than aBlockIndexLimit
@ -206,8 +207,6 @@ protected:
PRInt32 aBlockIndexLimit); PRInt32 aBlockIndexLimit);
enum BlockClass { enum BlockClass {
// block belongs to mFreeBlockList because it's free
FREE_BLOCK,
// block belongs to mMetadataBlockList because data has been consumed // block belongs to mMetadataBlockList because data has been consumed
// from it in "metadata mode" --- in particular blocks read during // from it in "metadata mode" --- in particular blocks read during
// Ogg seeks go into this class. These blocks may have played data // Ogg seeks go into this class. These blocks may have played data
@ -222,8 +221,8 @@ protected:
READAHEAD_BLOCK READAHEAD_BLOCK
}; };
struct Block { struct BlockOwner {
Block() : mStream(nsnull), mClass(FREE_BLOCK) {} BlockOwner() : mStream(nsnull), mClass(READAHEAD_BLOCK) {}
// The stream that owns this block, or null if the block is free. // The stream that owns this block, or null if the block is free.
nsMediaCacheStream* mStream; nsMediaCacheStream* mStream;
@ -232,22 +231,35 @@ protected:
// Time at which this block was last used. Valid only if // Time at which this block was last used. Valid only if
// mClass is METADATA_BLOCK or PLAYED_BLOCK. // mClass is METADATA_BLOCK or PLAYED_BLOCK.
TimeStamp mLastUseTime; TimeStamp mLastUseTime;
// The class is FREE_BLOCK if and only if mStream is null
BlockClass mClass; BlockClass mClass;
}; };
struct Block {
// Free blocks have an empty mOwners array
nsTArray<BlockOwner> mOwners;
};
// Get the BlockList that the block should belong to given its // Get the BlockList that the block should belong to given its
// current mClass and mStream // current owner
BlockList* GetListForBlock(Block* aBlock); BlockList* GetListForBlock(BlockOwner* aBlock);
// Add the block to the free list, mark it FREE_BLOCK, and mark // Get the BlockOwner for the given block index and owning stream
// its stream (if any) as not having the block in cache // (returns null if the stream does not own the block)
BlockOwner* GetBlockOwner(PRInt32 aBlockIndex, nsMediaCacheStream* aStream);
// Returns true iff the block is free
PRBool IsBlockFree(PRInt32 aBlockIndex)
{ return mIndex[aBlockIndex].mOwners.IsEmpty(); }
// Add the block to the free list and mark its streams as not having
// the block in cache
void FreeBlock(PRInt32 aBlock); void FreeBlock(PRInt32 aBlock);
// Mark aStream as not having the block, removing it as an owner. If
// the block has no more owners it's added to the free list.
void RemoveBlockOwner(PRInt32 aBlockIndex, nsMediaCacheStream* aStream);
// Swap all metadata associated with the two blocks. The caller // Swap all metadata associated with the two blocks. The caller
// is responsible for swapping up any cache file state. // is responsible for swapping up any cache file state.
void SwapBlocks(PRInt32 aBlockIndex1, PRInt32 aBlockIndex2); void SwapBlocks(PRInt32 aBlockIndex1, PRInt32 aBlockIndex2);
// Insert the block into the readahead block list for its stream // Insert the block into the readahead block list for the stream
// at the right point in the list. // at the right point in the list.
void InsertReadaheadBlock(PRInt32 aBlockIndex); void InsertReadaheadBlock(BlockOwner* aBlockOwner, PRInt32 aBlockIndex);
// Guess the duration until block aBlock will be next used // Guess the duration until block aBlock will be next used
TimeDuration PredictNextUse(TimeStamp aNow, PRInt32 aBlock); TimeDuration PredictNextUse(TimeStamp aNow, PRInt32 aBlock);
@ -274,10 +286,6 @@ protected:
PRInt64 mFDCurrentPos; PRInt64 mFDCurrentPos;
// The list of free blocks; they are not ordered. // The list of free blocks; they are not ordered.
BlockList mFreeBlocks; BlockList mFreeBlocks;
// The list of metadata blocks; the first block is the most recently used
BlockList mMetadataBlocks;
// The list of played-back blocks; the first block is the most recently used
BlockList mPlayedBlocks;
// True if an event to run Update() has been queued but not processed // True if an event to run Update() has been queued but not processed
PRPackedBool mUpdateQueued; PRPackedBool mUpdateQueued;
#ifdef DEBUG #ifdef DEBUG
@ -616,7 +624,7 @@ nsMediaCache::FindBlockForIncomingData(TimeStamp aNow,
PRInt32 blockIndex = FindReusableBlock(aNow, aStream, PRInt32 blockIndex = FindReusableBlock(aNow, aStream,
aStream->mChannelOffset/BLOCK_SIZE, PR_INT32_MAX); aStream->mChannelOffset/BLOCK_SIZE, PR_INT32_MAX);
if (blockIndex < 0 || mIndex[blockIndex].mStream) { if (blockIndex < 0 || !IsBlockFree(blockIndex)) {
// The block returned is already allocated. // The block returned is already allocated.
// Don't reuse it if a) there's room to expand the cache or // Don't reuse it if a) there's room to expand the cache or
// b) the data we're going to store in the free block is not higher // b) the data we're going to store in the free block is not higher
@ -635,6 +643,20 @@ nsMediaCache::FindBlockForIncomingData(TimeStamp aNow,
return blockIndex; return blockIndex;
} }
PRBool
nsMediaCache::BlockIsReusable(PRInt32 aBlockIndex)
{
Block* block = &mIndex[aBlockIndex];
for (PRUint32 i = 0; i < block->mOwners.Length(); ++i) {
nsMediaCacheStream* stream = block->mOwners[i].mStream;
if (stream->mPinCount >= 0 ||
stream->mStreamOffset/BLOCK_SIZE == block->mOwners[i].mStreamBlock) {
return PR_FALSE;
}
}
return PR_TRUE;
}
void void
nsMediaCache::AppendMostReusableBlock(BlockList* aBlockList, nsMediaCache::AppendMostReusableBlock(BlockList* aBlockList,
nsTArray<PRUint32>* aResult, nsTArray<PRUint32>* aResult,
@ -647,12 +669,10 @@ nsMediaCache::AppendMostReusableBlock(BlockList* aBlockList,
return; return;
do { do {
// Don't consider blocks for pinned streams, or blocks that are // Don't consider blocks for pinned streams, or blocks that are
// beyond the specified limit, or the block that contains its stream's // beyond the specified limit, or a block that contains a stream's
// current read position (such a block contains both played data // current read position (such a block contains both played data
// and readahead data) // and readahead data)
nsMediaCacheStream* stream = mIndex[blockIndex].mStream; if (blockIndex < aBlockIndexLimit && BlockIsReusable(blockIndex)) {
if (stream->mPinCount == 0 && blockIndex < aBlockIndexLimit &&
stream->mStreamOffset/BLOCK_SIZE != mIndex[blockIndex].mStreamBlock) {
aResult->AppendElement(blockIndex); aResult->AppendElement(blockIndex);
return; return;
} }
@ -677,7 +697,7 @@ nsMediaCache::FindReusableBlock(TimeStamp aNow,
PRUint32 freeBlockScanEnd = PRUint32 freeBlockScanEnd =
PR_MIN(length, prevCacheBlock + FREE_BLOCK_SCAN_LIMIT); PR_MIN(length, prevCacheBlock + FREE_BLOCK_SCAN_LIMIT);
for (PRUint32 i = prevCacheBlock; i < freeBlockScanEnd; ++i) { for (PRUint32 i = prevCacheBlock; i < freeBlockScanEnd; ++i) {
if (mIndex[i].mClass == FREE_BLOCK) if (IsBlockFree(i))
return i; return i;
} }
} }
@ -697,25 +717,20 @@ nsMediaCache::FindReusableBlock(TimeStamp aNow,
// linked lists are ordered by increasing time of next use. This is // linked lists are ordered by increasing time of next use. This is
// actually the whole point of having the linked lists. // actually the whole point of having the linked lists.
nsAutoTArray<PRUint32,8> candidates; nsAutoTArray<PRUint32,8> candidates;
AppendMostReusableBlock(&mMetadataBlocks, &candidates, length);
AppendMostReusableBlock(&mPlayedBlocks, &candidates, length);
for (PRUint32 i = 0; i < mStreams.Length(); ++i) { for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
nsMediaCacheStream* stream = mStreams[i]; nsMediaCacheStream* stream = mStreams[i];
// Don't consider a) blocks for pinned streams or b) blocks in if (stream->mPinCount > 0) {
// non-seekable streams that contain data ahead of the current reader // No point in even looking at this stream's blocks
// position. In the latter case, if we remove the block we won't be continue;
// able to seek back to read it later. }
if (!stream->mReadaheadBlocks.IsEmpty() && stream->mIsSeekable &&
stream->mPinCount == 0) { AppendMostReusableBlock(&stream->mMetadataBlocks, &candidates, length);
// Find a readahead block that's in the given limit AppendMostReusableBlock(&stream->mPlayedBlocks, &candidates, length);
PRInt32 blockIndex = stream->mReadaheadBlocks.GetLastBlock();
do { // Don't consider readahead blocks in non-seekable streams. If we
if (PRUint32(blockIndex) < length) { // remove the block we won't be able to seek back to read it later.
candidates.AppendElement(blockIndex); if (stream->mIsSeekable) {
break; AppendMostReusableBlock(&stream->mReadaheadBlocks, &candidates, length);
}
blockIndex = stream->mReadaheadBlocks.GetPrevBlock(blockIndex);
} while (blockIndex >= 0);
} }
} }
@ -729,34 +744,19 @@ nsMediaCache::FindReusableBlock(TimeStamp aNow,
} }
} }
#ifdef DEBUG
for (PRUint32 blockIndex = 0; blockIndex < length; ++blockIndex) {
Block* block = &mIndex[blockIndex];
nsMediaCacheStream* stream = block->mStream;
NS_ASSERTION(!stream || stream->mPinCount > 0 ||
(!stream->mIsSeekable && block->mClass == READAHEAD_BLOCK) ||
stream->mStreamOffset/BLOCK_SIZE == block->mStreamBlock ||
PredictNextUse(aNow, blockIndex) <= latestUse,
"We missed a block that should be replaced");
}
#endif
return latestUseBlock; return latestUseBlock;
} }
nsMediaCache::BlockList* nsMediaCache::BlockList*
nsMediaCache::GetListForBlock(Block* aBlock) nsMediaCache::GetListForBlock(BlockOwner* aBlock)
{ {
switch (aBlock->mClass) { switch (aBlock->mClass) {
case FREE_BLOCK:
NS_ASSERTION(!aBlock->mStream, "Free block has a stream?");
return &mFreeBlocks;
case METADATA_BLOCK: case METADATA_BLOCK:
NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?"); NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?");
return &mMetadataBlocks; return &aBlock->mStream->mMetadataBlocks;
case PLAYED_BLOCK: case PLAYED_BLOCK:
NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?"); NS_ASSERTION(aBlock->mStream, "Metadata block has no stream?");
return &mPlayedBlocks; return &aBlock->mStream->mPlayedBlocks;
case READAHEAD_BLOCK: case READAHEAD_BLOCK:
NS_ASSERTION(aBlock->mStream, "Readahead block has no stream?"); NS_ASSERTION(aBlock->mStream, "Readahead block has no stream?");
return &aBlock->mStream->mReadaheadBlocks; return &aBlock->mStream->mReadaheadBlocks;
@ -766,6 +766,17 @@ nsMediaCache::GetListForBlock(Block* aBlock)
} }
} }
nsMediaCache::BlockOwner*
nsMediaCache::GetBlockOwner(PRInt32 aBlockIndex, nsMediaCacheStream* aStream)
{
Block* block = &mIndex[aBlockIndex];
for (PRUint32 i = 0; i < block->mOwners.Length(); ++i) {
if (block->mOwners[i].mStream == aStream)
return &block->mOwners[i];
}
return nsnull;
}
void void
nsMediaCache::SwapBlocks(PRInt32 aBlockIndex1, PRInt32 aBlockIndex2) nsMediaCache::SwapBlocks(PRInt32 aBlockIndex1, PRInt32 aBlockIndex2)
{ {
@ -774,43 +785,80 @@ nsMediaCache::SwapBlocks(PRInt32 aBlockIndex1, PRInt32 aBlockIndex2)
Block* block1 = &mIndex[aBlockIndex1]; Block* block1 = &mIndex[aBlockIndex1];
Block* block2 = &mIndex[aBlockIndex2]; Block* block2 = &mIndex[aBlockIndex2];
Block tmp = *block1; block1->mOwners.SwapElements(block2->mOwners);
*block1 = *block2;
*block2 = tmp;
// Now all references to block1 have to be replaced with block2 and // Now all references to block1 have to be replaced with block2 and
// vice versa // vice versa.
// First update stream references to blocks via mBlocks.
if (block1->mStream) { const Block* blocks[] = { block1, block2 };
block1->mStream->mBlocks[block1->mStreamBlock] = aBlockIndex1; PRInt32 blockIndices[] = { aBlockIndex1, aBlockIndex2 };
} for (PRInt32 i = 0; i < 2; ++i) {
if (block2->mStream) { for (PRUint32 j = 0; j < blocks[i]->mOwners.Length(); ++j) {
block2->mStream->mBlocks[block2->mStreamBlock] = aBlockIndex2; const BlockOwner* b = &blocks[i]->mOwners[j];
b->mStream->mBlocks[b->mStreamBlock] = blockIndices[i];
}
} }
BlockList* list1 = GetListForBlock(block1); // Now update references to blocks in block lists.
list1->NotifyBlockSwapped(aBlockIndex1, aBlockIndex2); mFreeBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
BlockList* list2 = GetListForBlock(block2);
// We have to be careful we don't swap the same reference twice! nsTHashtable<nsPtrHashKey<nsMediaCacheStream> > visitedStreams;
if (list1 != list2) { visitedStreams.Init();
list2->NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
for (PRInt32 i = 0; i < 2; ++i) {
for (PRUint32 j = 0; j < blocks[i]->mOwners.Length(); ++j) {
nsMediaCacheStream* stream = blocks[i]->mOwners[j].mStream;
// Make sure that we don't update the same stream twice --- that
// would result in swapping the block references back again!
if (visitedStreams.GetEntry(stream))
continue;
visitedStreams.PutEntry(stream);
stream->mReadaheadBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
stream->mPlayedBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
stream->mMetadataBlocks.NotifyBlockSwapped(aBlockIndex1, aBlockIndex2);
}
} }
Verify(); Verify();
} }
void
nsMediaCache::RemoveBlockOwner(PRInt32 aBlockIndex, nsMediaCacheStream* aStream)
{
Block* block = &mIndex[aBlockIndex];
for (PRUint32 i = 0; i < block->mOwners.Length(); ++i) {
BlockOwner* bo = &block->mOwners[i];
if (bo->mStream == aStream) {
GetListForBlock(bo)->RemoveBlock(aBlockIndex);
bo->mStream->mBlocks[bo->mStreamBlock] = -1;
block->mOwners.RemoveElementAt(i);
if (block->mOwners.IsEmpty()) {
mFreeBlocks.AddFirstBlock(aBlockIndex);
}
return;
}
}
}
void void
nsMediaCache::FreeBlock(PRInt32 aBlock) nsMediaCache::FreeBlock(PRInt32 aBlock)
{ {
PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor); PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
Block* block = &mIndex[aBlock]; Block* block = &mIndex[aBlock];
GetListForBlock(block)->RemoveBlock(aBlock); if (block->mOwners.IsEmpty()) {
if (block->mStream) { // already free
block->mStream->mBlocks[block->mStreamBlock] = -1; return;
} }
block->mStream = nsnull;
block->mClass = FREE_BLOCK; LOG(PR_LOG_DEBUG, ("Released block %d", aBlock));
for (PRUint32 i = 0; i < block->mOwners.Length(); ++i) {
BlockOwner* bo = &block->mOwners[i];
GetListForBlock(bo)->RemoveBlock(aBlock);
bo->mStream->mBlocks[bo->mStreamBlock] = -1;
}
block->mOwners.Clear();
mFreeBlocks.AddFirstBlock(aBlock); mFreeBlocks.AddFirstBlock(aBlock);
Verify(); Verify();
} }
@ -819,36 +867,50 @@ TimeDuration
nsMediaCache::PredictNextUse(TimeStamp aNow, PRInt32 aBlock) nsMediaCache::PredictNextUse(TimeStamp aNow, PRInt32 aBlock)
{ {
PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor); PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
NS_ASSERTION(!IsBlockFree(aBlock), "aBlock is free");
Block* block = &mIndex[aBlock]; Block* block = &mIndex[aBlock];
// Blocks can be belong to multiple streams. The predicted next use
switch (block->mClass) { // time is the earliest time predicted by any of the streams.
case METADATA_BLOCK: TimeDuration result;
// This block should be managed in LRU mode. For metadata we predict for (PRUint32 i = 0; i < block->mOwners.Length(); ++i) {
// that the time until the next use is the time since the last use. BlockOwner* bo = &block->mOwners[i];
return aNow - block->mLastUseTime; TimeDuration prediction;
case PLAYED_BLOCK: switch (bo->mClass) {
// This block should be managed in LRU mode, and we should impose case METADATA_BLOCK:
// a "replay delay" to reflect the likelihood of replay happening // This block should be managed in LRU mode. For metadata we predict
NS_ASSERTION(PRInt64(block->mStreamBlock)*BLOCK_SIZE < // that the time until the next use is the time since the last use.
block->mStream->mStreamOffset, prediction = aNow - bo->mLastUseTime;
"Played block after the current stream position?"); break;
return aNow - block->mLastUseTime + case PLAYED_BLOCK:
// This block should be managed in LRU mode, and we should impose
// a "replay delay" to reflect the likelihood of replay happening
NS_ASSERTION(PRInt64(bo->mStreamBlock)*BLOCK_SIZE <
bo->mStream->mStreamOffset,
"Played block after the current stream position?");
prediction = aNow - bo->mLastUseTime +
TimeDuration::FromSeconds(REPLAY_DELAY); TimeDuration::FromSeconds(REPLAY_DELAY);
case READAHEAD_BLOCK: { break;
PRInt64 bytesAhead = case READAHEAD_BLOCK: {
PRInt64(block->mStreamBlock)*BLOCK_SIZE - block->mStream->mStreamOffset; PRInt64 bytesAhead =
NS_ASSERTION(bytesAhead >= 0, PRInt64(bo->mStreamBlock)*BLOCK_SIZE - bo->mStream->mStreamOffset;
"Readahead block before the current stream position?"); NS_ASSERTION(bytesAhead >= 0,
PRInt64 millisecondsAhead = "Readahead block before the current stream position?");
bytesAhead*1000/block->mStream->mPlaybackBytesPerSecond; PRInt64 millisecondsAhead =
return TimeDuration::FromMilliseconds( bytesAhead*1000/bo->mStream->mPlaybackBytesPerSecond;
PR_MIN(millisecondsAhead, PR_INT32_MAX)); prediction = TimeDuration::FromMilliseconds(
} PR_MIN(millisecondsAhead, PR_INT32_MAX));
default: break;
NS_ERROR("Invalid class for predicting next use"); }
return TimeDuration(0); default:
NS_ERROR("Invalid class for predicting next use");
return TimeDuration(0);
}
if (i == 0 || prediction < result) {
result = prediction;
}
} }
return result;
} }
TimeDuration TimeDuration
@ -900,8 +962,7 @@ nsMediaCache::Update()
TimeDuration latestPredictedUseForOverflow = 0; TimeDuration latestPredictedUseForOverflow = 0;
for (PRInt32 blockIndex = mIndex.Length() - 1; blockIndex >= maxBlocks; for (PRInt32 blockIndex = mIndex.Length() - 1; blockIndex >= maxBlocks;
--blockIndex) { --blockIndex) {
nsMediaCacheStream* stream = mIndex[blockIndex].mStream; if (IsBlockFree(blockIndex)) {
if (!stream) {
// Don't count overflowing free blocks in our free block count // Don't count overflowing free blocks in our free block count
--freeBlockCount; --freeBlockCount;
continue; continue;
@ -913,21 +974,23 @@ nsMediaCache::Update()
// Now try to move overflowing blocks to the main part of the cache. // Now try to move overflowing blocks to the main part of the cache.
for (PRInt32 blockIndex = mIndex.Length() - 1; blockIndex >= maxBlocks; for (PRInt32 blockIndex = mIndex.Length() - 1; blockIndex >= maxBlocks;
--blockIndex) { --blockIndex) {
Block* block = &mIndex[blockIndex]; if (IsBlockFree(blockIndex))
nsMediaCacheStream* stream = block->mStream;
if (!stream)
continue; continue;
Block* block = &mIndex[blockIndex];
// Try to relocate the block close to other blocks for the first stream.
// There is no point in trying ot make it close to other blocks in
// *all* the streams it might belong to.
PRInt32 destinationBlockIndex = PRInt32 destinationBlockIndex =
FindReusableBlock(now, stream, block->mStreamBlock, maxBlocks); FindReusableBlock(now, block->mOwners[0].mStream,
block->mOwners[0].mStreamBlock, maxBlocks);
if (destinationBlockIndex < 0) { if (destinationBlockIndex < 0) {
// Nowhere to place this overflow block. We won't be able to // Nowhere to place this overflow block. We won't be able to
// place any more overflow blocks. // place any more overflow blocks.
break; break;
} }
Block* destinationBlock = &mIndex[destinationBlockIndex]; if (IsBlockFree(destinationBlockIndex) ||
if (destinationBlock->mClass == FREE_BLOCK ||
PredictNextUse(now, destinationBlockIndex) > latestPredictedUseForOverflow) { PredictNextUse(now, destinationBlockIndex) > latestPredictedUseForOverflow) {
// Reuse blocks in the main part of the cache that are less useful than // Reuse blocks in the main part of the cache that are less useful than
// the least useful overflow blocks // the least useful overflow blocks
@ -945,19 +1008,14 @@ nsMediaCache::Update()
} else { } else {
// If the write fails we may have corrupted the destination // If the write fails we may have corrupted the destination
// block. Free it now. // block. Free it now.
LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld) (trimming cache)", LOG(PR_LOG_DEBUG, ("Released block %d (trimming cache)",
destinationBlockIndex, destinationBlock->mStream, destinationBlockIndex));
destinationBlock->mStreamBlock,
(long long)destinationBlock->mStreamBlock*BLOCK_SIZE));
FreeBlock(destinationBlockIndex); FreeBlock(destinationBlockIndex);
} }
// Free the overflowing block even if the copy failed. // Free the overflowing block even if the copy failed.
if (block->mClass != FREE_BLOCK) { LOG(PR_LOG_DEBUG, ("Released block %d (trimming cache)",
LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld) (trimming cache)", blockIndex));
blockIndex, block->mStream, block->mStreamBlock, FreeBlock(blockIndex);
(long long)block->mStreamBlock*BLOCK_SIZE));
FreeBlock(blockIndex);
}
} }
} }
} }
@ -1145,51 +1203,53 @@ nsMediaCache::Verify()
PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor); PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
mFreeBlocks.Verify(); mFreeBlocks.Verify();
mPlayedBlocks.Verify();
mMetadataBlocks.Verify();
for (PRUint32 i = 0; i < mStreams.Length(); ++i) { for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
nsMediaCacheStream* stream = mStreams[i]; nsMediaCacheStream* stream = mStreams[i];
stream->mReadaheadBlocks.Verify(); stream->mReadaheadBlocks.Verify();
if (!stream->mReadaheadBlocks.IsEmpty()) { stream->mPlayedBlocks.Verify();
// Verify that the readahead blocks are listed in stream block order stream->mMetadataBlocks.Verify();
PRInt32 block = stream->mReadaheadBlocks.GetFirstBlock();
PRInt32 lastStreamBlock = -1; // Verify that the readahead blocks are listed in stream block order
do { PRInt32 block = stream->mReadaheadBlocks.GetFirstBlock();
NS_ASSERTION(mIndex[block].mStream == stream, "Bad stream"); PRInt32 lastStreamBlock = -1;
NS_ASSERTION(lastStreamBlock < PRInt32(mIndex[block].mStreamBlock), while (block >= 0) {
"Blocks not increasing in readahead stream"); PRUint32 j = 0;
lastStreamBlock = PRInt32(mIndex[block].mStreamBlock); while (mIndex[block].mOwners[j].mStream != stream) {
block = stream->mReadaheadBlocks.GetNextBlock(block); ++j;
} while (block >= 0); }
PRInt32 nextStreamBlock =
PRInt32(mIndex[block].mOwners[j].mStreamBlock);
NS_ASSERTION(lastStreamBlock < nextStreamBlock,
"Blocks not increasing in readahead stream");
lastStreamBlock = nextStreamBlock;
block = stream->mReadaheadBlocks.GetNextBlock(block);
} }
} }
} }
#endif #endif
void void
nsMediaCache::InsertReadaheadBlock(PRInt32 aBlockIndex) nsMediaCache::InsertReadaheadBlock(BlockOwner* aBlockOwner,
PRInt32 aBlockIndex)
{ {
PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor); PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mMonitor);
Block* block = &mIndex[aBlockIndex];
nsMediaCacheStream* stream = block->mStream;
if (stream->mReadaheadBlocks.IsEmpty()) {
stream->mReadaheadBlocks.AddFirstBlock(aBlockIndex);
return;
}
// Find the last block whose stream block is before aBlockIndex's // Find the last block whose stream block is before aBlockIndex's
// stream block, and insert after it // stream block, and insert after it
nsMediaCacheStream* stream = aBlockOwner->mStream;
PRInt32 readaheadIndex = stream->mReadaheadBlocks.GetLastBlock(); PRInt32 readaheadIndex = stream->mReadaheadBlocks.GetLastBlock();
do { while (readaheadIndex >= 0) {
if (mIndex[readaheadIndex].mStreamBlock < block->mStreamBlock) { BlockOwner* bo = GetBlockOwner(readaheadIndex, stream);
NS_ASSERTION(bo, "stream must own its blocks");
if (bo->mStreamBlock < aBlockOwner->mStreamBlock) {
stream->mReadaheadBlocks.AddAfter(aBlockIndex, readaheadIndex); stream->mReadaheadBlocks.AddAfter(aBlockIndex, readaheadIndex);
return; return;
} }
NS_ASSERTION(mIndex[readaheadIndex].mStreamBlock > block->mStreamBlock, NS_ASSERTION(bo->mStreamBlock > aBlockOwner->mStreamBlock,
"Duplicated blocks??"); "Duplicated blocks??");
readaheadIndex = stream->mReadaheadBlocks.GetPrevBlock(readaheadIndex); readaheadIndex = stream->mReadaheadBlocks.GetPrevBlock(readaheadIndex);
} while (readaheadIndex >= 0); }
stream->mReadaheadBlocks.AddFirstBlock(aBlockIndex); stream->mReadaheadBlocks.AddFirstBlock(aBlockIndex);
Verify(); Verify();
} }
@ -1206,45 +1266,44 @@ nsMediaCache::AllocateAndWriteBlock(nsMediaCacheStream* aStream, const void* aDa
aStream->mBlocks.AppendElement(-1); aStream->mBlocks.AppendElement(-1);
} }
if (aStream->mBlocks[streamBlockIndex] >= 0) { if (aStream->mBlocks[streamBlockIndex] >= 0) {
// Release the existing cache entry for this stream block // We no longer want to own this block
PRInt32 globalBlockIndex = aStream->mBlocks[streamBlockIndex]; PRInt32 globalBlockIndex = aStream->mBlocks[streamBlockIndex];
LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)", LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
globalBlockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE)); globalBlockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
FreeBlock(globalBlockIndex); RemoveBlockOwner(globalBlockIndex, aStream);
} }
TimeStamp now = TimeStamp::Now(); TimeStamp now = TimeStamp::Now();
PRInt32 blockIndex = FindBlockForIncomingData(now, aStream); PRInt32 blockIndex = FindBlockForIncomingData(now, aStream);
if (blockIndex >= 0) { if (blockIndex >= 0) {
Block* block = &mIndex[blockIndex]; FreeBlock(blockIndex);
if (block->mClass != FREE_BLOCK) {
LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
blockIndex, block->mStream, block->mStreamBlock, (long long)block->mStreamBlock*BLOCK_SIZE));
FreeBlock(blockIndex);
}
NS_ASSERTION(block->mClass == FREE_BLOCK, "Block should be free now!");
Block* block = &mIndex[blockIndex];
LOG(PR_LOG_DEBUG, ("Allocated block %d to stream %p block %d(%lld)", LOG(PR_LOG_DEBUG, ("Allocated block %d to stream %p block %d(%lld)",
blockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE)); blockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
block->mStream = aStream; BlockOwner* bo = block->mOwners.AppendElement();
block->mStreamBlock = streamBlockIndex; if (!bo)
block->mLastUseTime = now; return;
bo->mStream = aStream;
bo->mStreamBlock = streamBlockIndex;
bo->mLastUseTime = now;
aStream->mBlocks[streamBlockIndex] = blockIndex; aStream->mBlocks[streamBlockIndex] = blockIndex;
mFreeBlocks.RemoveBlock(blockIndex); mFreeBlocks.RemoveBlock(blockIndex);
if (streamBlockIndex*BLOCK_SIZE < aStream->mStreamOffset) { if (streamBlockIndex*BLOCK_SIZE < aStream->mStreamOffset) {
block->mClass = aMode == nsMediaCacheStream::MODE_PLAYBACK bo->mClass = aMode == nsMediaCacheStream::MODE_PLAYBACK
? PLAYED_BLOCK : METADATA_BLOCK; ? PLAYED_BLOCK : METADATA_BLOCK;
// This must be the most-recently-used block, since we // This must be the most-recently-used block, since we
// marked it as used now (which may be slightly bogus, but we'll // marked it as used now (which may be slightly bogus, but we'll
// treat it as used for simplicity). // treat it as used for simplicity).
GetListForBlock(block)->AddFirstBlock(blockIndex); GetListForBlock(bo)->AddFirstBlock(blockIndex);
Verify(); Verify();
} else { } else {
// This may not be the latest readahead block, although it usually // This may not be the latest readahead block, although it usually
// will be. We may have to scan for the right place to insert // will be. We may have to scan for the right place to insert
// the block in the list. // the block in the list.
block->mClass = READAHEAD_BLOCK; bo->mClass = READAHEAD_BLOCK;
InsertReadaheadBlock(blockIndex); InsertReadaheadBlock(bo, blockIndex);
} }
nsresult rv = WriteCacheFile(blockIndex*BLOCK_SIZE, aData, BLOCK_SIZE); nsresult rv = WriteCacheFile(blockIndex*BLOCK_SIZE, aData, BLOCK_SIZE);
@ -1292,7 +1351,7 @@ nsMediaCache::ReleaseStreamBlocks(nsMediaCacheStream* aStream)
if (blockIndex >= 0) { if (blockIndex >= 0) {
LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)", LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
blockIndex, aStream, i, (long long)i*BLOCK_SIZE)); blockIndex, aStream, i, (long long)i*BLOCK_SIZE));
FreeBlock(blockIndex); RemoveBlockOwner(blockIndex, aStream);
} }
} }
} }
@ -1302,7 +1361,7 @@ nsMediaCache::Truncate()
{ {
PRUint32 end; PRUint32 end;
for (end = mIndex.Length(); end > 0; --end) { for (end = mIndex.Length(); end > 0; --end) {
if (mIndex[end - 1].mStream) if (!IsBlockFree(end - 1))
break; break;
mFreeBlocks.RemoveBlock(end - 1); mFreeBlocks.RemoveBlock(end - 1);
} }
@ -1317,7 +1376,7 @@ nsMediaCache::Truncate()
} }
void void
nsMediaCache::NoteBlockUsage(PRInt32 aBlockIndex, nsMediaCache::NoteBlockUsage(nsMediaCacheStream* aStream, PRInt32 aBlockIndex,
nsMediaCacheStream::ReadMode aMode, nsMediaCacheStream::ReadMode aMode,
TimeStamp aNow) TimeStamp aNow)
{ {
@ -1328,25 +1387,25 @@ nsMediaCache::NoteBlockUsage(PRInt32 aBlockIndex,
return; return;
} }
Block* block = &mIndex[aBlockIndex]; BlockOwner* bo = GetBlockOwner(aBlockIndex, aStream);
if (block->mClass == FREE_BLOCK) { if (!bo) {
// this block is not in the cache yet // this block is not in the cache yet
return; return;
} }
// The following check has to be <= because the stream offset has // The following check has to be <= because the stream offset has
// not yet been updated for the data read from this block // not yet been updated for the data read from this block
NS_ASSERTION(block->mStreamBlock*BLOCK_SIZE <= block->mStream->mStreamOffset, NS_ASSERTION(bo->mStreamBlock*BLOCK_SIZE <= bo->mStream->mStreamOffset,
"Using a block that's behind the read position?"); "Using a block that's behind the read position?");
GetListForBlock(block)->RemoveBlock(aBlockIndex); GetListForBlock(bo)->RemoveBlock(aBlockIndex);
block->mClass = bo->mClass =
(aMode == nsMediaCacheStream::MODE_METADATA || block->mClass == METADATA_BLOCK) (aMode == nsMediaCacheStream::MODE_METADATA || bo->mClass == METADATA_BLOCK)
? METADATA_BLOCK : PLAYED_BLOCK; ? METADATA_BLOCK : PLAYED_BLOCK;
// Since this is just being used now, it can definitely be at the front // Since this is just being used now, it can definitely be at the front
// of mMetadataBlocks or mPlayedBlocks // of mMetadataBlocks or mPlayedBlocks
GetListForBlock(block)->AddFirstBlock(aBlockIndex); GetListForBlock(bo)->AddFirstBlock(aBlockIndex);
block->mLastUseTime = aNow; bo->mLastUseTime = aNow;
Verify(); Verify();
} }
@ -1369,7 +1428,7 @@ nsMediaCache::NoteSeek(nsMediaCacheStream* aStream, PRInt64 aOldOffset)
if (cacheBlockIndex >= 0) { if (cacheBlockIndex >= 0) {
// Marking the block used may not be exactly what we want but // Marking the block used may not be exactly what we want but
// it's simple // it's simple
NoteBlockUsage(cacheBlockIndex, nsMediaCacheStream::MODE_PLAYBACK, NoteBlockUsage(aStream, cacheBlockIndex, nsMediaCacheStream::MODE_PLAYBACK,
now); now);
} }
++blockIndex; ++blockIndex;
@ -1386,15 +1445,16 @@ nsMediaCache::NoteSeek(nsMediaCacheStream* aStream, PRInt64 aOldOffset)
while (blockIndex < endIndex) { while (blockIndex < endIndex) {
PRInt32 cacheBlockIndex = aStream->mBlocks[endIndex - 1]; PRInt32 cacheBlockIndex = aStream->mBlocks[endIndex - 1];
if (cacheBlockIndex >= 0) { if (cacheBlockIndex >= 0) {
Block* block = &mIndex[cacheBlockIndex]; BlockOwner* bo = GetBlockOwner(cacheBlockIndex, aStream);
if (block->mClass != METADATA_BLOCK) { NS_ASSERTION(bo, "Stream doesn't own its blocks?");
mPlayedBlocks.RemoveBlock(cacheBlockIndex); if (bo->mClass == PLAYED_BLOCK) {
block->mClass = READAHEAD_BLOCK; aStream->mPlayedBlocks.RemoveBlock(cacheBlockIndex);
bo->mClass = READAHEAD_BLOCK;
// Adding this as the first block is sure to be OK since // Adding this as the first block is sure to be OK since
// this must currently be the earliest readahead block // this must currently be the earliest readahead block
// (that's why we're proceeding backwards from the end of // (that's why we're proceeding backwards from the end of
// the seeked range to the start) // the seeked range to the start)
GetListForBlock(block)->AddFirstBlock(cacheBlockIndex); aStream->mReadaheadBlocks.AddFirstBlock(cacheBlockIndex);
Verify(); Verify();
} }
} }
@ -1831,7 +1891,7 @@ nsMediaCacheStream::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
if (mCurrentMode == MODE_METADATA) { if (mCurrentMode == MODE_METADATA) {
mMetadataInPartialBlockBuffer = PR_TRUE; mMetadataInPartialBlockBuffer = PR_TRUE;
} }
gMediaCache->NoteBlockUsage(cacheBlock, mCurrentMode, TimeStamp::Now()); gMediaCache->NoteBlockUsage(this, cacheBlock, mCurrentMode, TimeStamp::Now());
} else { } else {
if (cacheBlock < 0) { if (cacheBlock < 0) {
if (count > 0) { if (count > 0) {
@ -1850,7 +1910,7 @@ nsMediaCacheStream::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
continue; continue;
} }
gMediaCache->NoteBlockUsage(cacheBlock, mCurrentMode, TimeStamp::Now()); gMediaCache->NoteBlockUsage(this, cacheBlock, mCurrentMode, TimeStamp::Now());
PRInt64 offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock; PRInt64 offset = cacheBlock*BLOCK_SIZE + offsetInStreamBlock;
nsresult rv = gMediaCache->ReadCacheFile(offset, aBuffer + count, size, &bytes); nsresult rv = gMediaCache->ReadCacheFile(offset, aBuffer + count, size, &bytes);

View File

@ -436,6 +436,10 @@ private:
// block is the earliest in the stream (so the last block will be the // block is the earliest in the stream (so the last block will be the
// least valuable). // least valuable).
BlockList mReadaheadBlocks; BlockList mReadaheadBlocks;
// The list of metadata blocks; the first block is the most recently used
BlockList mMetadataBlocks;
// The list of played-back blocks; the first block is the most recently used
BlockList mPlayedBlocks;
// The last reported estimate of the decoder's playback rate // The last reported estimate of the decoder's playback rate
PRUint32 mPlaybackBytesPerSecond; PRUint32 mPlaybackBytesPerSecond;
// The number of times this stream has been Pinned without a // The number of times this stream has been Pinned without a