xref: /haiku/src/kits/media/MediaExtractor.cpp (revision 6e82e428596071bbde44e296d04740f1f5d54d03)
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 		memset(&fStreamInfo[i].encodedFormat, 0,
100 			sizeof(fStreamInfo[i].encodedFormat));
101 
102 		if (fStreamInfo[i].chunkCache->InitCheck() != B_OK) {
103 			fInitStatus = B_NO_MEMORY;
104 			return;
105 		}
106 	}
107 
108 	// create all stream cookies
109 	for (int32 i = 0; i < fStreamCount; i++) {
110 		if (fReader->AllocateCookie(i, &fStreamInfo[i].cookie) != B_OK) {
111 			fStreamInfo[i].cookie = 0;
112 			fStreamInfo[i].hasCookie = false;
113 			fStreamInfo[i].status = B_ERROR;
114 			ERROR("MediaExtractor::MediaExtractor: AllocateCookie for stream %"
115 				B_PRId32 " failed\n", i);
116 		} else
117 			fStreamInfo[i].hasCookie = true;
118 	}
119 
120 	// get info for all streams
121 	for (int32 i = 0; i < fStreamCount; i++) {
122 		if (fStreamInfo[i].status != B_OK)
123 			continue;
124 
125 		int64 frameCount;
126 		bigtime_t duration;
127 		if (fReader->GetStreamInfo(fStreamInfo[i].cookie, &frameCount,
128 				&duration, &fStreamInfo[i].encodedFormat,
129 				&fStreamInfo[i].infoBuffer, &fStreamInfo[i].infoBufferSize)
130 					!= B_OK) {
131 			fStreamInfo[i].status = B_ERROR;
132 			ERROR("MediaExtractor::MediaExtractor: GetStreamInfo for "
133 				"stream %" B_PRId32 " failed\n", i);
134 		}
135 	}
136 
137 #if !DISABLE_CHUNK_CACHE
138 	// start extractor thread
139 	fExtractorThread = spawn_thread(_ExtractorEntry, "media extractor thread",
140 		B_NORMAL_PRIORITY + 4, this);
141 	resume_thread(fExtractorThread);
142 #endif
143 }
144 
145 
146 MediaExtractor::~MediaExtractor()
147 {
148 	CALLED();
149 
150 	// stop the extractor thread, if still running
151 	StopProcessing();
152 
153 	// free all stream cookies
154 	// and chunk caches
155 	for (int32 i = 0; i < fStreamCount; i++) {
156 		if (fStreamInfo[i].hasCookie)
157 			fReader->FreeCookie(fStreamInfo[i].cookie);
158 
159 		delete fStreamInfo[i].chunkCache;
160 	}
161 
162 	gPluginManager.DestroyReader(fReader);
163 
164 	delete[] fStreamInfo;
165 	// fSource is owned by the BMediaFile
166 }
167 
168 
169 status_t
170 MediaExtractor::InitCheck()
171 {
172 	CALLED();
173 	return fInitStatus;
174 }
175 
176 
177 BDataIO*
178 MediaExtractor::Source() const
179 {
180 	return fSource;
181 }
182 
183 
184 void
185 MediaExtractor::GetFileFormatInfo(media_file_format* fileFormat) const
186 {
187 	CALLED();
188 	*fileFormat = fFileFormat;
189 }
190 
191 
192 status_t
193 MediaExtractor::GetMetaData(BMessage* _data) const
194 {
195 	CALLED();
196 	return fReader->GetMetaData(_data);
197 }
198 
199 
200 int32
201 MediaExtractor::StreamCount()
202 {
203 	CALLED();
204 	return fStreamCount;
205 }
206 
207 
208 const char*
209 MediaExtractor::Copyright()
210 {
211 	return fReader->Copyright();
212 }
213 
214 
215 const media_format*
216 MediaExtractor::EncodedFormat(int32 stream)
217 {
218 	return &fStreamInfo[stream].encodedFormat;
219 }
220 
221 
222 int64
223 MediaExtractor::CountFrames(int32 stream) const
224 {
225 	CALLED();
226 	if (fStreamInfo[stream].status != B_OK)
227 		return 0LL;
228 
229 	int64 frameCount;
230 	bigtime_t duration;
231 	media_format format;
232 	const void* infoBuffer;
233 	size_t infoSize;
234 
235 	fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration,
236 		&format, &infoBuffer, &infoSize);
237 
238 	return frameCount;
239 }
240 
241 
242 bigtime_t
243 MediaExtractor::Duration(int32 stream) const
244 {
245 	CALLED();
246 
247 	if (fStreamInfo[stream].status != B_OK)
248 		return 0LL;
249 
250 	int64 frameCount;
251 	bigtime_t duration;
252 	media_format format;
253 	const void* infoBuffer;
254 	size_t infoSize;
255 
256 	fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration,
257 		&format, &infoBuffer, &infoSize);
258 
259 	return duration;
260 }
261 
262 
263 status_t
264 MediaExtractor::Seek(int32 stream, uint32 seekTo, int64* _frame,
265 	bigtime_t* _time)
266 {
267 	CALLED();
268 
269 	stream_info& info = fStreamInfo[stream];
270 	if (info.status != B_OK)
271 		return info.status;
272 
273 	BAutolock _(info.chunkCache);
274 
275 	status_t status = fReader->Seek(info.cookie, seekTo, _frame, _time);
276 	if (status != B_OK)
277 		return status;
278 
279 	// clear buffered chunks after seek
280 	info.chunkCache->MakeEmpty();
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 void
445 MediaExtractor::_ExtractorThread()
446 {
447 	while (true) {
448 		status_t status;
449 		do {
450 			status = acquire_sem(fExtractorWaitSem);
451 		} while (status == B_INTERRUPTED);
452 
453 		if (status != B_OK) {
454 			// we were asked to quit
455 			return;
456 		}
457 
458 		// Iterate over all streams until they are all filled
459 
460 		int32 streamsFilled;
461 		do {
462 			streamsFilled = 0;
463 
464 			for (int32 stream = 0; stream < fStreamCount; stream++) {
465 				stream_info& info = fStreamInfo[stream];
466 				if (info.status != B_OK) {
467 					streamsFilled++;
468 					continue;
469 				}
470 
471 				BAutolock _(info.chunkCache);
472 
473 				if (!info.chunkCache->SpaceLeft()
474 					|| !info.chunkCache->ReadNextChunk(fReader, info.cookie))
475 					streamsFilled++;
476 			}
477 		} while (streamsFilled < fStreamCount);
478 	}
479 }
480