2 * Copyright (c) 2005, David McPaul
5 * Redistribution and use in source and binary forms, with or without modification,
6 * are permitted provided that the following conditions are met:
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.
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.
27 #include "MP4Parser.h"
28 #include "MP4FileReader.h"
31 #include <SupportKit.h>
36 extern AtomBase
*GetAtom(BPositionIO
*pStream
);
39 MP4FileReader::MP4FileReader(BPositionIO
*pStream
)
43 // Find Size of Stream, need to rethink this for non seekable streams
44 theStream
->Seek(0,SEEK_END
);
45 StreamSize
= theStream
->Position();
46 theStream
->Seek(0,SEEK_SET
);
53 MP4FileReader::~MP4FileReader()
61 MP4FileReader::IsEndOfData(off_t pPosition
)
65 // check all mdat atoms to make sure pPosition is within one of them
66 for (uint32 index
=0; index
< CountChildAtoms('mdat'); index
++) {
67 aAtomBase
= GetChildAtom(uint32('mdat'),index
);
68 if ((aAtomBase
) && (aAtomBase
->GetAtomSize() > 8)) {
69 MDATAtom
*aMDATAtom
= dynamic_cast<MDATAtom
*>(aAtomBase
);
70 if (pPosition
>= aMDATAtom
->GetAtomOffset() && pPosition
<= aMDATAtom
->GetEOF()) {
81 MP4FileReader::IsEndOfFile(off_t position
)
83 return (position
>= StreamSize
);
88 MP4FileReader::IsEndOfFile()
90 return theStream
->Position() >= StreamSize
;
95 MP4FileReader::AddChild(AtomBase
*childAtom
)
98 atomChildren
.push_back(childAtom
);
107 MP4FileReader::GetChildAtom(uint32 patomType
, uint32 offset
)
109 for (uint32 i
= 0; i
< TotalChildren
; i
++) {
110 if (atomChildren
[i
]->IsType(patomType
)) {
111 // found match, skip if offset non zero.
113 return atomChildren
[i
];
118 if (atomChildren
[i
]->IsContainer()) {
120 AtomBase
*aAtomBase
= (dynamic_cast<AtomContainer
*>(atomChildren
[i
])->GetChildAtom(patomType
, offset
));
122 // found in container
134 MP4FileReader::CountChildAtoms(uint32 patomType
)
138 while (GetChildAtom(patomType
, count
) != NULL
) {
145 MP4FileReader::GetTrack(uint32 streamIndex
)
147 if (tracks
[streamIndex
] == NULL
) {
148 AtomBase
*aAtomBase
= GetChildAtom(uint32('trak'), streamIndex
);
150 tracks
[streamIndex
] = (dynamic_cast<TRAKAtom
*>(aAtomBase
));
154 if (tracks
[streamIndex
] != NULL
) {
155 return tracks
[streamIndex
];
159 char msg
[100]; sprintf(msg
, "Bad Stream Index %ld\n", streamIndex
);
165 return NULL
; // Never to happen
170 MP4FileReader::GetMVHDAtom()
174 if (theMVHDAtom
== NULL
) {
175 aAtomBase
= GetChildAtom(uint32('mvhd'));
176 theMVHDAtom
= dynamic_cast<MVHDAtom
*>(aAtomBase
);
179 // Assert(theMVHDAtom != NULL,"Movie has no movie header atom");
185 MP4FileReader::GetMovieTimeScale()
187 return GetMVHDAtom()->GetTimeScale();
192 MP4FileReader::GetMovieDuration()
194 return bigtime_t((GetMVHDAtom()->GetDuration() * 1000000.0) / GetMovieTimeScale());
199 MP4FileReader::GetStreamCount()
201 // count the number of tracks in the file
202 return CountChildAtoms(uint32('trak'));
207 MP4FileReader::GetVideoDuration(uint32 streamIndex
)
209 if (IsVideo(streamIndex
)) {
210 return GetTrack(streamIndex
)->Duration(1);
218 MP4FileReader::GetAudioDuration(uint32 streamIndex
)
220 if (IsAudio(streamIndex
)) {
221 return GetTrack(streamIndex
)->Duration(1);
229 MP4FileReader::GetMaxDuration()
231 int32 videoIndex
= -1;
232 int32 audioIndex
= -1;
234 // find the active video and audio tracks
235 for (uint32 i
= 0; i
< GetStreamCount(); i
++) {
236 if (GetTrack(i
)->IsActive()) {
237 if (GetTrack(i
)->IsAudio()) {
238 audioIndex
= int32(i
);
240 if (GetTrack(i
)->IsVideo()) {
241 videoIndex
= int32(i
);
246 if (videoIndex
>= 0 && audioIndex
>= 0) {
247 return max_c(GetVideoDuration(videoIndex
),
248 GetAudioDuration(audioIndex
));
250 if (videoIndex
< 0 && audioIndex
>= 0) {
251 return GetAudioDuration(audioIndex
);
253 if (videoIndex
>= 0 && audioIndex
< 0) {
254 return GetVideoDuration(videoIndex
);
262 MP4FileReader::GetFrameCount(uint32 streamIndex
)
264 return GetTrack(streamIndex
)->FrameCount();
268 MP4FileReader::GetAudioChunkCount(uint32 streamIndex
)
270 if (IsAudio(streamIndex
)) {
271 return GetTrack(streamIndex
)->GetTotalChunks();
278 MP4FileReader::IsVideo(uint32 streamIndex
)
280 return GetTrack(streamIndex
)->IsVideo();
285 MP4FileReader::IsAudio(uint32 streamIndex
)
287 return GetTrack(streamIndex
)->IsAudio();
292 MP4FileReader::GetFirstFrameInChunk(uint32 streamIndex
, uint32 pChunkIndex
)
294 return GetTrack(streamIndex
)->GetFirstSampleInChunk(pChunkIndex
);
299 MP4FileReader::GetNoFramesInChunk(uint32 streamIndex
, uint32 pChunkIndex
)
301 return GetTrack(streamIndex
)->GetNoSamplesInChunk(pChunkIndex
);
305 MP4FileReader::GetChunkForFrame(uint32 streamIndex
, uint32 pFrameNo
) {
306 uint32 OffsetInChunk
;
307 return GetTrack(streamIndex
)->GetChunkForSample(pFrameNo
, &OffsetInChunk
);
312 MP4FileReader::GetOffsetForFrame(uint32 streamIndex
, uint32 pFrameNo
)
314 TRAKAtom
*aTrakAtom
= GetTrack(streamIndex
);
316 if (pFrameNo
< aTrakAtom
->FrameCount()) {
317 // Get time for Frame
318 bigtime_t Time
= aTrakAtom
->GetTimeForFrame(pFrameNo
);
320 // Get Sample for Time
321 uint32 SampleNo
= aTrakAtom
->GetSampleForTime(Time
);
323 // Get Chunk For Sample and the offset for the frame within that chunk
324 uint32 OffsetInChunk
;
325 uint32 ChunkIndex
= aTrakAtom
->GetChunkForSample(SampleNo
, &OffsetInChunk
);
326 // Get Offset For Chunk
327 uint64 OffsetNo
= aTrakAtom
->GetOffsetForChunk(ChunkIndex
);
329 if (ChunkIndex
!= 0) {
330 uint32 SizeForSample
;
331 // Adjust the Offset for the Offset in the chunk
332 if (aTrakAtom
->IsSingleSampleSize()) {
333 SizeForSample
= aTrakAtom
->GetSizeForSample(SampleNo
);
334 OffsetNo
= OffsetNo
+ (OffsetInChunk
* SizeForSample
);
336 // This is bad news performance wise
337 for (uint32 i
=1;i
<=OffsetInChunk
;i
++) {
338 SizeForSample
= aTrakAtom
->GetSizeForSample(SampleNo
-i
);
339 OffsetNo
= OffsetNo
+ SizeForSample
;
344 // printf("frame %ld, time %Ld, sample %ld, Chunk %ld, OffsetInChunk %ld, Offset %Ld\n",pFrameNo, Time, SampleNo, ChunkNo, OffsetInChunk, OffsetNo);
353 MP4FileReader::GetOffsetForChunk(uint32 streamIndex
, uint32 pChunkIndex
)
355 return GetTrack(streamIndex
)->GetOffsetForChunk(pChunkIndex
);
360 MP4FileReader::ParseFile()
363 while (IsEndOfFile() == false) {
364 aChild
= GetAtom(theStream
);
365 if (AddChild(aChild
)) {
366 aChild
->ProcessMetaData();
372 for (uint32 i
=0;i
<TotalChildren
;i
++) {
373 atomChildren
[i
]->DisplayAtoms();
381 const mp4_main_header
*
382 MP4FileReader::MovMainHeader()
384 // Fill In theMainHeader
385 // uint32 micro_sec_per_frame;
386 // uint32 max_bytes_per_sec;
387 // uint32 padding_granularity;
389 // uint32 total_frames;
390 // uint32 initial_frames;
392 // uint32 suggested_buffer_size;
396 uint32 videoStream
= 0;
398 theMainHeader
.streams
= GetStreamCount();
399 theMainHeader
.flags
= 0;
400 theMainHeader
.initial_frames
= 0;
402 while (videoStream
< theMainHeader
.streams
) {
403 if (IsVideo(videoStream
) && IsActive(videoStream
))
409 if (videoStream
>= theMainHeader
.streams
) {
410 theMainHeader
.width
= 0;
411 theMainHeader
.height
= 0;
412 theMainHeader
.total_frames
= 0;
413 theMainHeader
.suggested_buffer_size
= 0;
414 theMainHeader
.micro_sec_per_frame
= 0;
416 theMainHeader
.width
= VideoFormat(videoStream
)->width
;
417 theMainHeader
.height
= VideoFormat(videoStream
)->height
;
418 theMainHeader
.total_frames
= GetFrameCount(videoStream
);
419 theMainHeader
.suggested_buffer_size
= theMainHeader
.width
* theMainHeader
.height
* VideoFormat(videoStream
)->bit_count
/ 8;
420 theMainHeader
.micro_sec_per_frame
= uint32(1000000.0 / VideoFormat(videoStream
)->FrameRate
);
423 theMainHeader
.padding_granularity
= 0;
424 theMainHeader
.max_bytes_per_sec
= 0;
426 return &theMainHeader
;
430 const AudioMetaData
*
431 MP4FileReader::AudioFormat(uint32 streamIndex
, size_t *size
)
433 if (IsAudio(streamIndex
)) {
434 AtomBase
*aAtomBase
= GetChildAtom(uint32('trak'),streamIndex
);
437 TRAKAtom
*aTrakAtom
= dynamic_cast<TRAKAtom
*>(aAtomBase
);
439 aAtomBase
= aTrakAtom
->GetChildAtom(uint32('stsd'),0);
441 STSDAtom
*aSTSDAtom
= dynamic_cast<STSDAtom
*>(aAtomBase
);
443 // Fill in the AudioMetaData structure
444 AudioDescription aAudioDescription
= aSTSDAtom
->GetAsAudio();
446 theAudio
.compression
= aAudioDescription
.codecid
;
447 theAudio
.codecSubType
= aAudioDescription
.codecSubType
;
449 theAudio
.NoOfChannels
= aAudioDescription
.theAudioSampleEntry
.ChannelCount
;
451 // Fix for broken mp4's with 0 SampleSize, default to 16 bits
452 if (aAudioDescription
.theAudioSampleEntry
.SampleSize
== 0) {
453 theAudio
.SampleSize
= 16;
455 theAudio
.SampleSize
= aAudioDescription
.theAudioSampleEntry
.SampleSize
;
458 theAudio
.SampleRate
= aAudioDescription
.theAudioSampleEntry
.SampleRate
;
459 theAudio
.FrameSize
= aAudioDescription
.FrameSize
;
460 if (aAudioDescription
.BufferSize
== 0) {
461 theAudio
.BufferSize
= uint32((theAudio
.SampleSize
* theAudio
.NoOfChannels
* theAudio
.FrameSize
) / 8);
463 theAudio
.BufferSize
= aAudioDescription
.BufferSize
;
466 theAudio
.BitRate
= aAudioDescription
.BitRate
;
468 theAudio
.theDecoderConfig
= aAudioDescription
.theDecoderConfig
;
469 theAudio
.DecoderConfigSize
= aAudioDescription
.DecoderConfigSize
;
480 MP4FileReader::VideoFormat(uint32 streamIndex
)
482 if (IsVideo(streamIndex
)) {
483 AtomBase
*aAtomBase
= GetChildAtom(uint32('trak'),streamIndex
);
486 TRAKAtom
*aTrakAtom
= dynamic_cast<TRAKAtom
*>(aAtomBase
);
488 aAtomBase
= aTrakAtom
->GetChildAtom(uint32('stsd'),0);
490 STSDAtom
*aSTSDAtom
= dynamic_cast<STSDAtom
*>(aAtomBase
);
491 VideoDescription aVideoDescription
= aSTSDAtom
->GetAsVideo();
493 theVideo
.compression
= aVideoDescription
.codecid
;
494 theVideo
.codecSubType
= aVideoDescription
.codecSubType
;
496 theVideo
.width
= aVideoDescription
.theVideoSampleEntry
.Width
;
497 theVideo
.height
= aVideoDescription
.theVideoSampleEntry
.Height
;
498 theVideo
.planes
= aVideoDescription
.theVideoSampleEntry
.Depth
;
499 theVideo
.BufferSize
= aVideoDescription
.theVideoSampleEntry
.Width
* aVideoDescription
.theVideoSampleEntry
.Height
* aVideoDescription
.theVideoSampleEntry
.Depth
/ 8;
500 theVideo
.bit_count
= aVideoDescription
.theVideoSampleEntry
.Depth
;
501 theVideo
.image_size
= aVideoDescription
.theVideoSampleEntry
.Height
* aVideoDescription
.theVideoSampleEntry
.Width
;
502 theVideo
.HorizontalResolution
= aVideoDescription
.theVideoSampleEntry
.HorizontalResolution
;
503 theVideo
.VerticalResolution
= aVideoDescription
.theVideoSampleEntry
.VerticalResolution
;
504 theVideo
.FrameCount
= aVideoDescription
.theVideoSampleEntry
.FrameCount
;
506 theVideo
.theDecoderConfig
= aVideoDescription
.theDecoderConfig
;
507 theVideo
.DecoderConfigSize
= aVideoDescription
.DecoderConfigSize
;
509 aAtomBase
= aTrakAtom
->GetChildAtom(uint32('stts'),0);
511 STTSAtom
*aSTTSAtom
= dynamic_cast<STTSAtom
*>(aAtomBase
);
513 theVideo
.FrameRate
= ((aSTTSAtom
->GetSUMCounts() * 1000000.0) / aTrakAtom
->Duration(1));
525 const mp4_stream_header
*
526 MP4FileReader::StreamFormat(uint32 streamIndex
)
528 if (IsActive(streamIndex
) == false) {
532 // Fill In a Stream Header
533 theStreamHeader
.length
= 0;
535 if (IsVideo(streamIndex
)) {
536 theStreamHeader
.rate
= uint32(1000000.0*VideoFormat(streamIndex
)->FrameRate
);
537 theStreamHeader
.scale
= 1000000L;
538 theStreamHeader
.length
= GetFrameCount(streamIndex
);
541 if (IsAudio(streamIndex
)) {
542 theStreamHeader
.rate
= uint32(AudioFormat(streamIndex
)->SampleRate
);
543 theStreamHeader
.scale
= 1;
544 theStreamHeader
.length
= GetFrameCount(streamIndex
);
545 theStreamHeader
.sample_size
= AudioFormat(streamIndex
)->SampleSize
;
546 theStreamHeader
.suggested_buffer_size
= AudioFormat(streamIndex
)->BufferSize
;
549 return &theStreamHeader
;
554 MP4FileReader::GetFrameSize(uint32 streamIndex
, uint32 pFrameNo
)
556 if (pFrameNo
< GetTrack(streamIndex
)->FrameCount()) {
557 uint32 SampleNo
= GetTrack(streamIndex
)->GetSampleForFrame(pFrameNo
);
558 return GetTrack(streamIndex
)->GetSizeForSample(SampleNo
);
565 MP4FileReader::isValidFrame(uint32 streamIndex
, uint32 pFrameNo
) {
566 return (pFrameNo
< GetTrack(streamIndex
)->FrameCount());
570 MP4FileReader::isValidChunkIndex(uint32 streamIndex
, uint32 pChunkIndex
) {
571 return (pChunkIndex
> 0 && pChunkIndex
<= GetTrack(streamIndex
)->ChunkCount());
576 MP4FileReader::GetChunkSize(uint32 streamIndex
, uint32 pChunkIndex
)
578 return GetTrack(streamIndex
)->GetChunkSize(pChunkIndex
);
583 MP4FileReader::IsKeyFrame(uint32 streamIndex
, uint32 pFrameNo
)
585 if (IsAudio(streamIndex
)) {
589 return GetTrack(streamIndex
)->IsSyncSample(pFrameNo
);
593 MP4FileReader::GetTimeForFrame(uint32 streamIndex
, uint32 pFrameNo
) {
595 return GetTrack(streamIndex
)->GetTimeForFrame(pFrameNo
);
600 MP4FileReader::GetFrameForTime(uint32 streamIndex
, bigtime_t time
) {
601 return GetTrack(streamIndex
)->GetFrameForTime(time
);
606 MP4FileReader::GetFrameForSample(uint32 streamIndex
, uint32 sample
) {
607 if (IsVideo(streamIndex
)) {
608 return sample
; // frame = sample for video
611 if (IsAudio(streamIndex
)) {
612 bigtime_t time
= bigtime_t((sample
* 1000000.0) / GetTrack(streamIndex
)->GetSampleRate());
613 return GetTrack(streamIndex
)->GetFrameForTime(time
);
621 MP4FileReader::GetSampleForTime(uint32 streamIndex
, bigtime_t time
) {
622 return GetTrack(streamIndex
)->GetSampleForTime(time
);
627 MP4FileReader::GetTimeForSample(uint32 streamIndex
, uint32 sample
) {
628 return GetTimeForFrame(streamIndex
, GetFrameForSample(streamIndex
, sample
));
633 MP4FileReader::GetBufferForChunk(uint32 streamIndex
, uint32 pChunkIndex
, off_t
*start
, uint32
*size
, bool *keyframe
, bigtime_t
*time
, uint32
*framesInBuffer
) {
635 if (isValidChunkIndex(streamIndex
, pChunkIndex
) == false) {
644 *start
= GetOffsetForChunk(streamIndex
, pChunkIndex
);
645 *size
= GetChunkSize(streamIndex
, pChunkIndex
);
647 uint32 frameNo
= GetFirstFrameInChunk(streamIndex
, pChunkIndex
);
649 if ((*start
> 0) && (*size
> 0)) {
650 *keyframe
= IsKeyFrame(streamIndex
, frameNo
);
653 *framesInBuffer
= GetNoFramesInChunk(streamIndex
, pChunkIndex
);
654 *time
= GetTimeForFrame(streamIndex
, frameNo
);
656 if (IsEndOfFile(*start
+ *size
) || IsEndOfData(*start
+ *size
)) {
660 return *start
> 0 && *size
> 0;
665 MP4FileReader::GetBufferForFrame(uint32 streamIndex
, uint32 pFrameNo
, off_t
*start
, uint32
*size
, bool *keyframe
, bigtime_t
*time
, uint32
*framesInBuffer
) {
666 // Get the offset for a given frame and the size of the remaining bytes in the chunk
667 // framesInBuffer is how many frames there are in the buffer.
669 if (isValidFrame(streamIndex
, pFrameNo
) == false) {
678 *start
= GetOffsetForFrame(streamIndex
, pFrameNo
);
679 *size
= GetFrameSize(streamIndex
, pFrameNo
);
681 if ((*start
> 0) && (*size
> 0)) {
682 *keyframe
= IsKeyFrame(streamIndex
, pFrameNo
);
686 *time
= GetTimeForFrame(streamIndex
, pFrameNo
);
688 if (IsEndOfFile(*start
+ *size
) || IsEndOfData(*start
+ *size
)) {
692 return *start
> 0 && *size
> 0;
697 MP4FileReader::IsActive(uint32 streamIndex
)
699 // Don't use helper method streamIndex is likely invalid
700 AtomBase
*aAtomBase
= GetChildAtom(uint32('trak'), streamIndex
);
702 return dynamic_cast<TRAKAtom
*>(aAtomBase
)->IsActive();
711 MP4FileReader::IsSupported(BPositionIO
*source
)
713 AtomBase
*aAtom
= GetAtom(source
);
715 if (dynamic_cast<FTYPAtom
*>(aAtom
)) {
716 aAtom
->ProcessMetaData();
717 printf("ftyp atom found checking brands...");
718 // MP4 files start with a ftyp atom that does not contain a qt brand
719 if (!dynamic_cast<FTYPAtom
*>(aAtom
)->HasBrand(uint32('qt '))) {
720 printf("no quicktime brand found must be mp4\n");
723 printf("quicktime brand found\n");
728 printf("NO ftyp atom found, cannot be mp4\n");