* Copyright (c) 2005, David McPaul
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "MOVParser.h"
#include "MOVFileReader.h"
#include <DataIO.h>
#include <SupportKit.h>
#include <iostream>
extern AtomBase *getAtom(BPositionIO *pStream);
MOVFileReader::MOVFileReader(BPositionIO *pStream)
{
theStream = pStream;
theStream->Seek(0,SEEK_END);
StreamSize = theStream->Position();
theStream->Seek(0,SEEK_SET);
TotalChildren = 0;
theMVHDAtom = NULL;
}
MOVFileReader::~MOVFileReader()
{
theStream = NULL;
theMVHDAtom = NULL;
}
bool MOVFileReader::IsEndOfData(off_t pPosition)
{
AtomBase *aAtomBase;
for (uint32 index=0;index<CountChildAtoms('mdat');index++) {
aAtomBase = GetChildAtom(uint32('mdat'),index);
if ((aAtomBase) && (aAtomBase->getAtomSize() > 8)) {
MDATAtom *aMDATAtom = dynamic_cast<MDATAtom *>(aAtomBase);
if (pPosition >= aMDATAtom->getAtomOffset() && pPosition <= aMDATAtom->getEOF()) {
return false;
}
}
}
return true;
}
bool MOVFileReader::IsEndOfFile(off_t pPosition)
{
return (pPosition >= StreamSize);
}
bool MOVFileReader::IsEndOfFile()
{
return (theStream->Position() >= StreamSize);
}
bool MOVFileReader::AddChild(AtomBase *pChildAtom)
{
if (pChildAtom) {
atomChildren[TotalChildren++] = pChildAtom;
return true;
}
return false;
}
AtomBase *MOVFileReader::GetChildAtom(uint32 patomType, uint32 offset)
{
for (uint32 i=0;i<TotalChildren;i++) {
if (atomChildren[i]->IsType(patomType)) {
if (offset == 0) {
return atomChildren[i];
} else {
offset--;
}
} else {
if (atomChildren[i]->IsContainer()) {
AtomBase *aAtomBase = (dynamic_cast<AtomContainer *>(atomChildren[i])->GetChildAtom(patomType, offset));
if (aAtomBase) {
return aAtomBase;
}
}
}
}
return NULL;
}
uint32 MOVFileReader::CountChildAtoms(uint32 patomType)
{
uint32 count = 0;
while (GetChildAtom(patomType, count) != NULL) {
count++;
}
return count;
}
MVHDAtom *MOVFileReader::getMVHDAtom()
{
AtomBase *aAtomBase;
if (theMVHDAtom == NULL) {
aAtomBase = GetChildAtom(uint32('mvhd'));
theMVHDAtom = dynamic_cast<MVHDAtom *>(aAtomBase);
}
return theMVHDAtom;
}
uint32 MOVFileReader::getMovieTimeScale()
{
return getMVHDAtom()->getTimeScale();
}
bigtime_t MOVFileReader::getMovieDuration()
{
return ((bigtime_t(getMVHDAtom()->getDuration()) * 1000000L) / getMovieTimeScale());
}
uint32 MOVFileReader::getStreamCount()
{
return (CountChildAtoms(uint32('trak')));
}
bigtime_t MOVFileReader::getVideoDuration(uint32 stream_index)
{
AtomBase *aAtomBase;
aAtomBase = GetChildAtom(uint32('trak'),stream_index);
if ((aAtomBase) && (dynamic_cast<TRAKAtom *>(aAtomBase)->IsVideo())) {
return (dynamic_cast<TRAKAtom *>(aAtomBase)->Duration(1));
}
return 0;
}
bigtime_t MOVFileReader::getAudioDuration(uint32 stream_index)
{
AtomBase *aAtomBase;
aAtomBase = GetChildAtom(uint32('trak'),stream_index);
if ((aAtomBase) && (dynamic_cast<TRAKAtom *>(aAtomBase)->IsAudio())) {
return (dynamic_cast<TRAKAtom *>(aAtomBase)->Duration(1));
}
return 0;
}
bigtime_t MOVFileReader::getMaxDuration()
{
AtomBase *aAtomBase;
int32 video_index,audio_index;
video_index = -1;
audio_index = -1;
for (uint32 i=0;i<getStreamCount();i++) {
aAtomBase = GetChildAtom(uint32('trak'),i);
if ((aAtomBase) && (dynamic_cast<TRAKAtom *>(aAtomBase)->IsActive())) {
if (dynamic_cast<TRAKAtom *>(aAtomBase)->IsAudio()) {
audio_index = int32(i);
}
if (dynamic_cast<TRAKAtom *>(aAtomBase)->IsVideo()) {
video_index = int32(i);
}
}
}
if ((video_index >= 0) && (audio_index >= 0)) {
return MAX(getVideoDuration(video_index),getAudioDuration(audio_index));
}
if ((video_index < 0) && (audio_index >= 0)) {
return getAudioDuration(audio_index);
}
if ((video_index >= 0) && (audio_index < 0)) {
return getVideoDuration(video_index);
}
return 0;
}
uint32 MOVFileReader::getVideoFrameCount(uint32 stream_index)
{
AtomBase *aAtomBase;
aAtomBase = GetChildAtom(uint32('trak'),stream_index);
if ((aAtomBase) && (dynamic_cast<TRAKAtom *>(aAtomBase)->IsVideo())) {
return dynamic_cast<TRAKAtom *>(aAtomBase)->FrameCount();
}
return 1;
}
uint32 MOVFileReader::getAudioFrameCount(uint32 stream_index)
{
if (IsAudio(stream_index)) {
return uint32(((getAudioDuration(stream_index) * AudioFormat(stream_index)->SampleRate) / 1000000L) + 0.5);
}
return 0;
}
bool MOVFileReader::IsVideo(uint32 stream_index)
{
AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index);
if (aAtomBase) {
return (dynamic_cast<TRAKAtom *>(aAtomBase)->IsVideo());
}
return false;
}
bool MOVFileReader::IsAudio(uint32 stream_index)
{
AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index);
if (aAtomBase) {
return (dynamic_cast<TRAKAtom *>(aAtomBase)->IsAudio());
}
return false;
}
uint32 MOVFileReader::getFirstFrameInChunk(uint32 stream_index, uint32 pChunkID)
{
AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index);
if (aAtomBase) {
TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase);
return aTrakAtom->getFirstSampleInChunk(pChunkID);
}
return 0;
}
uint32 MOVFileReader::getNoFramesInChunk(uint32 stream_index, uint32 pChunkID)
{
AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index);
if (aAtomBase) {
TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase);
return aTrakAtom->getNoSamplesInChunk(pChunkID);
}
return 0;
}
uint64 MOVFileReader::getOffsetForFrame(uint32 stream_index, uint32 pFrameNo, uint32 *chunkFrameCount)
{
AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index);
if (aAtomBase) {
TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase);
if (pFrameNo < aTrakAtom->FrameCount()) {
uint32 SampleNo = aTrakAtom->getSampleForFrame(pFrameNo);
uint32 OffsetInChunk;
uint32 ChunkID = aTrakAtom->getChunkForSample(SampleNo, &OffsetInChunk);
uint64 OffsetNo = aTrakAtom->getOffsetForChunk(ChunkID);
if (IsAudio(stream_index)) {
*chunkFrameCount = aTrakAtom->getNoSamplesInChunk(ChunkID) - OffsetInChunk;
} else {
*chunkFrameCount = 1;
}
if (ChunkID != 0) {
uint32 SampleSize;
if (aTrakAtom->IsSingleSampleSize()) {
SampleSize = aTrakAtom->getSizeForSample(SampleNo);
OffsetNo = OffsetNo + (OffsetInChunk * SampleSize);
} else {
for (uint32 i=1;i<=OffsetInChunk;i++) {
SampleSize = aTrakAtom->getSizeForSample(SampleNo - i);
OffsetNo = OffsetNo + SampleSize;
}
}
}
return OffsetNo;
}
}
return 0;
}
void MOVFileReader::BuildSuperIndex()
{
AtomBase *aAtomBase;
for (uint32 stream=0;stream<getStreamCount();stream++) {
aAtomBase = GetChildAtom(uint32('trak'),stream);
if (aAtomBase) {
TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase);
for (uint32 chunkid=1;chunkid<=aTrakAtom->getTotalChunks();chunkid++) {
theChunkSuperIndex.AddChunkIndex(stream,chunkid,aTrakAtom->getOffsetForChunk(chunkid));
}
}
}
aAtomBase = GetChildAtom(uint32('mdat'),0);
if (aAtomBase) {
MDATAtom *aMdatAtom = dynamic_cast<MDATAtom *>(aAtomBase);
theChunkSuperIndex.AddChunkIndex(0,0,aMdatAtom->getEOF());
}
}
status_t MOVFileReader::ParseFile()
{
AtomBase *aChild;
while (IsEndOfFile() == false) {
aChild = getAtom(theStream);
if (AddChild(aChild)) {
aChild->ProcessMetaData();
}
}
for (uint32 i=0;i<TotalChildren;i++) {
atomChildren[i]->DisplayAtoms();
}
BuildSuperIndex();
return B_OK;
}
const mov_main_header *MOVFileReader::MovMainHeader()
{
uint32 videoStream = 0;
theMainHeader.streams = getStreamCount();
theMainHeader.flags = 0;
theMainHeader.initial_frames = 0;
while ( videoStream < theMainHeader.streams ) {
if (IsVideo(videoStream) && IsActive(videoStream)) {
break;
}
videoStream++;
}
if (videoStream >= theMainHeader.streams) {
theMainHeader.width = 0;
theMainHeader.height = 0;
theMainHeader.total_frames = 0;
theMainHeader.suggested_buffer_size = 0;
theMainHeader.micro_sec_per_frame = 0;
} else {
theMainHeader.width = VideoFormat(videoStream)->width;
theMainHeader.height = VideoFormat(videoStream)->height;
theMainHeader.total_frames = getVideoFrameCount(videoStream);
theMainHeader.suggested_buffer_size = theMainHeader.width * theMainHeader.height * VideoFormat(videoStream)->bit_count / 8;
theMainHeader.micro_sec_per_frame = uint32(1000000.0 / VideoFormat(videoStream)->FrameRate);
}
theMainHeader.padding_granularity = 0;
theMainHeader.max_bytes_per_sec = 0;
return &theMainHeader;
}
const AudioMetaData *MOVFileReader::AudioFormat(uint32 stream_index)
{
if (IsAudio(stream_index)) {
AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index);
if (aAtomBase) {
TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase);
aAtomBase = aTrakAtom->GetChildAtom(uint32('stsd'),0);
if (aAtomBase) {
STSDAtom *aSTSDAtom = dynamic_cast<STSDAtom *>(aAtomBase);
SoundDescriptionV1 aSoundDescription = aSTSDAtom->getAsAudio();
theAudio.compression = aSoundDescription.basefields.DataFormat;
theAudio.NoOfChannels = aSoundDescription.desc.NoOfChannels;
theAudio.SampleSize = aSoundDescription.desc.SampleSize;
theAudio.SampleRate = aSoundDescription.desc.SampleRate / 65536;
if (aSoundDescription.bytesPerFrame == 0) {
theAudio.FrameSize = aSoundDescription.desc.SampleSize / 8;
} else {
theAudio.FrameSize = aSoundDescription.bytesPerFrame;
}
if (aSoundDescription.bytesPerPacket == 0) {
theAudio.BufferSize = uint32((theAudio.SampleSize * theAudio.NoOfChannels * theAudio.FrameSize) / 8);
} else {
theAudio.BufferSize = aSoundDescription.bytesPerPacket;
}
theAudio.BitRate = theAudio.SampleSize * theAudio.NoOfChannels * theAudio.SampleRate;
theAudio.theVOL = aSoundDescription.theVOL;
theAudio.VOLSize = aSoundDescription.VOLSize;
return &theAudio;
}
}
}
return NULL;
}
const VideoMetaData *MOVFileReader::VideoFormat(uint32 stream_index)
{
if (IsVideo(stream_index)) {
AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index);
if (aAtomBase) {
TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase);
aAtomBase = aTrakAtom->GetChildAtom(uint32('stsd'),0);
if (aAtomBase) {
STSDAtom *aSTSDAtom = dynamic_cast<STSDAtom *>(aAtomBase);
VideoDescriptionV0 aVideoDescriptionV0 = aSTSDAtom->getAsVideo();
theVideo.width = aVideoDescriptionV0.desc.Width;
theVideo.height = aVideoDescriptionV0.desc.Height;
theVideo.size = aVideoDescriptionV0.desc.DataSize;
theVideo.planes = aVideoDescriptionV0.desc.Depth;
theVideo.bit_count = aVideoDescriptionV0.desc.Depth;
theVideo.compression = aVideoDescriptionV0.basefields.DataFormat;
theVideo.image_size = aVideoDescriptionV0.desc.Height * aVideoDescriptionV0.desc.Width;
theVideo.HorizontalResolution = aVideoDescriptionV0.desc.HorizontalResolution;
theVideo.VerticalResolution = aVideoDescriptionV0.desc.VerticalResolution;
theVideo.theVOL = aVideoDescriptionV0.theVOL;
theVideo.VOLSize = aVideoDescriptionV0.VOLSize;
aAtomBase = aTrakAtom->GetChildAtom(uint32('stts'),0);
if (aAtomBase) {
STTSAtom *aSTTSAtom = dynamic_cast<STTSAtom *>(aAtomBase);
theVideo.FrameRate = ((aSTTSAtom->getSUMCounts() * 1000000.0L) / aTrakAtom->Duration(1));
return &theVideo;
}
}
}
}
return NULL;
}
const mov_stream_header *MOVFileReader::StreamFormat(uint32 stream_index)
{
theStreamHeader.length = 0;
if (IsActive(stream_index) == false) {
return NULL;
}
if (IsVideo(stream_index)) {
theStreamHeader.rate = uint32(1000000L*VideoFormat(stream_index)->FrameRate);
theStreamHeader.scale = 1000000L;
theStreamHeader.length = getVideoFrameCount(stream_index);
}
if (IsAudio(stream_index)) {
theStreamHeader.rate = uint32(AudioFormat(stream_index)->SampleRate);
theStreamHeader.scale = 1;
theStreamHeader.length = getAudioFrameCount(stream_index);
theStreamHeader.sample_size = AudioFormat(stream_index)->SampleSize;
theStreamHeader.suggested_buffer_size = theStreamHeader.rate * theStreamHeader.sample_size;
}
return &theStreamHeader;
}
uint32
MOVFileReader::getChunkSize(uint32 streamIndex, uint32 frameNo)
{
AtomBase *aAtomBase = GetChildAtom(uint32('trak'), streamIndex);
if (aAtomBase == NULL)
return 0;
TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase);
if (frameNo < aTrakAtom->FrameCount()) {
uint32 SampleNo = aTrakAtom->getSampleForFrame(frameNo);
if (IsAudio(streamIndex)) {
uint32 OffsetInChunk;
uint32 ChunkID = aTrakAtom->getChunkForSample(SampleNo, &OffsetInChunk);
off_t chunkStart = aTrakAtom->getOffsetForChunk(ChunkID);
return theChunkSuperIndex.getChunkSize(streamIndex, ChunkID, chunkStart);
}
if (IsVideo(streamIndex)) {
return aTrakAtom->getSizeForSample(SampleNo);
}
}
return 0;
}
bool
MOVFileReader::IsKeyFrame(uint32 stream_index, uint32 pFrameNo)
{
AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index);
if (aAtomBase) {
TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase);
uint32 SampleNo = aTrakAtom->getSampleForFrame(pFrameNo);
return aTrakAtom->IsSyncSample(SampleNo);
}
return false;
}
bool MOVFileReader::GetNextChunkInfo(uint32 stream_index, uint32 pFrameNo, off_t *start, uint32 *size, bool *keyframe, uint32 *chunkFrameCount)
{
*start = getOffsetForFrame(stream_index, pFrameNo, chunkFrameCount);
*size = getChunkSize(stream_index, pFrameNo);
if ((*start > 0) && (*size > 0)) {
*keyframe = IsKeyFrame(stream_index, pFrameNo);
}
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");
if (IsEndOfFile(*start + *size) || IsEndOfData(*start + *size)) {
return false;
}
return *start > 0 && *size > 0;
}
bool MOVFileReader::IsActive(uint32 stream_index)
{
AtomBase *aAtomBase = GetChildAtom(uint32('trak'),stream_index);
if (aAtomBase) {
TRAKAtom *aTrakAtom = dynamic_cast<TRAKAtom *>(aAtomBase);
return aTrakAtom->IsActive();
}
return false;
}
bool MOVFileReader::IsSupported(BPositionIO *source)
{
AtomBase *aAtom;
aAtom = getAtom(source);
if (aAtom) {
if (dynamic_cast<FTYPAtom *>(aAtom)) {
printf("ftyp atom found checking for qt brand\n");
aAtom->ProcessMetaData();
return dynamic_cast<FTYPAtom *>(aAtom)->HasBrand(uint32('qt '));
} else {
return (aAtom->IsKnown());
}
}
return false;
}