2 * Copyright 2004-2007, Marcus Overhagen. All rights reserved.
3 * Copyright 2008, Maurice Kalinowski. All rights reserved.
4 * Copyright 2009-2012, Axel Dörfler, axeld@pinc-software.de.
6 * Distributed under the terms of the MIT License.
10 #include "MediaExtractor.h"
18 #include "ChunkCache.h"
20 #include "PluginManager.h"
23 // should be 0, to disable the chunk cache set it to 1
24 #define DISABLE_CHUNK_CACHE 0
27 static const size_t kMaxCacheBytes
= 3 * 1024 * 1024;
30 class MediaExtractorChunkProvider
: public ChunkProvider
{
32 MediaExtractorChunkProvider(MediaExtractor
* extractor
, int32 stream
)
34 fExtractor(extractor
),
39 virtual status_t
GetNextChunk(const void** _chunkBuffer
, size_t* _chunkSize
,
40 media_header
*mediaHeader
)
42 return fExtractor
->GetNextChunk(fStream
, _chunkBuffer
, _chunkSize
,
47 MediaExtractor
* fExtractor
;
55 MediaExtractor::MediaExtractor(BDataIO
* source
, int32 flags
)
65 #if !DISABLE_CHUNK_CACHE
66 // start extractor thread
67 fExtractorWaitSem
= create_sem(1, "media extractor thread sem");
68 if (fExtractorWaitSem
< 0) {
69 fInitStatus
= fExtractorWaitSem
;
74 fInitStatus
= gPluginManager
.CreateReader(&fReader
, &fStreamCount
,
75 &fFileFormat
, source
);
76 if (fInitStatus
!= B_OK
)
79 fStreamInfo
= new stream_info
[fStreamCount
];
81 // initialize stream infos
82 for (int32 i
= 0; i
< fStreamCount
; i
++) {
83 fStreamInfo
[i
].status
= B_OK
;
84 fStreamInfo
[i
].cookie
= 0;
85 fStreamInfo
[i
].hasCookie
= false;
86 fStreamInfo
[i
].infoBuffer
= 0;
87 fStreamInfo
[i
].infoBufferSize
= 0;
88 fStreamInfo
[i
].chunkCache
89 = new ChunkCache(fExtractorWaitSem
, kMaxCacheBytes
);
90 fStreamInfo
[i
].lastChunk
= NULL
;
91 memset(&fStreamInfo
[i
].encodedFormat
, 0,
92 sizeof(fStreamInfo
[i
].encodedFormat
));
94 if (fStreamInfo
[i
].chunkCache
->InitCheck() != B_OK
) {
95 fInitStatus
= B_NO_MEMORY
;
100 // create all stream cookies
101 for (int32 i
= 0; i
< fStreamCount
; i
++) {
102 if (fReader
->AllocateCookie(i
, &fStreamInfo
[i
].cookie
) != B_OK
) {
103 fStreamInfo
[i
].cookie
= 0;
104 fStreamInfo
[i
].hasCookie
= false;
105 fStreamInfo
[i
].status
= B_ERROR
;
106 ERROR("MediaExtractor::MediaExtractor: AllocateCookie for stream %"
107 B_PRId32
" failed\n", i
);
109 fStreamInfo
[i
].hasCookie
= true;
112 // get info for all streams
113 for (int32 i
= 0; i
< fStreamCount
; i
++) {
114 if (fStreamInfo
[i
].status
!= B_OK
)
119 if (fReader
->GetStreamInfo(fStreamInfo
[i
].cookie
, &frameCount
,
120 &duration
, &fStreamInfo
[i
].encodedFormat
,
121 &fStreamInfo
[i
].infoBuffer
, &fStreamInfo
[i
].infoBufferSize
)
123 fStreamInfo
[i
].status
= B_ERROR
;
124 ERROR("MediaExtractor::MediaExtractor: GetStreamInfo for "
125 "stream %" B_PRId32
" failed\n", i
);
129 #if !DISABLE_CHUNK_CACHE
130 // start extractor thread
131 fExtractorThread
= spawn_thread(_ExtractorEntry
, "media extractor thread",
132 B_NORMAL_PRIORITY
+ 4, this);
133 resume_thread(fExtractorThread
);
138 MediaExtractor::~MediaExtractor()
142 #if !DISABLE_CHUNK_CACHE
143 // terminate extractor thread
144 delete_sem(fExtractorWaitSem
);
147 wait_for_thread(fExtractorThread
, &status
);
150 // free all stream cookies
152 for (int32 i
= 0; i
< fStreamCount
; i
++) {
153 if (fStreamInfo
[i
].hasCookie
)
154 fReader
->FreeCookie(fStreamInfo
[i
].cookie
);
156 delete fStreamInfo
[i
].chunkCache
;
159 gPluginManager
.DestroyReader(fReader
);
161 delete[] fStreamInfo
;
162 // fSource is owned by the BMediaFile
167 MediaExtractor::InitCheck()
175 MediaExtractor::GetFileFormatInfo(media_file_format
* fileFormat
) const
178 *fileFormat
= fFileFormat
;
183 MediaExtractor::GetMetaData(BMessage
* _data
) const
186 return fReader
->GetMetaData(_data
);
191 MediaExtractor::StreamCount()
199 MediaExtractor::Copyright()
201 return fReader
->Copyright();
206 MediaExtractor::EncodedFormat(int32 stream
)
208 return &fStreamInfo
[stream
].encodedFormat
;
213 MediaExtractor::CountFrames(int32 stream
) const
216 if (fStreamInfo
[stream
].status
!= B_OK
)
222 const void* infoBuffer
;
225 fReader
->GetStreamInfo(fStreamInfo
[stream
].cookie
, &frameCount
, &duration
,
226 &format
, &infoBuffer
, &infoSize
);
233 MediaExtractor::Duration(int32 stream
) const
237 if (fStreamInfo
[stream
].status
!= B_OK
)
243 const void* infoBuffer
;
246 fReader
->GetStreamInfo(fStreamInfo
[stream
].cookie
, &frameCount
, &duration
,
247 &format
, &infoBuffer
, &infoSize
);
254 MediaExtractor::Seek(int32 stream
, uint32 seekTo
, int64
* _frame
,
259 stream_info
& info
= fStreamInfo
[stream
];
260 if (info
.status
!= B_OK
)
263 BAutolock
_(info
.chunkCache
);
265 status_t status
= fReader
->Seek(info
.cookie
, seekTo
, _frame
, _time
);
269 // clear buffered chunks after seek
270 info
.chunkCache
->MakeEmpty();
277 MediaExtractor::FindKeyFrame(int32 stream
, uint32 seekTo
, int64
* _frame
,
278 bigtime_t
* _time
) const
282 stream_info
& info
= fStreamInfo
[stream
];
283 if (info
.status
!= B_OK
)
286 return fReader
->FindKeyFrame(info
.cookie
, seekTo
, _frame
, _time
);
291 MediaExtractor::GetNextChunk(int32 stream
, const void** _chunkBuffer
,
292 size_t* _chunkSize
, media_header
* mediaHeader
)
294 stream_info
& info
= fStreamInfo
[stream
];
296 if (info
.status
!= B_OK
)
299 #if DISABLE_CHUNK_CACHE
300 return fReader
->GetNextChunk(fStreamInfo
[stream
].cookie
, _chunkBuffer
,
301 _chunkSize
, mediaHeader
);
303 BAutolock
_(info
.chunkCache
);
305 _RecycleLastChunk(info
);
307 // Retrieve next chunk - read it directly, if the cache is drained
308 chunk_buffer
* chunk
= info
.chunkCache
->NextChunk(fReader
, info
.cookie
);
313 info
.lastChunk
= chunk
;
315 *_chunkBuffer
= chunk
->buffer
;
316 *_chunkSize
= chunk
->size
;
317 *mediaHeader
= chunk
->header
;
319 return chunk
->status
;
325 MediaExtractor::CreateDecoder(int32 stream
, Decoder
** _decoder
,
326 media_codec_info
* codecInfo
)
330 status_t status
= fStreamInfo
[stream
].status
;
331 if (status
!= B_OK
) {
332 ERROR("MediaExtractor::CreateDecoder can't create decoder for "
333 "stream %" B_PRId32
": %s\n", stream
, strerror(status
));
337 // TODO: Here we should work out a way so that if there is a setup
338 // failure we can try the next decoder
340 status
= gPluginManager
.CreateDecoder(&decoder
,
341 fStreamInfo
[stream
].encodedFormat
);
342 if (status
!= B_OK
) {
344 char formatString
[256];
345 string_for_format(fStreamInfo
[stream
].encodedFormat
, formatString
,
346 sizeof(formatString
));
348 ERROR("MediaExtractor::CreateDecoder gPluginManager.CreateDecoder "
349 "failed for stream %" B_PRId32
", format: %s: %s\n", stream
,
350 formatString
, strerror(status
));
355 ChunkProvider
* chunkProvider
356 = new(std::nothrow
) MediaExtractorChunkProvider(this, stream
);
357 if (chunkProvider
== NULL
) {
358 gPluginManager
.DestroyDecoder(decoder
);
359 ERROR("MediaExtractor::CreateDecoder can't create chunk provider "
360 "for stream %" B_PRId32
"\n", stream
);
364 decoder
->SetChunkProvider(chunkProvider
);
366 status
= decoder
->Setup(&fStreamInfo
[stream
].encodedFormat
,
367 fStreamInfo
[stream
].infoBuffer
, fStreamInfo
[stream
].infoBufferSize
);
368 if (status
!= B_OK
) {
369 gPluginManager
.DestroyDecoder(decoder
);
370 ERROR("MediaExtractor::CreateDecoder Setup failed for stream %" B_PRId32
371 ": %s\n", stream
, strerror(status
));
375 status
= gPluginManager
.GetDecoderInfo(decoder
, codecInfo
);
376 if (status
!= B_OK
) {
377 gPluginManager
.DestroyDecoder(decoder
);
378 ERROR("MediaExtractor::CreateDecoder GetCodecInfo failed for stream %"
379 B_PRId32
": %s\n", stream
, strerror(status
));
389 MediaExtractor::GetStreamMetaData(int32 stream
, BMessage
* _data
) const
391 const stream_info
& info
= fStreamInfo
[stream
];
393 if (info
.status
!= B_OK
)
396 return fReader
->GetStreamMetaData(fStreamInfo
[stream
].cookie
, _data
);
401 MediaExtractor::_RecycleLastChunk(stream_info
& info
)
403 if (info
.lastChunk
!= NULL
) {
404 info
.chunkCache
->RecycleChunk(info
.lastChunk
);
405 info
.lastChunk
= NULL
;
411 MediaExtractor::_ExtractorEntry(void* extractor
)
413 static_cast<MediaExtractor
*>(extractor
)->_ExtractorThread();
419 MediaExtractor::_ExtractorThread()
424 status
= acquire_sem(fExtractorWaitSem
);
425 } while (status
== B_INTERRUPTED
);
427 if (status
!= B_OK
) {
428 // we were asked to quit
432 // Iterate over all streams until they are all filled
438 for (int32 stream
= 0; stream
< fStreamCount
; stream
++) {
439 stream_info
& info
= fStreamInfo
[stream
];
440 if (info
.status
!= B_OK
) {
445 BAutolock
_(info
.chunkCache
);
447 if (!info
.chunkCache
->SpaceLeft()
448 || !info
.chunkCache
->ReadNextChunk(fReader
, info
.cookie
))
451 } while (streamsFilled
< fStreamCount
);