/*---------------------------------------------------------------------------- * File: RTP_STAT.C * Product: RTP/RTCP implementation * Description: Provides statistical calculations for RTP packets * * * INTEL Corporation Proprietary Information * This listing is supplied under the terms of a license agreement with * Intel Corporation and may not be copied nor disclosed except in * accordance with the terms of that agreement. * Copyright (c) 1995 Intel Corporation. *--------------------------------------------------------------------------*/ #include "rrcm.h" #define DBG_JITTER_ENABLE 0 /*--------------------------------------------------------------------------- / Global Variables /--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------- / External Variables /--------------------------------------------------------------------------*/ extern PRTP_CONTEXT pRTPContext; extern RRCM_WS RRCMws; #ifdef _DEBUG extern char debug_string[]; #endif /*---------------------------------------------------------------------------- * Function : calculateJitter * Description: Determines jitter between current and last received packet. * * Input : pRTPHeader : -> to the RTP header * pSSRC : -> to the session's SSRC list * * Note: Implementataion adapted from IETF RFC1889 * * Return: RRCM_NoError = OK. * Otherwise(!=0) = Error. ---------------------------------------------------------------------------*/ DWORD calculateJitter (RTP_HDR_T *pRTPHeader, PSSRC_ENTRY pSSRC) { DWORD dwStatus = RRCM_NoError; DWORD streamClk; DWORD dwTmp; int dwPropagationTime; // packet's transmit time int dwIASourceTime; // Packet's timestamp for IA int delta; // of 2 consec. packets IN_OUT_STR ("RTP : Enter calculateJitter()\n"); // Convert the RTP timestamp to host order RRCMws.ntohl (pSSRC->RTPsd, pRTPHeader->ts, (PDWORD)&dwIASourceTime); // lock access EnterCriticalSection (&pSSRC->critSect); // Take the difference, after having adjusted the clock to the payload // type frequency streamClk = ((PSSRC_ENTRY)pSSRC->pRTCPses->XmtSSRCList.prev)->dwStreamClock; if (streamClk) { dwTmp = streamClk / 1000; // update the time to be in unit of the source clock dwPropagationTime = (timeGetTime() * dwTmp) - dwIASourceTime; } else dwPropagationTime = timeGetTime() - dwIASourceTime; // initialize for the first valid packet, otherwise jitter will be off if (pSSRC->rcvInfo.dwPropagationTime == 0) { pSSRC->rcvInfo.dwPropagationTime = dwPropagationTime; LeaveCriticalSection (&pSSRC->critSect); IN_OUT_STR ("RTP : Exit calculateJitter()\n"); return (dwStatus); } #if DBG_JITTER_ENABLE wsprintf(debug_string, "RTP : Time: %ld - Src Timestamp: %ld", timeGetTime(), dwIASourceTime); RRCM_DBG_MSG (debug_string, 0, NULL, 0, DBG_TRACE); wsprintf(debug_string, "RTP : Propagation (Src unit): %ld", dwPropagationTime); RRCM_DBG_MSG (debug_string, 0, NULL, 0, DBG_TRACE); wsprintf(debug_string, "RTP : Previous Propagation (Src unit): %ld", pSSRC->rcvInfo.dwPropagationTime); RRCM_DBG_MSG (debug_string, 0, NULL, 0, DBG_TRACE); #endif // Determine the difference in the transit times and save the latest delta = dwPropagationTime - pSSRC->rcvInfo.dwPropagationTime; if (delta < 0) delta = -delta; // check for a wrap-around, which is always possible, and avoid sending // the jitter through the roof - It will take a long time thereafter to // go back down to a reasonable level // Check against arbitrary large number if (delta > 20000) { pSSRC->rcvInfo.dwPropagationTime = dwPropagationTime; LeaveCriticalSection (&pSSRC->critSect); IN_OUT_STR ("RTP : Exit calculateJitter()\n"); return (dwStatus); } #if DBG_JITTER_ENABLE wsprintf(debug_string, "RTP : Delta (Src unit): %ld", delta); RRCM_DBG_MSG (debug_string, 0, NULL, 0, DBG_TRACE); #endif pSSRC->rcvInfo.dwPropagationTime = dwPropagationTime; #ifdef ENABLE_FLOATING_POINT // This is the RFC way to do it pSSRC->rcvInfo.interJitter += ((1./16.) * ((double)delta - pSSRC->rcvInfo.interJitter)); #else // and this is when we need to remove floating point operation pSSRC->rcvInfo.interJitter += (delta - (((long)pSSRC->rcvInfo.interJitter + 8) >> 4)); #endif LeaveCriticalSection (&pSSRC->critSect); #if DBG_JITTER_ENABLE if (streamClk) { wsprintf(debug_string, "RTP : iJitter: %ld - iJitter (msec): %ld", pSSRC->rcvInfo.interJitter, (pSSRC->rcvInfo.interJitter / (streamClk / 1000))); } else { wsprintf(debug_string, "RTP : iJitter: %ld - Delta: %ld", pSSRC->rcvInfo.interJitter, delta); } RRCM_DBG_MSG (debug_string, 0, NULL, 0, DBG_TRACE); wsprintf(debug_string, "RTP : Next RTCP RR iJitter: %ld", (pSSRC->rcvInfo.interJitter >> 4)); RRCM_DBG_MSG (debug_string, 0, NULL, 0, DBG_TRACE); #endif IN_OUT_STR ("RTP : Exit calculateJitter()\n"); return (dwStatus); } /*---------------------------------------------------------------------------- * Function : initRTPStats * Description: initializes statistics table for newly recieved SSRC * * Input : RTPSequence : Sequence number received in the packet. * NB: Must be in LittleEndian(IA) format * pSSRC : -> to SSRC table entry for this terminal * * Note: Implementataion adapted from draftspec 08, Appendix A.1 * * Return: None. ---------------------------------------------------------------------------*/ void initRTPStats (WORD RTPSequence, PSSRC_ENTRY pSSRC) { IN_OUT_STR ("RTP : Enter initRTPStats()\n"); pSSRC->rcvInfo.dwNumPcktRcvd = 0; pSSRC->rcvInfo.dwPrvNumPcktRcvd = 0; pSSRC->rcvInfo.dwExpectedPrior = 0; pSSRC->rcvInfo.dwNumBytesRcvd = 0; pSSRC->rcvInfo.dwBadSeqNum = RTP_SEQ_MOD + 1; // Out of range pSSRC->rcvInfo.XtendedSeqNum.seq_union.RTPSequence.wSequenceNum = RTPSequence; pSSRC->rcvInfo.XtendedSeqNum.seq_union.RTPSequence.wCycle = 0; #if 0 // as per the RFC, but always 1 packet off by doing this ??? pSSRC->rcvInfo.dwBaseRcvSeqNum = RTPSequence - 1; #else pSSRC->rcvInfo.dwBaseRcvSeqNum = RTPSequence; #endif IN_OUT_STR ("RTP : Exit initRTPStats()\n"); } /*---------------------------------------------------------------------------- * Function : sequenceCheck * Description: Determines whether received packet sequence number is in a * valid range to include for statistical tracking purposes. * * Input : RTPSequence : Sequence number received in the packet. * NB: Must be in LittleEndian(IA) format * pSSRC : -> to SSRC table entry for this terminal * * Note: Implementataion adapted from draftspec 08, Appendix A.1 * * Return: TRUE = OK. * FALSE = Stale or invalid data. ---------------------------------------------------------------------------*/ #if 1 BOOL sequenceCheck (WORD RTPSequence, PSSRC_ENTRY pSSRC) { WORD delta = RTPSequence - pSSRC->rcvInfo.XtendedSeqNum.seq_union.RTPSequence.wSequenceNum; IN_OUT_STR ("RTP : Enter sequenceCheck()\n"); // Have we received enough consecutive sequence numbered pckts in order // to valide ? if (pSSRC->rcvInfo.dwProbation) { // Is the sequence received the expected one ? if (RTPSequence == (pSSRC->rcvInfo.XtendedSeqNum.seq_union.RTPSequence.wSequenceNum + 1)) { // Decrement the number of consecutive packets we need before we // consider statistics to be valid pSSRC->rcvInfo.dwProbation--; pSSRC->rcvInfo.XtendedSeqNum.seq_union.RTPSequence.wSequenceNum = RTPSequence; if (pSSRC->rcvInfo.dwProbation == 0) { initRTPStats(RTPSequence, pSSRC); IN_OUT_STR ("RTP : Exit sequenceCheck()\n"); return TRUE; } } else { pSSRC->rcvInfo.dwProbation = MIN_SEQUENTIAL - 1; pSSRC->rcvInfo.XtendedSeqNum.seq_union.RTPSequence.wSequenceNum = RTPSequence; } IN_OUT_STR ("RTP : Exit sequenceCheck()\n"); return FALSE; } else if (delta < MAX_DROPOUT) { // In order with permissible gap if (RTPSequence < pSSRC->rcvInfo.XtendedSeqNum.seq_union.RTPSequence.wSequenceNum) // sequence number wrapped - count another 64K cycle pSSRC->rcvInfo.XtendedSeqNum.seq_union.RTPSequence.wCycle += 1; pSSRC->rcvInfo.XtendedSeqNum.seq_union.RTPSequence.wSequenceNum = RTPSequence; } else if (delta <= RTP_SEQ_MOD - MAX_MISORDER) { // the sequence number made a very large jump if (RTPSequence == pSSRC->rcvInfo.dwBadSeqNum) // two sequential packet. Assume the other side restarted w/o telling // us, so just re-sync, i.e., pretend this was the first packet initRTPStats(RTPSequence, pSSRC); else { pSSRC->rcvInfo.dwBadSeqNum = (RTPSequence + 1) & (RTP_SEQ_MOD - 1); IN_OUT_STR ("RTP : Exit sequenceCheck()\n"); return FALSE; } } else { // duplicate or reordered packet } IN_OUT_STR ("RTP : Exit sequenceCheck()\n"); return (TRUE); } #else //BOOL sequenceCheck (WORD RTPSequence, // PSSRC_ENTRY lpSSRCList) //{ // BOOL bStatus; // WORD delta; // //#ifdef IN_OUT_CHK // OutputDebugString ("\nEnter sequenceCheck()"); //#endif // // // Have we received a couple of consecutive sequence numbered packets for // // validation? // if (lpSSRCList->probation) { // // // Default status is don't include since the source hasn't been validated yet // bStatus = FALSE; // // // Is the sequence received the expected one? // if (RTPSequence == (lpSSRCList->XtendedSeqNum.seq_union.RTPSequence.wSequenceNum + 1)) { // // Decrement the number of consecutive packets we need before we // // consider statistics to be valid // lpSSRCList->probation--; // lpSSRCList->XtendedSeqNum.seq_union.RTPSequence.wSequenceNum = RTPSequence; // if (lpSSRCList->probation == 0) { // initRTPStats(RTPSequence, lpSSRCList); // bStatus = TRUE; // } // } // else { // lpSSRCList->probation = MIN_SEQUENTIAL - 1; // lpSSRCList->XtendedSeqNum.seq_union.RTPSequence.wSequenceNum = RTPSequence; // } // } // else { // // Default status is include since the source has been validated // bStatus = TRUE; // // // First consider the case where delta is positive (or a duplicate packet) // if (RTPSequence >= lpSSRCList->XtendedSeqNum.seq_union.RTPSequence.wSequenceNum) { // // delta = RTPSequence - lpSSRCList->XtendedSeqNum.seq_union.RTPSequence.wSequenceNum; // // if (delta < MAX_DROPOUT) { // // packets may be missing, but not too many so as to be deemed restarted // lpSSRCList->XtendedSeqNum.seq_union.RTPSequence.wSequenceNum = RTPSequence; // } // else if (delta > (RTP_SEQ_MOD - MAX_MISORDER)) { // // There has been a recent wraparound, and this is just a recent old packet // // Nothing to do but include for statistical processing // } // else { // // there was a very large jump in sequence numbers // if (RTPSequence == lpSSRCList->badSeqNum ) { // // Two sequential packets after what was thought was a bad packet or // // (assume a very large jump and proceed as if the sender restarted // // without telling us) or a new terminal is in the session. // initRTPStats(RTPSequence, lpSSRCList); // } // else { // lpSSRCList->badSeqNum = (RTPSequence + 1) & (RTP_SEQ_MOD - 1); // bStatus = FALSE; // } // } // } // else { // // sequence number is less than the last we received. Could be either // // a recent late packet, a very late packet, a wraparound or a restartup // // of a new session for an SSRC from which we hadn't received a BYE // // delta = lpSSRCList->XtendedSeqNum.seq_union.RTPSequence.wSequenceNum - RTPSequence; // // if (delta < MAX_MISORDER) { // // Packet arrived a little bit late, it's still OK // // do nothing here, will be counted in stat routines // } // else if (delta > (RTP_SEQ_MOD - MAX_DROPOUT)) { // // wrap around, adjust cycle number and sequence number // lpSSRCList->XtendedSeqNum.seq_union.RTPSequence.cycle++; // lpSSRCList->XtendedSeqNum.seq_union.RTPSequence.wSequenceNum = RTPSequence; // } // else { // // there was a very large jump in sequence numbers // if (RTPSequence == lpSSRCList->badSeqNum) { // // Two sequential packets after what was thought was a bad packet. // // Assume a very large jump and proceed as if the sender restarted // // without telling us // initRTPStats(RTPSequence, lpSSRCList); // } // else { // lpSSRCList->badSeqNum = (RTPSequence + 1) & (RTP_SEQ_MOD - 1); // bStatus = FALSE; // } // } // } // } // //#ifdef IN_OUT_CHK // OutputDebugString ("\nExit sequenceCheck()"); //#endif // // return (bStatus); //} #endif /*---------------------------------------------------------------------------- * Function : updateRTPStats * Description: Updates statistics for RTP packets received from net * * Input : pRTPHeader : -> to packet's RTP header field * pSSRC : -> to remote source's statistics table * cbTransferred : Number of bytes transferred * * * Return: RRCM_NoError = OK. * Otherwise(!=0) = Initialization Error. ---------------------------------------------------------------------------*/ DWORD updateRTPStats (RTP_HDR_T *pRTPHeader, PSSRC_ENTRY pSSRC, DWORD cbTransferred) { WORD RTPSequenceNum; IN_OUT_STR ("RTP : Enter updateRTPStats()\n"); // Update statistics only if the data looks good. Check the sequence number // to ensure it is within an appropriate range. First, we must convert the // sequence number to IA (little Endian) format RRCMws.ntohs (pSSRC->RTPsd, pRTPHeader->seq, (unsigned short *)&RTPSequenceNum); if (sequenceCheck (RTPSequenceNum, pSSRC)) { // lock access to data EnterCriticalSection (&pSSRC->critSect); // update number of packet received pSSRC->rcvInfo.dwNumPcktRcvd++; // Number octets received (exclusive of header) depends on whether // a mixer (CSRC != 0) was involved if (pRTPHeader->cc == 0) { pSSRC->rcvInfo.dwNumBytesRcvd += (cbTransferred - (sizeof(RTP_HDR_T) - sizeof(pRTPHeader->csrc[0]))); } else { pSSRC->rcvInfo.dwNumBytesRcvd += (cbTransferred - sizeof(RTP_HDR_T) + ((pRTPHeader->cc - 1) * sizeof(pRTPHeader->csrc[0]))); } // Packet received sequentially in order (difference // of 1, or -1 if wraparound) save new current // sequence number RRCMws.ntohs (pSSRC->RTPsd, pRTPHeader->seq, (unsigned short *)&pSSRC->xmtInfo.dwCurXmtSeqNum); // Calculate JITTER calculateJitter (pRTPHeader, pSSRC); // unlock access to data LeaveCriticalSection (&pSSRC->critSect); } IN_OUT_STR ("RTP : Exit updateRTPStats()\n"); return (RRCM_NoError); } // [EOF]