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