diff -r 000000000000 -r 71ca22bcf22a mmserv/thumbnailengine/TneProcessorSrc/mp4parser.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mmserv/thumbnailengine/TneProcessorSrc/mp4parser.cpp Tue Feb 02 01:08:46 2010 +0200 @@ -0,0 +1,739 @@ +/* +* Copyright (c) 2006 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* This component and the accompanying materials are made available +* under the terms of "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: +* +* Description: TNE Parser +* +*/ + + + +// INCLUDE FILES +#include "mp4parser.h" +#include "TNEProcessorImpl.h" +#include "TNEVideosettings.h" + +// ASSERTIONS +#define MPASSERT(x) __ASSERT_DEBUG(x, User::Panic(_L("CTNEProcessorImpl"), EInternalAssertionFailure)) + +#ifdef _DEBUG +#include +#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; + +// ================= MEMBER FUNCTIONS ======================= + +// C++ default constructor can NOT contain any code, that +// might leave. +// + +CMP4Parser::CMP4Parser() +{ + iMP4Handle = 0; + iVideoType = 0; + iBytesRead = 0; + iFirstRead = ETrue; // added for Mp4 + iFirstFrameInfo = ETrue; // added for Mp4 + iOutputNumberOfFrames = 0; + iStreamSource = ESourceFile; + iFrameNumber=0; + iFirstTimeClipParsing=ETrue; + iStartFrameIndex=0; +} + +// Two-phased constructor. + +CMP4Parser* CMP4Parser::NewL(CTNEProcessorImpl* aProcessor, RFile& aFileHandle) +{ + CMP4Parser *self = new (ELeave) CMP4Parser; + CleanupStack::PushL(self); + + self->ConstructL(aProcessor, aFileHandle); + + CleanupStack::Pop(); // self + + return self; +} + +// Symbian OS default constructor can leave. + +void CMP4Parser::ConstructL(CTNEProcessorImpl* aProcessor, RFile& aFileHandle) +{ + MP4Err error; + iProcessor = aProcessor; + + // open MP4 library + if ( iStreamSource == ESourceFile ) + { + iFileHandle.Duplicate(aFileHandle); + error = MP4ParseOpenFileHandle(&iMP4Handle, &iFileHandle); + } + else + { + error = MP4ParseOpen(&iMP4Handle, 0); + } + + if (error != MP4_OK) + { + if (error == MP4_OUT_OF_MEMORY) + User::Leave(KErrNoMemory); + else + User::Leave(KErrorCode); + } +} + +// Destructor +CMP4Parser::~CMP4Parser() +{ + // Close the Duplicate file handle + iFileHandle.Close(); + + if (iMP4Handle) + MP4ParseClose(iMP4Handle); + + iMP4Handle = 0; +} + + +// --------------------------------------------------------- +// 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: + case MP4_TYPE_AVC_PROFILE_BASELINE: + // @@ YHK HARI IMP Do we need to support other AVC profiles ?? + + MPASSERT( type == iVideoType ); + iNextFrameType = EFrameTypeVideo; + break; + + default: + return KErrNotSupported; + } + } + else + { + // library reads the file + //MPASSERT(iStreamSource == ESourceFile); + type = iVideoType; + 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) +{ + +// @@ YHK if the frame typ ei s audio do we need to skip +// it or can we hard code it to video type the 3GP lib +// will return the appropriate type of the frame information. + + + 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 = iVideoType; // 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 && 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.iFileFormat = EFileFormatUnrecognized; + aStreamParameters.iVideoFormat = EVideoFormatNone; + aStreamParameters.iVideoLength = 0; + aStreamParameters.iStreamLength = 0; + aStreamParameters.iVideoPicturePeriodNsec = 0; + aStreamParameters.iCanSeek = EFalse; + aStreamParameters.iFrameRate=0; + aStreamParameters.iVideoTimeScale=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); + } + + + if (aStreamParameters.iVideoLength > 0) + aStreamParameters.iStreamLength = aStreamParameters.iVideoLength; + + aStreamParameters.iFrameRate = frameRate; + + if(hasVideo) + { + iNumberOfFrames = GetNumberOfVideoFrames(); + MPASSERT(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 <= KTNEResolutionQCIF.iWidth ) + { + iMaxVideoFrameLength = KMaxCodedPictureSizeMPEG4L0BQCIF;//distinction between L0 and L0B not possible here + } + else if (aStreamParameters.iVideoWidth <= KTNEResolutionCIF.iWidth ) + { + iMaxVideoFrameLength = KMaxCodedPictureSizeMPEG4CIF; + } + else + { + // VGA + iMaxVideoFrameLength = KMaxCodedPictureSizeVGA; + } + } + else if ( iVideoType == MP4_TYPE_AVC_PROFILE_BASELINE || + iVideoType == MP4_TYPE_AVC_PROFILE_MAIN || + iVideoType == MP4_TYPE_AVC_PROFILE_EXTENDED ) + { + if (iVideoType == MP4_TYPE_AVC_PROFILE_BASELINE) + aStreamParameters.iVideoFormat = EVideoFormatAVCProfileBaseline; + else if (iVideoType == MP4_TYPE_AVC_PROFILE_MAIN) + aStreamParameters.iVideoFormat = EVideoFormatAVCProfileMain; + else if (iVideoType == MP4_TYPE_AVC_PROFILE_EXTENDED) + aStreamParameters.iVideoFormat = EVideoFormatAVCProfileExtended; + + if ( aStreamParameters.iVideoWidth <= KTNEResolutionQCIF.iWidth ) + { + iMaxVideoFrameLength = KMaxCodedPictureSizeMPEG4L0BQCIF;//distinction between L0 and L0B not possible here + } + else if (aStreamParameters.iVideoWidth <= KTNEResolutionCIF.iWidth ) + { + iMaxVideoFrameLength = KMaxCodedPictureSizeMPEG4CIF; + } + else + { + // VGA + iMaxVideoFrameLength = KMaxCodedPictureSizeVGA; + } + } + } + + + if ( iVideoType == MP4_TYPE_MPEG4_VIDEO || + iVideoType == MP4_TYPE_MPEG4_VIDEO || + iVideoType == MP4_TYPE_NONE ) + aStreamParameters.iFileFormat = EFileFormatMP4; + + else if (iVideoType != MP4_TYPE_NONE) + aStreamParameters.iFileFormat = EFileFormat3GP; + + if ( aStreamParameters.iStreamLength == 0 ) + aStreamParameters.iFileFormat = EFileFormatUnrecognized; + + if ( aStreamParameters.iVideoFormat != EVideoFormatNone ) + { + aStreamParameters.iHaveVideo = ETrue; + } + + aStreamParameters.iMaxPacketSize = 0; // N/A + aStreamParameters.iLogicalChannelNumberVideo = 0; // N/A + + // get stream description + error = MP4ParseRequestStreamDescription(iMP4Handle, (mp4_u32 *)&aStreamParameters.iStreamSize, + (mp4_u32 *)&aStreamParameters.iStreamBitrate); + if ( error != MP4_OK ) + User::Leave(KErrorCode); + + 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::SeekOptimalIntraFrame +// Seeks to INTRA frame position before given time +// (other items were commented in a header). +// --------------------------------------------------------- +// +TInt CMP4Parser::SeekOptimalIntraFrame(TTimeIntervalMicroSeconds aStartTime, TInt aIndex) +{ + 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) ); + + // 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) + { + // at least one INTRA frame upto its last P frame will be ignored + // re-calculate the number of frames accordingly + + MPASSERT(aIndex > 0); + + // search backwards from cut point to intra to find the index of the intra frame + TUint timeInMs; + TInt timeInTicks; + for (TInt i = aIndex; i >= 0; i--) + { + timeInMs = GetVideoFrameStartTime(i, &timeInTicks); + if ( timeInMs == videoTime ) + { + iStartFrameIndex = i; + revisedNumberOfFrames = iNumberOfFrames - iStartFrameIndex; + break; + } + } + + // update movie and clip number of frames + iOutputNumberOfFrames -= iStartFrameIndex; + iNumberOfFrames = revisedNumberOfFrames; + + // return to beginning of I-frame + error = MP4ParseSeek(iMP4Handle, startPosition, &audioTime, &videoTime, ETrue); + if (error != MP4_OK) + { + return KErrorCode; + } + else + { + 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; + +} + + +// @@ YHK need this for getting the optimal frame +// --------------------------------------------------------- +// 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); + + error = MP4ParseGetVideoFrameStartTime(iMP4Handle, aIndex, (mp4_u32*)aTimeStampInTicks, &timeStampInMs); + + if (error != MP4_OK) + return KErrorCode; + + 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); +} + + +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; +} + +// End of File