1/* 2 * Copyright (c) 2005, David McPaul 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without modification, 6 * are permitted provided that the following conditions are met: 7 * 8 * * Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 18 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 19 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 22 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 23 * OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 27#include "MOVParser.h" 28#include "MOVFileReader.h" 29 30#include <DataIO.h> 31#include <SupportKit.h> 32 33#include <iostream> 34 35 36extern AtomBase *getAtom(BPositionIO *pStream); 37 38 39MOVFileReader::MOVFileReader(BPositionIO *pStream) 40{ 41 theStream = pStream; 42 43 // Find Size of Stream, need to rethink this for non seekable streams 44 theStream->Seek(0,SEEK_END); 45 StreamSize = theStream->Position(); 46 47 theStream->Seek(0,SEEK_SET); 48 TotalChildren = 0; 49 50 theMVHDAtom = NULL; 51} 52 53MOVFileReader::~MOVFileReader() 54{ 55 theStream = NULL; 56 theMVHDAtom = NULL; 57} 58 59bool MOVFileReader::IsEndOfData(off_t pPosition) 60{ 61AtomBase *aAtomBase; 62 63 for (uint32 index=0;index<CountChildAtoms('mdat');index++) { 64 aAtomBase = GetChildAtom(uint32('mdat'),index); 65 if ((aAtomBase) && (aAtomBase->getAtomSize() > 8)) { 66 MDATAtom *aMDATAtom = dynamic_cast<MDATAtom *>(aAtomBase); 67 if (pPosition >= aMDATAtom->getAtomOffset() && pPosition <= aMDATAtom->getEOF()) { 68 return false; 69 } 70 } 71 } 72 73 return true; 74} 75 76bool MOVFileReader::IsEndOfFile(off_t pPosition) 77{ 78 return (pPosition >= StreamSize); 79} 80 81bool MOVFileReader::IsEndOfFile() 82{ 83 return (theStream->Position() >= StreamSize); 84} 85 86bool MOVFileReader::AddChild(AtomBase *pChildAtom) 87{ 88 if (pChildAtom) { 89 atomChildren[TotalChildren++] = pChildAtom; 90 return true; 91 } 92 return false; 93} 94 95AtomBase *MOVFileReader::GetChildAtom(uint32 patomType, uint32 offset) 96{ 97 for (uint32 i=0;i<TotalChildren;i++) { 98 if (atomChildren[i]->IsType(patomType)) { 99 // found match, skip if offset non zero. 100 if (offset == 0) { 101 return atomChildren[i]; 102 } else { 103 offset--; 104 } 105 } else { 106 if (atomChildren[i]->IsContainer()) { 107 // search container 108 AtomBase *aAtomBase = (dynamic_cast<AtomContainer *>(atomChildren[i])->GetChildAtom(patomType, offset)); 109 if (aAtomBase) { 110 // found in container 111 return aAtomBase; 112 } 113 // not found 114 } 115 } 116 } 117 return NULL; 118} 119 120uint32 MOVFileReader::CountChildAtoms(uint32 patomType) 121{ 122 uint32 count = 0; 123 124 while (GetChildAtom(patomType, count) != NULL) { 125 count++; 126 } 127 return count; 128} 129 130MVHDAtom *MOVFileReader::getMVHDAtom() 131{ 132AtomBase *aAtomBase; 133 134 if (theMVHDAtom == NULL) { 135 aAtomBase = GetChildAtom(uint32('mvhd')); 136 theMVHDAtom = dynamic_cast<MVHDAtom *>(aAtomBase); 137 } 138 139 // Assert(theMVHDAtom != NULL,"Movie has no movie header atom"); 140 return theMVHDAtom; 141} 142 143uint32 MOVFileReader::getMovieTimeScale() 144{ 145 return getMVHDAtom()->getTimeScale(); 146} 147 148bigtime_t MOVFileReader::getMovieDuration() 149{ 150 return ((bigtime_t(getMVHDAtom()->getDuration()) * 1000000L) / getMovieTimeScale()); 151} 152 153uint32 MOVFileReader::getStreamCount() 154{ 155 // count the number of tracks in the file 156 return (CountChildAtoms(uint32('trak'))); 157} 158 159bigtime_t MOVFileReader::getVideoDuration(uint32 stream_index) 160{ 161AtomBase *aAtomBase; 162 163 aAtomBase = GetChildAtom(uint32('trak'),stream_index); 164 165 if ((aAtomBase) && (dynamic_cast<TRAKAtom *>(aAtomBase)->IsVideo())) { 166 return (dynamic_cast<TRAKAtom *>(aAtomBase)->Duration(1)); 167 } 168 169 return 0; 170} 171 172bigtime_t MOVFileReader::getAudioDuration(uint32 stream_index) 173{ 174AtomBase *aAtomBase; 175 176 aAtomBase = GetChildAtom(uint32('trak'),stream_index); 177 178 if ((aAtomBase) && (dynamic_cast<TRAKAtom *>(aAtomBase)->IsAudio())) { 179 return (dynamic_cast<TRAKAtom *>(aAtomBase)->Duration(1)); 180 } 181 182 return 0; 183} 184 185bigtime_t MOVFileReader::getMaxDuration() 186{ 187AtomBase *aAtomBase; 188int32 video_index,audio_index; 189 video_index = -1; 190 audio_index = -1; 191 192 // find the active video and audio tracks 193 for (uint32 i=0;i<getStreamCount();i++) { 194 aAtomBase = GetChildAtom(uint32('trak'),i); 195 196 if ((aAtomBase) && (dynamic_cast<TRAKAtom *>(aAtomBase)->IsActive())) { 197 if (dynamic_cast<TRAKAtom *>(aAtomBase)->IsAudio()) { 198 audio_index = int32(i); 199 } 200 if (dynamic_cast<TRAKAtom *>(aAtomBase)->IsVideo()) { 201 video_index = int32(i); 202 } 203 } 204 } 205 206 if ((video_index >= 0) && (audio_index >= 0)) { 207 return MAX(getVideoDuration(video_index),getAudioDuration(audio_index)); 208 } 209 if ((video_index < 0) && (audio_index >= 0)) { 210 return getAudioDuration(audio_index); 211 } 212 if ((video_index >= 0) && (audio_index < 0)) { 213 return getVideoDuration(video_index); 214 } 215 216 return 0; 217} 218 219uint32 MOVFileReader::getVideoFrameCount(uint32 stream_index) 220{ 221AtomBase *aAtomBase; 222 223 aAtomBase = GetChildAtom(uint32('trak'),stream_index); 224 225 if ((aAtomBase) && (dynamic_cast<TRAKAtom *>(aAtomBase)->IsVideo())) { 226 227 return dynamic_cast<TRAKAtom *>(aAtomBase)->FrameCount(); 228 } 229 230 return 1; 231} 232 233uint32 MOVFileReader::getAudioFrameCount(uint32 stream_index) 234{ 235 if (IsAudio(stream_index)) { 236 return uint32(((getAudioDuration(stream_index) * AudioFormat(stream_index)->SampleRate) / 1000000L) + 0.5); 237 } 238 239 return 0; 240} 241 242bool MOVFileReader::IsVideo(uint32 stream_index) 243{ 244 // Look for a trak with a vmhd atom 245 246 AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index); 247 248 if (aAtomBase) { 249 return (dynamic_cast<TRAKAtom *>(aAtomBase)->IsVideo()); 250 } 251 252 // No trak 253 return false; 254} 255 256bool MOVFileReader::IsAudio(uint32 stream_index) 257{ 258 // Look for a trak with a smhd atom 259 260 AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index); 261 262 if (aAtomBase) { 263 return (dynamic_cast<TRAKAtom *>(aAtomBase)->IsAudio()); 264 } 265 266 // No trak 267 return false; 268} 269 270uint32 MOVFileReader::getFirstFrameInChunk(uint32 stream_index, uint32 pChunkID) 271{ 272 // Find Track 273 AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index); 274 if (aAtomBase) { 275 TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase); 276 277 return aTrakAtom->getFirstSampleInChunk(pChunkID); 278 } 279 280 return 0; 281} 282 283uint32 MOVFileReader::getNoFramesInChunk(uint32 stream_index, uint32 pChunkID) 284{ 285 // Find Track 286 AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index); 287 if (aAtomBase) { 288 TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase); 289 290 return aTrakAtom->getNoSamplesInChunk(pChunkID); 291 } 292 293 return 0; 294} 295 296uint64 MOVFileReader::getOffsetForFrame(uint32 stream_index, uint32 pFrameNo, uint32 *chunkFrameCount) 297{ 298 // Find Track 299 AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index); 300 if (aAtomBase) { 301 TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase); 302 303 if (pFrameNo < aTrakAtom->FrameCount()) { 304 // Get Sample for Frame 305 uint32 SampleNo = aTrakAtom->getSampleForFrame(pFrameNo); 306 // Get Chunk For Sample and the offset for the frame within that chunk 307 uint32 OffsetInChunk; 308 uint32 ChunkID = aTrakAtom->getChunkForSample(SampleNo, &OffsetInChunk); 309 // Get Offset For Chunk 310 uint64 OffsetNo = aTrakAtom->getOffsetForChunk(ChunkID); 311 312 if (IsAudio(stream_index)) { 313 // For audio we return all frames in Chunk less any offset 314 *chunkFrameCount = aTrakAtom->getNoSamplesInChunk(ChunkID) - OffsetInChunk; 315 } else { 316 // For video we always return 1 frame at a time 317 *chunkFrameCount = 1; 318 } 319 320 if (ChunkID != 0) { 321 uint32 SampleSize; 322 // Adjust the Offset for the Offset in the chunk 323 if (aTrakAtom->IsSingleSampleSize()) { 324 SampleSize = aTrakAtom->getSizeForSample(SampleNo); 325 OffsetNo = OffsetNo + (OffsetInChunk * SampleSize); 326 } else { 327 // This is bad news performance wise 328 for (uint32 i=1;i<=OffsetInChunk;i++) { 329 SampleSize = aTrakAtom->getSizeForSample(SampleNo - i); 330 OffsetNo = OffsetNo + SampleSize; 331 } 332 } 333 } 334 335 return OffsetNo; 336 } 337 } 338 339 return 0; 340} 341 342void MOVFileReader::BuildSuperIndex() 343{ 344AtomBase *aAtomBase; 345 346 for (uint32 stream=0;stream<getStreamCount();stream++) { 347 aAtomBase = GetChildAtom(uint32('trak'),stream); 348 if (aAtomBase) { 349 TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase); 350 for (uint32 chunkid=1;chunkid<=aTrakAtom->getTotalChunks();chunkid++) { 351 theChunkSuperIndex.AddChunkIndex(stream,chunkid,aTrakAtom->getOffsetForChunk(chunkid)); 352 } 353 } 354 } 355 356 // Add end of file to index 357 aAtomBase = GetChildAtom(uint32('mdat'),0); 358 if (aAtomBase) { 359 MDATAtom *aMdatAtom = dynamic_cast<MDATAtom *>(aAtomBase); 360 theChunkSuperIndex.AddChunkIndex(0,0,aMdatAtom->getEOF()); 361 } 362} 363 364status_t MOVFileReader::ParseFile() 365{ 366 AtomBase *aChild; 367 while (IsEndOfFile() == false) { 368 aChild = getAtom(theStream); 369 if (AddChild(aChild)) { 370 aChild->ProcessMetaData(); 371 } 372 } 373 374 // Debug info 375 for (uint32 i=0;i<TotalChildren;i++) { 376 atomChildren[i]->DisplayAtoms(); 377 } 378 379 BuildSuperIndex(); 380 381 return B_OK; 382} 383 384const mov_main_header *MOVFileReader::MovMainHeader() 385{ 386 // Fill In theMainHeader 387// uint32 micro_sec_per_frame; 388// uint32 max_bytes_per_sec; 389// uint32 padding_granularity; 390// uint32 flags; 391// uint32 total_frames; 392// uint32 initial_frames; 393// uint32 streams; 394// uint32 suggested_buffer_size; 395// uint32 width; 396// uint32 height; 397 398 uint32 videoStream = 0; 399 400 theMainHeader.streams = getStreamCount(); 401 theMainHeader.flags = 0; 402 theMainHeader.initial_frames = 0; 403 404 while ( videoStream < theMainHeader.streams ) { 405 if (IsVideo(videoStream) && IsActive(videoStream)) { 406 break; 407 } 408 videoStream++; 409 } 410 411 if (videoStream >= theMainHeader.streams) { 412 theMainHeader.width = 0; 413 theMainHeader.height = 0; 414 theMainHeader.total_frames = 0; 415 theMainHeader.suggested_buffer_size = 0; 416 theMainHeader.micro_sec_per_frame = 0; 417 } else { 418 theMainHeader.width = VideoFormat(videoStream)->width; 419 theMainHeader.height = VideoFormat(videoStream)->height; 420 theMainHeader.total_frames = getVideoFrameCount(videoStream); 421 theMainHeader.suggested_buffer_size = theMainHeader.width * theMainHeader.height * VideoFormat(videoStream)->bit_count / 8; 422 theMainHeader.micro_sec_per_frame = uint32(1000000.0 / VideoFormat(videoStream)->FrameRate); 423 } 424 425 theMainHeader.padding_granularity = 0; 426 theMainHeader.max_bytes_per_sec = 0; 427 428 return &theMainHeader; 429} 430 431const AudioMetaData *MOVFileReader::AudioFormat(uint32 stream_index) 432{ 433 if (IsAudio(stream_index)) { 434 435 AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index); 436 437 if (aAtomBase) { 438 TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase); 439 440 aAtomBase = aTrakAtom->GetChildAtom(uint32('stsd'),0); 441 if (aAtomBase) { 442 STSDAtom *aSTSDAtom = dynamic_cast<STSDAtom *>(aAtomBase); 443 444 // Fill In a wave_format_ex structure 445 SoundDescriptionV1 aSoundDescription = aSTSDAtom->getAsAudio(); 446 447 // Hmm 16bit format 32 bit FourCC. 448 theAudio.compression = aSoundDescription.basefields.DataFormat; 449 450 theAudio.NoOfChannels = aSoundDescription.desc.NoOfChannels; 451 theAudio.SampleSize = aSoundDescription.desc.SampleSize; 452 theAudio.SampleRate = aSoundDescription.desc.SampleRate / 65536; // Convert from fixed point decimal to float 453 454 if (aSoundDescription.bytesPerFrame == 0) { 455 // XXX probably not right 456 theAudio.FrameSize = aSoundDescription.desc.SampleSize / 8; 457 } else { 458 theAudio.FrameSize = aSoundDescription.bytesPerFrame; 459 } 460 461 if (aSoundDescription.bytesPerPacket == 0) { 462 theAudio.BufferSize = uint32((theAudio.SampleSize * theAudio.NoOfChannels * theAudio.FrameSize) / 8); 463 } else { 464 theAudio.BufferSize = aSoundDescription.bytesPerPacket; 465 } 466 467 theAudio.BitRate = theAudio.SampleSize * theAudio.NoOfChannels * theAudio.SampleRate; 468 469 theAudio.theVOL = aSoundDescription.theVOL; 470 theAudio.VOLSize = aSoundDescription.VOLSize; 471 472// // Do we have a wave structure 473// if ((aSoundDescription.samplesPerPacket == 0) && (aSoundDescription.theWaveFormat.format_tag != 0)) { 474// theAudio.PacketSize = aSoundDescription.theWaveFormat.frames_per_sec; 475// } else { 476// // no wave structure need to calculate these 477// theAudio.PacketSize = uint32((theAudio.SampleRate * theAudio.NoOfChannels * theAudio.SampleSize) / 8); 478// } 479 480 return &theAudio; 481 } 482 } 483 } 484 485 return NULL; 486} 487 488const VideoMetaData *MOVFileReader::VideoFormat(uint32 stream_index) 489{ 490 if (IsVideo(stream_index)) { 491 492 AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index); 493 494 if (aAtomBase) { 495 TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase); 496 497 aAtomBase = aTrakAtom->GetChildAtom(uint32('stsd'),0); 498 499 if (aAtomBase) { 500 STSDAtom *aSTSDAtom = dynamic_cast<STSDAtom *>(aAtomBase); 501 502 VideoDescriptionV0 aVideoDescriptionV0 = aSTSDAtom->getAsVideo(); 503 504 theVideo.width = aVideoDescriptionV0.desc.Width; 505 theVideo.height = aVideoDescriptionV0.desc.Height; 506 theVideo.size = aVideoDescriptionV0.desc.DataSize; 507 theVideo.planes = aVideoDescriptionV0.desc.Depth; 508 theVideo.bit_count = aVideoDescriptionV0.desc.Depth; 509 theVideo.compression = aVideoDescriptionV0.basefields.DataFormat; 510 theVideo.image_size = aVideoDescriptionV0.desc.Height * aVideoDescriptionV0.desc.Width; 511 theVideo.HorizontalResolution = aVideoDescriptionV0.desc.HorizontalResolution; 512 theVideo.VerticalResolution = aVideoDescriptionV0.desc.VerticalResolution; 513 514 theVideo.theVOL = aVideoDescriptionV0.theVOL; 515 theVideo.VOLSize = aVideoDescriptionV0.VOLSize; 516 517 aAtomBase = aTrakAtom->GetChildAtom(uint32('stts'),0); 518 if (aAtomBase) { 519 STTSAtom *aSTTSAtom = dynamic_cast<STTSAtom *>(aAtomBase); 520 521 theVideo.FrameRate = ((aSTTSAtom->getSUMCounts() * 1000000.0L) / aTrakAtom->Duration(1)); 522 523 return &theVideo; 524 } 525 } 526 } 527 } 528 529 return NULL; 530} 531 532const mov_stream_header *MOVFileReader::StreamFormat(uint32 stream_index) 533{ 534 // Fill In a Stream Header 535 theStreamHeader.length = 0; 536 537 if (IsActive(stream_index) == false) { 538 return NULL; 539 } 540 541 if (IsVideo(stream_index)) { 542 theStreamHeader.rate = uint32(1000000L*VideoFormat(stream_index)->FrameRate); 543 theStreamHeader.scale = 1000000L; 544 theStreamHeader.length = getVideoFrameCount(stream_index); 545 } 546 547 if (IsAudio(stream_index)) { 548 theStreamHeader.rate = uint32(AudioFormat(stream_index)->SampleRate); 549 theStreamHeader.scale = 1; 550 theStreamHeader.length = getAudioFrameCount(stream_index); 551 theStreamHeader.sample_size = AudioFormat(stream_index)->SampleSize; 552 theStreamHeader.suggested_buffer_size = theStreamHeader.rate * theStreamHeader.sample_size; 553 } 554 555 return &theStreamHeader; 556} 557 558 559uint32 560MOVFileReader::getChunkSize(uint32 streamIndex, uint32 frameNo) 561{ 562 AtomBase *aAtomBase = GetChildAtom(uint32('trak'), streamIndex); 563 if (aAtomBase == NULL) 564 return 0; 565 566 TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase); 567 568 if (frameNo < aTrakAtom->FrameCount()) { 569 uint32 SampleNo = aTrakAtom->getSampleForFrame(frameNo); 570 571 if (IsAudio(streamIndex)) { 572 // Audio Chunk Size is all samples in the chunk 573 // Get Chunk For Sample and the offset for the frame within that chunk 574 uint32 OffsetInChunk; 575 uint32 ChunkID = aTrakAtom->getChunkForSample(SampleNo, &OffsetInChunk); 576 577 off_t chunkStart = aTrakAtom->getOffsetForChunk(ChunkID); 578 return theChunkSuperIndex.getChunkSize(streamIndex, ChunkID, chunkStart); 579 } 580 581 if (IsVideo(streamIndex)) { 582 // We read video in Sample by Sample so chunk size is Sample Size 583 return aTrakAtom->getSizeForSample(SampleNo); 584 } 585 } 586 587 return 0; 588} 589 590 591bool 592MOVFileReader::IsKeyFrame(uint32 stream_index, uint32 pFrameNo) 593{ 594 AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index); 595 if (aAtomBase) { 596 TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase); 597 598 uint32 SampleNo = aTrakAtom->getSampleForFrame(pFrameNo); 599 return aTrakAtom->IsSyncSample(SampleNo); 600 } 601 602 return false; 603} 604 605bool MOVFileReader::GetNextChunkInfo(uint32 stream_index, uint32 pFrameNo, off_t *start, uint32 *size, bool *keyframe, uint32 *chunkFrameCount) 606{ 607 *start = getOffsetForFrame(stream_index, pFrameNo, chunkFrameCount); 608 *size = getChunkSize(stream_index, pFrameNo); 609 610 if ((*start > 0) && (*size > 0)) { 611 *keyframe = IsKeyFrame(stream_index, pFrameNo); 612 } 613 614 printf("(%ld) frame %ld start %Ld, size %ld, eof %s, eod %s\n",stream_index, pFrameNo,*start,*size, IsEndOfFile(*start + *size) ? "true" : "false", IsEndOfData(*start + *size) ? "true" : "false"); 615 616 // TODO need a better method for detecting End of Data 617 if (IsEndOfFile(*start + *size) || IsEndOfData(*start + *size)) { 618 return false; 619 } 620 621 return *start > 0 && *size > 0; 622} 623 624bool MOVFileReader::IsActive(uint32 stream_index) 625{ 626 AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index); 627 if (aAtomBase) { 628 TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase); 629 return aTrakAtom->IsActive(); 630 } 631 632 return false; 633} 634 635/* static */ 636bool MOVFileReader::IsSupported(BPositionIO *source) 637{ 638 // MOV files normally do not have ftyp atoms 639 // But when they do we need to check if they have a qt brand 640 // No qt brand means the file is likely to be a MP4 file 641 642 AtomBase *aAtom; 643 644 aAtom = getAtom(source); 645 646 if (aAtom) { 647 if (dynamic_cast<FTYPAtom *>(aAtom)) { 648 printf("ftyp atom found checking for qt brand\n"); 649 aAtom->ProcessMetaData(); 650 // MP4 files start with a ftyp atom that contains an isom brand 651 // MOV files with a ftyp atom contain the qt brand 652 return dynamic_cast<FTYPAtom *>(aAtom)->HasBrand(uint32('qt ')); 653 } else { 654 // no ftyp atom so just see if we know the atom we have 655 return (aAtom->IsKnown()); 656 } 657 } 658 659 return false; 660} 661