使用rtcp实现音视频同步
image
正文
一 基础知识
音视频同步是指音视频的rtp时间戳同步. audio/video rtp 时间戳不能自己同步,需要audio/video rtcp同步。
1 RTCP时间戳
发送端以一定的频率发送RTCP SR(Sender Report)这个包,SR分为视频SR和音频SR,SR包内包含一个RTP时间戳和对应的NTP时间戳,可以用<ntp,rtp>对做音视频同步.(同步过程在后面)
Rtcp sr 格式
image.png
2 音频时间戳
例如:一个音频包打包20ms的数据.采样率48k.对应的采样数为 48000 * 20 / 1000 = 960,也就是说每个音频包里携带960个音频采样,因为1个采样对应1个时间戳,那么相邻两个音频RTP包的时间戳之差就是960。
image.png
3 视频时间戳
视频采样率是帧率,视频时间戳的单位为1/90k秒.比如: 25帧,每帧40ms.40ms有多少时间戳的基本单位呢? 40除以1/90k等于3600.
扩展内容:
Single Nalu:如果一个视频帧包含1个NALU,可以单独打包成一个RTP包,那么RTP时间戳就对应这个帧的采集时间;
FU-A:如果一个视频帧的NALU过大(超过MTU)需要拆分成多个包,可以使用FU-A方式来拆分并打到不同的RTP包里,那么这几个包的RTP时间戳是一样的;
STAP-A:如果某帧较大不能单独打包,但是该帧内部单独的NALU比较小,可以使用STAP-A方式合并多个NALU打包发送,但是这些NALU的时间戳必须一致,打包后的RTP时间戳也必须一致
4 ntp时间戳
NTP时间戳是从1900年1月1日00:00:00以来经过的秒数.
rtp是相对时间,ntp是绝对时间.rtp时间戳和ntp时间戳表示的意义是相同的. 可以互相转换,rtp=f(ntp) 类似中文名张三,英文名zhangjohn.
5 播放时间
rtp时间戳如何转换成pts即显示时间的呢? pts= rtp 时间戳 *timebase.
例如:flv封装格式的time_base为{1,1000},ts封装格式的time_base为{1,90000}.
flv: pkt_pts=80,pkt_pts_time=80/1000=0.080000;
pkt_pts=120,pkt_pts_time=0.120000;
pkt_pts=160,pkt_pts_time=0.160000;
pkt_pts=200,pkt_pts_time=0.200000
ts: pkt_pts=7200,pkt_pts_time=7200/90000=0.080000;
pkt_pts=10800,pkt_pts_time=0.120000;
pkt_pts=14400,pkt_pts_time=0.160000;
pkt_pts=18000,pkt_pts_time=0.200000
总结:ntp,rtp,pts表示的是同一帧的时间.ntp是绝对时间,rtp是相对时间,pts是播放时间.rtp是用频率表示,pts是用秒表示.
如果音视频都从0同步开始,rtp等于pts.
如果不是同步开始,pts需要rtp 同步加减一个offer.具体实例看下文. rtp和pts是编解码时间即播放时间,不是传输时间,传输延时与rtp时间戳没关系.
二 音视频同步
音视频时间戳增长rtp增量是不同的,所以需要换算成ntp时间同步.
1 理想情况:
音视频同时编码,而且视频播放频率始终不变. 使用rtp时间戳同步就行.
image.png
因为音视频被映射到同一个时间轴上了,音频和视频帧间的相对关系很清楚.
同时RTP规范要求时间戳的初始值应该是一个随机值,那么假设音频帧时间戳的初始值是随机值1234,视频帧时间戳的初始值是随机值5678,看起来应该是下面这样
image.png
其实道理和上面一样.rtp可以同步,但是这是理想情况并不是实际情况,实际情况需要进一步处理.
2 实际情况
2.1 音视频不是同时产生
RTP规范并没有规定第一个视频帧的时间戳和第一个音频帧的时间戳必须或者应该对应到绝对时间轴的同一个点上 。
也就是说开始的一小段时间内可能只有音频或者视频,比如开始摄像头没有开,只有音频没有视频.也可能的开始网络丢包,同时视频的降低帧率,有可能出现这样的时间戳序列:0,丢包+降帧率,16200,... 第一个视频帧rtp timestamp 是16200,就无法映射到绝对时间轴上.
实际情况如下图:
发送端的音视频流并没有对齐,但是周期地发送SR包,接收端得到音视频SR包的RTP时间戳、NTP时间戳后通过线性回归得到NTP时间戳Tntp和RTP时间戳Trtp时间戳的对应关系:
- Tntp_audio = f(Trtp_audio)
- Tntp_video = f(Trtp_video)
其中Tntp = f(Trtp) = kTrtp + b 为线性函数,这样接收端每收到一个RTP包,都可以将RTP时间戳换算成NTP时间戳,从而在同一时间基准下进行音视频同步。
notes:rtp和ntp转换是线性关系:以最简单的情况为例:绝对时间ntp=x+80ms,rtp=8090=7200;
则rtp=(ntp-x)90,所以是线性关系.
2.2 使用rtcp同步.(这节是重点)
分成两种情况,a) 视频同步到音频; b) 音频同步到视频. 通常使用前者.
同步原理:将audio/video第一帧时间戳同步到同一个绝对时间上.
实例分析:
问:音频采样率8k,视频帧率25. Audio从头开始,音频时间戳320会在多少ms播放呢?
答:320/8=40ms.
问:那么收到的视频帧rtp时间戳是7200应该在多少ms播放呢?
答:7200/90=80ms播放。答案是错误的.
因为第一个音频是从0开始播放的,但是第一个视频帧可能是在第1ms开始的,而不是从0ms开始的.
如果视频从1ms开始,rtcp第一个音视频帧 video rtp 是3600,它表示41ms。而audio rtp 是160表示40ms.所以先要计算audio,video的绝对时间差,倒退开始的时间差.
视频同步到音频为例:
diff=(video ntp time)-(audio ntp time)=1ms (rtcp中NTP时间相减).
对应的rtp时间相减,3600-90=3510.3510就是视频ntp对应的40ms绝对时间(time base).
后面rtp 7200是多少ms呢?
7200-3510=7200-(3600-90)=3600+90=40ms+1=41ms;即在播放base 40ms之后的41ms播放,等于81ms的地方播放.
后续的每个audio/video rtp timestamp减去这个base就是播放时间.实现同步.
音频同步到视频原理一样.
如果不能理解上面的实例,可以画图将音视频ntp时间,rtp时间画到同一个时间轴上即可.
三 音视频同步的应用场景
音视频同步应用分成两种:一种是用于传输,一种是用于播放器.
1 用于传输
传输本来可以不用同步直接转给播放器处理,但是给第三方库的时候需要同步,比如将流转给ffmpeg。ffmpeg 要求转入的流的第一帧,必须audio,video 是同步的. 不能audio从0ms开始,video从1ms开始,那样送流给ffmpeg一定音视频不同步.所以给ffmpeg之前,需要做音视频同步.根据上面的方法就可以实现。
notes: ffmpeg源码要求第一帧同步开始:https://www.jianshu.com/p/67d3e6a1d72e
2 播放器的同步播放
播放器在上面同步的基础上,还需要增加缓存. 缓存设计分成两种情况,视频快于音频和音频快于视频.
i) 视频快于音频那么扩大视频缓冲区,缩短音频缓冲区,让视频帧等一等音频帧。
ii) 音频快于视频那么扩大音频缓冲区,缩短视频缓冲区,让音频帧等一等音频帧
四 QA
QA1:什么情况会出现不同步,为什么要同步?
比如视频第1ms才有数据,但是在第0ms播放,其他帧都是提前1ms,变成音视频不同步.
QA2:什么时候同步,需要多少次同步?
第一帧开始同步。根据上面的算法,第几个rtcp包都能同步,从哪里开始同步后面就跟着全同步了.
播放当然需要从开始同步哈,所以都是从第一帧同步。
只需要同步一次,后面就是对于基准0的播放时间了。
QA3: 音视频同步与帧率的关系?
动态帧率与时间戳同步没关系,播放器会根据fps读取正确的ts。帧率不同只是视频包多少不同,每个包的time stamp和ntp时间都是一一对应的.
QA4: 传输延时与rtp时间戳有没有关系?
ats:20,40,60,...
vts: 41,82,123....
会不会出现这种时间序列,累计造成时间戳不同步。
这个数列是不对.
40ms肯定能编解码完成,不是编解码要41ms。而是开始第一帧发送的晚造成41ms.
数列应该是41,81,121...
QA5: 两个不同的主播合流,如果有一个主播的时间戳不准确如何同步?
比如:第一个13:00:00: 编码:40ms第一帧
第二个在13:00:00:30ms开始编码,但是它的时间有问题,显示0:0:0:0开始
如何将13:00:00:30ms map 0:0:0:0.
没有实际经验。原理应该和音视频同步类似,同步到其中一个主播时间轴即可。
哪位网友有经验欢迎补充
五 实现
uint32_t ntp_compact_audio = get_compact_ntp_time( ntp_audio );
uint32_t ntp_compact_video = get_compact_ntp_time( ntp_video );
gint64 diff_ms;
if( ntp_compact_video > ntp_compact_audio ) //audio_ts be ahead of video_ts
{
diff_ms = ntp_compact_video - ntp_compact_audio;
diff_ms = ( diff_ms * 1000 + ( 1 << 16 ) / 2 ) / ( 1 << 16 );
diff_ms = diff_ms > 1 ? diff_ms : 1;
uint32_t diff_ts = diff_ms * 90;
//v_sr_ts is rtp ts
base_timestamp_video = v_sr_ts - diff_ts;
base_timestamp_audio = a_sr_ts;
}
部分引用:https://blog.csdn.net/sonysuqin/article/details/107297157