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
29class MediaExtractorChunkProvider : public ChunkProvider {
30public:
31	MediaExtractorChunkProvider(MediaExtractor* extractor, int32 stream)
32		:
33		fExtractor(extractor),
34		fStream(stream)
35	{
36	}
37
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
45private:
46	MediaExtractor*	fExtractor;
47	int32			fStream;
48};
49
50
51// #pragma mark -
52
53
54MediaExtractor::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
65void
66MediaExtractor::_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
149MediaExtractor::~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
172status_t
173MediaExtractor::InitCheck()
174{
175	CALLED();
176	return fInitStatus;
177}
178
179
180void
181MediaExtractor::GetFileFormatInfo(media_file_format* fileFormat) const
182{
183	CALLED();
184	*fileFormat = fFileFormat;
185}
186
187
188status_t
189MediaExtractor::GetMetaData(BMessage* _data) const
190{
191	CALLED();
192	return fReader->GetMetaData(_data);
193}
194
195
196int32
197MediaExtractor::StreamCount()
198{
199	CALLED();
200	return fStreamCount;
201}
202
203
204const char*
205MediaExtractor::Copyright()
206{
207	return fReader->Copyright();
208}
209
210
211const media_format*
212MediaExtractor::EncodedFormat(int32 stream)
213{
214	return &fStreamInfo[stream].encodedFormat;
215}
216
217
218int64
219MediaExtractor::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
238bigtime_t
239MediaExtractor::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
259status_t
260MediaExtractor::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
286status_t
287MediaExtractor::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
300status_t
301MediaExtractor::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
334status_t
335MediaExtractor::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
398status_t
399MediaExtractor::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
410void
411MediaExtractor::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
426void
427MediaExtractor::_RecycleLastChunk(stream_info& info)
428{
429	if (info.lastChunk != NULL) {
430		info.chunkCache->RecycleChunk(info.lastChunk);
431		info.lastChunk = NULL;
432	}
433}
434
435
436status_t
437MediaExtractor::_ExtractorEntry(void* extractor)
438{
439	static_cast<MediaExtractor*>(extractor)->_ExtractorThread();
440	return B_OK;
441}
442
443
444size_t
445MediaExtractor::_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
467void
468MediaExtractor::_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