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