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 #include <InterfacePrivate.h> 18 19 #include "ChunkCache.h" 20 #include "MediaDebug.h" 21 #include "MediaMisc.h" 22 #include "PluginManager.h" 23 24 25 // should be 0, to disable the chunk cache set it to 1 26 #define DISABLE_CHUNK_CACHE 0 27 28 29 class MediaExtractorChunkProvider : public ChunkProvider { 30 public: 31 MediaExtractorChunkProvider(MediaExtractor* extractor, int32 stream) 32 : 33 fExtractor(extractor), 34 fStream(stream) 35 { 36 } 37 38 virtual status_t GetNextChunk(const void** _chunkBuffer, size_t* _chunkSize, 39 media_header *mediaHeader) 40 { 41 return fExtractor->GetNextChunk(fStream, _chunkBuffer, _chunkSize, 42 mediaHeader); 43 } 44 45 private: 46 MediaExtractor* fExtractor; 47 int32 fStream; 48 }; 49 50 51 // #pragma mark - 52 53 54 MediaExtractor::MediaExtractor(BDataIO* source, int32 flags) 55 : 56 fExtractorThread(-1), 57 fReader(NULL), 58 fStreamInfo(NULL), 59 fStreamCount(0) 60 { 61 _Init(source, flags); 62 } 63 64 65 void 66 MediaExtractor::_Init(BDataIO* source, int32 flags) 67 { 68 CALLED(); 69 70 fSource = source; 71 72 #if !DISABLE_CHUNK_CACHE 73 // start extractor thread 74 fExtractorWaitSem = create_sem(1, "media extractor thread sem"); 75 if (fExtractorWaitSem < 0) { 76 fInitStatus = fExtractorWaitSem; 77 return; 78 } 79 #endif 80 81 fInitStatus = gPluginManager.CreateReader(&fReader, &fStreamCount, 82 &fFileFormat, source); 83 if (fInitStatus != B_OK) 84 return; 85 86 fStreamInfo = new stream_info[fStreamCount]; 87 88 // initialize stream infos 89 for (int32 i = 0; i < fStreamCount; i++) { 90 fStreamInfo[i].status = B_OK; 91 fStreamInfo[i].cookie = 0; 92 fStreamInfo[i].hasCookie = false; 93 fStreamInfo[i].infoBuffer = 0; 94 fStreamInfo[i].infoBufferSize = 0; 95 fStreamInfo[i].lastChunk = NULL; 96 fStreamInfo[i].chunkCache = NULL; 97 fStreamInfo[i].encodedFormat.Clear(); 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 #if !DISABLE_CHUNK_CACHE 129 // Allocate our ChunkCache 130 size_t chunkCacheMaxBytes = _CalculateChunkBuffer(i); 131 fStreamInfo[i].chunkCache 132 = new ChunkCache(fExtractorWaitSem, chunkCacheMaxBytes); 133 if (fStreamInfo[i].chunkCache->InitCheck() != B_OK) { 134 fInitStatus = B_NO_MEMORY; 135 return; 136 } 137 #endif 138 } 139 140 #if !DISABLE_CHUNK_CACHE 141 // start extractor thread 142 fExtractorThread = spawn_thread(_ExtractorEntry, "media extractor thread", 143 B_NORMAL_PRIORITY + 4, this); 144 resume_thread(fExtractorThread); 145 #endif 146 } 147 148 149 MediaExtractor::~MediaExtractor() 150 { 151 CALLED(); 152 153 // stop the extractor thread, if still running 154 StopProcessing(); 155 156 // free all stream cookies 157 // and chunk caches 158 for (int32 i = 0; i < fStreamCount; i++) { 159 if (fStreamInfo[i].hasCookie) 160 fReader->FreeCookie(fStreamInfo[i].cookie); 161 162 delete fStreamInfo[i].chunkCache; 163 } 164 165 gPluginManager.DestroyReader(fReader); 166 167 delete[] fStreamInfo; 168 // fSource is owned by the BMediaFile 169 } 170 171 172 status_t 173 MediaExtractor::InitCheck() 174 { 175 CALLED(); 176 return fInitStatus; 177 } 178 179 180 void 181 MediaExtractor::GetFileFormatInfo(media_file_format* fileFormat) const 182 { 183 CALLED(); 184 *fileFormat = fFileFormat; 185 } 186 187 188 status_t 189 MediaExtractor::GetMetaData(BMessage* _data) const 190 { 191 CALLED(); 192 return fReader->GetMetaData(_data); 193 } 194 195 196 int32 197 MediaExtractor::StreamCount() 198 { 199 CALLED(); 200 return fStreamCount; 201 } 202 203 204 const char* 205 MediaExtractor::Copyright() 206 { 207 return fReader->Copyright(); 208 } 209 210 211 const media_format* 212 MediaExtractor::EncodedFormat(int32 stream) 213 { 214 return &fStreamInfo[stream].encodedFormat; 215 } 216 217 218 int64 219 MediaExtractor::CountFrames(int32 stream) const 220 { 221 CALLED(); 222 if (fStreamInfo[stream].status != B_OK) 223 return 0LL; 224 225 int64 frameCount; 226 bigtime_t duration; 227 media_format format; 228 const void* infoBuffer; 229 size_t infoSize; 230 231 fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration, 232 &format, &infoBuffer, &infoSize); 233 234 return frameCount; 235 } 236 237 238 bigtime_t 239 MediaExtractor::Duration(int32 stream) const 240 { 241 CALLED(); 242 243 if (fStreamInfo[stream].status != B_OK) 244 return 0LL; 245 246 int64 frameCount; 247 bigtime_t duration; 248 media_format format; 249 const void* infoBuffer; 250 size_t infoSize; 251 252 fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration, 253 &format, &infoBuffer, &infoSize); 254 255 return duration; 256 } 257 258 259 status_t 260 MediaExtractor::Seek(int32 stream, uint32 seekTo, int64* _frame, 261 bigtime_t* _time) 262 { 263 CALLED(); 264 265 stream_info& info = fStreamInfo[stream]; 266 if (info.status != B_OK) 267 return info.status; 268 269 #if !DISABLE_CHUNK_CACHE 270 BAutolock _(info.chunkCache); 271 #endif 272 273 status_t status = fReader->Seek(info.cookie, seekTo, _frame, _time); 274 if (status != B_OK) 275 return status; 276 277 #if !DISABLE_CHUNK_CACHE 278 // clear buffered chunks after seek 279 info.chunkCache->MakeEmpty(); 280 #endif 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 size_t 445 MediaExtractor::_CalculateChunkBuffer(int32 stream) 446 { 447 // WARNING: magic 448 // Your A/V may skip frames, chunks or not play at all if the cache size 449 // is insufficient. Unfortunately there's currently no safe way to 450 // calculate it. 451 452 size_t cacheSize = 3 * 1024 * 1024; 453 454 const media_format* format = EncodedFormat(stream); 455 if (format->IsVideo()) { 456 // For video, have space for at least two frames 457 int32 rowSize = BPrivate::get_bytes_per_row(format->ColorSpace(), 458 format->Width()); 459 if (rowSize > 0) { 460 cacheSize = max_c(cacheSize, rowSize * format->Height() * 2); 461 } 462 } 463 return ROUND_UP_TO_PAGE(cacheSize); 464 } 465 466 467 void 468 MediaExtractor::_ExtractorThread() 469 { 470 while (true) { 471 status_t status; 472 do { 473 status = acquire_sem(fExtractorWaitSem); 474 } while (status == B_INTERRUPTED); 475 476 if (status != B_OK) { 477 // we were asked to quit 478 return; 479 } 480 481 // Iterate over all streams until they are all filled 482 483 int32 streamsFilled; 484 do { 485 streamsFilled = 0; 486 487 for (int32 stream = 0; stream < fStreamCount; stream++) { 488 stream_info& info = fStreamInfo[stream]; 489 if (info.status != B_OK) { 490 streamsFilled++; 491 continue; 492 } 493 494 BAutolock _(info.chunkCache); 495 496 if (!info.chunkCache->SpaceLeft() 497 || !info.chunkCache->ReadNextChunk(fReader, info.cookie)) 498 streamsFilled++; 499 } 500 } while (streamsFilled < fStreamCount); 501 } 502 } 503