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
18#include "ChunkCache.h"
19#include "debug.h"
20#include "PluginManager.h"
21
22
23// should be 0, to disable the chunk cache set it to 1
24#define DISABLE_CHUNK_CACHE 0
25
26
27static const size_t kMaxCacheBytes = 3 * 1024 * 1024;
28
29
30class MediaExtractorChunkProvider : public ChunkProvider {
31public:
32	MediaExtractorChunkProvider(MediaExtractor* extractor, int32 stream)
33		:
34		fExtractor(extractor),
35		fStream(stream)
36	{
37	}
38
39	virtual status_t GetNextChunk(const void** _chunkBuffer, size_t* _chunkSize,
40		media_header *mediaHeader)
41	{
42		return fExtractor->GetNextChunk(fStream, _chunkBuffer, _chunkSize,
43			mediaHeader);
44	}
45
46private:
47	MediaExtractor*	fExtractor;
48	int32			fStream;
49};
50
51
52// #pragma mark -
53
54
55MediaExtractor::MediaExtractor(BDataIO* source, int32 flags)
56	:
57	fExtractorThread(-1),
58	fSource(source),
59	fReader(NULL),
60	fStreamInfo(NULL),
61	fStreamCount(0)
62{
63	CALLED();
64
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;
70		return;
71	}
72#endif
73
74	fInitStatus = gPluginManager.CreateReader(&fReader, &fStreamCount,
75		&fFileFormat, source);
76	if (fInitStatus != B_OK)
77		return;
78
79	fStreamInfo = new stream_info[fStreamCount];
80
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));
93
94		if (fStreamInfo[i].chunkCache->InitCheck() != B_OK) {
95			fInitStatus = B_NO_MEMORY;
96			return;
97		}
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
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);
134#endif
135}
136
137
138MediaExtractor::~MediaExtractor()
139{
140	CALLED();
141
142#if !DISABLE_CHUNK_CACHE
143	// terminate extractor thread
144	delete_sem(fExtractorWaitSem);
145
146	status_t status;
147	wait_for_thread(fExtractorThread, &status);
148#endif
149
150	// free all stream cookies
151	// and chunk caches
152	for (int32 i = 0; i < fStreamCount; i++) {
153		if (fStreamInfo[i].hasCookie)
154			fReader->FreeCookie(fStreamInfo[i].cookie);
155
156		delete fStreamInfo[i].chunkCache;
157	}
158
159	gPluginManager.DestroyReader(fReader);
160
161	delete[] fStreamInfo;
162	// fSource is owned by the BMediaFile
163}
164
165
166status_t
167MediaExtractor::InitCheck()
168{
169	CALLED();
170	return fInitStatus;
171}
172
173
174void
175MediaExtractor::GetFileFormatInfo(media_file_format* fileFormat) const
176{
177	CALLED();
178	*fileFormat = fFileFormat;
179}
180
181
182status_t
183MediaExtractor::GetMetaData(BMessage* _data) const
184{
185	CALLED();
186	return fReader->GetMetaData(_data);
187}
188
189
190int32
191MediaExtractor::StreamCount()
192{
193	CALLED();
194	return fStreamCount;
195}
196
197
198const char*
199MediaExtractor::Copyright()
200{
201	return fReader->Copyright();
202}
203
204
205const media_format*
206MediaExtractor::EncodedFormat(int32 stream)
207{
208	return &fStreamInfo[stream].encodedFormat;
209}
210
211
212int64
213MediaExtractor::CountFrames(int32 stream) const
214{
215	CALLED();
216	if (fStreamInfo[stream].status != B_OK)
217		return 0LL;
218
219	int64 frameCount;
220	bigtime_t duration;
221	media_format format;
222	const void* infoBuffer;
223	size_t infoSize;
224
225	fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration,
226		&format, &infoBuffer, &infoSize);
227
228	return frameCount;
229}
230
231
232bigtime_t
233MediaExtractor::Duration(int32 stream) const
234{
235	CALLED();
236
237	if (fStreamInfo[stream].status != B_OK)
238		return 0LL;
239
240	int64 frameCount;
241	bigtime_t duration;
242	media_format format;
243	const void* infoBuffer;
244	size_t infoSize;
245
246	fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration,
247		&format, &infoBuffer, &infoSize);
248
249	return duration;
250}
251
252
253status_t
254MediaExtractor::Seek(int32 stream, uint32 seekTo, int64* _frame,
255	bigtime_t* _time)
256{
257	CALLED();
258
259	stream_info& info = fStreamInfo[stream];
260	if (info.status != B_OK)
261		return info.status;
262
263	BAutolock _(info.chunkCache);
264
265	status_t status = fReader->Seek(info.cookie, seekTo, _frame, _time);
266	if (status != B_OK)
267		return status;
268
269	// clear buffered chunks after seek
270	info.chunkCache->MakeEmpty();
271
272	return B_OK;
273}
274
275
276status_t
277MediaExtractor::FindKeyFrame(int32 stream, uint32 seekTo, int64* _frame,
278	bigtime_t* _time) const
279{
280	CALLED();
281
282	stream_info& info = fStreamInfo[stream];
283	if (info.status != B_OK)
284		return info.status;
285
286	return fReader->FindKeyFrame(info.cookie, seekTo, _frame, _time);
287}
288
289
290status_t
291MediaExtractor::GetNextChunk(int32 stream, const void** _chunkBuffer,
292	size_t* _chunkSize, media_header* mediaHeader)
293{
294	stream_info& info = fStreamInfo[stream];
295
296	if (info.status != B_OK)
297		return info.status;
298
299#if DISABLE_CHUNK_CACHE
300	return fReader->GetNextChunk(fStreamInfo[stream].cookie, _chunkBuffer,
301		_chunkSize, mediaHeader);
302#else
303	BAutolock _(info.chunkCache);
304
305	_RecycleLastChunk(info);
306
307	// Retrieve next chunk - read it directly, if the cache is drained
308	chunk_buffer* chunk = info.chunkCache->NextChunk(fReader, info.cookie);
309
310	if (chunk == NULL)
311		return B_NO_MEMORY;
312
313	info.lastChunk = chunk;
314
315	*_chunkBuffer = chunk->buffer;
316	*_chunkSize = chunk->size;
317	*mediaHeader = chunk->header;
318
319	return chunk->status;
320#endif
321}
322
323
324status_t
325MediaExtractor::CreateDecoder(int32 stream, Decoder** _decoder,
326	media_codec_info* codecInfo)
327{
328	CALLED();
329
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));
334		return status;
335	}
336
337	// TODO: Here we should work out a way so that if there is a setup
338	// failure we can try the next decoder
339	Decoder* decoder;
340	status = gPluginManager.CreateDecoder(&decoder,
341		fStreamInfo[stream].encodedFormat);
342	if (status != B_OK) {
343#if DEBUG
344		char formatString[256];
345		string_for_format(fStreamInfo[stream].encodedFormat, formatString,
346			sizeof(formatString));
347
348		ERROR("MediaExtractor::CreateDecoder gPluginManager.CreateDecoder "
349			"failed for stream %" B_PRId32 ", format: %s: %s\n", stream,
350			formatString, strerror(status));
351#endif
352		return status;
353	}
354
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);
361		return B_NO_MEMORY;
362	}
363
364	decoder->SetChunkProvider(chunkProvider);
365
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));
372		return status;
373	}
374
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));
380		return status;
381	}
382
383	*_decoder = decoder;
384	return B_OK;
385}
386
387
388status_t
389MediaExtractor::GetStreamMetaData(int32 stream, BMessage* _data) const
390{
391	const stream_info& info = fStreamInfo[stream];
392
393	if (info.status != B_OK)
394		return info.status;
395
396	return fReader->GetStreamMetaData(fStreamInfo[stream].cookie, _data);
397}
398
399
400void
401MediaExtractor::_RecycleLastChunk(stream_info& info)
402{
403	if (info.lastChunk != NULL) {
404		info.chunkCache->RecycleChunk(info.lastChunk);
405		info.lastChunk = NULL;
406	}
407}
408
409
410status_t
411MediaExtractor::_ExtractorEntry(void* extractor)
412{
413	static_cast<MediaExtractor*>(extractor)->_ExtractorThread();
414	return B_OK;
415}
416
417
418void
419MediaExtractor::_ExtractorThread()
420{
421	while (true) {
422		status_t status;
423		do {
424			status = acquire_sem(fExtractorWaitSem);
425		} while (status == B_INTERRUPTED);
426
427		if (status != B_OK) {
428			// we were asked to quit
429			return;
430		}
431
432		// Iterate over all streams until they are all filled
433
434		int32 streamsFilled;
435		do {
436			streamsFilled = 0;
437
438			for (int32 stream = 0; stream < fStreamCount; stream++) {
439				stream_info& info = fStreamInfo[stream];
440				if (info.status != B_OK) {
441					streamsFilled++;
442					continue;
443				}
444
445				BAutolock _(info.chunkCache);
446
447				if (!info.chunkCache->SpaceLeft()
448					|| !info.chunkCache->ReadNextChunk(fReader, info.cookie))
449					streamsFilled++;
450			}
451		} while (streamsFilled < fStreamCount);
452	}
453}
454