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 fStreamer(NULL), 60 fStreamInfo(NULL), 61 fStreamCount(0) 62 { 63 _Init(source, flags); 64 } 65 66 67 MediaExtractor::MediaExtractor(const BUrl& url, int32 flags) 68 : 69 fExtractorThread(-1), 70 fReader(NULL), 71 fStreamer(NULL), 72 fStreamInfo(NULL), 73 fStreamCount(0) 74 { 75 BDataIO* source = NULL; 76 fInitStatus = gPluginManager.CreateStreamer(&fStreamer, url, &source); 77 if (fInitStatus != B_OK) 78 return; 79 80 _Init(source, flags); 81 } 82 83 84 void 85 MediaExtractor::_Init(BDataIO* source, int32 flags) 86 { 87 CALLED(); 88 89 fSource = source; 90 91 #if !DISABLE_CHUNK_CACHE 92 // start extractor thread 93 fExtractorWaitSem = create_sem(1, "media extractor thread sem"); 94 if (fExtractorWaitSem < 0) { 95 fInitStatus = fExtractorWaitSem; 96 return; 97 } 98 #endif 99 100 fInitStatus = gPluginManager.CreateReader(&fReader, &fStreamCount, 101 &fFileFormat, source); 102 if (fInitStatus != B_OK) 103 return; 104 105 fStreamInfo = new stream_info[fStreamCount]; 106 107 // initialize stream infos 108 for (int32 i = 0; i < fStreamCount; i++) { 109 fStreamInfo[i].status = B_OK; 110 fStreamInfo[i].cookie = 0; 111 fStreamInfo[i].hasCookie = false; 112 fStreamInfo[i].infoBuffer = 0; 113 fStreamInfo[i].infoBufferSize = 0; 114 fStreamInfo[i].chunkCache 115 = new ChunkCache(fExtractorWaitSem, kMaxCacheBytes); 116 fStreamInfo[i].lastChunk = NULL; 117 memset(&fStreamInfo[i].encodedFormat, 0, 118 sizeof(fStreamInfo[i].encodedFormat)); 119 120 if (fStreamInfo[i].chunkCache->InitCheck() != B_OK) { 121 fInitStatus = B_NO_MEMORY; 122 return; 123 } 124 } 125 126 // create all stream cookies 127 for (int32 i = 0; i < fStreamCount; i++) { 128 if (fReader->AllocateCookie(i, &fStreamInfo[i].cookie) != B_OK) { 129 fStreamInfo[i].cookie = 0; 130 fStreamInfo[i].hasCookie = false; 131 fStreamInfo[i].status = B_ERROR; 132 ERROR("MediaExtractor::MediaExtractor: AllocateCookie for stream %" 133 B_PRId32 " failed\n", i); 134 } else 135 fStreamInfo[i].hasCookie = true; 136 } 137 138 // get info for all streams 139 for (int32 i = 0; i < fStreamCount; i++) { 140 if (fStreamInfo[i].status != B_OK) 141 continue; 142 143 int64 frameCount; 144 bigtime_t duration; 145 if (fReader->GetStreamInfo(fStreamInfo[i].cookie, &frameCount, 146 &duration, &fStreamInfo[i].encodedFormat, 147 &fStreamInfo[i].infoBuffer, &fStreamInfo[i].infoBufferSize) 148 != B_OK) { 149 fStreamInfo[i].status = B_ERROR; 150 ERROR("MediaExtractor::MediaExtractor: GetStreamInfo for " 151 "stream %" B_PRId32 " failed\n", i); 152 } 153 } 154 155 #if !DISABLE_CHUNK_CACHE 156 // start extractor thread 157 fExtractorThread = spawn_thread(_ExtractorEntry, "media extractor thread", 158 B_NORMAL_PRIORITY + 4, this); 159 resume_thread(fExtractorThread); 160 #endif 161 } 162 163 164 MediaExtractor::~MediaExtractor() 165 { 166 CALLED(); 167 168 #if !DISABLE_CHUNK_CACHE 169 // terminate extractor thread 170 delete_sem(fExtractorWaitSem); 171 172 status_t status; 173 wait_for_thread(fExtractorThread, &status); 174 #endif 175 176 // free all stream cookies 177 // and chunk caches 178 for (int32 i = 0; i < fStreamCount; i++) { 179 if (fStreamInfo[i].hasCookie) 180 fReader->FreeCookie(fStreamInfo[i].cookie); 181 182 delete fStreamInfo[i].chunkCache; 183 } 184 185 gPluginManager.DestroyReader(fReader); 186 187 if (fStreamer != NULL) 188 gPluginManager.DestroyStreamer(fStreamer); 189 190 delete[] fStreamInfo; 191 // fSource is owned by the BMediaFile 192 } 193 194 195 status_t 196 MediaExtractor::InitCheck() 197 { 198 CALLED(); 199 return fInitStatus; 200 } 201 202 203 BDataIO* 204 MediaExtractor::Source() const 205 { 206 return fSource; 207 } 208 209 210 void 211 MediaExtractor::GetFileFormatInfo(media_file_format* fileFormat) const 212 { 213 CALLED(); 214 *fileFormat = fFileFormat; 215 } 216 217 218 status_t 219 MediaExtractor::GetMetaData(BMessage* _data) const 220 { 221 CALLED(); 222 return fReader->GetMetaData(_data); 223 } 224 225 226 int32 227 MediaExtractor::StreamCount() 228 { 229 CALLED(); 230 return fStreamCount; 231 } 232 233 234 const char* 235 MediaExtractor::Copyright() 236 { 237 return fReader->Copyright(); 238 } 239 240 241 const media_format* 242 MediaExtractor::EncodedFormat(int32 stream) 243 { 244 return &fStreamInfo[stream].encodedFormat; 245 } 246 247 248 int64 249 MediaExtractor::CountFrames(int32 stream) const 250 { 251 CALLED(); 252 if (fStreamInfo[stream].status != B_OK) 253 return 0LL; 254 255 int64 frameCount; 256 bigtime_t duration; 257 media_format format; 258 const void* infoBuffer; 259 size_t infoSize; 260 261 fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration, 262 &format, &infoBuffer, &infoSize); 263 264 return frameCount; 265 } 266 267 268 bigtime_t 269 MediaExtractor::Duration(int32 stream) const 270 { 271 CALLED(); 272 273 if (fStreamInfo[stream].status != B_OK) 274 return 0LL; 275 276 int64 frameCount; 277 bigtime_t duration; 278 media_format format; 279 const void* infoBuffer; 280 size_t infoSize; 281 282 fReader->GetStreamInfo(fStreamInfo[stream].cookie, &frameCount, &duration, 283 &format, &infoBuffer, &infoSize); 284 285 return duration; 286 } 287 288 289 status_t 290 MediaExtractor::Seek(int32 stream, uint32 seekTo, int64* _frame, 291 bigtime_t* _time) 292 { 293 CALLED(); 294 295 stream_info& info = fStreamInfo[stream]; 296 if (info.status != B_OK) 297 return info.status; 298 299 BAutolock _(info.chunkCache); 300 301 status_t status = fReader->Seek(info.cookie, seekTo, _frame, _time); 302 if (status != B_OK) 303 return status; 304 305 // clear buffered chunks after seek 306 info.chunkCache->MakeEmpty(); 307 308 return B_OK; 309 } 310 311 312 status_t 313 MediaExtractor::FindKeyFrame(int32 stream, uint32 seekTo, int64* _frame, 314 bigtime_t* _time) const 315 { 316 CALLED(); 317 318 stream_info& info = fStreamInfo[stream]; 319 if (info.status != B_OK) 320 return info.status; 321 322 return fReader->FindKeyFrame(info.cookie, seekTo, _frame, _time); 323 } 324 325 326 status_t 327 MediaExtractor::GetNextChunk(int32 stream, const void** _chunkBuffer, 328 size_t* _chunkSize, media_header* mediaHeader) 329 { 330 stream_info& info = fStreamInfo[stream]; 331 332 if (info.status != B_OK) 333 return info.status; 334 335 #if DISABLE_CHUNK_CACHE 336 return fReader->GetNextChunk(fStreamInfo[stream].cookie, _chunkBuffer, 337 _chunkSize, mediaHeader); 338 #else 339 BAutolock _(info.chunkCache); 340 341 _RecycleLastChunk(info); 342 343 // Retrieve next chunk - read it directly, if the cache is drained 344 chunk_buffer* chunk = info.chunkCache->NextChunk(fReader, info.cookie); 345 346 if (chunk == NULL) 347 return B_NO_MEMORY; 348 349 info.lastChunk = chunk; 350 351 *_chunkBuffer = chunk->buffer; 352 *_chunkSize = chunk->size; 353 *mediaHeader = chunk->header; 354 355 return chunk->status; 356 #endif 357 } 358 359 360 status_t 361 MediaExtractor::CreateDecoder(int32 stream, Decoder** _decoder, 362 media_codec_info* codecInfo) 363 { 364 CALLED(); 365 366 status_t status = fStreamInfo[stream].status; 367 if (status != B_OK) { 368 ERROR("MediaExtractor::CreateDecoder can't create decoder for " 369 "stream %" B_PRId32 ": %s\n", stream, strerror(status)); 370 return status; 371 } 372 373 // TODO: Here we should work out a way so that if there is a setup 374 // failure we can try the next decoder 375 Decoder* decoder; 376 status = gPluginManager.CreateDecoder(&decoder, 377 fStreamInfo[stream].encodedFormat); 378 if (status != B_OK) { 379 #if DEBUG 380 char formatString[256]; 381 string_for_format(fStreamInfo[stream].encodedFormat, formatString, 382 sizeof(formatString)); 383 384 ERROR("MediaExtractor::CreateDecoder gPluginManager.CreateDecoder " 385 "failed for stream %" B_PRId32 ", format: %s: %s\n", stream, 386 formatString, strerror(status)); 387 #endif 388 return status; 389 } 390 391 ChunkProvider* chunkProvider 392 = new(std::nothrow) MediaExtractorChunkProvider(this, stream); 393 if (chunkProvider == NULL) { 394 gPluginManager.DestroyDecoder(decoder); 395 ERROR("MediaExtractor::CreateDecoder can't create chunk provider " 396 "for stream %" B_PRId32 "\n", stream); 397 return B_NO_MEMORY; 398 } 399 400 decoder->SetChunkProvider(chunkProvider); 401 402 status = decoder->Setup(&fStreamInfo[stream].encodedFormat, 403 fStreamInfo[stream].infoBuffer, fStreamInfo[stream].infoBufferSize); 404 if (status != B_OK) { 405 gPluginManager.DestroyDecoder(decoder); 406 ERROR("MediaExtractor::CreateDecoder Setup failed for stream %" B_PRId32 407 ": %s\n", stream, strerror(status)); 408 return status; 409 } 410 411 status = gPluginManager.GetDecoderInfo(decoder, codecInfo); 412 if (status != B_OK) { 413 gPluginManager.DestroyDecoder(decoder); 414 ERROR("MediaExtractor::CreateDecoder GetCodecInfo failed for stream %" 415 B_PRId32 ": %s\n", stream, strerror(status)); 416 return status; 417 } 418 419 *_decoder = decoder; 420 return B_OK; 421 } 422 423 424 status_t 425 MediaExtractor::GetStreamMetaData(int32 stream, BMessage* _data) const 426 { 427 const stream_info& info = fStreamInfo[stream]; 428 429 if (info.status != B_OK) 430 return info.status; 431 432 return fReader->GetStreamMetaData(fStreamInfo[stream].cookie, _data); 433 } 434 435 436 void 437 MediaExtractor::_RecycleLastChunk(stream_info& info) 438 { 439 if (info.lastChunk != NULL) { 440 info.chunkCache->RecycleChunk(info.lastChunk); 441 info.lastChunk = NULL; 442 } 443 } 444 445 446 status_t 447 MediaExtractor::_ExtractorEntry(void* extractor) 448 { 449 static_cast<MediaExtractor*>(extractor)->_ExtractorThread(); 450 return B_OK; 451 } 452 453 454 void 455 MediaExtractor::_ExtractorThread() 456 { 457 while (true) { 458 status_t status; 459 do { 460 status = acquire_sem(fExtractorWaitSem); 461 } while (status == B_INTERRUPTED); 462 463 if (status != B_OK) { 464 // we were asked to quit 465 return; 466 } 467 468 // Iterate over all streams until they are all filled 469 470 int32 streamsFilled; 471 do { 472 streamsFilled = 0; 473 474 for (int32 stream = 0; stream < fStreamCount; stream++) { 475 stream_info& info = fStreamInfo[stream]; 476 if (info.status != B_OK) { 477 streamsFilled++; 478 continue; 479 } 480 481 BAutolock _(info.chunkCache); 482 483 if (!info.chunkCache->SpaceLeft() 484 || !info.chunkCache->ReadNextChunk(fReader, info.cookie)) 485 streamsFilled++; 486 } 487 } while (streamsFilled < fStreamCount); 488 } 489 } 490