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