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 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 fReader(NULL), 59 fStreamInfo(NULL), 60 fStreamCount(0) 61 { 62 _Init(source, flags); 63 } 64 65 66 void 67 MediaExtractor::_Init(BDataIO* source, int32 flags) 68 { 69 CALLED(); 70 71 fSource = source; 72 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; 79 } 80 #endif 81 82 fInitStatus = gPluginManager.CreateReader(&fReader, &fStreamCount, 83 &fFileFormat, source); 84 if (fInitStatus != B_OK) 85 return; 86 87 fStreamInfo = new stream_info[fStreamCount]; 88 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)); 101 102 if (fStreamInfo[i].chunkCache->InitCheck() != B_OK) { 103 fInitStatus = B_NO_MEMORY; 104 return; 105 } 106 } 107 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; 118 } 119 120 // get info for all streams 121 for (int32 i = 0; i < fStreamCount; i++) { 122 if (fStreamInfo[i].status != B_OK) 123 continue; 124 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); 134 } 135 } 136 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 143 } 144 145 146 MediaExtractor::~MediaExtractor() 147 { 148 CALLED(); 149 150 // stop the extractor thread, if still running 151 StopProcessing(); 152 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); 158 159 delete fStreamInfo[i].chunkCache; 160 } 161 162 gPluginManager.DestroyReader(fReader); 163 164 delete[] fStreamInfo; 165 // fSource is owned by the BMediaFile 166 } 167 168 169 status_t 170 MediaExtractor::InitCheck() 171 { 172 CALLED(); 173 return fInitStatus; 174 } 175 176 177 BDataIO* 178 MediaExtractor::Source() const 179 { 180 return fSource; 181 } 182 183 184 void 185 MediaExtractor::GetFileFormatInfo(media_file_format* fileFormat) const 186 { 187 CALLED(); 188 *fileFormat = fFileFormat; 189 } 190 191 192 status_t 193 MediaExtractor::GetMetaData(BMessage* _data) const 194 { 195 CALLED(); 196 return fReader->GetMetaData(_data); 197 } 198 199 200 int32 201 MediaExtractor::StreamCount() 202 { 203 CALLED(); 204 return fStreamCount; 205 } 206 207 208 const char* 209 MediaExtractor::Copyright() 210 { 211 return fReader->Copyright(); 212 } 213 214 215 const media_format* 216 MediaExtractor::EncodedFormat(int32 stream) 217 { 218 return &fStreamInfo[stream].encodedFormat; 219 } 220 221 222 int64 223 MediaExtractor::CountFrames(int32 stream) const 224 { 225 CALLED(); 226 if (fStreamInfo[stream].status != B_OK) 227 return 0LL; 228 229 int64 frameCount; 230 bigtime_t duration; 231 media_format format; 232 const void* infoBuffer; 233 size_t infoSize; 234 235 fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration, 236 &format, &infoBuffer, &infoSize); 237 238 return frameCount; 239 } 240 241 242 bigtime_t 243 MediaExtractor::Duration(int32 stream) const 244 { 245 CALLED(); 246 247 if (fStreamInfo[stream].status != B_OK) 248 return 0LL; 249 250 int64 frameCount; 251 bigtime_t duration; 252 media_format format; 253 const void* infoBuffer; 254 size_t infoSize; 255 256 fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration, 257 &format, &infoBuffer, &infoSize); 258 259 return duration; 260 } 261 262 263 status_t 264 MediaExtractor::Seek(int32 stream, uint32 seekTo, int64* _frame, 265 bigtime_t* _time) 266 { 267 CALLED(); 268 269 stream_info& info = fStreamInfo[stream]; 270 if (info.status != B_OK) 271 return info.status; 272 273 BAutolock _(info.chunkCache); 274 275 status_t status = fReader->Seek(info.cookie, seekTo, _frame, _time); 276 if (status != B_OK) 277 return status; 278 279 // clear buffered chunks after seek 280 info.chunkCache->MakeEmpty(); 281 282 return B_OK; 283 } 284 285 286 status_t 287 MediaExtractor::FindKeyFrame(int32 stream, uint32 seekTo, int64* _frame, 288 bigtime_t* _time) const 289 { 290 CALLED(); 291 292 stream_info& info = fStreamInfo[stream]; 293 if (info.status != B_OK) 294 return info.status; 295 296 return fReader->FindKeyFrame(info.cookie, seekTo, _frame, _time); 297 } 298 299 300 status_t 301 MediaExtractor::GetNextChunk(int32 stream, const void** _chunkBuffer, 302 size_t* _chunkSize, media_header* mediaHeader) 303 { 304 stream_info& info = fStreamInfo[stream]; 305 306 if (info.status != B_OK) 307 return info.status; 308 309 #if DISABLE_CHUNK_CACHE 310 return fReader->GetNextChunk(fStreamInfo[stream].cookie, _chunkBuffer, 311 _chunkSize, mediaHeader); 312 #else 313 BAutolock _(info.chunkCache); 314 315 _RecycleLastChunk(info); 316 317 // Retrieve next chunk - read it directly, if the cache is drained 318 chunk_buffer* chunk = info.chunkCache->NextChunk(fReader, info.cookie); 319 320 if (chunk == NULL) 321 return B_NO_MEMORY; 322 323 info.lastChunk = chunk; 324 325 *_chunkBuffer = chunk->buffer; 326 *_chunkSize = chunk->size; 327 *mediaHeader = chunk->header; 328 329 return chunk->status; 330 #endif 331 } 332 333 334 status_t 335 MediaExtractor::CreateDecoder(int32 stream, Decoder** _decoder, 336 media_codec_info* codecInfo) 337 { 338 CALLED(); 339 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; 345 } 346 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)); 357 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; 363 } 364 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; 372 } 373 374 decoder->SetChunkProvider(chunkProvider); 375 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; 383 } 384 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; 391 } 392 393 *_decoder = decoder; 394 return B_OK; 395 } 396 397 398 status_t 399 MediaExtractor::GetStreamMetaData(int32 stream, BMessage* _data) const 400 { 401 const stream_info& info = fStreamInfo[stream]; 402 403 if (info.status != B_OK) 404 return info.status; 405 406 return fReader->GetStreamMetaData(fStreamInfo[stream].cookie, _data); 407 } 408 409 410 void 411 MediaExtractor::StopProcessing() 412 { 413 #if !DISABLE_CHUNK_CACHE 414 if (fExtractorWaitSem > -1) { 415 // terminate extractor thread 416 delete_sem(fExtractorWaitSem); 417 fExtractorWaitSem = -1; 418 419 status_t status; 420 wait_for_thread(fExtractorThread, &status); 421 } 422 #endif 423 } 424 425 426 void 427 MediaExtractor::_RecycleLastChunk(stream_info& info) 428 { 429 if (info.lastChunk != NULL) { 430 info.chunkCache->RecycleChunk(info.lastChunk); 431 info.lastChunk = NULL; 432 } 433 } 434 435 436 status_t 437 MediaExtractor::_ExtractorEntry(void* extractor) 438 { 439 static_cast<MediaExtractor*>(extractor)->_ExtractorThread(); 440 return B_OK; 441 } 442 443 444 void 445 MediaExtractor::_ExtractorThread() 446 { 447 while (true) { 448 status_t status; 449 do { 450 status = acquire_sem(fExtractorWaitSem); 451 } while (status == B_INTERRUPTED); 452 453 if (status != B_OK) { 454 // we were asked to quit 455 return; 456 } 457 458 // Iterate over all streams until they are all filled 459 460 int32 streamsFilled; 461 do { 462 streamsFilled = 0; 463 464 for (int32 stream = 0; stream < fStreamCount; stream++) { 465 stream_info& info = fStreamInfo[stream]; 466 if (info.status != B_OK) { 467 streamsFilled++; 468 continue; 469 } 470 471 BAutolock _(info.chunkCache); 472 473 if (!info.chunkCache->SpaceLeft() 474 || !info.chunkCache->ReadNextChunk(fReader, info.cookie)) 475 streamsFilled++; 476 } 477 } while (streamsFilled < fStreamCount); 478 } 479 } 480