503 lines
11 KiB
C++
503 lines
11 KiB
C++
/*
|
|
* 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 <new>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <Autolock.h>
|
|
#include <InterfacePrivate.h>
|
|
|
|
#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<MediaExtractor*>(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);
|
|
}
|
|
}
|