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