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