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