1,音频知识:
采样频率是指将模拟声音波形进行数字化时,每秒钟抽取声波幅度样本的次数。
。正常人听觉的频率范围大约在20Hz~20kHz之间,根据奈奎斯特采样理论,为了保证声音不失真,采样频率应该在40kHz左右。常用的音频采样频率有8kHz、
11.025kHz、22.05kHz、16kHz、37.8kHz、44.1kHz、48kHz等,如果采用更高的采样频率,还可以达到DVD的音质
对采样率为44.1kHz的AAC音频进行解码时,一帧的解码时间须控制在23.22毫秒内。
背景知识:
(一个AAC原始帧包含一段时间内1024个采样及相关数据)
分析:
1) AAC
音频帧的播放时间=一个AAC帧对应的采样样本的个数/采样频率(单位为s)
一帧 1024个 sample。采样率 Samplerate 44100KHz,每秒44100个sample, 所以根据公式 音频帧的播放时间=一个AAC帧对应的采样样本的个数/采样频率
当前AAC一帧的播放时间是= 1024*1000000/44100= 22.32ms(单位为ms)
2,LIVE555中音视频同步的处理部分:RTPSsource.cpp源码分析:
/*
noteIncomingPacket的实质是:将 RTP timestamp 转换为 'wall clock' time用于计算抖动,
每次接收到一个rtp包后,都会用此函数计算抖动。逻辑完全取决于系统时间的精确度,没有任何校正机制。
live555是在哪里实现时间校正的呢?答案是利用RTSP客户端(数据的接收者)利用RTCP返回的Sender Report,
然后利用其中的NTP Timestamp和RTP timestamp, 对fSyncTimestamp和fSyncTime进行校正。
实现音视频同步 (live555)总体思路是:把A/V的RTP时间戳同步到RTCP的绝对时间(NTP Timestamp),实现A/V同步
*/
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 += packetSize; //fTotBytesReceived_lo是一个无符号32位整数,当达到最大值后,会从新开始计算。
if (fTotBytesReceived_lo < prevTotBytesReceived_lo) { // wrap-around
++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)) { //seqNum - oldSeqNum > 0
// This packet was not an old packet received out of order, so check it:
//当前序列号大于前一次的序列号,看似是合法的rtp包。但还是要检测是否出现溢出归零的情况。
if (seqNumDifference >= 0x8000) {
// The sequence number wrapped around, so start a new cycle:前后序列号差距如此大,则认为溢出,高位(高两字节)须加一。
seqNumCycle += 0x10000;
}
newSeqNum = seqNumCycle | seqNum;// 得到完整序列号。
if (newSeqNum > fHighestExtSeqNumReceived) {//记录最大序列号。
fHighestExtSeqNumReceived = newSeqNum;//最大序列号保存到成员变量里面
}
} else if (fTotNumPacketsReceived > 1) {//当前序列号小于前一序列号?//先前已经接收到了rtp包。有rtp包已经存在
// This packet was an old packet received out of order 这个包是旧包,收到时失序。
if ((int)seqNumDifference >= 0x8000) {//序列号是递减的? 这儿溢出了就要减一? 看似是退播可能有此现象。
// 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 记录数据包间的延迟 使用8字节,高四字节记录秒数,低四字节记录微秒。
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规范中附录A.8中的代码。)
如果数据包的时间戳与前一包相同(这表示一个多数据包片段),或者被明确告知不使用这个数据包,则不使用该数据包。
网络抖动
1、又称为包抖动(Packet delay variation)是由同一应用的任意两个相邻数据包在传输路由中经过网络延迟而产生;
在计算机网络中,抖动被用于描述包在网络中的传输延时变化。
但这种描述不精确,正式描述应该是PDV(packet delay variation)网络延时变化。
定义见于RFC 3393,IP Packet Delay Variation Metric for IP Performance Metrics (IPPM),IETF (2002)
PDV 是评价一个网络性能的重要要素。
具有固定传输延时的网络没有抖动。包抖动是包延时与平均传输延时的差值的平均值。
计算方法:抖动率由相邻数据包延迟时间差除以数据包序号差得到;
计算步骤:
1.计算端到端延迟,就是指数据包的接收时间与发送时间之差;
接收端节点N↓[2]收到数据包的时间减去发送端节点N↓[1]发出数据包的时间,就是端到端延迟,公式如下:
端到端延迟=数据包的接收时间-数据包的发送时间;
2.抖动率=(数据包P↓[j]的延迟-数据包P↓[i]的延迟)/(数据包P↓[j]的序号j-数据包P↓[i]的序号i)
数据包P↓[j]的延迟=数据包P↓[j]接收时间-数据包P↓[j]发送时间
数据包P↓[i]的延迟=数据包P↓[i]接收时间-数据包P↓[i]发送时间
2、延迟和抖动
延迟和抖动是网络性能的重要参数。
延迟是不可避免的,而抖动是可以通过某些技术方案优化的,常见于缓冲技术
*/
if (useForJitterCalculation
&& rtpTimestamp != fPreviousPacketRTPTimestamp) {//rtpTimestamp,rtp包头部记录的时间戳
unsigned arrival = (timestampFrequency*timeNow.tv_sec);
arrival += (unsigned)//计算rtp包的理论抵达时间转换时戳, 以频率为单位, 通常情况下该单位为90khz, 即90000先转换秒, 再转换微妙
((2.0*timestampFrequency*timeNow.tv_usec + 1000000.0)/2000000);
// note: rounding (+1000000 / 2000000表示向上圆整, 5入)
int transit = arrival - rtpTimestamp;//理论和实际的时间差值。
if (fLastTransit == (~0))
{
fLastTransit = transit; // hack for first time//第一个rtp包。
}
else
{
int d = transit - fLastTransit;//求本次差值和上次差值的差异
fLastTransit = transit;//把当前差值放到上次差值成员变量里面,下次使用
if (d < 0) d = -d;//取正数
//计算出抖动值。每次d的权重会越来越低,变体为:( (double)d )/16 + (15.0/16.0)*fJitter。
fJitter += (1.0 / 16.0) * ((double)d - fJitter);
}
}
/*
// Return the 'presentation time' that corresponds to "rtpTimestamp":
返回对应于“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.)
这是我们第一次看到时间戳,所以使用当前的“tick”时间作为同步时间。(稍后接收RTCPSR时将更正这一点。)
获取首个RTP时,将系统时间作为首个'wall clock' time。
后续,当RTP timestamp发生变化时,要将变化的部分转换为real time:
*/
fSyncTimestamp = rtpTimestamp;//fSyncTimestamp上次的rtp头部时间戳。
fSyncTime = timeNow; //fSyncTime上次计算出来的呈现时间,基于接收端的本地时间,gettimeofday(&timeNow, NULL);。
}
/*
时间戳单位:时间戳计算的单位不是秒之类的单位,而是由采样频率所代替的单位,目的是为了使时间戳单位更为精准。
比如说一个音频的采样频率为8000Hz,那么我们可以把时间戳单位设为1 / 8000
*/
int timestampDiff = rtpTimestamp - fSyncTimestamp;//rtp头部时间戳和上次的rtp头部时间戳的差值。
// Note: This works even if the timestamp wraps around(as long as "int" is 32 bits)
// 注意:即使时间戳环绕时也有效(只要“int”是32位)
//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 seconds, uSeconds;
if (timeDiff >= 0.0) {
seconds = fSyncTime.tv_sec + (unsigned)(timeDiff);//取秒数。
uSeconds = fSyncTime.tv_usec
+ (unsigned)((timeDiff - (unsigned)timeDiff)*MILLION);//取微妙数。
if (uSeconds >= MILLION) {//微秒溢出,秒须加一,微秒基值不一定恰好是0,所以:微秒 = 微秒 - 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;//RTP Timestamp, 默认第N帧的rtpTimestamp为第N+1帧的fSyncTimestamp 更新RTP包时间戳
fSyncTime = resultPresentationTime;//'wall clock' time, 默认第N帧的'wall clock' time为第N+1帧的fSyncTime
//fSyncTime 同步时间=当前时间+RTP时间戳差值。
fPreviousPacketRTPTimestamp = rtpTimestamp;//更新RTP包时间戳。
char cBuf[1024] = { 0 };
sprintf(cBuf, "\n noteIncomingPacket timeDiff=%d fSyncTimestamp = 0x%08x, fSyncTime.tv_sec=0x%08x, fSyncTime.tv_usec=0x%08x \n",
timeDiff, rtpTimestamp, fSyncTime.tv_sec, fSyncTime.tv_usec);
OutputDebugString(cBuf);
}
/*
校正程序
实现音视频同步 (live555)总体思路是:
把A/V的RTP时间戳同步到RTCP的绝对时间(NTP Timestamp),实现A/V同步
live555是在哪里实现时间校正的呢?
答案是RTSP Server利用RTCP返回的Sender Report, 然后利用其中的NTP Timestamp和RTP timestamp,
对fSyncTimestamp和fSyncTime进行校正。
通过Sender Report,分别对视频和音频的时间及时进行校正,即可保证视音频同步。
*/
void RTPReceptionStats::noteIncomingSR(u_int32_t ntpTimestampMSW,
u_int32_t ntpTimestampLSW,
u_int32_t rtpTimestamp) {
fLastReceivedSR_NTPmsw = ntpTimestampMSW;
fLastReceivedSR_NTPlsw = ntpTimestampLSW;
gettimeofday(&fLastReceivedSR_time, NULL);
// Use this SR to update time synchronization information:
// ntpTimestampMSW : NTP timestamp, most significant word (64位NTP时间戳的高32位)单位秒,1970年1月1日0时0分0秒以来的秒数
/*
皮秒(英语:picosecond ),天文学名词,符号是ps,1皮秒等于一万亿分之一(10的负12次方)秒。
1,000,000 皮秒 = 1微秒 1,000,000,000,000 皮秒 = 1E12皮秒=1秒
1 second = 1,000,000,000,000 picoseconds, 而2^32=4294967296,很明显用32bits无法精确到1 picosecond,
1,000,000,000,000 picoseconds劈成32位精度如下:1,000,000,000,000/4294967296 = 232.83064365386962890625
于是有:(10^12)ps /(2^32)≈232ps = 1 LSW
求微秒:先将LSW乘以232转为ps,然后ps除以10^6就得到us了
double per = 232.83064365386962890625; return (unsigned int)(lsw*per/1000000);
优化:(((lsw) >> 12) - 759 * ((((lsw) >> 10) + 32768) >> 16));
https://blog.csdn.net/u010399029/article/details/77689828 NTP的时间戳的LSW转换为unix的usec
https://blog.csdn.net/leesphone/article/details/5572171 RTCP中的NTP的时间计算方法
*/
fSyncTimestamp = rtpTimestamp;
fSyncTime.tv_sec = ntpTimestampMSW - 0x83AA7E80; // 1/1/1900 -> 1/1/1970 1970年1月1日0时0分0秒以来的秒数
// ntpTimestampLSW : NTP timestamp, least significant word (64位NTP时间戳的低32位) 单位232皮秒
double microseconds = (ntpTimestampLSW*15625.0)/0x04000000; // 10^6/2^32
fSyncTime.tv_usec = (unsigned)(microseconds+0.5);
fHasBeenSynchronized = True;
char cBuf[1024] = { 0 };
sprintf(cBuf, "\n noteIncomingSR fSyncTimestamp = 0x%x, fSyncTime.tv_sec=0x%x, fSyncTime.tv_usec=0x%x \n",
rtpTimestamp, fSyncTime.tv_sec, fSyncTime.tv_usec);
OutputDebugString(cBuf);
}