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