1 /*
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.
5 *
6 * Distributed under the terms of the MIT License.
7 */
8
9
10 #include "MediaExtractor.h"
11
12 #include <new>
13 #include <stdio.h>
14 #include <string.h>
15
16 #include <Autolock.h>
17 #include <InterfacePrivate.h>
18
19 #include "ChunkCache.h"
20 #include "MediaDebug.h"
21 #include "MediaMisc.h"
22 #include "PluginManager.h"
23
24
25 // should be 0, to disable the chunk cache set it to 1
26 #define DISABLE_CHUNK_CACHE 0
27
28
29 class MediaExtractorChunkProvider : public ChunkProvider {
30 public:
MediaExtractorChunkProvider(MediaExtractor * extractor,int32 stream)31 MediaExtractorChunkProvider(MediaExtractor* extractor, int32 stream)
32 :
33 fExtractor(extractor),
34 fStream(stream)
35 {
36 }
37
GetNextChunk(const void ** _chunkBuffer,size_t * _chunkSize,media_header * mediaHeader)38 virtual status_t GetNextChunk(const void** _chunkBuffer, size_t* _chunkSize,
39 media_header *mediaHeader)
40 {
41 return fExtractor->GetNextChunk(fStream, _chunkBuffer, _chunkSize,
42 mediaHeader);
43 }
44
45 private:
46 MediaExtractor* fExtractor;
47 int32 fStream;
48 };
49
50
51 // #pragma mark -
52
53
MediaExtractor(BDataIO * source,int32 flags)54 MediaExtractor::MediaExtractor(BDataIO* source, int32 flags)
55 :
56 fExtractorThread(-1),
57 fReader(NULL),
58 fStreamInfo(NULL),
59 fStreamCount(0)
60 {
61 _Init(source, flags);
62 }
63
64
65 void
_Init(BDataIO * source,int32 flags)66 MediaExtractor::_Init(BDataIO* source, int32 flags)
67 {
68 CALLED();
69
70 fSource = source;
71
72 #if !DISABLE_CHUNK_CACHE
73 // start extractor thread
74 fExtractorWaitSem = create_sem(1, "media extractor thread sem");
75 if (fExtractorWaitSem < 0) {
76 fInitStatus = fExtractorWaitSem;
77 return;
78 }
79 #endif
80
81 fInitStatus = gPluginManager.CreateReader(&fReader, &fStreamCount,
82 &fFileFormat, source);
83 if (fInitStatus != B_OK)
84 return;
85
86 fStreamInfo = new stream_info[fStreamCount];
87
88 // initialize stream infos
89 for (int32 i = 0; i < fStreamCount; i++) {
90 fStreamInfo[i].status = B_OK;
91 fStreamInfo[i].cookie = 0;
92 fStreamInfo[i].hasCookie = false;
93 fStreamInfo[i].infoBuffer = 0;
94 fStreamInfo[i].infoBufferSize = 0;
95 fStreamInfo[i].lastChunk = NULL;
96 fStreamInfo[i].chunkCache = NULL;
97 fStreamInfo[i].encodedFormat.Clear();
98 }
99
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);
108 } else
109 fStreamInfo[i].hasCookie = true;
110 }
111
112 // get info for all streams
113 for (int32 i = 0; i < fStreamCount; i++) {
114 if (fStreamInfo[i].status != B_OK)
115 continue;
116
117 int64 frameCount;
118 bigtime_t duration;
119 if (fReader->GetStreamInfo(fStreamInfo[i].cookie, &frameCount,
120 &duration, &fStreamInfo[i].encodedFormat,
121 &fStreamInfo[i].infoBuffer, &fStreamInfo[i].infoBufferSize)
122 != B_OK) {
123 fStreamInfo[i].status = B_ERROR;
124 ERROR("MediaExtractor::MediaExtractor: GetStreamInfo for "
125 "stream %" B_PRId32 " failed\n", i);
126 }
127
128 #if !DISABLE_CHUNK_CACHE
129 // Allocate our ChunkCache
130 size_t chunkCacheMaxBytes = _CalculateChunkBuffer(i);
131 fStreamInfo[i].chunkCache
132 = new ChunkCache(fExtractorWaitSem, chunkCacheMaxBytes);
133 if (fStreamInfo[i].chunkCache->InitCheck() != B_OK) {
134 fInitStatus = B_NO_MEMORY;
135 return;
136 }
137 #endif
138 }
139
140 #if !DISABLE_CHUNK_CACHE
141 // start extractor thread
142 fExtractorThread = spawn_thread(_ExtractorEntry, "media extractor thread",
143 B_NORMAL_PRIORITY + 4, this);
144 resume_thread(fExtractorThread);
145 #endif
146 }
147
148
~MediaExtractor()149 MediaExtractor::~MediaExtractor()
150 {
151 CALLED();
152
153 // stop the extractor thread, if still running
154 StopProcessing();
155
156 // free all stream cookies
157 // and chunk caches
158 for (int32 i = 0; i < fStreamCount; i++) {
159 if (fStreamInfo[i].hasCookie)
160 fReader->FreeCookie(fStreamInfo[i].cookie);
161
162 delete fStreamInfo[i].chunkCache;
163 }
164
165 gPluginManager.DestroyReader(fReader);
166
167 delete[] fStreamInfo;
168 // fSource is owned by the BMediaFile
169 }
170
171
172 status_t
InitCheck()173 MediaExtractor::InitCheck()
174 {
175 CALLED();
176 return fInitStatus;
177 }
178
179
180 void
GetFileFormatInfo(media_file_format * fileFormat) const181 MediaExtractor::GetFileFormatInfo(media_file_format* fileFormat) const
182 {
183 CALLED();
184 *fileFormat = fFileFormat;
185 }
186
187
188 status_t
GetMetaData(BMessage * _data) const189 MediaExtractor::GetMetaData(BMessage* _data) const
190 {
191 CALLED();
192 return fReader->GetMetaData(_data);
193 }
194
195
196 int32
StreamCount()197 MediaExtractor::StreamCount()
198 {
199 CALLED();
200 return fStreamCount;
201 }
202
203
204 const char*
Copyright()205 MediaExtractor::Copyright()
206 {
207 return fReader->Copyright();
208 }
209
210
211 const media_format*
EncodedFormat(int32 stream)212 MediaExtractor::EncodedFormat(int32 stream)
213 {
214 return &fStreamInfo[stream].encodedFormat;
215 }
216
217
218 int64
CountFrames(int32 stream) const219 MediaExtractor::CountFrames(int32 stream) const
220 {
221 CALLED();
222 if (fStreamInfo[stream].status != B_OK)
223 return 0LL;
224
225 int64 frameCount;
226 bigtime_t duration;
227 media_format format;
228 const void* infoBuffer;
229 size_t infoSize;
230
231 fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration,
232 &format, &infoBuffer, &infoSize);
233
234 return frameCount;
235 }
236
237
238 bigtime_t
Duration(int32 stream) const239 MediaExtractor::Duration(int32 stream) const
240 {
241 CALLED();
242
243 if (fStreamInfo[stream].status != B_OK)
244 return 0LL;
245
246 int64 frameCount;
247 bigtime_t duration;
248 media_format format;
249 const void* infoBuffer;
250 size_t infoSize;
251
252 fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration,
253 &format, &infoBuffer, &infoSize);
254
255 return duration;
256 }
257
258
259 status_t
Seek(int32 stream,uint32 seekTo,int64 * _frame,bigtime_t * _time)260 MediaExtractor::Seek(int32 stream, uint32 seekTo, int64* _frame,
261 bigtime_t* _time)
262 {
263 CALLED();
264
265 stream_info& info = fStreamInfo[stream];
266 if (info.status != B_OK)
267 return info.status;
268
269 #if !DISABLE_CHUNK_CACHE
270 BAutolock _(info.chunkCache);
271 #endif
272
273 status_t status = fReader->Seek(info.cookie, seekTo, _frame, _time);
274 if (status != B_OK)
275 return status;
276
277 #if !DISABLE_CHUNK_CACHE
278 // clear buffered chunks after seek
279 info.chunkCache->MakeEmpty();
280 #endif
281
282 return B_OK;
283 }
284
285
286 status_t
FindKeyFrame(int32 stream,uint32 seekTo,int64 * _frame,bigtime_t * _time) const287 MediaExtractor::FindKeyFrame(int32 stream, uint32 seekTo, int64* _frame,
288 bigtime_t* _time) const
289 {
290 CALLED();
291
292 stream_info& info = fStreamInfo[stream];
293 if (info.status != B_OK)
294 return info.status;
295
296 return fReader->FindKeyFrame(info.cookie, seekTo, _frame, _time);
297 }
298
299
300 status_t
GetNextChunk(int32 stream,const void ** _chunkBuffer,size_t * _chunkSize,media_header * mediaHeader)301 MediaExtractor::GetNextChunk(int32 stream, const void** _chunkBuffer,
302 size_t* _chunkSize, media_header* mediaHeader)
303 {
304 stream_info& info = fStreamInfo[stream];
305
306 if (info.status != B_OK)
307 return info.status;
308
309 #if DISABLE_CHUNK_CACHE
310 return fReader->GetNextChunk(fStreamInfo[stream].cookie, _chunkBuffer,
311 _chunkSize, mediaHeader);
312 #else
313 BAutolock _(info.chunkCache);
314
315 _RecycleLastChunk(info);
316
317 // Retrieve next chunk - read it directly, if the cache is drained
318 chunk_buffer* chunk = info.chunkCache->NextChunk(fReader, info.cookie);
319
320 if (chunk == NULL)
321 return B_NO_MEMORY;
322
323 info.lastChunk = chunk;
324
325 *_chunkBuffer = chunk->buffer;
326 *_chunkSize = chunk->size;
327 *mediaHeader = chunk->header;
328
329 return chunk->status;
330 #endif
331 }
332
333
334 status_t
CreateDecoder(int32 stream,Decoder ** _decoder,media_codec_info * codecInfo)335 MediaExtractor::CreateDecoder(int32 stream, Decoder** _decoder,
336 media_codec_info* codecInfo)
337 {
338 CALLED();
339
340 status_t status = fStreamInfo[stream].status;
341 if (status != B_OK) {
342 ERROR("MediaExtractor::CreateDecoder can't create decoder for "
343 "stream %" B_PRId32 ": %s\n", stream, strerror(status));
344 return status;
345 }
346
347 // TODO: Here we should work out a way so that if there is a setup
348 // failure we can try the next decoder
349 Decoder* decoder;
350 status = gPluginManager.CreateDecoder(&decoder,
351 fStreamInfo[stream].encodedFormat);
352 if (status != B_OK) {
353 #if DEBUG
354 char formatString[256];
355 string_for_format(fStreamInfo[stream].encodedFormat, formatString,
356 sizeof(formatString));
357
358 ERROR("MediaExtractor::CreateDecoder gPluginManager.CreateDecoder "
359 "failed for stream %" B_PRId32 ", format: %s: %s\n", stream,
360 formatString, strerror(status));
361 #endif
362 return status;
363 }
364
365 ChunkProvider* chunkProvider
366 = new(std::nothrow) MediaExtractorChunkProvider(this, stream);
367 if (chunkProvider == NULL) {
368 gPluginManager.DestroyDecoder(decoder);
369 ERROR("MediaExtractor::CreateDecoder can't create chunk provider "
370 "for stream %" B_PRId32 "\n", stream);
371 return B_NO_MEMORY;
372 }
373
374 decoder->SetChunkProvider(chunkProvider);
375
376 status = decoder->Setup(&fStreamInfo[stream].encodedFormat,
377 fStreamInfo[stream].infoBuffer, fStreamInfo[stream].infoBufferSize);
378 if (status != B_OK) {
379 gPluginManager.DestroyDecoder(decoder);
380 ERROR("MediaExtractor::CreateDecoder Setup failed for stream %" B_PRId32
381 ": %s\n", stream, strerror(status));
382 return status;
383 }
384
385 status = gPluginManager.GetDecoderInfo(decoder, codecInfo);
386 if (status != B_OK) {
387 gPluginManager.DestroyDecoder(decoder);
388 ERROR("MediaExtractor::CreateDecoder GetCodecInfo failed for stream %"
389 B_PRId32 ": %s\n", stream, strerror(status));
390 return status;
391 }
392
393 *_decoder = decoder;
394 return B_OK;
395 }
396
397
398 status_t
GetStreamMetaData(int32 stream,BMessage * _data) const399 MediaExtractor::GetStreamMetaData(int32 stream, BMessage* _data) const
400 {
401 const stream_info& info = fStreamInfo[stream];
402
403 if (info.status != B_OK)
404 return info.status;
405
406 return fReader->GetStreamMetaData(fStreamInfo[stream].cookie, _data);
407 }
408
409
410 void
StopProcessing()411 MediaExtractor::StopProcessing()
412 {
413 #if !DISABLE_CHUNK_CACHE
414 if (fExtractorWaitSem > -1) {
415 // terminate extractor thread
416 delete_sem(fExtractorWaitSem);
417 fExtractorWaitSem = -1;
418
419 status_t status;
420 wait_for_thread(fExtractorThread, &status);
421 }
422 #endif
423 }
424
425
426 void
_RecycleLastChunk(stream_info & info)427 MediaExtractor::_RecycleLastChunk(stream_info& info)
428 {
429 if (info.lastChunk != NULL) {
430 info.chunkCache->RecycleChunk(info.lastChunk);
431 info.lastChunk = NULL;
432 }
433 }
434
435
436 status_t
_ExtractorEntry(void * extractor)437 MediaExtractor::_ExtractorEntry(void* extractor)
438 {
439 static_cast<MediaExtractor*>(extractor)->_ExtractorThread();
440 return B_OK;
441 }
442
443
444 size_t
_CalculateChunkBuffer(int32 stream)445 MediaExtractor::_CalculateChunkBuffer(int32 stream)
446 {
447 // WARNING: magic
448 // Your A/V may skip frames, chunks or not play at all if the cache size
449 // is insufficient. Unfortunately there's currently no safe way to
450 // calculate it.
451
452 size_t cacheSize = 3 * 1024 * 1024;
453
454 const media_format* format = EncodedFormat(stream);
455 if (format->IsVideo()) {
456 // For video, have space for at least two frames
457 int32 rowSize = BPrivate::get_bytes_per_row(format->ColorSpace(),
458 format->Width());
459 if (rowSize > 0) {
460 cacheSize = max_c(cacheSize, rowSize * format->Height() * 2);
461 }
462 }
463 return ROUND_UP_TO_PAGE(cacheSize);
464 }
465
466
467 void
_ExtractorThread()468 MediaExtractor::_ExtractorThread()
469 {
470 while (true) {
471 status_t status;
472 do {
473 status = acquire_sem(fExtractorWaitSem);
474 } while (status == B_INTERRUPTED);
475
476 if (status != B_OK) {
477 // we were asked to quit
478 return;
479 }
480
481 // Iterate over all streams until they are all filled
482
483 int32 streamsFilled;
484 do {
485 streamsFilled = 0;
486
487 for (int32 stream = 0; stream < fStreamCount; stream++) {
488 stream_info& info = fStreamInfo[stream];
489 if (info.status != B_OK) {
490 streamsFilled++;
491 continue;
492 }
493
494 BAutolock _(info.chunkCache);
495
496 if (!info.chunkCache->SpaceLeft()
497 || !info.chunkCache->ReadNextChunk(fReader, info.cookie))
498 streamsFilled++;
499 }
500 } while (streamsFilled < fStreamCount);
501 }
502 }
503