xref: /haiku/src/kits/media/MediaExtractor.cpp (revision 3af8011358bd4c624a0979336d48dabb466171ed)
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:
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 
45 private:
46 	MediaExtractor*	fExtractor;
47 	int32			fStream;
48 };
49 
50 
51 // #pragma mark -
52 
53 
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
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 
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
173 MediaExtractor::InitCheck()
174 {
175 	CALLED();
176 	return fInitStatus;
177 }
178 
179 
180 void
181 MediaExtractor::GetFileFormatInfo(media_file_format* fileFormat) const
182 {
183 	CALLED();
184 	*fileFormat = fFileFormat;
185 }
186 
187 
188 status_t
189 MediaExtractor::GetMetaData(BMessage* _data) const
190 {
191 	CALLED();
192 	return fReader->GetMetaData(_data);
193 }
194 
195 
196 int32
197 MediaExtractor::StreamCount()
198 {
199 	CALLED();
200 	return fStreamCount;
201 }
202 
203 
204 const char*
205 MediaExtractor::Copyright()
206 {
207 	return fReader->Copyright();
208 }
209 
210 
211 const media_format*
212 MediaExtractor::EncodedFormat(int32 stream)
213 {
214 	return &fStreamInfo[stream].encodedFormat;
215 }
216 
217 
218 int64
219 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
239 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
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
287 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
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
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
399 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
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
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
437 MediaExtractor::_ExtractorEntry(void* extractor)
438 {
439 	static_cast<MediaExtractor*>(extractor)->_ExtractorThread();
440 	return B_OK;
441 }
442 
443 
444 size_t
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
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