haiku/src/kits/media/MediaExtractor.cpp

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);
}
}