diff -r 000000000000 -r 1bce908db942 multimediacommscontroller/mmccvideosourcesink/src/mccvideojitterbuffer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/multimediacommscontroller/mmccvideosourcesink/src/mccvideojitterbuffer.cpp Tue Feb 02 01:04:58 2010 +0200 @@ -0,0 +1,654 @@ +/* +* Copyright (c) 2003 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: +* +*/ + + + + +// INCLUDES FILES +#include "mccvideojitterbuffer.h" +#include "mccvideosourcesinklogs.h" +#include "mccdef.h" +#include + +// CONSTANTS +const TUint KMccMilliSecondInMicroSecs = 1000; +const TUint KMccMinInactivityTimeoutInMicroSecs = 1000000; // 1 sec + +#define MCC_CONVERT_TO_90KHZ_CLOCK( valInMicroSecs ) ( valInMicroSecs / 100 * 9 ) + +// --------------------------------------------------------------------------- +// CMccVideoPacket::NewLC +// --------------------------------------------------------------------------- +// +CMccVideoJitterBuffer::CMccVideoPacket* CMccVideoJitterBuffer::CMccVideoPacket::NewLC( + TUint aStreamId, + const TRtpRecvHeader& aHeaderInfo, + const TDesC8& aPayloadData, + TBool aImportantData ) + { + CMccVideoPacket* self = + new ( ELeave ) CMccVideoPacket( aStreamId, aHeaderInfo, aImportantData ); + CleanupStack::PushL( self ); + self->ConstructL( aPayloadData ); + return self; + } + +// --------------------------------------------------------------------------- +// CMccVideoPacket::~CMccVideoPacket +// --------------------------------------------------------------------------- +// +CMccVideoJitterBuffer::CMccVideoPacket::~CMccVideoPacket() + { + delete iPayloadData; + } + +// --------------------------------------------------------------------------- +// CMccVideoPacket::StreamId +// --------------------------------------------------------------------------- +// +TUint CMccVideoJitterBuffer::CMccVideoPacket::StreamId() + { + return iStreamId; + } + +// --------------------------------------------------------------------------- +// CMccVideoPacket::HeaderInfo +// --------------------------------------------------------------------------- +// +TRtpRecvHeader& CMccVideoJitterBuffer::CMccVideoPacket::HeaderInfo() + { + return iHeaderInfo; + } + +// --------------------------------------------------------------------------- +// CMccVideoPacket::PayloadData +// --------------------------------------------------------------------------- +// +const TDesC8& CMccVideoJitterBuffer::CMccVideoPacket::PayloadData() + { + return *iPayloadData; + } + +// --------------------------------------------------------------------------- +// CMccVideoPacket::CMccVideoPacket +// --------------------------------------------------------------------------- +// +CMccVideoJitterBuffer::CMccVideoPacket::CMccVideoPacket( + TUint aStreamId, + const TRtpRecvHeader& aHeaderInfo, + TBool aImportantData ) : + iStreamId( aStreamId ), + iHeaderInfo( aHeaderInfo ), + iImportantData( aImportantData ) + { + } + +// --------------------------------------------------------------------------- +// CMccVideoPacket::ConstructL +// --------------------------------------------------------------------------- +// +void CMccVideoJitterBuffer::CMccVideoPacket::ConstructL( const TDesC8& aPayloadData ) + { + iPayloadData = aPayloadData.AllocL(); + } + + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::InsertInSeqNumOrder +// --------------------------------------------------------------------------- +// +TInt CMccVideoJitterBuffer::CMccVideoPacket::InsertInSeqNumOrder( + const CMccVideoPacket& aPacket1, const CMccVideoPacket& aPacket2 ) + { + if ( aPacket1.iHeaderInfo.iSeqNum == aPacket2.iHeaderInfo.iSeqNum ) + { + return 0; + } + return ( aPacket1.iHeaderInfo.iSeqNum < aPacket2.iHeaderInfo.iSeqNum ) ? -1 : 1; + } + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::DroppingAllowed +// --------------------------------------------------------------------------- +// +TBool CMccVideoJitterBuffer::CMccVideoPacket::DroppingAllowed() const + { + return ( !iImportantData ); + } + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::CMccVideoJitterBuffer +// C++ default constructor can NOT contain any code, that might leave. +// --------------------------------------------------------------------------- +// +CMccVideoJitterBuffer::CMccVideoJitterBuffer( + MMccVideoJitterBufferObserver& aObserver, + CXPSPacketSink& aPacketSink, + TUint aInactivityTimeoutInMs ) : + CTimer( CActive::EPriorityStandard ), + iObserver( aObserver ), + iPacketSink( aPacketSink ), + iInactivityTimeoutInMicroSecs( aInactivityTimeoutInMs * KMccMilliSecondInMicroSecs ), + iPreviousTime( 0 ) + { + CActiveScheduler::Add( this ); + + iInactivityTimeoutInMicroSecs = aInactivityTimeoutInMs * KMccMilliSecondInMicroSecs; + + __V_SOURCESINK_CONTROLL_INT1( + "CMccVideoJitterBuffer::CMccVideoJitterBuffer, inactivity timeout:", + iInactivityTimeoutInMicroSecs ) + + if ( iInactivityTimeoutInMicroSecs < KMccMinInactivityTimeoutInMicroSecs ) + { + iInactivityTimeoutInMicroSecs = KMccMinInactivityTimeoutInMicroSecs; + __V_SOURCESINK_CONTROLL_INT1( + "CMccVideoJitterBuffer::CMccVideoJitterBuffer, inactivity timeout adjusted:", + iInactivityTimeoutInMicroSecs ) + } + } + + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::ConstructL +// Symbian 2nd phase constructor can leave. +// --------------------------------------------------------------------------- +// +void CMccVideoJitterBuffer::ConstructL() + { + __V_SOURCESINK_CONTROLL( "CMccVideoJitterBuffer::ConstructL" ) + + CTimer::ConstructL(); + + ConfigureL( KMccJitterBufferDefaultLowLimit, + KMccJitterBufferDefaultHighLimit, + KMccJitterBufferDefaultPlayThreshold, + KMccJitterBufferDefaultMaxSize, + KMccDefaultVideoFrameRate ); + + __V_SOURCESINK_CONTROLL( "CMccVideoJitterBuffer::ConstructL, exit" ) + } + + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::NewL +// Two-phased constructor. +// --------------------------------------------------------------------------- +// +CMccVideoJitterBuffer* CMccVideoJitterBuffer::NewL( + MMccVideoJitterBufferObserver& aObserver, + CXPSPacketSink& aPacketSink, + TUint aInactivityTimeoutInMs ) + { + CMccVideoJitterBuffer* self = CMccVideoJitterBuffer::NewLC( + aObserver, aPacketSink, aInactivityTimeoutInMs ); + CleanupStack::Pop( self ); + return self; + } + + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::NewLC +// Two-phased constructor. +// --------------------------------------------------------------------------- +// +CMccVideoJitterBuffer* CMccVideoJitterBuffer::NewLC( + MMccVideoJitterBufferObserver& aObserver, + CXPSPacketSink& aPacketSink, + TUint aInactivityTimeoutInMs ) + { + CMccVideoJitterBuffer* self = new( ELeave ) CMccVideoJitterBuffer( + aObserver, aPacketSink, aInactivityTimeoutInMs ); + CleanupStack::PushL( self ); + self->ConstructL(); + return self; + } + + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::~CMccVideoJitterBuffer +// Destructor. +// --------------------------------------------------------------------------- +// +CMccVideoJitterBuffer::~CMccVideoJitterBuffer() + { + __V_SOURCESINK_CONTROLL( "CMccVideoJitterBuffer::~CMccVideoJitterBuffer" ) + + iQueue.ResetAndDestroy(); + + __V_SOURCESINK_CONTROLL( "CMccVideoJitterBuffer::~CMccVideoJitterBuffer, exit" ) + } + + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::ConfigureL +// Jitterbuffer gathers data until defined play threshold is exceeded. +// After that, packets are posted with certain interval towards Helix. In case +// buffer is close to running out of data (low limit reached), posting interval +// is increased in order to increase data amount in buffer. Ideal situation +// is that buffer has all the time some data so that playback can be kept +// smooth. If high limit is reached, posting interval is tightened to avoid +// max limit reach. If max limit is anyhow reached, jitterbuffer makes room +// by just pushing some amount of oldest data towards Helix. +// +// --------------------------------------------------------------------------- +// +void CMccVideoJitterBuffer::ConfigureL( + TUint aLowLimitMs, + TUint aHighLimitMs, + TUint aPlayThresholdMs, + TUint aMaxSizeMs, + TUint aFrameRateFps ) + { + __V_SOURCESINK_CONTROLL_INT1( + "CMccVideoJitterBuffer::ConfigureL, framerate:", aFrameRateFps ) + __V_SOURCESINK_CONTROLL_INT1( "low limit:", aLowLimitMs ) + __V_SOURCESINK_CONTROLL_INT1( "high limit:", aHighLimitMs ) + __V_SOURCESINK_CONTROLL_INT1( "play threshold:", aPlayThresholdMs ) + __V_SOURCESINK_CONTROLL_INT1( "max size:", aMaxSizeMs ) + + __ASSERT_ALWAYS( aFrameRateFps > 0, User::Leave( KErrArgument ) ); + __ASSERT_ALWAYS( aLowLimitMs < aPlayThresholdMs, User::Leave( KErrArgument ) ); + __ASSERT_ALWAYS( aPlayThresholdMs < aHighLimitMs, User::Leave( KErrArgument ) ); + __ASSERT_ALWAYS( aHighLimitMs < aMaxSizeMs, User::Leave( KErrArgument ) ); + + iLowLimitMicroSecs = aLowLimitMs * KMccMilliSecondInMicroSecs; + iHighLimitMicroSecs = aHighLimitMs * KMccMilliSecondInMicroSecs; + iPlayThresholdMicroSecs = aPlayThresholdMs * KMccMilliSecondInMicroSecs; + iMaxSizeMicroSecs = aMaxSizeMs * KMccMilliSecondInMicroSecs; + iFrameRate = aFrameRateFps; + + const TInt KMccSecondInMicroSecs = 1000000; + + const TReal KMccLowLimitInterval = 1.4; + const TReal KMccHighLimitInterval = 0.7; + iNormalInterval = KMccSecondInMicroSecs / iFrameRate; + iLowLimitInterval = (TInt)( iNormalInterval.Int() * KMccLowLimitInterval ); + iHighLimitInterval = (TInt)( iNormalInterval.Int() * KMccHighLimitInterval ); + + iCurrentInterval = iNormalInterval; + + __V_SOURCESINK_CONTROLL_INT1( "normal interval:", iNormalInterval.Int() ) + __V_SOURCESINK_CONTROLL_INT1( "low limit interval:", iLowLimitInterval.Int() ) + __V_SOURCESINK_CONTROLL_INT1( "high limit interval:", iHighLimitInterval.Int() ) + } + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::EnqueueL +// Packets are posted towards Helix in receiving order, queuing could be +// seq num based but out of order packets are rare. +// --------------------------------------------------------------------------- +// +CMccVideoJitterBuffer::TMccPacketBufferingStatus CMccVideoJitterBuffer::EnqueueL( + TUint aStreamId, + const TRtpRecvHeader& aHeaderInfo, + const TDesC8& aPayloadData, + TBool aImportantData ) + { + CMccVideoPacket* packet = + CMccVideoPacket::NewLC( aStreamId, aHeaderInfo, aPayloadData, aImportantData ); + TLinearOrder ordering( CMccVideoPacket::InsertInSeqNumOrder ); + iQueue.InsertInOrderAllowRepeatsL( packet, ordering ); + CleanupStack::Pop( packet ); + + UpdateFrameCount( *packet, ETrue ); + + if ( PlayThresholdExceeded() ) + { + if ( MaxSizeExceeded() ) + { + MakeRoom(); + } + + Start(); + } + + return PlayingStatus(); + } + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::Play +// --------------------------------------------------------------------------- +// +void CMccVideoJitterBuffer::Play() + { + if ( PlayThresholdExceeded() ) + { + Start(); + } + } + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::Pause +// --------------------------------------------------------------------------- +// +void CMccVideoJitterBuffer::Pause() + { + Cancel(); + } + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::PlayThresholdInMs +// --------------------------------------------------------------------------- +// +TInt CMccVideoJitterBuffer::PlayThresholdInMs() + { + const TInt KMccMilliSecondInMicroSecs = 1000; + return ( iPlayThresholdMicroSecs / KMccMilliSecondInMicroSecs ); + } + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::Start +// --------------------------------------------------------------------------- +// +void CMccVideoJitterBuffer::Start() + { + TTimeIntervalMicroSeconds32 newCurrentInterval = CheckCurrentInterval(); + if ( newCurrentInterval != iCurrentInterval ) + { + __V_SOURCESINK_CONTROLL_INT1( + "CMccVideoJitterBuffer::Start, new interval", newCurrentInterval.Int() ) + + Cancel(); + } + + if ( !IsActive() ) + { + iCurrentInterval = newCurrentInterval; + CTimer::HighRes( iCurrentInterval ); + } + } + + +// --------------------------------------------------------------------------- +// From class CActive. +// --------------------------------------------------------------------------- +// +void CMccVideoJitterBuffer::RunL() + { + __V_SOURCESINK_CONTROLL( "CMccVideoJitterBuffer::RunL" ) + + if ( PostFirstFrame() ) + { + Start(); + } + + __V_SOURCESINK_CONTROLL( "CMccVideoJitterBuffer::RunL, exit" ) + } + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::PlayThresholdExceeded +// --------------------------------------------------------------------------- +// +TBool CMccVideoJitterBuffer::PlayThresholdExceeded() + { + if ( !iPlayThresholdExeeced && + ( iFrameCount * iNormalInterval.Int() >= iPlayThresholdMicroSecs ) ) + { + iPlayThresholdExeeced = ETrue; + } + return iPlayThresholdExeeced; + } + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::CheckCurrentInterval +// --------------------------------------------------------------------------- +// +TTimeIntervalMicroSeconds32 CMccVideoJitterBuffer::CheckCurrentInterval() + { + TTimeIntervalMicroSeconds32 interval( iNormalInterval ); + TUint32 dataAmountInMicroSecs( iFrameCount * iNormalInterval.Int() ); + if ( iQueue.Count() == 0 || dataAmountInMicroSecs < iLowLimitMicroSecs ) + { + interval = iLowLimitInterval; + } + else if ( dataAmountInMicroSecs > iHighLimitMicroSecs ) + { + interval = iHighLimitInterval; + } + else if ( iCurrentInterval == iLowLimitInterval && + dataAmountInMicroSecs < iPlayThresholdMicroSecs ) + { + // If on low interval, normalize only once at normal level, otherwise + // low limit might be reached shortly again. + interval = iLowLimitInterval; + } + else + { + } + return interval; + } + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::UpdateFrameCount +// --------------------------------------------------------------------------- +// +void CMccVideoJitterBuffer::UpdateFrameCount( CMccVideoPacket& aPacket, TBool aIsAdded ) + { + if ( aPacket.HeaderInfo().iMarker != 0 ) + { + if ( aIsAdded ) + { + iFrameCount++; + } + else if ( iFrameCount > 0 ) + { + iFrameCount--; + } + else + { + // NOP + } + + __V_SOURCESINK_CONTROLL_INT1( + "CMccVideoJitterBuffer::UpdateFrameCount, count", iFrameCount ) + } + } + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::PostFirstFrame +// --------------------------------------------------------------------------- +// +TBool CMccVideoJitterBuffer::PostFirstFrame() + { + TBool posted( EFalse ); + TInt frameEndIndex( KErrNotFound ); + TUint32 frameTimeStamp( 0 ); + for ( TInt i = 0; i < iQueue.Count() && !posted; i++ ) + { + CMccVideoPacket* packet = iQueue[ i ]; + + if ( frameEndIndex != KErrNotFound ) + { + TRtpRecvHeader& recvHeader = packet->HeaderInfo(); + if ( recvHeader.iTimestamp != frameTimeStamp && packet->DroppingAllowed() ) + { + // Timestamp mismatch, packet does not actually belong + // to this frame, drop it silently + __V_SOURCESINK_CONTROLL( + "CMccVideoJitterBuffer::PostBuffer, packet dropped due incorrect ts" ) + } + else + { + // Always modify timestamp to zero which causes that Helix + // lets packet go straight away to decoder. + DoRtpHeaderModify( recvHeader ); + + TInt ret = iPacketSink.Enqueue( + packet->StreamId(), recvHeader, packet->PayloadData() ); + + if ( ret != KErrNone ) + { + iObserver.ErrorOccured( ret ); + } + else + { + posted = ( frameEndIndex == i ); + } + } + } + else if ( packet->HeaderInfo().iMarker != 0 ) + { + // Full frame in queue, post all packets belonging to the frame + frameEndIndex = i; + frameTimeStamp = packet->HeaderInfo().iTimestamp; + i = -1; // Start again from beginning + } + else + { + } + } + + // Delete all posted packets + for ( TInt j = frameEndIndex; j >= 0; j-- ) + { + CMccVideoPacket* packet = iQueue[ j ]; + UpdateFrameCount( *packet, EFalse ); + delete packet; + iQueue.Remove( j ); + } + + return posted; + } + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::MakeRoom +// --------------------------------------------------------------------------- +// +void CMccVideoJitterBuffer::MakeRoom() + { + __V_SOURCESINK_CONTROLL( "CMccVideoJitterBuffer::MakeRoom" ) + + // Make room by pushing some amount from beginning of the queue + // towards helix. + TBool post = PostFirstFrame(); + while ( post ) + { + __V_SOURCESINK_CONTROLL( "CMccVideoJitterBuffer::MakeRoom, post" ) + + TTimeIntervalMicroSeconds32 interval = CheckCurrentInterval(); + if ( interval <= iHighLimitInterval ) + { + // Data amount still exceeds high limit + post = PostFirstFrame(); + } + else + { + post = EFalse; + } + } + + __V_SOURCESINK_CONTROLL( "CMccVideoJitterBuffer::MakeRoom, exit" ) + } + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::MaxSizeExceeded +// --------------------------------------------------------------------------- +// +TBool CMccVideoJitterBuffer::MaxSizeExceeded() + { + TUint32 dataAmountInMicroSecs( iFrameCount * iNormalInterval.Int() ); + return ( dataAmountInMicroSecs >= iMaxSizeMicroSecs ); + } + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::CheckPostingInactivity +// --------------------------------------------------------------------------- +// +void CMccVideoJitterBuffer::CheckPostingInactivity( + TInt64 aTimeFromPreviousFrame ) + { + if ( aTimeFromPreviousFrame > (TInt64)iInactivityTimeoutInMicroSecs ) + { + // If frames haven't been posted within inactivity time, next packet + // will cause again "playing started" status report + iPlaybackStarted = EFalse; + } + } + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::PlayingStatus +// --------------------------------------------------------------------------- +// +CMccVideoJitterBuffer::TMccPacketBufferingStatus CMccVideoJitterBuffer::PlayingStatus() + { + TMccPacketBufferingStatus bufferingStatus( EBuffering ); + if ( PlayThresholdExceeded() ) + { + if ( iPlaybackStarted ) + { + bufferingStatus = EPlaying; + } + else + { + bufferingStatus = EPlayingStarted; + iPlaybackStarted = ETrue; + } + } + return bufferingStatus; + } + +// --------------------------------------------------------------------------- +// CMccVideoJitterBuffer::DoRtpHeaderModify +// Always overwrite timestamps by using own real time clock. There's some +// implementations which do not use RTP timestamps correctly so this +// approach is for better interoperability. Also hide seqnum gaps from Helix. +// --------------------------------------------------------------------------- +// +void CMccVideoJitterBuffer::DoRtpHeaderModify( TRtpRecvHeader& aRecvHeader ) + { + if ( iPreviousTime.Int64() == 0 ) + { + iPreviousTime.HomeTime(); + iCurrentRealtimeTimestamp = aRecvHeader.iTimestamp; + } + else if ( aRecvHeader.iTimestamp != iPreviousReceivedTimestamp ) + { + TTime currentTime; + currentTime.HomeTime(); + TTimeIntervalMicroSeconds timeFromPreviousFrame = + currentTime.MicroSecondsFrom( iPreviousTime ); + iCurrentRealtimeTimestamp += + MCC_CONVERT_TO_90KHZ_CLOCK( timeFromPreviousFrame.Int64() ); + + CheckPostingInactivity( timeFromPreviousFrame.Int64() ); + + iPreviousTime = currentTime; + } + else + { + // NOP + } + + __V_SOURCESINK_CONTROLL_INT2( + "Timestamp mod, orig:", aRecvHeader.iTimestamp, " mod:", iCurrentRealtimeTimestamp ) + + iPreviousReceivedTimestamp = aRecvHeader.iTimestamp; + aRecvHeader.iTimestamp = iCurrentRealtimeTimestamp; + + if ( !iSeqNum ) + { + iSeqNum = aRecvHeader.iSeqNum; + } + else + { + aRecvHeader.iSeqNum = ++iSeqNum; + } + } + +// End of file +