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