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 "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 27static const size_t kMaxCacheBytes = 3 * 1024 * 1024; 28 29 30class MediaExtractorChunkProvider : public ChunkProvider { 31public: 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 46private: 47 MediaExtractor* fExtractor; 48 int32 fStream; 49}; 50 51 52// #pragma mark - 53 54 55MediaExtractor::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 B_PRId32 " 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 %" B_PRId32 " 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 138MediaExtractor::~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 166status_t 167MediaExtractor::InitCheck() 168{ 169 CALLED(); 170 return fInitStatus; 171} 172 173 174void 175MediaExtractor::GetFileFormatInfo(media_file_format* fileFormat) const 176{ 177 CALLED(); 178 *fileFormat = fFileFormat; 179} 180 181 182status_t 183MediaExtractor::GetMetaData(BMessage* _data) const 184{ 185 CALLED(); 186 return fReader->GetMetaData(_data); 187} 188 189 190int32 191MediaExtractor::StreamCount() 192{ 193 CALLED(); 194 return fStreamCount; 195} 196 197 198const char* 199MediaExtractor::Copyright() 200{ 201 return fReader->Copyright(); 202} 203 204 205const media_format* 206MediaExtractor::EncodedFormat(int32 stream) 207{ 208 return &fStreamInfo[stream].encodedFormat; 209} 210 211 212int64 213MediaExtractor::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 232bigtime_t 233MediaExtractor::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 253status_t 254MediaExtractor::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 276status_t 277MediaExtractor::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 290status_t 291MediaExtractor::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 324status_t 325MediaExtractor::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 %" B_PRId32 ": %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 %" B_PRId32 ", format: %s: %s\n", stream, 350 formatString, 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 %" B_PRId32 "\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 %" B_PRId32 371 ": %s\n", 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 B_PRId32 ": %s\n", stream, strerror(status)); 380 return status; 381 } 382 383 *_decoder = decoder; 384 return B_OK; 385} 386 387 388status_t 389MediaExtractor::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 400void 401MediaExtractor::_RecycleLastChunk(stream_info& info) 402{ 403 if (info.lastChunk != NULL) { 404 info.chunkCache->RecycleChunk(info.lastChunk); 405 info.lastChunk = NULL; 406 } 407} 408 409 410status_t 411MediaExtractor::_ExtractorEntry(void* extractor) 412{ 413 static_cast<MediaExtractor*>(extractor)->_ExtractorThread(); 414 return B_OK; 415} 416 417 418void 419MediaExtractor::_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