vfs: check userland buffers before reading them.
[haiku.git] / src / kits / media / MediaExtractor.cpp
blob8696279827c3fb2c8520979214fe859352be9bc8
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.
6 * Distributed under the terms of the MIT License.
7 */
10 #include "MediaExtractor.h"
12 #include <new>
13 #include <stdio.h>
14 #include <string.h>
16 #include <Autolock.h>
18 #include "ChunkCache.h"
19 #include "debug.h"
20 #include "PluginManager.h"
23 // should be 0, to disable the chunk cache set it to 1
24 #define DISABLE_CHUNK_CACHE 0
27 static const size_t kMaxCacheBytes = 3 * 1024 * 1024;
30 class MediaExtractorChunkProvider : public ChunkProvider {
31 public:
32 MediaExtractorChunkProvider(MediaExtractor* extractor, int32 stream)
34 fExtractor(extractor),
35 fStream(stream)
39 virtual status_t GetNextChunk(const void** _chunkBuffer, size_t* _chunkSize,
40 media_header *mediaHeader)
42 return fExtractor->GetNextChunk(fStream, _chunkBuffer, _chunkSize,
43 mediaHeader);
46 private:
47 MediaExtractor* fExtractor;
48 int32 fStream;
52 // #pragma mark -
55 MediaExtractor::MediaExtractor(BDataIO* source, int32 flags)
57 fExtractorThread(-1),
58 fReader(NULL),
59 fStreamInfo(NULL),
60 fStreamCount(0)
62 _Init(source, flags);
66 void
67 MediaExtractor::_Init(BDataIO* source, int32 flags)
69 CALLED();
71 fSource = source;
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;
80 #endif
82 fInitStatus = gPluginManager.CreateReader(&fReader, &fStreamCount,
83 &fFileFormat, source);
84 if (fInitStatus != B_OK)
85 return;
87 fStreamInfo = new stream_info[fStreamCount];
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));
102 if (fStreamInfo[i].chunkCache->InitCheck() != B_OK) {
103 fInitStatus = B_NO_MEMORY;
104 return;
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;
120 // get info for all streams
121 for (int32 i = 0; i < fStreamCount; i++) {
122 if (fStreamInfo[i].status != B_OK)
123 continue;
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);
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
146 MediaExtractor::~MediaExtractor()
148 CALLED();
150 // stop the extractor thread, if still running
151 StopProcessing();
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);
159 delete fStreamInfo[i].chunkCache;
162 gPluginManager.DestroyReader(fReader);
164 delete[] fStreamInfo;
165 // fSource is owned by the BMediaFile
169 status_t
170 MediaExtractor::InitCheck()
172 CALLED();
173 return fInitStatus;
177 BDataIO*
178 MediaExtractor::Source() const
180 return fSource;
184 void
185 MediaExtractor::GetFileFormatInfo(media_file_format* fileFormat) const
187 CALLED();
188 *fileFormat = fFileFormat;
192 status_t
193 MediaExtractor::GetMetaData(BMessage* _data) const
195 CALLED();
196 return fReader->GetMetaData(_data);
200 int32
201 MediaExtractor::StreamCount()
203 CALLED();
204 return fStreamCount;
208 const char*
209 MediaExtractor::Copyright()
211 return fReader->Copyright();
215 const media_format*
216 MediaExtractor::EncodedFormat(int32 stream)
218 return &fStreamInfo[stream].encodedFormat;
222 int64
223 MediaExtractor::CountFrames(int32 stream) const
225 CALLED();
226 if (fStreamInfo[stream].status != B_OK)
227 return 0LL;
229 int64 frameCount;
230 bigtime_t duration;
231 media_format format;
232 const void* infoBuffer;
233 size_t infoSize;
235 fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration,
236 &format, &infoBuffer, &infoSize);
238 return frameCount;
242 bigtime_t
243 MediaExtractor::Duration(int32 stream) const
245 CALLED();
247 if (fStreamInfo[stream].status != B_OK)
248 return 0LL;
250 int64 frameCount;
251 bigtime_t duration;
252 media_format format;
253 const void* infoBuffer;
254 size_t infoSize;
256 fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration,
257 &format, &infoBuffer, &infoSize);
259 return duration;
263 status_t
264 MediaExtractor::Seek(int32 stream, uint32 seekTo, int64* _frame,
265 bigtime_t* _time)
267 CALLED();
269 stream_info& info = fStreamInfo[stream];
270 if (info.status != B_OK)
271 return info.status;
273 BAutolock _(info.chunkCache);
275 status_t status = fReader->Seek(info.cookie, seekTo, _frame, _time);
276 if (status != B_OK)
277 return status;
279 // clear buffered chunks after seek
280 info.chunkCache->MakeEmpty();
282 return B_OK;
286 status_t
287 MediaExtractor::FindKeyFrame(int32 stream, uint32 seekTo, int64* _frame,
288 bigtime_t* _time) const
290 CALLED();
292 stream_info& info = fStreamInfo[stream];
293 if (info.status != B_OK)
294 return info.status;
296 return fReader->FindKeyFrame(info.cookie, seekTo, _frame, _time);
300 status_t
301 MediaExtractor::GetNextChunk(int32 stream, const void** _chunkBuffer,
302 size_t* _chunkSize, media_header* mediaHeader)
304 stream_info& info = fStreamInfo[stream];
306 if (info.status != B_OK)
307 return info.status;
309 #if DISABLE_CHUNK_CACHE
310 return fReader->GetNextChunk(fStreamInfo[stream].cookie, _chunkBuffer,
311 _chunkSize, mediaHeader);
312 #else
313 BAutolock _(info.chunkCache);
315 _RecycleLastChunk(info);
317 // Retrieve next chunk - read it directly, if the cache is drained
318 chunk_buffer* chunk = info.chunkCache->NextChunk(fReader, info.cookie);
320 if (chunk == NULL)
321 return B_NO_MEMORY;
323 info.lastChunk = chunk;
325 *_chunkBuffer = chunk->buffer;
326 *_chunkSize = chunk->size;
327 *mediaHeader = chunk->header;
329 return chunk->status;
330 #endif
334 status_t
335 MediaExtractor::CreateDecoder(int32 stream, Decoder** _decoder,
336 media_codec_info* codecInfo)
338 CALLED();
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;
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));
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;
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;
374 decoder->SetChunkProvider(chunkProvider);
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;
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;
393 *_decoder = decoder;
394 return B_OK;
398 status_t
399 MediaExtractor::GetStreamMetaData(int32 stream, BMessage* _data) const
401 const stream_info& info = fStreamInfo[stream];
403 if (info.status != B_OK)
404 return info.status;
406 return fReader->GetStreamMetaData(fStreamInfo[stream].cookie, _data);
410 void
411 MediaExtractor::StopProcessing()
413 #if !DISABLE_CHUNK_CACHE
414 if (fExtractorWaitSem > -1) {
415 // terminate extractor thread
416 delete_sem(fExtractorWaitSem);
417 fExtractorWaitSem = -1;
419 status_t status;
420 wait_for_thread(fExtractorThread, &status);
422 #endif
426 void
427 MediaExtractor::_RecycleLastChunk(stream_info& info)
429 if (info.lastChunk != NULL) {
430 info.chunkCache->RecycleChunk(info.lastChunk);
431 info.lastChunk = NULL;
436 status_t
437 MediaExtractor::_ExtractorEntry(void* extractor)
439 static_cast<MediaExtractor*>(extractor)->_ExtractorThread();
440 return B_OK;
444 void
445 MediaExtractor::_ExtractorThread()
447 while (true) {
448 status_t status;
449 do {
450 status = acquire_sem(fExtractorWaitSem);
451 } while (status == B_INTERRUPTED);
453 if (status != B_OK) {
454 // we were asked to quit
455 return;
458 // Iterate over all streams until they are all filled
460 int32 streamsFilled;
461 do {
462 streamsFilled = 0;
464 for (int32 stream = 0; stream < fStreamCount; stream++) {
465 stream_info& info = fStreamInfo[stream];
466 if (info.status != B_OK) {
467 streamsFilled++;
468 continue;
471 BAutolock _(info.chunkCache);
473 if (!info.chunkCache->SpaceLeft()
474 || !info.chunkCache->ReadNextChunk(fReader, info.cookie))
475 streamsFilled++;
477 } while (streamsFilled < fStreamCount);