使用rtcp实现音视频同步
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用rtcp实现音视频同步相关的知识,希望对你有一定的参考价值。
参考技术A音视频同步是指音视频的rtp时间戳同步. audio/video rtp 时间戳不能自己同步,需要audio/video rtcp同步。
发送端以一定的频率发送RTCP SR(Sender Report)这个包,SR分为视频SR和音频SR,SR包内包含一个RTP时间戳和对应的NTP时间戳,可以用<ntp,rtp>对做音视频同步.(同步过程在后面)
Rtcp sr 格式
例如:一个音频包打包20ms的数据.采样率48k.对应的采样数为 48000 * 20 / 1000 = 960,也就是说每个音频包里携带960个音频采样,因为1个采样对应1个时间戳,那么相邻两个音频RTP包的时间戳之差就是960。
视频采样率是帧率,视频时间戳的单位为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时间戳也必须一致
NTP时间戳是从1900年1月1日00:00:00以来经过的秒数.
rtp是相对时间,ntp是绝对时间.rtp时间戳和ntp时间戳表示的意义是相同的. 可以互相转换,rtp=f(ntp) 类似中文名张三,英文名zhangjohn.
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时间同步.
音视频同时编码,而且视频播放频率始终不变. 使用rtp时间戳同步就行.
因为音视频被映射到同一个时间轴上了,音频和视频帧间的相对关系很清楚.
同时RTP规范要求时间戳的初始值应该是一个随机值,那么假设音频帧时间戳的初始值是随机值1234,视频帧时间戳的初始值是随机值5678,看起来应该是下面这样
2.1 音视频不是同时产生
RTP规范并没有规定第一个视频帧的时间戳和第一个音频帧的时间戳必须或者应该对应到绝对时间轴的同一个点上 。
也就是说开始的一小段时间内可能只有音频或者视频,比如开始摄像头没有开,只有音频没有视频.也可能的开始网络丢包,同时视频的降低帧率,有可能出现这样的时间戳序列:0,丢包+降帧率,16200,... 第一个视频帧rtp timestamp 是16200,就无法映射到绝对时间轴上.
实际情况如下图:
音视频同步应用分成两种:一种是用于传输,一种是用于播放器.
1 用于传输
传输本来可以不用同步直接转给播放器处理,但是给第三方库的时候需要同步,比如将流转给ffmpeg。ffmpeg 要求转入的流的第一帧,必须audio,video 是同步的. 不能audio从0ms开始,video从1ms开始,那样送流给ffmpeg一定音视频不同步.所以给ffmpeg之前,需要做音视频同步.根据上面的方法就可以实现。
notes: ffmpeg源码要求第一帧同步开始: https://www.jianshu.com/p/67d3e6a1d72e
2 播放器的同步播放
播放器在上面同步的基础上,还需要增加缓存. 缓存设计分成两种情况,视频快于音频和音频快于视频.
i) 视频快于音频那么扩大视频缓冲区,缩短音频缓冲区,让视频帧等一等音频帧。
ii) 音频快于视频那么扩大音频缓冲区,缩短视频缓冲区,让音频帧等一等音频帧
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 );
//v_sr_ts is rtp ts
base_timestamp_video = v_sr_ts - diff_ts
RTP RTCP在音视频传输与同步方面的使用
转自:http://blog.csdn.net/kof98765/article/details/17733701
1 音视频实时传输
1.1 Jrtplib库介绍
本系统采用开源库Jrtplib进行RTP传输模块的开发。Jrtplib库是由比利时Hasselt大学EDM(Expertise Centre for Digital Media)开发的一个用C++语言实现的完全开源的RTP库,目前已经可以运行在Windows、Linux、FreeBSD、Solaris、Unix和VxWorks等多种操作系统上,其现有的最新版本为Jrtplib3.7.1。
Jrtplib完全遵循RFC3550标准设计,提供了丰富的接口,使用win32 socket实现网络通讯。应用该库进行开发设计时,开发人员只需要关心RTP负载、RTCP负载、时间戳增量、发送目标地址及端口、RTCP带宽及发送间隔等最基本的问题,而不必考虑一些琐碎的细节,可以从Jrtplib的网站(http://lumumba.luc.ac.be/jori/jrtplib/jrtplib.html)下载更新的源码包。
1.2 Jrtplib初始化
在使用Jrtplib进行实时流媒体数据传输之前,首先应该生成RTPSession类的一个实例来表示此次RTP会话,然后调用Create() 方法来对其进行初始化操作。RTPSession类的Create()方法只有一个参数,用来指明此次RTP会话所采用的端口号。示例1中给出了一个最简单的初始化框架,它只是完成了RTP会话的初始化工作,还不具备任何实际的功能。
1、initial.cpp
#include "rtpsession.h"
int main(void)
{
RTPSession sess;
sess.Create(5000);
return 0;
}
如果RTP会话创建过程失败,Create()方法将会返回一个负数,通过它虽然可以很容易地判断出函数调用究竟是成功的还是失败的,但却很难明白出错的原因到底什么。Jrtplib采用了统一的错误处理机制,它提供的所有函数如果返回负数就表明出现了某种形式的错误,而具体的出错信息则可以通过调用 RTPGetErrorString()函数得到。RTPGetErrorString()函数将错误代码作为参数传入,然后返回该错误代码所对应的错误信息。示例2给出了一个更加完整的初始化框架,它可以对RTP会话初始化过程中所产生的错误进行更好的处理:
2、framework.cpp
#include
#include "rtpsession.h"
int main(void)
{
RTPSession sess;
int status;
char* msg;
sess.Create(6000);
msg = RTPGetErrorString(status);
printf("Error String:%s\\n",msg);
return 0;
}
设置恰当的时戳单元,是RTP会话初始化过程所要进行的另外一项重要工作,这是通过调用RTPSession类的SetTimestampUnit ()方法来实现的,该方法同样也只有一个参数,表示的是以秒为单元的时戳单元。例如,当使用RTP会话传输8000Hz采样的音频数据时,由于时戳每秒钟将递增8000,所以时戳单元相应地应该被设置成1/8000:
sess.SetTimestampUnit(1.0/8000.0);
1.3 Jrtplib数据发送
当RTP会话成功建立起来之后,接下去就可以开始进行流媒体数据的实时传输了。首先需要设置好数据发送的目标地址,RTP协议允许同一会话存在多个目标地址,这可以通过调用RTPSession类的AddDestination()、DeleteDestination()和 ClearDestinations()方法来完成。例如,下面的语句表示的是让RTP会话将数据发送到本地主机的6000端口:
unsigned long addr = ntohl(inet_addr("127.0.0.1"));
sess.AddDestination(addr, 6000);
目标地址全部指定之后,接着就可以调用RTPSession类的SendPacket()方法,向所有的目标地址发送流媒体数据。SendPacket()是RTPSession类提供的一个重载函数,它具有下列多种形式:
int SendPacket(void *data,int len)
int SendPacket(void *data,int len,unsigned char pt,bool mark,unsigned long timestampinc)
int SendPacket(void *data,int len,unsigned short hdrextID,void *hdrextdata,int numhdrextwords)
int SendPacket(void *data,int len,unsigned char pt,bool mark,unsigned long timestampinc,unsigned short hdrextID,void *hdrextdata,int numhdrextwords)
SendPacket()最典型的用法是类似于下面的语句,其中第一个参数是要被发送的数据,而第二个参数则指明将要发送数据的长度,再往后依次是RTP负载类型、标识和时戳增量。
sess.SendPacket(buffer,5,0,false,10);
对于同一个RTP会话来讲,负载类型、标识和时戳增量通常来讲都是相同的,Jrtplib允许将它们设置为会话的默认参数,这是通过调用 RTPSession类的SetDefaultPayloadType()、SetDefaultMark()和 SetDefaultTimeStampIncrement()方法来完成的。为RTP会话设置这些默认参数的好处是可以简化数据的发送,例如,如果为 RTP会话设置了默认参数:
sess.SetDefaultPayloadType(0);
sess.SetDefaultMark(false);
sess.SetDefaultTimeStampIncrement(10);
之后在进行数据发送时只需指明要发送的数据及其长度就可以了:
sess.SendPacket(buffer,5);
1.4 Jrtplib数据接收
对于流媒体数据的接收端,首先需要调用RTPSession类的PollData()方法来接收发送过来的RTP或者RTCP数据报。由于同一个 RTP会话中允许有多个参与者(源),既可以通过调用RTPSession类的GotoFirstSource()和GotoNextSource() 方法来遍历所有的源,也可以通过调用RTPSession类的GotoFirstSourceWithData()和 GotoNextSourceWithData()方法来遍历那些携带有数据的源。在从RTP会话中检测出有效的数据源之后,接下去就可以调用 RTPSession类的GetNextPacket()方法从中抽取RTP数据报,当接收到的RTP数据报处理完之后,一定要记得及时释放。下面的代码示范了该如何对接收到的RTP数据报进行处理:
if (sess.GotoFirstSourceWithData()) {
do {
RTPPacket *pack;
pack = sess.GetNextPacket(); // 处理接收到的数据
delete pack;
} while (sess.GotoNextSourceWithData());
}
Jrtplib为RTP数据报定义了三种接收模式,其中每种接收模式都具体规定了哪些到达的RTP数据报将会被接受,而哪些到达的RTP数据报将会被拒绝。通过调用RTPSession类的SetReceiveMode()方法可以设置下列这些接收模式:
a) RECEIVEMODE_ALL 缺省的接收模式,所有到达的RTP数据报都将被接受;
b) RECEIVEMODE_IGNORESOME 除了某些特定的发送者之外,所有到达的RTP数据报都将被接受,而被拒绝的发送者列表可以通过调用AddToIgnoreList()、DeleteFromIgnoreList()和ClearIgnoreList()方法来进行设置;
c) RECEIVEMODE_ACCEPTSOME 除了某些特定的发送者之外,所有到达的RTP数据报都将被拒绝,而被接受的发送者列表可以通过调用AddToAcceptList ()、DeleteFromAcceptList()和ClearAcceptList ()方法来进行设置。
2 音视频同步
2.1 RTCP控制参数
由于音视频流作为不同的RTP会话传送,它们在RTP层无直接关联。尽管由一个数据源发出的不同的流具有不同的同步源标识(SSRC),为能进行流同步,RTCP要求发送方给接收方传送一个唯一的标识数据源的规范名(canonical name),应用层藉此关联音视频流,以便实现同步。
RTP/ RTCP中有时间戳(相对和绝对)和序列号等信息,可以利用它实现基于时间戳的多媒体流同步。使用相对时间戳和序列号实现流内同步;使用相对和绝对时间戳的对应关系实现流间同步。获得相对与绝对时间戳的算法如下:
while ((pack = GetNextPacket()) != NULL)
{
if(srcdat->SR_HasInfo() && srcdat->SR_GetRTPTimestamp() != app->mvideortcprtp)
{
app->mvideortcprtp = srcdat->SR_GetRTPTimestamp();
app->mvideortcpntp = srcdat->SR_GetNTPTimestamp().GetMSW();
srcdat->FlushPackets(); }
DeletePacket(pack);
}
2.2 音视频流间同步实现
发送端在发送音视频数据时,同时也会发送SR包,这样可以使接收方能够正确使音视频同步播放。具体实现方法是在接收方每次接收数据包后,再遍历一次数据源,获取所有源端的SS_RTPTime与SS_NTPTime这两则数据,通过获取音频端与视频端的数据,可以利用下面的公式进行计算。先描述变量如表2.1所列:
表 2.1 变量描述表
类型
RTP数据
NTP数据
RTP时戳频率
音频
Audio_SRRTPTime
Audio_SRNTPTime
Audio_Fre
视频
Video_SRRTPTime
Video_SRNTPTime
Video_Fre
从SR包中可以读出音频与视频的RTP与NTP数据,而需要计算的是时戳频率,利用下述公式:
Audio_Fre=(AudioSRRTPtime2- AudioSRRTPtime1)/( AudioSRNTPtime2- AudioSRNTPtime1); (4.3)
Video_Fre=( VideoSRRTPtime2- VideoSRRTPtime1)/( VideoSRNTPtime2- Video SRNTPtime1); (4.4)
然后计算视频RTP的时间,即:
Video_RTPTime=Video_SRRTPTime+(Audio_SRNTP-Video_SRRTP)×Video_Fre;
(4.5)
最后按式 (4.6)即可计算出Video_TRUERTP时间,将其与从RTP包中读出的时间进行比较,就可以进行同步播放了。
(Video_TRUERTP-Video_RTPTime)/Video_Fre=(Audio_TRUERTP-Audio_SRRTPTime)/Audio_Fre; (4.6)
下面采用临时缓冲区的方法来同步音视频。因为要保证音频优先正常传输,所以本系统以音频为主轴,视频为辅轴。以最大延迟时间为缓冲区大小,存放M个音频帧数据。当接收端获得M个音频帧后开始播放音频,每次获得视频帧时,就计算出当前视频应当播放RTP时间与现有RTP时间进行比对,如若在120ms以内,迅速播放;延迟120ms以上,则扔掉,然后比对下一帧;还在120ms以内,放入视频缓冲区内进行储存,如果视频缓冲区的大小超过了最大临时缓冲区的数值,依旧开始播放。
以上是关于使用rtcp实现音视频同步的主要内容,如果未能解决你的问题,请参考以下文章