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