/*
* Copyright (c) 2010 Ixonos Plc.
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - Initial contribution
*
* Contributors:
* Ixonos Plc
*
* Description:
*
*/
// INCLUDE FILES
#include "movieprocessorimpl.h"
#include "mp4parser.h"
#include "vedvideosettings.h"
#include "vedaudiosettings.h"
#include "vedvolreader.h"
#include "vedavcedit.h"
// ASSERTIONS
#define MPASSERT(x) __ASSERT_DEBUG(x, User::Panic(_L("CMovieProcessorImpl"), EInternalAssertionFailure))
#ifdef _DEBUG
#include <e32svr.h>
#define PRINT(x) RDebug::Print x;
#else
#define PRINT(x)
#endif
// LOCAL CONSTANTS AND MACROS
#ifdef _DEBUG
const TInt KErrorCode = CParser::EParserFailure;
#else
const TInt KErrorCode = KErrGeneral;
#endif
//const TUint KNumFramesInOneRun = 10;
const TUint KVOLHeaderBufferSize = 256;
const TUint KAVCDCRBufferSize = 1024;
const TUint KMinBitrate = 128;
// ================= MEMBER FUNCTIONS =======================
// C++ default constructor can NOT contain any code, that
// might leave.
//
CMP4Parser::CMP4Parser()
{
iMP4Handle = 0;
iVideoType = 0;
iAudioType = 0;
iBytesRead = 0;
iFirstRead = ETrue; // added for Mp4
iFirstFrameInfo = ETrue; // added for Mp4
iOutputNumberOfFrames = 0;
iStreamSource = ESourceNone;
iFrameNumber=0;
iFirstTimeClipParsing=ETrue;
iStartFrameIndex=0;
}
// Two-phased constructor.
CMP4Parser* CMP4Parser::NewL(CMovieProcessorImpl* aProcessor, const TDesC &aFileName)
{
CMP4Parser *self = new (ELeave) CMP4Parser;
CleanupStack::PushL(self);
if ( aFileName.Length() > 0 )
self->iStreamSource = ESourceFile;
else
self->iStreamSource = ESourceUser;
self->ConstructL(aProcessor,aFileName);
CleanupStack::Pop(); // self
return self;
}
CMP4Parser* CMP4Parser::NewL(CMovieProcessorImpl* aProcessor, RFile* aFileHandle)
{
CMP4Parser *self = new (ELeave) CMP4Parser;
CleanupStack::PushL(self);
self->iStreamSource = ESourceFile;
self->ConstructL(aProcessor,aFileHandle);
CleanupStack::Pop(); // self
return self;
}
// Symbian OS default constructor can leave.
void CMP4Parser::ConstructL(CMovieProcessorImpl* aProcessor, const TDesC &aFileName)
{
MP4Err error;
iProcessor = aProcessor;
// open MP4 library
if ( iStreamSource == ESourceFile )
{
TBuf<258> temp(aFileName);
temp.ZeroTerminate();
MP4FileName name = reinterpret_cast<MP4FileName>( const_cast<TUint16*>(temp.Ptr()) );
error = MP4ParseOpen(&iMP4Handle, name);
if ( error == MP4_OK )
{
// set buffer sizes; only parser buffer size is effective for this instance
error = MP4SetCustomFileBufferSizes(iMP4Handle, K3gpMp4ComposerWriteBufferSize, K3gpMp4ComposerNrOfWriteBuffers, K3gpMp4ParserReadBufferSize );
}
}
else
{
// buffer
error = MP4ParseOpen(&iMP4Handle, 0);
}
if (error == MP4_OUT_OF_MEMORY)
{
User::Leave(KErrNoMemory);
}
else if ( error != MP4_OK )
{
User::Leave(KErrorCode);
}
}
void CMP4Parser::ConstructL(CMovieProcessorImpl* aProcessor, RFile* aFileHandle)
{
MP4Err error;
iProcessor = aProcessor;
// open MP4 library
error = MP4ParseOpenFileHandle(&iMP4Handle, aFileHandle);
if ( error == MP4_OK )
{
// set buffer sizes; only parser buffer size is effective for this instance
error = MP4SetCustomFileBufferSizes(iMP4Handle, K3gpMp4ComposerWriteBufferSize, K3gpMp4ComposerNrOfWriteBuffers, K3gpMp4ParserReadBufferSize );
}
if (error == MP4_OUT_OF_MEMORY)
{
User::Leave(KErrNoMemory);
}
else if ( error != MP4_OK )
{
User::Leave(KErrorCode);
}
}
// Destructor
CMP4Parser::~CMP4Parser()
{
if (iMP4Handle)
MP4ParseClose(iMP4Handle);
iMP4Handle = 0;
}
// ---------------------------------------------------------
// CMP4Parser::WriteDataBlockL
// Write a block of data to parser
// (other items were commented in a header).
// ---------------------------------------------------------
//
TInt CMP4Parser::WriteDataBlock(const TDes8& aBlock)
{
MPASSERT(iStreamSource != ESourceFile);
MP4Err error = MP4ParseWriteData(iMP4Handle, (mp4_u8*)(aBlock.Ptr()), mp4_u32(aBlock.Length()) );
if ( error == MP4_OUT_OF_MEMORY )
return KErrNoMemory;
else if ( error == MP4_ERROR )
return KErrorCode;
else
return KErrNone;
}
// ---------------------------------------------------------
// CMP4Parser::GetNextFrameInformation
// Get type (streaming-case), length and availability of next frame to be fetched
// using MP4 library API functions
// (other items were commented in a header).
// ---------------------------------------------------------
//
TInt CMP4Parser::GetNextFrameInformation(TFrameType& aType, TUint& aLength, TBool& aIsAvailable)
{
// If aType == EFrameTypeNone, the type of next frame is retrieved
// (valid only in streaming case)
// Otherwise, only the length of next specified type of frame is retrieved
MPASSERT(iStreamSource != ESourceNone);
mp4_u32 type = MP4_TYPE_NONE;
MP4Err error;
aIsAvailable = 0;
if ( iNextFrameType == EFrameTypeNone )
// if the mp4 library is reading a file, a frame has always been read when
// we come here
// otherwise it might be that a complete frame was not available yet
// and we come here to ask again
{
if ( aType == EFrameTypeNone )
{
MPASSERT(iStreamSource == ESourceUser);
// get next frame type
error = MP4ParseNextFrameType(iMP4Handle, &type);
if ( error == MP4_NOT_AVAILABLE )
return KErrNone;
else if ( error == MP4_NO_FRAME )
return EParserEndOfStream; // no video or audio frames left, stream ended
else if ( error == MP4_INVALID_INPUT_STREAM )
return KErrCorrupt;
else if ( error != MP4_OK )
return KErrorCode;
else
{
MPASSERT(error == MP4_OK);
}
switch ( type )
{
case MP4_TYPE_H263_PROFILE_0:
case MP4_TYPE_MPEG4_VIDEO:
MPASSERT( type == iVideoType );
iNextFrameType = EFrameTypeVideo;
break;
case MP4_TYPE_AMR_NB:
MPASSERT( type == iAudioType );
iNextFrameType = EFrameTypeAudio;
break;
default:
return KErrNotSupported;
}
}
else
{
// library reads the file
//MPASSERT(iStreamSource == ESourceFile);
type = ( aType == EFrameTypeVideo ) ? iVideoType : iAudioType;
iNextFrameType = aType;
}
// get length for the frame
mp4_u32 length = 0, mp4Specific = 0;
MPASSERT( type != MP4_TYPE_NONE );
if ( (iFirstFrameInfo) &&
((iVideoType == MP4_TYPE_MPEG4_VIDEO) || (iVideoType == MP4_TYPE_AVC_PROFILE_BASELINE)) )
{
error = MP4ParseReadVideoDecoderSpecificInfo( iMP4Handle, 0, 0, &mp4Specific );
iFirstFrameInfo = EFalse;
}
error = MP4ParseNextFrameSize(iMP4Handle, type, &length);
MPASSERT( error != MP4_NOT_AVAILABLE );
if ( length == 0 || error == MP4_NO_REQUESTED_FRAME )
{
// file-reading case: all frames of this type have been read
// and the caller should try with the other type
MPASSERT( length == 0 );
iNextFrameType = EFrameTypeNone;
aLength = 0;
return KErrNone;
}
else if ( error == MP4_INVALID_INPUT_STREAM )
return KErrCorrupt;
else if ( error != MP4_OK )
return KErrorCode;
else if ( length > iMaxVideoFrameLength )
{
PRINT((_L("CMP4Parser::GetNextFrameInformation() too large video frame size %d, return KErrCorrupt"),length));
return KErrCorrupt;
}
else
iNextFrameLength = TUint(length + mp4Specific);
}
MPASSERT(iNextFrameType != EFrameTypeNone);
MPASSERT(iNextFrameLength != 0);
// check if frame is available
if ( iStreamSource == ESourceUser )
{
error = MP4ParseIsFrameAvailable(iMP4Handle, type);
if ( error != MP4_OK && error != MP4_NOT_AVAILABLE )
return KErrorCode;
aIsAvailable = ( error == MP4_NOT_AVAILABLE ) ? EFalse : ETrue;
}
else
aIsAvailable = ETrue;
aType = iNextFrameType;
aLength = iNextFrameLength;
return KErrNone;
}
// ---------------------------------------------------------
// CMP4Parser::ReadFrames
// Read the next frame(s) from file / stream
// (other items were commented in a header).
// ---------------------------------------------------------
//
TInt CMP4Parser::ReadFrames(TDes8& aDstBuffer, TFrameType aType, TUint32& aNumRead,
TUint32& aTimeStamp)
{
MP4Err error;
mp4_u32 returnedSize = 0;
mp4_bool keyFrame = 0;
MPASSERT( iNextFrameType != EFrameTypeNone && aType == iNextFrameType );
MPASSERT( iNextFrameLength != 0 );
#ifdef _DEBUG
mp4_u32 type = MP4_TYPE_NONE; // buffer support
type = ( aType == EFrameTypeVideo ) ? iVideoType : iAudioType; // buffer support
if (iStreamSource == ESourceUser)
MPASSERT( MP4ParseIsFrameAvailable(iMP4Handle, type) == MP4_OK );
#endif
if (aType == EFrameTypeVideo)
{
TUint32 iTimeStampInTicks=0;
mp4_u32 mp4Specific = 0;
if ( (iFirstRead) &&
((iVideoType == MP4_TYPE_MPEG4_VIDEO) || (iVideoType == MP4_TYPE_AVC_PROFILE_BASELINE)) )
{
error = MP4ParseReadVideoDecoderSpecificInfo( iMP4Handle, (mp4_u8*)(aDstBuffer.Ptr() + aDstBuffer.Length()),
mp4_u32( aDstBuffer.MaxLength() ), &mp4Specific );
iFirstRead = EFalse;
}
error = MP4ParseReadVideoFrame(iMP4Handle, (mp4_u8*)(aDstBuffer.Ptr() + aDstBuffer.Length()+ mp4Specific),
mp4_u32( aDstBuffer.MaxLength() ), &returnedSize, (mp4_u32*)&aTimeStamp,
&keyFrame, &iTimeStampInTicks);
returnedSize += mp4Specific;
iFrameNumber++;
aNumRead = 1;
}
else
{
error = MP4ParseReadAudioFrames(iMP4Handle, (mp4_u8*)(aDstBuffer.Ptr()),
mp4_u32(aDstBuffer.MaxLength()), &returnedSize, (mp4_u32*)&aTimeStamp,
(mp4_u32*)&aNumRead, NULL);
//PRINT((_L("audio TS:%d, "), aTimeStamp));
}
MPASSERT(error != MP4_BUFFER_TOO_SMALL);
aDstBuffer.SetLength(aDstBuffer.Length() + TInt(returnedSize));
iBytesRead += returnedSize;
iNextFrameType = EFrameTypeNone;
iNextFrameLength = 0;
//PRINT((_L("error=%d, numReturned=%d, returnedSize=%d, bufferSize=%d\n"),
// error, aNumRead, returnedSize, aDstBuffer.MaxLength()));
if (error == MP4_NOT_AVAILABLE)
return EParserNotEnoughData;
else if ( error == MP4_INVALID_INPUT_STREAM )
return KErrCorrupt;
else if ( error != MP4_OK )
return KErrorCode;
else
return KErrNone;
}
// ---------------------------------------------------------
// CMP4Parser::Reset
// Resets the parser to its initial state
// (other items were commented in a header).
// ---------------------------------------------------------
//
TInt CMP4Parser::Reset()
{
MP4Err error;
if ( iStreamSource == ESourceFile )
{
mp4_u32 audioPos, videoPos;
// seek to very beginning
error = MP4ParseSeek(iMP4Handle, 0, &audioPos, &videoPos, EFalse);
if ( error != MP4_OK )
return KErrorCode;
MPASSERT( videoPos == 0 && (iAudioType == 0 || audioPos == 0) );
}
else
{
// close & open library to make sure old data is flushed
error = MP4ParseClose(iMP4Handle);
if ( error != MP4_OK )
return KErrorCode;
error = MP4ParseOpen(&iMP4Handle, 0);
if ( error != MP4_OK )
return KErrorCode;
}
iBytesRead = 0;
iNextFrameType = EFrameTypeNone;
iNextFrameLength = 0;
return KErrNone;
}
// ---------------------------------------------------------
// CMP4Parser::ParseHeaderL
// Get relevant stream parameters by calling mp4 library functions
// (other items were commented in a header).
// ---------------------------------------------------------
//
TInt CMP4Parser::ParseHeaderL(CParser::TStreamParameters& aStreamParameters)
{
PRINT((_L("CMP4Parser::ParseHeaderL() begin")));
MP4Err error;
mp4_double frameRate = 0;
TBool hasVideo = ETrue;
// Reset channel info
aStreamParameters.iHaveVideo = EFalse;
aStreamParameters.iHaveAudio = EFalse;
aStreamParameters.iNumDemuxChannels = 0;
aStreamParameters.iFileFormat = EFileFormatUnrecognized;
aStreamParameters.iVideoFormat = EVideoFormatNone;
aStreamParameters.iAudioFormat = EAudioFormatNone;
aStreamParameters.iVideoLength = 0;
aStreamParameters.iAudioLength = 0;
aStreamParameters.iStreamLength = 0;
aStreamParameters.iAudioFramesInSample = 0;
aStreamParameters.iVideoPicturePeriodNsec = 0;
aStreamParameters.iCanSeek = EFalse;
aStreamParameters.iFrameRate=0;
aStreamParameters.iVideoTimeScale=0;
aStreamParameters.iAudioTimeScale=0;
iAudioType = 0;
iVideoType = 0;
iNumberOfFrames=0;
// get video description
error = MP4ParseRequestVideoDescription(iMP4Handle, (mp4_u32 *)&aStreamParameters.iVideoLength,
&frameRate, &iVideoType, (mp4_u32 *)&aStreamParameters.iVideoWidth,
(mp4_u32 *)&aStreamParameters.iVideoHeight, (mp4_u32 *)&aStreamParameters.iVideoTimeScale);
if ( error == MP4_NOT_AVAILABLE )
User::Leave(KErrorCode);
else if ( error == MP4_INVALID_INPUT_STREAM )
User::Leave(KErrCorrupt);
else if ( error == MP4_NO_VIDEO )
{
hasVideo = EFalse;
aStreamParameters.iVideoWidth = aStreamParameters.iVideoHeight = 0;
}
else if ( error != MP4_OK )
User::Leave(KErrorCode);
else
{
MPASSERT(error == MP4_OK);
}
// get audio description. ask also for averagebitrate to get error if the track is empty; the information is needed later on
mp4_u32 averagebitrate = 0;
error = MP4ParseRequestAudioDescription(iMP4Handle, (mp4_u32 *)&aStreamParameters.iAudioLength,
&iAudioType, (mp4_u8*)&aStreamParameters.iAudioFramesInSample, (mp4_u32 *)&aStreamParameters.iAudioTimeScale, &averagebitrate );
if ( (error == MP4_ERROR) && ((iAudioType == MP4_TYPE_MPEG4_AUDIO) || (iAudioType == MP4_TYPE_AMR_NB) || (iAudioType == MP4_TYPE_AMR_WB)))
{
// a special case: there may be audio track but it is empty; if type was recognized, mark as no audio
PRINT((_L("CMP4Parser::ParseHeaderL() problems with getting audio description, mark no audio since audio type was recognized")));
iAudioType = MP4_NO_AUDIO;
error = MP4_NO_AUDIO;
}
if(error == MP4_NOT_AVAILABLE)
User::Leave(EParserNotEnoughData);
else if ( error == MP4_INVALID_INPUT_STREAM )
User::Leave(KErrCorrupt);
else if ( error != MP4_NO_AUDIO && error != MP4_OK )
User::Leave(KErrorCode);
else
{
MPASSERT(error == MP4_OK || error == MP4_NO_AUDIO);
}
// store the sample size for sanity checking purposes
iMaxAMRSampleSize = KVedMaxAMRFrameSize * aStreamParameters.iAudioFramesInSample;
if (aStreamParameters.iVideoLength > 0)
aStreamParameters.iStreamLength = aStreamParameters.iVideoLength;
if (aStreamParameters.iAudioLength > aStreamParameters.iVideoLength)
aStreamParameters.iStreamLength = aStreamParameters.iAudioLength;
aStreamParameters.iFrameRate = frameRate;
if(hasVideo)
{
if ( iVideoType == MP4_TYPE_MPEG4_VIDEO )
{
// read video resolution from VOL header
HBufC8* tmpBuffer = (HBufC8*) HBufC8::NewLC(KVOLHeaderBufferSize);
TPtr8 tmpPtr = tmpBuffer->Des();
mp4_u32 volSize = 0;
MP4Err volError = 0;
volError = MP4ParseReadVideoDecoderSpecificInfo( iMP4Handle,
(mp4_u8*)(tmpPtr.Ptr()),
KVOLHeaderBufferSize,
&volSize );
if ( volError != MP4_OK )
{
User::Leave(KErrorCode);
}
tmpPtr.SetLength(volSize);
CVedVolReader* tmpVolReader = CVedVolReader::NewL();
CleanupStack::PushL(tmpVolReader);
tmpVolReader->ParseVolHeaderL(tmpPtr);
aStreamParameters.iVideoWidth = tmpVolReader->Width();
aStreamParameters.iVideoHeight = tmpVolReader->Height();
CleanupStack::PopAndDestroy(tmpVolReader);
CleanupStack::PopAndDestroy(tmpBuffer);
}
else if ( iVideoType == MP4_TYPE_AVC_PROFILE_BASELINE )
{
// read resolution from SPS
HBufC8* tmpBuffer = (HBufC8*) HBufC8::NewLC(KAVCDCRBufferSize);
TPtr8 ptr = tmpBuffer->Des();
// read decoder specific info
User::LeaveIfError( ReadAVCDecoderSpecificInfo(ptr) );
// create AVC editing instance
CVedAVCEdit* avcEdit = CVedAVCEdit::NewL();
CleanupStack::PushL(avcEdit);
TSize resolution(0,0);
User::LeaveIfError( avcEdit->GetResolution(ptr, resolution) );
CleanupStack::PopAndDestroy(avcEdit);
CleanupStack::PopAndDestroy(tmpBuffer);
aStreamParameters.iVideoWidth = resolution.iWidth;
aStreamParameters.iVideoHeight = resolution.iHeight;
}
iNumberOfFrames = GetNumberOfVideoFrames();
MPASSERT(iNumberOfFrames);
if (iFirstTimeClipParsing) // update only at the first parsing of a clip
{
// update the frame numbers
iOutputNumberOfFrames = iProcessor->GetOutputNumberOfFrames();
if (iOutputNumberOfFrames == 0)
{
iOutputNumberOfFrames += iNumberOfFrames; // total number of frames in all clips
}
else if (!iProcessor->IsThumbnailInProgress())
{
iOutputNumberOfFrames += iNumberOfFrames;
}
}
MPASSERT(frameRate > 0);
if (frameRate > 0)
aStreamParameters.iVideoPicturePeriodNsec = TInt64( TReal(1000000000) / TReal(frameRate) );
else
aStreamParameters.iVideoPicturePeriodNsec = TInt64(33366667);
if ( iVideoType == MP4_TYPE_H263_PROFILE_0 || iVideoType == MP4_TYPE_H263_PROFILE_3 )
{
TVideoClipProperties prop;
prop.iH263Level = 0;
MP4ParseGetVideoClipProperties(iMP4Handle, prop);
iMaxVideoFrameLength = KMaxCodedPictureSizeQCIF;
if (prop.iH263Level == 45)
{
aStreamParameters.iVideoFormat = EVideoFormatH263Profile0Level45;
}
else
{
aStreamParameters.iVideoFormat = EVideoFormatH263Profile0Level10;
}
}
else if ( iVideoType == MP4_TYPE_MPEG4_VIDEO )
{
aStreamParameters.iVideoFormat = EVideoFormatMPEG4;
if ( aStreamParameters.iVideoWidth <= KVedResolutionQCIF.iWidth )
{
iMaxVideoFrameLength = KMaxCodedPictureSizeMPEG4L0BQCIF;//distinction between L0 and L0B not possible here
}
else if (aStreamParameters.iVideoWidth <= KVedResolutionCIF.iWidth )
{
iMaxVideoFrameLength = KMaxCodedPictureSizeMPEG4CIF;
}
else
{
// VGA
iMaxVideoFrameLength = KMaxCodedPictureSizeVGA;
}
}
else if ( iVideoType == MP4_TYPE_AVC_PROFILE_BASELINE )
{
// : Is it possible to dig up the level here ??
aStreamParameters.iVideoFormat = EVideoFormatAVCProfileBaseline;
if ( aStreamParameters.iVideoWidth <= KVedResolutionQCIF.iWidth )
{
iMaxVideoFrameLength = KMaxCodedPictureSizeAVCLevel1B; //distinction between L0 and L0B not possible here ??
}
else if (aStreamParameters.iVideoWidth <= KVedResolutionCIF.iWidth )
{
iMaxVideoFrameLength = KMaxCodedPictureSizeAVCLevel1_2;
}
else
{
// default
iMaxVideoFrameLength = KMaxCodedPictureSizeAVCLevel1_2;
}
}
}
if ( error == MP4_OK )
{
// stream contains audio
if ( iAudioType == MP4_TYPE_AMR_NB )
aStreamParameters.iAudioFormat = EAudioFormatAMR;
else
{
if ( iAudioType == MP4_TYPE_MPEG4_AUDIO )
aStreamParameters.iAudioFormat = EAudioFormatAAC;
}
}
TBool videoMpeg4OrAVC = ( iVideoType == MP4_TYPE_MPEG4_VIDEO ||
iVideoType == MP4_TYPE_AVC_PROFILE_BASELINE );
if ( (videoMpeg4OrAVC && iAudioType == MP4_TYPE_MPEG4_AUDIO) ||
(videoMpeg4OrAVC && iAudioType == MP4_TYPE_NONE) ||
(iVideoType == MP4_TYPE_NONE && iAudioType == MP4_TYPE_MPEG4_AUDIO) )
aStreamParameters.iFileFormat = EFileFormatMP4;
else if (iVideoType != MP4_TYPE_NONE || iAudioType != MP4_TYPE_NONE)
aStreamParameters.iFileFormat = EFileFormat3GP;
if ( aStreamParameters.iStreamLength == 0 )
aStreamParameters.iFileFormat = EFileFormatUnrecognized;
if ( aStreamParameters.iVideoFormat != EVideoFormatNone )
{
aStreamParameters.iHaveVideo = ETrue;
aStreamParameters.iNumDemuxChannels++;
}
if ( aStreamParameters.iAudioFormat != EAudioFormatNone )
{
aStreamParameters.iHaveAudio = ETrue;
aStreamParameters.iNumDemuxChannels++;
}
aStreamParameters.iMaxPacketSize = 0; // N/A
aStreamParameters.iLogicalChannelNumberVideo = 0; // N/A
aStreamParameters.iLogicalChannelNumberAudio = 0; // N/A
// get stream description
error = MP4ParseRequestStreamDescription(iMP4Handle, (mp4_u32 *)&aStreamParameters.iStreamSize,
(mp4_u32 *)&aStreamParameters.iStreamBitrate);
if ( error != MP4_OK )
User::Leave(KErrorCode);
// do sanity-check for bitrate
if (aStreamParameters.iStreamBitrate < KMinBitrate)
User::Leave(KErrCorrupt);
aStreamParameters.iReferencePicturesNeeded = 0;
aStreamParameters.iNumScalabilityLayers = 0;
// determine if the stream contains INTRA frames so seeking is possible
// If the stream contains more than one INTRA frame, seeking is
// allowed.
if (hasVideo)
{
mp4_u32 position = aStreamParameters.iStreamLength + 1000;
mp4_u32 audioTime, videoTime;
// Seek past stream duration to find out the position of last INTRA frame
error = MP4ParseSeek(iMP4Handle, position, &audioTime, &videoTime, ETrue);
if ( error != MP4_OK )
User::Leave(KErrorCode);
if (videoTime != 0)
{
// at least two INTRAs
aStreamParameters.iCanSeek = ETrue;
}
// rewind file back to beginning
error = MP4ParseSeek(iMP4Handle, 0, &audioTime, &videoTime, EFalse);
if ( error != MP4_OK )
User::Leave(KErrorCode);
}
PRINT((_L("CMP4Parser::ParseHeaderL() end")));
return KErrNone;
}
// ---------------------------------------------------------
// CMP4Parser::IsStreamable
// Finds out whether input stream is multiplexed so that
// it can be streamed, i.e. played while receiving the stream.
// (other items were commented in a header).
// ---------------------------------------------------------
//
TInt CMP4Parser::IsStreamable()
{
MP4Err error;
error = MP4ParseIsStreamable(iMP4Handle);
if ( error == MP4_NOT_AVAILABLE )
return EParserNotEnoughData;
else if ( error == MP4_INVALID_INPUT_STREAM )
return KErrNotSupported;
else if ( error == MP4_ERROR )
return KErrorCode;
else
return ( error == MP4_OK ) ? 1 : 0;
}
// ---------------------------------------------------------
// CMP4Parser::Seek
// Seeks the file to desired position in milliseconds
// (other items were commented in a header).
// ---------------------------------------------------------
//
TInt CMP4Parser::Seek(TUint32 aPositionInMs, TUint32& anAudioTimeAfter, TUint32& aVideoTimeAfter)
{
MP4Err error = MP4_OK;
MPASSERT(iStreamSource == ESourceFile);
error = MP4ParseSeek(iMP4Handle, aPositionInMs, &anAudioTimeAfter, &aVideoTimeAfter, ETrue);
if (error != MP4_OK)
return KErrorCode;
return KErrNone;
}
TInt CMP4Parser::GetNumberOfFrames()
{
return iNumberOfFrames;
}
// ---------------------------------------------------------
// CMP4Parser::SeekOptimalIntraFrame
// Seeks to INTRA frame position before given time
// (other items were commented in a header).
// ---------------------------------------------------------
//
TInt CMP4Parser::SeekOptimalIntraFrame(TTimeIntervalMicroSeconds aStartTime, TInt /*aIndex*/, TBool aFirstTime)
{
MP4Err error = KErrNone;
TInt revisedNumberOfFrames = 0;
mp4_u32 audioTime = 0;
mp4_u32 videoTime = 0;
// mp4_u32 timeScale = 0;
// calculate the start time of the cut operation
TInt64 startTime = aStartTime.Int64() / TInt64(1000);
mp4_u32 startPosition = (mp4_u32)( I64INT(startTime) ); // in milliseconds
TBool intraFound = (startPosition == 0);
if (!aFirstTime)
intraFound = 0;
// First check if the first included frame is intra
if (!intraFound)
{
// seek to previous frame preceding start time or at start time
error = MP4ParseSeek(iMP4Handle, startPosition, &audioTime, &videoTime, EFalse);
if (error != MP4_OK)
{
return KErrorCode;
}
MPASSERT(videoTime <= startPosition);
// get index of the frame
TInt index = iProcessor->GetVideoFrameIndex(TTimeIntervalMicroSeconds(videoTime*1000));
if (videoTime < startPosition)
{
// if there was no frame at start time, seek
// one frame forward to the first included frame
index++;
}
// get frame type
mp4_bool frameType;
error = MP4ParseGetVideoFrameType(iMP4Handle, index, &frameType);
if (error != MP4_OK)
{
return KErrorCode;
}
if (frameType == 1)
{
intraFound = ETrue;
iStartFrameIndex = index;
mp4_u32 timeInTicks;
// get timestamp of matched frame to startPosition
error = MP4ParseGetVideoFrameStartTime(iMP4Handle, index, &timeInTicks, &startPosition);
if (error != MP4_OK)
{
return KErrorCode;
}
// Now seek to found Intra in 1 ms increments. The loop is needed
// because due to rounding error, MP4Parser may seek to previous
// frame instead of the I-frame at startPosition
TInt seekTime = startPosition;
videoTime = 0;
while (videoTime != startPosition)
{
error = MP4ParseSeek(iMP4Handle, seekTime, &audioTime, &videoTime, EFalse);
if (error != MP4_OK)
{
return KErrorCode;
}
MPASSERT(videoTime <= startPosition);
seekTime++; // add 1 ms
}
}
}
if (!intraFound)
{
// seek to the I-frame preceding the start time
error = MP4ParseSeek(iMP4Handle, startPosition, &audioTime, &videoTime, ETrue);
if (error != MP4_OK)
{
return KErrorCode;
}
}
if (videoTime != 0)
{
if (!intraFound)
{
// get index of the intra frame
TInt64 time = TInt64(TUint(videoTime)) * TInt64(1000);
iStartFrameIndex = iProcessor->GetVideoFrameIndex(TTimeIntervalMicroSeconds( time ));
}
if (aFirstTime)
{
revisedNumberOfFrames = iNumberOfFrames - iStartFrameIndex;
// update movie and clip number of frames
iOutputNumberOfFrames -= iStartFrameIndex;
iNumberOfFrames = revisedNumberOfFrames;
}
PRINT((_L("CMP4Parser::SeekOptimalIntraFrame() revised = %d"),revisedNumberOfFrames));
PRINT((_L("CMP4Parser::SeekOptimalIntraFrame() iNumberOfFrames = %d"),iNumberOfFrames));
PRINT((_L("CMP4Parser::SeekOptimalIntraFrame() iStartFrameIndex = %d"),iStartFrameIndex));
PRINT((_L("CMP4Parser::SeekOptimalIntraFrame() iOutputNumberOfFrames = %d"),iOutputNumberOfFrames));
iFrameNumber = iStartFrameIndex;
}
return KErrNone;
}
// ---------------------------------------------------------
// CMP4Parser::GetNumberOfVideoFrames
// Gets the number of video frames in clip
// (other items were commented in a header).
// ---------------------------------------------------------
//
TInt CMP4Parser::GetNumberOfVideoFrames()
{
mp4_u32 numberOfFrames = 0;
MP4Err error = 0;
error = MP4ParseGetNumberOfVideoFrames(iMP4Handle, &numberOfFrames);
if (error != MP4_OK)
return 0;
return numberOfFrames;
}
// ---------------------------------------------------------
// CMP4Parser::GetVideoFrameSize
// Gets the size of video frame at given index
// (other items were commented in a header).
// ---------------------------------------------------------
//
TInt CMP4Parser::GetVideoFrameSize(TInt aIndex)
{
mp4_u32 frameSize = 0;
mp4_u32 mp4Specific = 0;
MP4Err error = 0;
if ( aIndex == 0 && iVideoType == MP4_TYPE_MPEG4_VIDEO )
{
error = MP4ParseReadVideoDecoderSpecificInfo( iMP4Handle, 0, 0, &mp4Specific );
if ( error != MP4_OK && error != MP4_BUFFER_TOO_SMALL )
return KErrorCode;
}
error = MP4ParseGetVideoFrameSize(iMP4Handle, aIndex, &frameSize);
if (error != MP4_OK)
return KErrorCode;
return frameSize + mp4Specific;
}
// ---------------------------------------------------------
// CMP4Parser::GetVideoFrameStartTime
// Returns frame start time in millisec - optional iTimestamp for start time in ticks
// (other items were commented in a header).
// ---------------------------------------------------------
//
TInt CMP4Parser::GetVideoFrameStartTime(TInt aIndex, TInt* aTimeStampInTicks)
{
MP4Err error = 0;
mp4_u32 timeStampInMs = 0;
MPASSERT(aTimeStampInTicks);
PRINT((_L("CMP4Parser::GetVideoFrameStartTime(), get time for index %d"), aIndex));
error = MP4ParseGetVideoFrameStartTime(iMP4Handle, aIndex, (mp4_u32*)aTimeStampInTicks, &timeStampInMs);
if (error != MP4_OK)
{
PRINT((_L("CMP4Parser::GetVideoFrameStartTime(), error from MP4 parser %d"), error));
return KErrorCode;
}
PRINT((_L("CMP4Parser::GetVideoFrameStartTime(), time in ms %d"), timeStampInMs));
return timeStampInMs;
}
// ---------------------------------------------------------
// CMP4Parser::GetVideoFrameType
// Gets the type of video frame at given index
// (other items were commented in a header).
// ---------------------------------------------------------
//
TInt8 CMP4Parser::GetVideoFrameType(TInt aIndex)
{
MP4Err error = 0;
mp4_bool frameType;
error = MP4ParseGetVideoFrameType(iMP4Handle, aIndex, &frameType);
if (error != MP4_OK)
return KErrGeneral;
return TInt8(frameType);
}
// ---------------------------------------------------------
// CMP4Parser::GetVideoFrameProperties
// Gets frame properties
// (other items were commented in a header).
// ---------------------------------------------------------
//
TInt CMP4Parser::GetVideoFrameProperties(TFrameInfoParameters* aVideoFrameInfoArray,
TUint32 aStartIndex, TUint32 aSizeOfArray)
{
MP4Err error;
error = MP4GetVideoFrameProperties(iMP4Handle,aStartIndex,aSizeOfArray,aVideoFrameInfoArray);
if (error != MP4_OK)
return KErrorCode;
return KErrNone;
}
// ---------------------------------------------------------
// CMP4Parser::ParseAudioInfo
// Gets the frame information (frame size) for audio in the current clip
// (other items were commented in a header).
// ---------------------------------------------------------
//
TInt CMP4Parser::ParseAudioInfo(TInt& aAudioFrameSize)
{
MP4Err error;
mp4_u32 audioLength=0;
mp4_u32 audioType=0;
mp4_u32 audioFramesInSample=0;
mp4_u32 audioTimeScale=0;
mp4_u32 avgBitRate=0;
mp4_double frameLength = 0.02; // 20 ms
error = MP4ParseRequestAudioDescription(iMP4Handle, (mp4_u32 *)&audioLength,
&audioType, (mp4_u8*)&audioFramesInSample, (mp4_u32 *)&audioTimeScale, (mp4_u32*)&avgBitRate);
aAudioFrameSize = ((mp4_u32)((mp4_double)avgBitRate*frameLength)>>3);
return error;
}
TInt CMP4Parser::GetMP4SpecificSize()
{
MP4Err error = 0;
mp4_u32 mp4Specific = 0;
if ( iVideoType == MP4_TYPE_MPEG4_VIDEO )
{
error = MP4ParseReadVideoDecoderSpecificInfo( iMP4Handle, 0, 0, &mp4Specific );
if ( error != MP4_OK && error != MP4_BUFFER_TOO_SMALL )
return KErrGeneral;
}
return mp4Specific;
}
// ---------------------------------------------------------
// CMP4Parser::ReadAudioDecoderSpecificInfoL
// Gets the decoder specific info from the current file filled so that it can be composed to output file
// (other items were commented in a header).
// ---------------------------------------------------------
//
TInt CMP4Parser::ReadAudioDecoderSpecificInfoL(HBufC8*& aBytes, TInt aBufferSize)
{
aBytes = HBufC8::NewL(aBufferSize);
CleanupStack::PushL(aBytes);
mp4_u8 *buffer = new (ELeave) mp4_u8[aBufferSize];
mp4_u32 decspecinfosize = 0;
MP4Err err = MP4ParseReadAudioDecoderSpecificInfo(
iMP4Handle,
buffer,
aBufferSize,
&decspecinfosize);
if (err == MP4_OK)
{
for (TInt a = 0 ; a < (TInt)decspecinfosize ; a++)
{
aBytes->Des().Append(buffer[a]);
}
}
else
{
delete[] buffer;
buffer = 0;
CleanupStack::PopAndDestroy(aBytes);
aBytes = 0;
User::Leave(KErrGeneral);
}
delete[] buffer;
buffer = 0;
CleanupStack::Pop(aBytes);
return ETrue;
}
// ---------------------------------------------------------
// CMP4Parser::SetDefaultAudioDecoderSpecificInfoL
// Gets the default decoder specific info filled so that it can be composed to output file
// the default info is for a 16 KHz, mono LC type AAC only
// (other items were commented in a header).
// ---------------------------------------------------------
//
TInt CMP4Parser::SetDefaultAudioDecoderSpecificInfoL(HBufC8*& aBytes, TInt aBufferSize)
{
aBytes = HBufC8::NewL(aBufferSize);
CleanupStack::PushL(aBytes);
const TUint8 frameDecSpecInfo[] = {0x14,0x08}; //the decoder specific
const TInt frameSize = 2; //constant as maximum size of decoderspecific info is 2
for (TInt a = 0 ; a < frameSize ; a++)
{
aBytes->Des().Append(frameDecSpecInfo[a]);
}
CleanupStack::Pop(aBytes);
return ETrue;
}
TInt CMP4Parser::GetAudioBitrate(TInt& aBitrate)
{
mp4_u32 length;
mp4_u32 type;
mp4_u8 framesPerSample;
mp4_u32 timeScale;
mp4_u32 averageBitrate;
MP4Err error = 0;
error = MP4ParseRequestAudioDescription(iMP4Handle, &length,
&type, &framesPerSample, &timeScale, &averageBitrate);
if ( error != 0 )
return KErrGeneral;
aBitrate = averageBitrate;
return KErrNone;
}
TInt CMP4Parser::GetVideoFrameRate(TReal& aFrameRate)
{
mp4_u32 length;
mp4_double frameRate;
mp4_u32 videoType;
mp4_u32 width;
mp4_u32 height;
mp4_u32 timeScale;
MP4Err error = 0;
// get video description
error = MP4ParseRequestVideoDescription(iMP4Handle, &length, &frameRate,
&videoType, &width, &height, &timeScale);
if ( error != 0 )
return KErrGeneral;
TReal temp = frameRate * 2.0;
TInt temp2 = TInt(temp + 0.5);
aFrameRate = temp2 / 2.0;
return KErrNone;
}
TInt CMP4Parser::GetDecoderSpecificInfoSize()
{
MPASSERT(iMP4Handle);
mp4_u32 mp4Specific = 0;
MP4Err error = MP4ParseReadVideoDecoderSpecificInfo( iMP4Handle, 0, 0, &mp4Specific );
if ( error != MP4_OK && error != MP4_BUFFER_TOO_SMALL )
return KErrGeneral;
return mp4Specific;
}
TInt CMP4Parser::ReadAVCDecoderSpecificInfo(TDes8& buf)
{
mp4_u32 mp4Specific = 0;
MP4Err error = MP4ParseReadVideoDecoderSpecificInfo( iMP4Handle, (mp4_u8*)(buf.Ptr()),
mp4_u32( buf.MaxLength() ), &mp4Specific );
if (error != MP4_OK)
return KErrorCode;
buf.SetLength(mp4Specific);
return KErrNone;
}
TInt CMP4Parser::GetVideoDuration(TInt& aDurationInMs)
{
mp4_u32 length;
mp4_double frameRate;
mp4_u32 videoType;
mp4_u32 width;
mp4_u32 height;
mp4_u32 timeScale;
MP4Err error = 0;
// get video description
error = MP4ParseRequestVideoDescription(iMP4Handle, &length, &frameRate,
&videoType, &width, &height, &timeScale);
if ( error != MP4_OK )
return KErrGeneral;
aDurationInMs = length;
return KErrNone;
}
// End of File