xref: /haiku/src/kits/media/MediaExtractor.cpp (revision 15fb7d88e971c4d6c787c6a3a5c159afb1ebf77b)
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 "MediaDebug.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 void
177 MediaExtractor::GetFileFormatInfo(media_file_format* fileFormat) const
178 {
179 	CALLED();
180 	*fileFormat = fFileFormat;
181 }
182 
183 
184 status_t
185 MediaExtractor::GetMetaData(BMessage* _data) const
186 {
187 	CALLED();
188 	return fReader->GetMetaData(_data);
189 }
190 
191 
192 int32
193 MediaExtractor::StreamCount()
194 {
195 	CALLED();
196 	return fStreamCount;
197 }
198 
199 
200 const char*
201 MediaExtractor::Copyright()
202 {
203 	return fReader->Copyright();
204 }
205 
206 
207 const media_format*
208 MediaExtractor::EncodedFormat(int32 stream)
209 {
210 	return &fStreamInfo[stream].encodedFormat;
211 }
212 
213 
214 int64
215 MediaExtractor::CountFrames(int32 stream) const
216 {
217 	CALLED();
218 	if (fStreamInfo[stream].status != B_OK)
219 		return 0LL;
220 
221 	int64 frameCount;
222 	bigtime_t duration;
223 	media_format format;
224 	const void* infoBuffer;
225 	size_t infoSize;
226 
227 	fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration,
228 		&format, &infoBuffer, &infoSize);
229 
230 	return frameCount;
231 }
232 
233 
234 bigtime_t
235 MediaExtractor::Duration(int32 stream) const
236 {
237 	CALLED();
238 
239 	if (fStreamInfo[stream].status != B_OK)
240 		return 0LL;
241 
242 	int64 frameCount;
243 	bigtime_t duration;
244 	media_format format;
245 	const void* infoBuffer;
246 	size_t infoSize;
247 
248 	fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration,
249 		&format, &infoBuffer, &infoSize);
250 
251 	return duration;
252 }
253 
254 
255 status_t
256 MediaExtractor::Seek(int32 stream, uint32 seekTo, int64* _frame,
257 	bigtime_t* _time)
258 {
259 	CALLED();
260 
261 	stream_info& info = fStreamInfo[stream];
262 	if (info.status != B_OK)
263 		return info.status;
264 
265 	BAutolock _(info.chunkCache);
266 
267 	status_t status = fReader->Seek(info.cookie, seekTo, _frame, _time);
268 	if (status != B_OK)
269 		return status;
270 
271 	// clear buffered chunks after seek
272 	info.chunkCache->MakeEmpty();
273 
274 	return B_OK;
275 }
276 
277 
278 status_t
279 MediaExtractor::FindKeyFrame(int32 stream, uint32 seekTo, int64* _frame,
280 	bigtime_t* _time) const
281 {
282 	CALLED();
283 
284 	stream_info& info = fStreamInfo[stream];
285 	if (info.status != B_OK)
286 		return info.status;
287 
288 	return fReader->FindKeyFrame(info.cookie, seekTo, _frame, _time);
289 }
290 
291 
292 status_t
293 MediaExtractor::GetNextChunk(int32 stream, const void** _chunkBuffer,
294 	size_t* _chunkSize, media_header* mediaHeader)
295 {
296 	stream_info& info = fStreamInfo[stream];
297 
298 	if (info.status != B_OK)
299 		return info.status;
300 
301 #if DISABLE_CHUNK_CACHE
302 	return fReader->GetNextChunk(fStreamInfo[stream].cookie, _chunkBuffer,
303 		_chunkSize, mediaHeader);
304 #else
305 	BAutolock _(info.chunkCache);
306 
307 	_RecycleLastChunk(info);
308 
309 	// Retrieve next chunk - read it directly, if the cache is drained
310 	chunk_buffer* chunk = info.chunkCache->NextChunk(fReader, info.cookie);
311 
312 	if (chunk == NULL)
313 		return B_NO_MEMORY;
314 
315 	info.lastChunk = chunk;
316 
317 	*_chunkBuffer = chunk->buffer;
318 	*_chunkSize = chunk->size;
319 	*mediaHeader = chunk->header;
320 
321 	return chunk->status;
322 #endif
323 }
324 
325 
326 status_t
327 MediaExtractor::CreateDecoder(int32 stream, Decoder** _decoder,
328 	media_codec_info* codecInfo)
329 {
330 	CALLED();
331 
332 	status_t status = fStreamInfo[stream].status;
333 	if (status != B_OK) {
334 		ERROR("MediaExtractor::CreateDecoder can't create decoder for "
335 			"stream %" B_PRId32 ": %s\n", stream, strerror(status));
336 		return status;
337 	}
338 
339 	// TODO: Here we should work out a way so that if there is a setup
340 	// failure we can try the next decoder
341 	Decoder* decoder;
342 	status = gPluginManager.CreateDecoder(&decoder,
343 		fStreamInfo[stream].encodedFormat);
344 	if (status != B_OK) {
345 #if DEBUG
346 		char formatString[256];
347 		string_for_format(fStreamInfo[stream].encodedFormat, formatString,
348 			sizeof(formatString));
349 
350 		ERROR("MediaExtractor::CreateDecoder gPluginManager.CreateDecoder "
351 			"failed for stream %" B_PRId32 ", format: %s: %s\n", stream,
352 			formatString, strerror(status));
353 #endif
354 		return status;
355 	}
356 
357 	ChunkProvider* chunkProvider
358 		= new(std::nothrow) MediaExtractorChunkProvider(this, stream);
359 	if (chunkProvider == NULL) {
360 		gPluginManager.DestroyDecoder(decoder);
361 		ERROR("MediaExtractor::CreateDecoder can't create chunk provider "
362 			"for stream %" B_PRId32 "\n", stream);
363 		return B_NO_MEMORY;
364 	}
365 
366 	decoder->SetChunkProvider(chunkProvider);
367 
368 	status = decoder->Setup(&fStreamInfo[stream].encodedFormat,
369 		fStreamInfo[stream].infoBuffer, fStreamInfo[stream].infoBufferSize);
370 	if (status != B_OK) {
371 		gPluginManager.DestroyDecoder(decoder);
372 		ERROR("MediaExtractor::CreateDecoder Setup failed for stream %" B_PRId32
373 			": %s\n", stream, strerror(status));
374 		return status;
375 	}
376 
377 	status = gPluginManager.GetDecoderInfo(decoder, codecInfo);
378 	if (status != B_OK) {
379 		gPluginManager.DestroyDecoder(decoder);
380 		ERROR("MediaExtractor::CreateDecoder GetCodecInfo failed for stream %"
381 			B_PRId32 ": %s\n", stream, strerror(status));
382 		return status;
383 	}
384 
385 	*_decoder = decoder;
386 	return B_OK;
387 }
388 
389 
390 status_t
391 MediaExtractor::GetStreamMetaData(int32 stream, BMessage* _data) const
392 {
393 	const stream_info& info = fStreamInfo[stream];
394 
395 	if (info.status != B_OK)
396 		return info.status;
397 
398 	return fReader->GetStreamMetaData(fStreamInfo[stream].cookie, _data);
399 }
400 
401 
402 void
403 MediaExtractor::StopProcessing()
404 {
405 #if !DISABLE_CHUNK_CACHE
406 	if (fExtractorWaitSem > -1) {
407 		// terminate extractor thread
408 		delete_sem(fExtractorWaitSem);
409 		fExtractorWaitSem = -1;
410 
411 		status_t status;
412 		wait_for_thread(fExtractorThread, &status);
413 	}
414 #endif
415 }
416 
417 
418 void
419 MediaExtractor::_RecycleLastChunk(stream_info& info)
420 {
421 	if (info.lastChunk != NULL) {
422 		info.chunkCache->RecycleChunk(info.lastChunk);
423 		info.lastChunk = NULL;
424 	}
425 }
426 
427 
428 status_t
429 MediaExtractor::_ExtractorEntry(void* extractor)
430 {
431 	static_cast<MediaExtractor*>(extractor)->_ExtractorThread();
432 	return B_OK;
433 }
434 
435 
436 void
437 MediaExtractor::_ExtractorThread()
438 {
439 	while (true) {
440 		status_t status;
441 		do {
442 			status = acquire_sem(fExtractorWaitSem);
443 		} while (status == B_INTERRUPTED);
444 
445 		if (status != B_OK) {
446 			// we were asked to quit
447 			return;
448 		}
449 
450 		// Iterate over all streams until they are all filled
451 
452 		int32 streamsFilled;
453 		do {
454 			streamsFilled = 0;
455 
456 			for (int32 stream = 0; stream < fStreamCount; stream++) {
457 				stream_info& info = fStreamInfo[stream];
458 				if (info.status != B_OK) {
459 					streamsFilled++;
460 					continue;
461 				}
462 
463 				BAutolock _(info.chunkCache);
464 
465 				if (!info.chunkCache->SpaceLeft()
466 					|| !info.chunkCache->ReadNextChunk(fReader, info.cookie))
467 					streamsFilled++;
468 			}
469 		} while (streamsFilled < fStreamCount);
470 	}
471 }
472