/* * Copyright 2004-2007, Marcus Overhagen. All rights reserved. * Copyright 2008, Maurice Kalinowski. All rights reserved. * Copyright 2009-2012, Axel Dörfler, axeld@pinc-software.de. * * Distributed under the terms of the MIT License. */ #include "MediaExtractor.h" #include #include #include #include #include #include "ChunkCache.h" #include "MediaDebug.h" #include "MediaMisc.h" #include "PluginManager.h" // should be 0, to disable the chunk cache set it to 1 #define DISABLE_CHUNK_CACHE 0 class MediaExtractorChunkProvider : public ChunkProvider { public: MediaExtractorChunkProvider(MediaExtractor* extractor, int32 stream) : fExtractor(extractor), fStream(stream) { } virtual status_t GetNextChunk(const void** _chunkBuffer, size_t* _chunkSize, media_header *mediaHeader) { return fExtractor->GetNextChunk(fStream, _chunkBuffer, _chunkSize, mediaHeader); } private: MediaExtractor* fExtractor; int32 fStream; }; // #pragma mark - MediaExtractor::MediaExtractor(BDataIO* source, int32 flags) : fExtractorThread(-1), fReader(NULL), fStreamInfo(NULL), fStreamCount(0) { _Init(source, flags); } void MediaExtractor::_Init(BDataIO* source, int32 flags) { CALLED(); fSource = source; #if !DISABLE_CHUNK_CACHE // start extractor thread fExtractorWaitSem = create_sem(1, "media extractor thread sem"); if (fExtractorWaitSem < 0) { fInitStatus = fExtractorWaitSem; return; } #endif fInitStatus = gPluginManager.CreateReader(&fReader, &fStreamCount, &fFileFormat, source); if (fInitStatus != B_OK) return; fStreamInfo = new stream_info[fStreamCount]; // initialize stream infos for (int32 i = 0; i < fStreamCount; i++) { fStreamInfo[i].status = B_OK; fStreamInfo[i].cookie = 0; fStreamInfo[i].hasCookie = false; fStreamInfo[i].infoBuffer = 0; fStreamInfo[i].infoBufferSize = 0; fStreamInfo[i].lastChunk = NULL; fStreamInfo[i].chunkCache = NULL; fStreamInfo[i].encodedFormat.Clear(); } // create all stream cookies for (int32 i = 0; i < fStreamCount; i++) { if (fReader->AllocateCookie(i, &fStreamInfo[i].cookie) != B_OK) { fStreamInfo[i].cookie = 0; fStreamInfo[i].hasCookie = false; fStreamInfo[i].status = B_ERROR; ERROR("MediaExtractor::MediaExtractor: AllocateCookie for stream %" B_PRId32 " failed\n", i); } else fStreamInfo[i].hasCookie = true; } // get info for all streams for (int32 i = 0; i < fStreamCount; i++) { if (fStreamInfo[i].status != B_OK) continue; int64 frameCount; bigtime_t duration; if (fReader->GetStreamInfo(fStreamInfo[i].cookie, &frameCount, &duration, &fStreamInfo[i].encodedFormat, &fStreamInfo[i].infoBuffer, &fStreamInfo[i].infoBufferSize) != B_OK) { fStreamInfo[i].status = B_ERROR; ERROR("MediaExtractor::MediaExtractor: GetStreamInfo for " "stream %" B_PRId32 " failed\n", i); } #if !DISABLE_CHUNK_CACHE // Allocate our ChunkCache size_t chunkCacheMaxBytes = _CalculateChunkBuffer(i); fStreamInfo[i].chunkCache = new ChunkCache(fExtractorWaitSem, chunkCacheMaxBytes); if (fStreamInfo[i].chunkCache->InitCheck() != B_OK) { fInitStatus = B_NO_MEMORY; return; } #endif } #if !DISABLE_CHUNK_CACHE // start extractor thread fExtractorThread = spawn_thread(_ExtractorEntry, "media extractor thread", B_NORMAL_PRIORITY + 4, this); resume_thread(fExtractorThread); #endif } MediaExtractor::~MediaExtractor() { CALLED(); // stop the extractor thread, if still running StopProcessing(); // 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; } gPluginManager.DestroyReader(fReader); delete[] fStreamInfo; // fSource is owned by the BMediaFile } status_t MediaExtractor::InitCheck() { CALLED(); return fInitStatus; } void MediaExtractor::GetFileFormatInfo(media_file_format* fileFormat) const { CALLED(); *fileFormat = fFileFormat; } status_t MediaExtractor::GetMetaData(BMessage* _data) const { CALLED(); return fReader->GetMetaData(_data); } int32 MediaExtractor::StreamCount() { CALLED(); return fStreamCount; } const char* MediaExtractor::Copyright() { return fReader->Copyright(); } const media_format* MediaExtractor::EncodedFormat(int32 stream) { return &fStreamInfo[stream].encodedFormat; } int64 MediaExtractor::CountFrames(int32 stream) const { CALLED(); if (fStreamInfo[stream].status != B_OK) return 0LL; int64 frameCount; bigtime_t duration; media_format format; const void* infoBuffer; size_t infoSize; fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration, &format, &infoBuffer, &infoSize); return frameCount; } bigtime_t MediaExtractor::Duration(int32 stream) const { CALLED(); if (fStreamInfo[stream].status != B_OK) return 0LL; int64 frameCount; bigtime_t duration; media_format format; const void* infoBuffer; size_t infoSize; fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration, &format, &infoBuffer, &infoSize); return duration; } status_t MediaExtractor::Seek(int32 stream, uint32 seekTo, int64* _frame, bigtime_t* _time) { CALLED(); stream_info& info = fStreamInfo[stream]; if (info.status != B_OK) return info.status; #if !DISABLE_CHUNK_CACHE BAutolock _(info.chunkCache); #endif status_t status = fReader->Seek(info.cookie, seekTo, _frame, _time); if (status != B_OK) return status; #if !DISABLE_CHUNK_CACHE // clear buffered chunks after seek info.chunkCache->MakeEmpty(); #endif return B_OK; } status_t MediaExtractor::FindKeyFrame(int32 stream, uint32 seekTo, int64* _frame, bigtime_t* _time) const { CALLED(); stream_info& info = fStreamInfo[stream]; if (info.status != B_OK) return info.status; return fReader->FindKeyFrame(info.cookie, seekTo, _frame, _time); } status_t MediaExtractor::GetNextChunk(int32 stream, const void** _chunkBuffer, size_t* _chunkSize, media_header* mediaHeader) { stream_info& info = fStreamInfo[stream]; if (info.status != B_OK) return info.status; #if DISABLE_CHUNK_CACHE 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 = info.chunkCache->NextChunk(fReader, info.cookie); 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 MediaExtractor::CreateDecoder(int32 stream, Decoder** _decoder, media_codec_info* codecInfo) { CALLED(); status_t status = fStreamInfo[stream].status; if (status != B_OK) { ERROR("MediaExtractor::CreateDecoder can't create decoder for " "stream %" B_PRId32 ": %s\n", stream, strerror(status)); return status; } // TODO: Here we should work out a way so that if there is a setup // failure we can try the next decoder Decoder* decoder; status = gPluginManager.CreateDecoder(&decoder, fStreamInfo[stream].encodedFormat); if (status != B_OK) { #if DEBUG char formatString[256]; string_for_format(fStreamInfo[stream].encodedFormat, formatString, sizeof(formatString)); ERROR("MediaExtractor::CreateDecoder gPluginManager.CreateDecoder " "failed for stream %" B_PRId32 ", format: %s: %s\n", stream, formatString, strerror(status)); #endif return status; } ChunkProvider* chunkProvider = new(std::nothrow) MediaExtractorChunkProvider(this, stream); if (chunkProvider == NULL) { gPluginManager.DestroyDecoder(decoder); ERROR("MediaExtractor::CreateDecoder can't create chunk provider " "for stream %" B_PRId32 "\n", stream); return B_NO_MEMORY; } decoder->SetChunkProvider(chunkProvider); status = decoder->Setup(&fStreamInfo[stream].encodedFormat, fStreamInfo[stream].infoBuffer, fStreamInfo[stream].infoBufferSize); if (status != B_OK) { gPluginManager.DestroyDecoder(decoder); ERROR("MediaExtractor::CreateDecoder Setup failed for stream %" B_PRId32 ": %s\n", stream, strerror(status)); return status; } status = gPluginManager.GetDecoderInfo(decoder, codecInfo); if (status != B_OK) { gPluginManager.DestroyDecoder(decoder); ERROR("MediaExtractor::CreateDecoder GetCodecInfo failed for stream %" B_PRId32 ": %s\n", stream, strerror(status)); return status; } *_decoder = decoder; return B_OK; } status_t MediaExtractor::GetStreamMetaData(int32 stream, BMessage* _data) const { const stream_info& info = fStreamInfo[stream]; if (info.status != B_OK) return info.status; return fReader->GetStreamMetaData(fStreamInfo[stream].cookie, _data); } void MediaExtractor::StopProcessing() { #if !DISABLE_CHUNK_CACHE if (fExtractorWaitSem > -1) { // terminate extractor thread delete_sem(fExtractorWaitSem); fExtractorWaitSem = -1; status_t status; wait_for_thread(fExtractorThread, &status); } #endif } void MediaExtractor::_RecycleLastChunk(stream_info& info) { if (info.lastChunk != NULL) { info.chunkCache->RecycleChunk(info.lastChunk); info.lastChunk = NULL; } } status_t MediaExtractor::_ExtractorEntry(void* extractor) { static_cast(extractor)->_ExtractorThread(); return B_OK; } size_t MediaExtractor::_CalculateChunkBuffer(int32 stream) { // WARNING: magic // Your A/V may skip frames, chunks or not play at all if the cache size // is insufficient. Unfortunately there's currently no safe way to // calculate it. size_t cacheSize = 3 * 1024 * 1024; const media_format* format = EncodedFormat(stream); if (format->IsVideo()) { // For video, have space for at least two frames int32 rowSize = BPrivate::get_bytes_per_row(format->ColorSpace(), format->Width()); if (rowSize > 0) { cacheSize = max_c(cacheSize, rowSize * format->Height() * 2); } } return ROUND_UP_TO_PAGE(cacheSize); } void MediaExtractor::_ExtractorThread() { while (true) { status_t status; do { status = acquire_sem(fExtractorWaitSem); } while (status == B_INTERRUPTED); if (status != B_OK) { // we were asked to quit return; } // Iterate over all streams until they are all filled int32 streamsFilled; do { streamsFilled = 0; for (int32 stream = 0; stream < fStreamCount; stream++) { stream_info& info = fStreamInfo[stream]; if (info.status != B_OK) { streamsFilled++; continue; } BAutolock _(info.chunkCache); if (!info.chunkCache->SpaceLeft() || !info.chunkCache->ReadNextChunk(fReader, info.cookie)) streamsFilled++; } } while (streamsFilled < fStreamCount); } }