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