--- /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 <CXPSPacketSink.h>
+
+// 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<CMccVideoPacket> 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
+