live555分析:RTPReceptionStats::noteIncomingPacket函数详解

//奔函数用于计算抖动,每次接收到一个rtp包后,都会调用此函数计算抖动。
void RTPReceptionStats
::noteIncomingPacket(u_int16_t seqNum, u_int32_t rtpTimestamp,
             unsigned timestampFrequency,
             Boolean useForJitterCalculation,
             struct timeval& resultPresentationTime,
             Boolean& resultHasBeenSyncedUsingRTCP,
             unsigned packetSize) {
  if (!fHaveSeenInitialSequenceNumber) initSeqNum(seqNum);

  ++fNumPacketsReceivedSinceLastReset;  //上次重置后,接收到的rtp包个数。
  ++fTotNumPacketsReceived;                //一共接收到的rtp包个数。
  u_int32_t prevTotBytesReceived_lo = fTotBytesReceived_lo;   //fTotBytesReceived_lo,总共接收到的rtp字节数。
  fTotBytesReceived_lo += packetSize;
  if (fTotBytesReceived_lo < prevTotBytesReceived_lo) { // wrap-around   fTotBytesReceived_lo是一个无符号32位整数,当达到最大值后,会从新开始计算。
    ++fTotBytesReceived_hi;    //    归零次数。
  }

  // Check whether the new sequence number is the highest yet seen:
  unsigned oldSeqNum = (fHighestExtSeqNumReceived&0xFFFF);          //取低16位数据
  unsigned seqNumCycle = (fHighestExtSeqNumReceived&0xFFFF0000);      //取高16位数据
  unsigned seqNumDifference = (unsigned)((int)seqNum-(int)oldSeqNum);
  unsigned newSeqNum = 0;
  if (seqNumLT((u_int16_t)oldSeqNum, seqNum)) { //当前序列号大于前一次的序列号,看似是合法的rtp包。但还是要检测是否出现归零的情况。
    // This packet was not an old packet received out of order, so check it:

    if (seqNumDifference >= 0x8000) { //出现归零的情况,0x8000最高位(即16位)上为1,其余位为0,前后序列号差距如此到,假设出现归零的情况。
      // The sequence number wrapped around, so start a new cycle:
      seqNumCycle += 0x10000;
    }

    newSeqNum = seqNumCycle|seqNum; // 当前序列号和循坏次数做一个组合。
    if (newSeqNum > fHighestExtSeqNumReceived) { //当新的序号比前一次的序列号小时,if语句会有可能为true。
      fHighestExtSeqNumReceived = newSeqNum;  //新的组合保存到成员变量里面。
    }
  } else if (fTotNumPacketsReceived > 1) { //先前已经接收到了rtp包。有rtp包已经存在。
    // This packet was an old packet received out of order

    if ((int)seqNumDifference >= 0x8000) {//出现归零的情况,0x8000最高位(即16位)上为1,其余位为0,前后序列号差距如此到,假设出现归零的情况。
      // The sequence number wrapped around, so switch to an old cycle:
      seqNumCycle -= 0x10000;     //上次出现了一次归零的情况,所以这里要把循坏次数减一。
    }

    //这里的代码,上面出现过,见上面的解释。
    newSeqNum = seqNumCycle|seqNum;
    if (newSeqNum < fBaseExtSeqNumReceived) {
      fBaseExtSeqNumReceived = newSeqNum;
    }
  }

  // Record the inter-packet delay
  struct timeval timeNow;
  gettimeofday(&timeNow, NULL);
  if (fLastPacketReceptionTime.tv_sec != 0
      || fLastPacketReceptionTime.tv_usec != 0) {
    unsigned gap          //计算rtp包的时间间隔。单位:一百万分之一秒,微秒,1/1000000秒。
      = (timeNow.tv_sec - fLastPacketReceptionTime.tv_sec)*MILLION
      + timeNow.tv_usec - fLastPacketReceptionTime.tv_usec;
    if (gap > fMaxInterPacketGapUS) {  //记录出现过的最大rtp包时间间隔。
      fMaxInterPacketGapUS = gap;
    }
    if (gap < fMinInterPacketGapUS) {  //记录出现过的最小rtp包时间间隔。
      fMinInterPacketGapUS = gap;
    }
    fTotalInterPacketGaps.tv_usec += gap; //fTotalInterPacketGaps,两个rtp包到达的时间间隔累计。
    if (fTotalInterPacketGaps.tv_usec >= MILLION) { //数学计算,进1。
      ++fTotalInterPacketGaps.tv_sec;
      fTotalInterPacketGaps.tv_usec -= MILLION;
    }
  }
  fLastPacketReceptionTime = timeNow;  //fLastPacketReceptionTime,记录rtp包抵达的时间。

  // Compute the current ‘jitter‘ using the received packet‘s RTP timestamp,
  // and the RTP timestamp that would correspond to the current time.
  // (Use the code from appendix A.8 in the RTP spec.)
  // Note, however, that we don‘t use this packet if its timestamp is
  // the same as that of the previous packet (this indicates a multi-packet
  // fragment), or if we‘ve been explicitly told not to use this packet.
  /*
   * 如果当前rtp包时间戳和上一个rtp包时间戳相同,不用此rtp包计算抖动。fPreviousPacketRTPTimestamp==rtpTimestamp。
  */

  if (useForJitterCalculation
      && rtpTimestamp != fPreviousPacketRTPTimestamp) {         //rtpTimestamp,rtp包头部记录的时间戳。
    unsigned arrival = (timestampFrequency*timeNow.tv_sec);
    arrival += (unsigned)  //计算rtp包的理论抵达时间。
      ((2.0*timestampFrequency*timeNow.tv_usec + 1000000.0)/2000000);
            // note: rounding
    int transit = arrival - rtpTimestamp;  //理论和实际的时间差值。
    if (fLastTransit == (~0)) fLastTransit = transit; // hack for first time  //如果是第一个rtp包。
    int d = transit - fLastTransit;        //求本次差值和上次差值的差异。
    fLastTransit = transit;        //把当前差值放到上次差值成员变量里面,下次使用。
    if (d < 0) d = -d;   //取正数。
    fJitter += (1.0/16.0) * ((double)d - fJitter);   //计算出抖动值。每次d的权重会越来越低,变体为:( (double)d )/16 + (15.0/16.0)*fJitter。
  }

  // Return the ‘presentation time‘ that corresponds to "rtpTimestamp": //根据rtp头部的时间戳,计算显现时间。
  if (fSyncTime.tv_sec == 0 && fSyncTime.tv_usec == 0) {
    // This is the first timestamp that we‘ve seen, so use the current
    // ‘wall clock‘ time as the synchronization time.  (This will be
    // corrected later when we receive RTCP SRs.)
    fSyncTimestamp = rtpTimestamp;     //fSyncTimestamp上次的rtp头部时间戳。
    fSyncTime = timeNow;            //fSyncTime上次计算出来的呈现时间,基于接收端的本地时间,gettimeofday(&timeNow, NULL);。
  }

  int timestampDiff = rtpTimestamp - fSyncTimestamp;  //rtp头部时间戳和上次的rtp头部时间戳的差值。
      // Note: This works even if the timestamp wraps around
      // (as long as "int" is 32 bits)

  //timestampFrequency,采样率,每秒钟采样的数量。
  //rtpTimestamp,设备每采样一次,时间戳递增1。
  // Divide this by the timestamp frequency to get real time:
  double timeDiff = timestampDiff/(double)timestampFrequency; //获取前后rtp包的时间差,单位为妙。

  // Add this to the ‘sync time‘ to get our result:
  unsigned const million = 1000000;
  unsigned seconds, uSeconds;
  if (timeDiff >= 0.0) {
    seconds = fSyncTime.tv_sec + (unsigned)(timeDiff);  //取秒数。
    uSeconds = fSyncTime.tv_usec
      + (unsigned)((timeDiff - (unsigned)timeDiff)*million); //取微妙数。
    if (uSeconds >= million) {
      uSeconds -= million;
      ++seconds;
    }
  } else {    //timeDiff的负数这种情况的处理。
                      //如果当前rtp包的序列号小于前一次rtp包的序列号,并且它们的时间戳不同,就会有这种负数的情况。
    timeDiff = -timeDiff;
    seconds = fSyncTime.tv_sec - (unsigned)(timeDiff);
    uSeconds = fSyncTime.tv_usec
      - (unsigned)((timeDiff - (unsigned)timeDiff)*million);
    if ((int)uSeconds < 0) {
      uSeconds += million;
      --seconds;
    }
  }
  //已经计算出要呈现的时间,通过resultPresentationTime返回时间数值。
  resultPresentationTime.tv_sec = seconds;
  resultPresentationTime.tv_usec = uSeconds;
  resultHasBeenSyncedUsingRTCP = fHasBeenSynchronized;  //估计用于rtcp使用,标示当前的统计数据有没有被rtcp使用。

  //记录这次同步的时间戳和时间。
  // Save these as the new synchronization timestamp & time:
  fSyncTimestamp = rtpTimestamp;
  fSyncTime = resultPresentationTime;

  fPreviousPacketRTPTimestamp = rtpTimestamp;
}

资料:

  rtp抖动--RFC3550,附录A.8 https://www.ietf.org/rfc/rfc3550.txt

  网络图书--《RTP: Audio and Video for the Internet By Colin Perkings》,见:百度网盘\共享\G:\workspace\download\RTP -- Audio and Video for the Internet By Colin Perkings.pdf

完。

时间: 2024-10-20 03:26:37

live555分析:RTPReceptionStats::noteIncomingPacket函数详解的相关文章

c++ 虚函数详解

下面是对C++的虚函数的理解. 一,定义 简单地说,那些被virtual关键字修饰的成员函数,就是虚函数.虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离:用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略.下面来看一段简单的代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 classA { publi

linux中fork()函数详解[zz]

转载自:http://www.cnblogs.com/york-hust/archive/2012/11/23/2784534.html 一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事. 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间.然后把原来的进程的所有值都复制到新的新进程中,只有

【转】angularjs指令中的compile与link函数详解

这篇文章主要介绍了angularjs指令中的compile与link函数详解,本文同时诉大家complie,pre-link,post-link的用法与区别等内容,需要的朋友可以参考下 通常大家在使用ng中的指令的时候,用的链接函数最多的是link属性,下面这篇文章将告诉大家complie,pre-link,post-link的用法与区别. angularjs里的指令非常神奇,允许你创建非常语义化以及高度重用的组件,可以理解为web components的先驱者. 网上已经有很多介绍怎么使用指令

fork( )函数详解

 一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程, 也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事. 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间.然后把原来的进程的所有值都 复制到新的新进程中,只有少数值与原来的进程的值不同.相当于克隆了一个自己. 我们来看一个例子: /* *  fork_test.c *  version 1

fork()函数详解

linux中fork()函数详解(原创!!实例讲解) (转载)  一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程, 也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事. 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间.然后把原来的进程的所有值都 复制到新的新进程中,只有少数值与原来的进程的值不同.相当于克隆了一个自己. 我们来看一个例子

tcp_recvmsg 函数详解

看了很多网上关于tcp_recvmsg的文章,感觉解释的不太到位,或者很多都是空口说白话,昨天分析了一下午tcp_recvmsg,感觉了解了十之八九,现在贴出来和大家分享一下. 需要背景:了解tcp三个接收队列  prequeue,backlog,receive的各自用处. /* * This routine copies from a sock struct into the user buffer. * * Technical note: in 2.3 we work on _locked_

可变参数函数详解

可变参数函数又称参数个数可变函数(本文也简称变参函数),即函数参数数目可变.原型声明格式为: type VarArgFunc(type FixedArg1, type FixedArg2, -); 其中,参数可分为两部分:数目确定的固定参数和数目可变的可选参数.函数至少需要一个固定参数,其声明与普通函数参数相同:可选参数由于数目不定(0个或以上),声明时用"-"表示("-"用作参数占位符).固定参数和可选参数共同构成可变参数函数的参数列表. 由于参数数目不定,使用可

JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解

二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 exec方法的返回值 exec方法返回的其实并不是匹配结果字符串,而是一个对象,简单地修改一下execReg函数,来做一个实验就可以印证这一点: function execReg(reg, str) { var result = reg.exec(str); alert(typeof result

getline()函数详解 (2013-03-26 17:19:58)(转)

学习C++的同学可能都会遇到一个getline()函数,譬如在C++premer中,标准string类型第二小节就是“用getline读取整行文本”.书上给的程序如下: int main() { string line: while(getline(cin,line)) cout<<line<<endl; return 0; } 大家会发现运行时怎么也跳不出循环,甚至会发生各种莫名其妙的错误.这是为什么呢?在这里我给大家做一个详细的讲解. 首先给大家介绍一下getline()函数(