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