Author: axeld Date: 2009-11-25 15:24:52 +0100 (Wed, 25 Nov 2009) New Revision: 34243 Changeset: http://dev.haiku-os.org/changeset/34243/haiku Modified: haiku/trunk/headers/private/media/MediaExtractor.h haiku/trunk/src/kits/media/ChunkCache.cpp haiku/trunk/src/kits/media/ChunkCache.h haiku/trunk/src/kits/media/MediaExtractor.cpp Log: * Completely rewrote the ChunkCache - the previous version had some issues with regards to locking and seeking. * Furthermore, we now not only cache 4 chunks, but chunk up to a certain memory size (MediaExtractor uses 1 MB for now). * Since I still have occasional hickups, it looks like this wasn't the main cause for our audio problems. Still, this will reduce drive access considerably during play. Modified: haiku/trunk/headers/private/media/MediaExtractor.h =================================================================== --- haiku/trunk/headers/private/media/MediaExtractor.h 2009-11-25 12:40:27 UTC (rev 34242) +++ haiku/trunk/headers/private/media/MediaExtractor.h 2009-11-25 14:24:52 UTC (rev 34243) @@ -1,6 +1,8 @@ /* * Copyright 2004-2007, Marcus Overhagen. All rights reserved. * Copyright 2008, Maurice Kalinowski. All rights reserved. + * Copyright 2009, Axel Dörfler, axeld@xxxxxxxxxxxxxxxxx + * * Distributed under the terms of the MIT License. */ #ifndef _MEDIA_EXTRACTOR_H @@ -14,8 +16,11 @@ namespace BPrivate { namespace media { + class ChunkCache; +struct chunk_buffer; + struct stream_info { status_t status; void* cookie; @@ -23,9 +28,11 @@ const void* infoBuffer; size_t infoBufferSize; ChunkCache* chunkCache; + chunk_buffer* lastChunk; media_format encodedFormat; }; + class MediaExtractor { public: MediaExtractor(BDataIO* source, int32 flags); @@ -58,6 +65,7 @@ media_codec_info* codecInfo); private: + void _RecycleLastChunk(stream_info& info); static int32 _ExtractorEntry(void* arg); void _ExtractorThread(); @@ -66,7 +74,6 @@ sem_id fExtractorWaitSem; thread_id fExtractorThread; - volatile bool fTerminateExtractor; BDataIO* fSource; Reader* fReader; Modified: haiku/trunk/src/kits/media/ChunkCache.cpp =================================================================== --- haiku/trunk/src/kits/media/ChunkCache.cpp 2009-11-25 12:40:27 UTC (rev 34242) +++ haiku/trunk/src/kits/media/ChunkCache.cpp 2009-11-25 14:24:52 UTC (rev 34243) @@ -1,143 +1,144 @@ /* - * Copyright 2004, Marcus Overhagen. All rights reserved. + * Copyright 2009, Axel Dörfler, axeld@xxxxxxxxxxxxxxxxx * Distributed under the terms of the MIT License. */ #include "ChunkCache.h" -#include <string.h> +#include <new> +#include <stdlib.h> -#include <Autolock.h> +#include <Debug.h> -#include "debug.h" - -ChunkCache::ChunkCache() +chunk_buffer::chunk_buffer() : - fLocker("media chunk cache locker") + buffer(NULL), + size(0), + capacity(0) { - // fEmptyChunkCount must be one less than the real chunk count, - // because the buffer returned by GetNextChunk must be preserved - // until the next call of that function, and must not be overwritten. +} - fEmptyChunkCount = CHUNK_COUNT - 1; - fReadyChunkCount = 0; - fNeedsRefill = 1; - fGetWaitSem = create_sem(0, "media chunk cache sem"); +chunk_buffer::~chunk_buffer() +{ + free(buffer); +} - fNextPut = &fChunkInfos[0]; - fNextGet = &fChunkInfos[0]; - for (int i = 0; i < CHUNK_COUNT; i++) { - fChunkInfos[i].next = i == CHUNK_COUNT - 1 - ? &fChunkInfos[0] : &fChunkInfos[i + 1]; - fChunkInfos[i].buffer = NULL; - fChunkInfos[i].sizeUsed = 0; - fChunkInfos[i].sizeMax = 0; - fChunkInfos[i].status = B_ERROR; - } +// #pragma mark - + + +ChunkCache::ChunkCache(sem_id waitSem, size_t maxBytes) + : + BLocker("media chunk cache"), + fWaitSem(waitSem), + fMaxBytes(maxBytes), + fBytes(0) +{ } ChunkCache::~ChunkCache() { - delete_sem(fGetWaitSem); + while (chunk_buffer* chunk = fChunks.RemoveHead()) + delete chunk; - for (int i = 0; i < CHUNK_COUNT; i++) { - free(fChunkInfos[i].buffer); - } + while (chunk_buffer* chunk = fUnusedChunks.RemoveHead()) + delete chunk; + + while (chunk_buffer* chunk = fInFlightChunks.RemoveHead()) + delete chunk; } void ChunkCache::MakeEmpty() { - BAutolock _(fLocker); + ASSERT(IsLocked()); - fEmptyChunkCount = CHUNK_COUNT - 1; - fReadyChunkCount = 0; - fNextPut = &fChunkInfos[0]; - fNextGet = &fChunkInfos[0]; - atomic_or(&fNeedsRefill, 1); + fUnusedChunks.MoveFrom(&fChunks); + fBytes = 0; + + release_sem(fWaitSem); } bool -ChunkCache::NeedsRefill() +ChunkCache::SpaceLeft() const { - return atomic_or(&fNeedsRefill, 0); + ASSERT(IsLocked()); + + return fBytes < fMaxBytes; } -status_t -ChunkCache::GetNextChunk(const void** _chunkBuffer, size_t* _chunkSize, - media_header* mediaHeader) +chunk_buffer* +ChunkCache::NextChunk() { - uint8 retryCount = 0; + ASSERT(IsLocked()); -// printf("ChunkCache::GetNextChunk: %p fEmptyChunkCount %ld, fReadyChunkCount %ld\n", fNextGet, fEmptyChunkCount, fReadyChunkCount); -retry: - acquire_sem(fGetWaitSem); - - BAutolock locker(fLocker); - if (fReadyChunkCount == 0) { - locker.Unlock(); - - printf("ChunkCache::GetNextChunk: %p retrying\n", fNextGet); - // Limit to 5 retries - retryCount++; - if (retryCount > 4) - return B_ERROR; - - goto retry; + chunk_buffer* chunk = fChunks.RemoveHead(); + if (chunk != NULL) { + fBytes -= chunk->capacity; + fInFlightChunks.Add(chunk); + release_sem(fWaitSem); } - fEmptyChunkCount++; - fReadyChunkCount--; - atomic_or(&fNeedsRefill, 1); + return chunk; +} - locker.Unlock(); - *_chunkBuffer = fNextGet->buffer; - *_chunkSize = fNextGet->sizeUsed; - *mediaHeader = fNextGet->mediaHeader; - status_t status = fNextGet->status; - fNextGet = fNextGet->next; +/*! Moves the specified chunk from the in-flight list to the unused list. + This means the chunk data can be overwritten again. +*/ +void +ChunkCache::RecycleChunk(chunk_buffer* chunk) +{ + ASSERT(IsLocked()); - return status; + fInFlightChunks.Remove(chunk); + fUnusedChunks.Add(chunk); } -void -ChunkCache::PutNextChunk(const void* chunkBuffer, size_t chunkSize, - const media_header& mediaHeader, status_t status) +bool +ChunkCache::ReadNextChunk(Reader* reader, void* cookie) { -// printf("ChunkCache::PutNextChunk: %p fEmptyChunkCount %ld, fReadyChunkCount %ld\n", fNextPut, fEmptyChunkCount, fReadyChunkCount); + ASSERT(IsLocked()); - if (status == B_OK) { - if (fNextPut->sizeMax < chunkSize) { -// printf("ChunkCache::PutNextChunk: %p resizing from %ld to %ld\n", fNextPut, fNextPut->sizeMax, chunkSize); - free(fNextPut->buffer); - fNextPut->buffer = malloc((chunkSize + 1024) & ~1023); - fNextPut->sizeMax = chunkSize; - } - memcpy(fNextPut->buffer, chunkBuffer, chunkSize); - fNextPut->sizeUsed = chunkSize; + // retrieve chunk buffer + chunk_buffer* chunk = fUnusedChunks.RemoveHead(); + if (chunk == NULL) { + // allocate a new one + chunk = new(std::nothrow) chunk_buffer; + if (chunk == NULL) + return false; + } - fNextPut->mediaHeader = mediaHeader; - fNextPut->status = status; + const void* buffer; + size_t bufferSize; + chunk->status = reader->GetNextChunk(cookie, &buffer, &bufferSize, + &chunk->header); + if (chunk->status == B_OK) { + if (chunk->capacity < bufferSize) { + // adapt buffer size + free(chunk->buffer); + chunk->capacity = (bufferSize + 2047) & ~2047; + chunk->buffer = malloc(chunk->capacity); + if (chunk->buffer == NULL) { + delete chunk; + return false; + } + } - fNextPut = fNextPut->next; + memcpy(chunk->buffer, buffer, bufferSize); + chunk->size = bufferSize; + fBytes += chunk->capacity; + } - fLocker.Lock(); - fEmptyChunkCount--; - fReadyChunkCount++; - if (fEmptyChunkCount == 0) - atomic_and(&fNeedsRefill, 0); - fLocker.Unlock(); - - release_sem(fGetWaitSem); + fChunks.Add(chunk); + return chunk->status == B_OK; } Modified: haiku/trunk/src/kits/media/ChunkCache.h =================================================================== --- haiku/trunk/src/kits/media/ChunkCache.h 2009-11-25 12:40:27 UTC (rev 34242) +++ haiku/trunk/src/kits/media/ChunkCache.h 2009-11-25 14:24:52 UTC (rev 34243) @@ -1,5 +1,5 @@ /* - * Copyright 2004, Marcus Overhagen. All rights reserved. + * Copyright 2009, Axel Dörfler, axeld@xxxxxxxxxxxxxxxxx * Distributed under the terms of the MIT License. */ #ifndef _CHUNK_CACHE_H @@ -8,56 +8,54 @@ #include <Locker.h> #include <MediaDefs.h> +#include "ReaderPlugin.h" +#include <kernel/util/DoublyLinkedList.h> + namespace BPrivate { namespace media { -struct chunk_info { - chunk_info* next; +struct chunk_buffer; +typedef DoublyLinkedList<chunk_buffer> ChunkList; + +struct chunk_buffer : public DoublyLinkedListLinkImpl<chunk_buffer> { + chunk_buffer(); + ~chunk_buffer(); + void* buffer; - size_t sizeUsed; - size_t sizeMax; - media_header mediaHeader; + size_t size; + size_t capacity; + media_header header; status_t status; }; -class ChunkCache { +class ChunkCache : public BLocker { public: - ChunkCache(); + ChunkCache(sem_id waitSem, size_t maxBytes); ~ChunkCache(); void MakeEmpty(); - bool NeedsRefill(); + bool SpaceLeft() const; - status_t GetNextChunk(const void** _chunkBuffer, - size_t* _chunkSize, - media_header* mediaHeader); - void PutNextChunk(const void* chunkBuffer, - size_t chunkSize, - const media_header& mediaHeader, - status_t status); + chunk_buffer* NextChunk(); + void RecycleChunk(chunk_buffer* chunk); + bool ReadNextChunk(Reader* reader, void* cookie); private: - enum { CHUNK_COUNT = 5 }; - - chunk_info* fNextPut; - chunk_info* fNextGet; - chunk_info fChunkInfos[CHUNK_COUNT]; - - sem_id fGetWaitSem; - int32 fEmptyChunkCount; - int32 fReadyChunkCount; - int32 fNeedsRefill; - - BLocker fLocker; + sem_id fWaitSem; + size_t fMaxBytes; + size_t fBytes; + ChunkList fChunks; + ChunkList fUnusedChunks; + ChunkList fInFlightChunks; }; -} // namespace media -} // namespace BPrivate +} // namespace media +} // namespace BPrivate using namespace BPrivate::media; Modified: haiku/trunk/src/kits/media/MediaExtractor.cpp =================================================================== --- haiku/trunk/src/kits/media/MediaExtractor.cpp 2009-11-25 12:40:27 UTC (rev 34242) +++ haiku/trunk/src/kits/media/MediaExtractor.cpp 2009-11-25 14:24:52 UTC (rev 34243) @@ -1,6 +1,8 @@ /* * Copyright 2004-2007, Marcus Overhagen. All rights reserved. * Copyright 2008, Maurice Kalinowski. All rights reserved. + * Copyright 2009, Axel Dörfler, axeld@xxxxxxxxxxxxxxxxx + * * Distributed under the terms of the MIT License. */ @@ -13,8 +15,8 @@ #include <Autolock.h> +#include "ChunkCache.h" #include "debug.h" -#include "ChunkCache.h" #include "PluginManager.h" @@ -22,6 +24,9 @@ #define DISABLE_CHUNK_CACHE 0 +static const size_t kMaxCacheBytes = 1024 * 1024; + + class MediaExtractorChunkProvider : public ChunkProvider { public: MediaExtractorChunkProvider(MediaExtractor* extractor, int32 stream) @@ -48,21 +53,28 @@ MediaExtractor::MediaExtractor(BDataIO* source, int32 flags) + : + fExtractorThread(-1), + fSource(source), + fReader(NULL), + fStreamInfo(NULL), + fStreamCount(0) { CALLED(); - fSource = source; - fStreamInfo = NULL; - fExtractorThread = -1; - fExtractorWaitSem = -1; - fTerminateExtractor = false; +#if !DISABLE_CHUNK_CACHE + // start extractor thread + fExtractorWaitSem = create_sem(1, "media extractor thread sem"); + if (fExtractorWaitSem < 0) { + fInitStatus = fExtractorWaitSem; + return; + } +#endif + fInitStatus = _plugin_manager.CreateReader(&fReader, &fStreamCount, &fFileFormat, source); - if (fInitStatus != B_OK) { - fStreamCount = 0; - fReader = NULL; + if (fInitStatus != B_OK) return; - } fStreamInfo = new stream_info[fStreamCount]; @@ -73,7 +85,8 @@ fStreamInfo[i].hasCookie = true; fStreamInfo[i].infoBuffer = 0; fStreamInfo[i].infoBufferSize = 0; - fStreamInfo[i].chunkCache = new ChunkCache; + fStreamInfo[i].chunkCache + = new ChunkCache(fExtractorWaitSem, kMaxCacheBytes); memset(&fStreamInfo[i].encodedFormat, 0, sizeof(fStreamInfo[i].encodedFormat)); } @@ -106,11 +119,10 @@ } } -#if DISABLE_CHUNK_CACHE == 0 +#if !DISABLE_CHUNK_CACHE // start extractor thread - fExtractorWaitSem = create_sem(1, "media extractor thread sem"); fExtractorThread = spawn_thread(_ExtractorEntry, "media extractor thread", - 40, this); + B_NORMAL_PRIORITY + 4, this); resume_thread(fExtractorThread); #endif } @@ -120,24 +132,26 @@ { CALLED(); +#if !DISABLE_CHUNK_CACHE // terminate extractor thread - fTerminateExtractor = true; - release_sem(fExtractorWaitSem); - status_t err; - wait_for_thread(fExtractorThread, &err); delete_sem(fExtractorWaitSem); + status_t status; + wait_for_thread(fExtractorThread, &status); +#endif + // free all stream cookies // and chunk caches for (int32 i = 0; i < fStreamCount; i++) { if (fStreamInfo[i].hasCookie) fReader->FreeCookie(fStreamInfo[i].cookie); + delete fStreamInfo[i].chunkCache; } _plugin_manager.DestroyReader(fReader); - delete [] fStreamInfo; + delete[] fStreamInfo; // fSource is owned by the BMediaFile } @@ -211,7 +225,7 @@ int64 frameCount; bigtime_t duration; media_format format; - const void *infoBuffer; + const void* infoBuffer; size_t infoSize; fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration, @@ -222,21 +236,24 @@ status_t -MediaExtractor::Seek(int32 stream, uint32 seekTo, int64* frame, bigtime_t* time) +MediaExtractor::Seek(int32 stream, uint32 seekTo, int64* _frame, + bigtime_t* _time) { CALLED(); - if (fStreamInfo[stream].status != B_OK) - return fStreamInfo[stream].status; - status_t result; - result = fReader->Seek(fStreamInfo[stream].cookie, seekTo, frame, time); - if (result != B_OK) - return result; + stream_info& info = fStreamInfo[stream]; + if (info.status != B_OK) + return info.status; - // clear buffered chunks - fStreamInfo[stream].chunkCache->MakeEmpty(); - release_sem(fExtractorWaitSem); + BAutolock _(info.chunkCache); + status_t status = fReader->Seek(info.cookie, seekTo, _frame, _time); + if (status != B_OK) + return status; + + // clear buffered chunks after seek + info.chunkCache->MakeEmpty(); + return B_OK; } @@ -246,11 +263,21 @@ bigtime_t* _time) const { CALLED(); - if (fStreamInfo[stream].status != B_OK) - return fStreamInfo[stream].status; - return fReader->FindKeyFrame(fStreamInfo[stream].cookie, - seekTo, _frame, _time); + stream_info& info = fStreamInfo[stream]; + if (info.status != B_OK) + return info.status; + + BAutolock _(info.chunkCache); + + status_t status = fReader->FindKeyFrame(info.cookie, seekTo, _frame, _time); + if (status != B_OK) + return status; + + // clear buffered chunks after seek + info.chunkCache->MakeEmpty(); + + return B_OK; } @@ -258,20 +285,41 @@ MediaExtractor::GetNextChunk(int32 stream, const void** _chunkBuffer, size_t* _chunkSize, media_header* mediaHeader) { - if (fStreamInfo[stream].status != B_OK) - return fStreamInfo[stream].status; + stream_info& info = fStreamInfo[stream]; -#if DISABLE_CHUNK_CACHE > 0 + if (info.status != B_OK) + return info.status; + +#if DISABLE_CHUNK_CACHE static BLocker locker("media extractor next chunk"); BAutolock lock(locker); return fReader->GetNextChunk(fStreamInfo[stream].cookie, _chunkBuffer, _chunkSize, mediaHeader); +#else + BAutolock _(info.chunkCache); + + _RecycleLastChunk(info); + + // Retrieve next chunk - read it directly, if the cache is drained + chunk_buffer* chunk; + do { + chunk = info.chunkCache->NextChunk(); + if (chunk == NULL + && !info.chunkCache->ReadNextChunk(fReader, info.cookie)) + break; + } while (chunk == NULL); + + if (chunk == NULL) + return B_NO_MEMORY; + + info.lastChunk = chunk; + + *_chunkBuffer = chunk->buffer; + *_chunkSize = chunk->size; + *mediaHeader = chunk->header; + + return chunk->status; #endif - - status_t status = fStreamInfo[stream].chunkCache->GetNextChunk(_chunkBuffer, - _chunkSize, mediaHeader); - release_sem(fExtractorWaitSem); - return status; } @@ -339,6 +387,16 @@ } +void +MediaExtractor::_RecycleLastChunk(stream_info& info) +{ + if (info.lastChunk != NULL) { + info.chunkCache->RecycleChunk(info.lastChunk); + info.lastChunk = NULL; + } +} + + status_t MediaExtractor::_ExtractorEntry(void* extractor) { @@ -351,31 +409,35 @@ MediaExtractor::_ExtractorThread() { while (true) { - acquire_sem(fExtractorWaitSem); - if (fTerminateExtractor) + status_t status; + do { + status = acquire_sem(fExtractorWaitSem); + } while (status == B_INTERRUPTED); + + if (status != B_OK) { + // we were asked to quit return; + } - bool refillDone; + // Iterate over all streams until they are all filled + + int32 streamsFilled; do { - refillDone = false; + streamsFilled = 0; + for (int32 stream = 0; stream < fStreamCount; stream++) { - if (fStreamInfo[stream].status != B_OK) + stream_info& info = fStreamInfo[stream]; + if (info.status != B_OK) { + streamsFilled++; continue; + } - if (fStreamInfo[stream].chunkCache->NeedsRefill()) { - media_header mediaHeader; - const void* chunkBuffer; - size_t chunkSize; - status_t status = fReader->GetNextChunk( - fStreamInfo[stream].cookie, &chunkBuffer, &chunkSize, - &mediaHeader); - fStreamInfo[stream].chunkCache->PutNextChunk(chunkBuffer, - chunkSize, mediaHeader, status); - refillDone = true; - } + BAutolock _(info.chunkCache); + + if (!info.chunkCache->SpaceLeft() + || !info.chunkCache->ReadNextChunk(fReader, info.cookie)) + streamsFilled++; } - if (fTerminateExtractor) - return; - } while (refillDone); + } while (streamsFilled < fStreamCount); } }