WebRTC 音视频同步原理与实现

简介: 所有的基于网络传输的音视频采集播放系统都会存在音视频同步的问题,作为现代互联网实时音视频通信系统的代表,WebRTC 也不例外。本文将对音视频同步的原理以及 WebRTC 的实现做深入分析。

时间戳 (timestamp)

同步问题就是快慢的问题,就会牵扯到时间跟音视频流媒体的对应关系,就有了时间戳的概念。


时间戳用来定义媒体负载数据的采样时刻,从单调线性递增的时钟中获取,时钟的精度由 RTP 负载数据的采样频率决定。音频和视频的采样频率是不一样的,一般音频的采样频率有 16KHz、44.1KHz、48KHz 等,而视频反映在采样帧率上,一般帧率有 25fps、29.97fps、30fps 等。


习惯上音频的时间戳的增速就是其采样率,比如 16KHz 采样,每 10ms 采集一帧,则下一帧的时间戳,比上一帧的时间戳,从数值上多 16 x10=160,即音频时间戳增速为 16/ms。而视频的采样频率习惯上是按照 90KHz 来计算的,就是每秒 90K 个时钟 tick,之所以用 90K 是因为它正好是上面所说的视频帧率的倍数,所以就采用了 90K。所以视频帧的时间戳的增长速率就是 90/ms。


时间戳的生成

音频帧时间戳的生成

WebRTC 的音频帧的时间戳,从第一个包为 0,开始累加,每一帧增加 = 编码帧长 (ms) x 采样率 / 1000,如果采样率 16KHz,编码帧长 20ms,则每个音频帧的时间戳递增 20 x 16000/1000 = 320。这里只是说的未打包之前的音频帧的时间戳,而封装到 RTP 包里面的时候,会将这个音频帧的时间戳再累加上一个随机偏移量(构造函数里生成),然后作为此 RTP 包的时间戳,发送出去,如下面代码所示,注意,这个逻辑同样适用于视频包。

image.png

视频帧时间戳的生成

WebRTC 的视频帧,生成机制跟音频帧完全不同。视频帧的时间戳来源于系统时钟,采集完成后至编码之前的某个时刻(这个传递链路非常长,不同配置的视频帧,走不同的逻辑,会有不同的获取位置),获取当前系统的时间 timestamp_us_,然后算出此系统时间对应的 ntp_time_ms_,再根据此 ntp 时间算出原始视频帧的时间戳 timestamp_rtp_,参看下面的代码,计算逻辑也在 OnFrame 这个函数中。

image.png

为什么视频帧采用了跟音频帧不同的时间戳计算机制呢?我的理解,一般情况音频的采集设备的采样间隔和时钟精度更加准确,10ms 一帧,每秒是 100 帧,一般不会出现大的抖动,而视频帧的帧间隔时间较大采集精度,每秒 25 帧的话,就是 40ms 一帧。如果还采用音频的按照采样率来递增的话,可能会出现跟实际时钟对不齐的情况,所以就直接每取一帧,按照取出时刻的系统时钟算出一个时间戳,这样可以再现真实视频帧跟实际时间的对应关系。


跟上面音频一样,在封装到 RTP 包的时候,会将原始视频帧的时间戳累加上一个随机偏移量(此偏移量跟音频的并不是同一个值),作为此 RTP 包的时间戳发送出去。值得注意的是,这里计算的 NTP 时间戳根本就不会随着 RTP 数据包一起发送出去,因为 RTP 包的包头里面没有 NTP 字段,即使是扩展字段里,我们也没有放这个值,如下面视频的时间相关的扩展字段。

image.png

音视频同步核心依据

从上面可以看出,RTP 包里面只包含每个流的独立的、单调递增的时间戳信息,也就是说音频和视频两个时间戳完全是独立的,没有关系的,无法只根据这个信息来进行同步,因为无法对两个流的时间进行关联,我们需要一种映射关系,将两个独立的时间戳关联起来。


这个时候 RTCP 包里面的一种发送端报告分组 SR (SenderReport) 包就上场了,详情请参考 RFC3550


image.png

SR 包的其中一个作用就是来告诉我们每个流的 RTP 包的时间戳和 NTP 时间的对应关系的。靠的就是上边图片中标出的 NTP 时间戳和 RTP 时间戳,通过 RFC3550 的描述,我们知道这两个时间戳对应的是同一个时刻,这个时刻表示此 SR 包生成的时刻。这就是我们对音视频进行同步的最核心的依据,所有的其它计算都是围绕这个核心依据来展开的。

SR 包的生成


由上面论述可知,NTP 时间和 RTP 时间戳是同一时刻的不同表示,只是精度和单位不一样。NTP 时间是绝对时间,以毫秒为单位,而 RTP 时间戳则和媒体的采样频率有关,是一个单调递增数值。生成 SR 包的过程在 RTCPSender::BuildSR(const RtcpContext& ctx) 函数里面,老版本里面有 bug,写死了采样率为 8K,新版本已经修复,下面截图是老版本的代码:
image.gifimage.png

计算的思路如下

首先,我们要获取当前时刻(即 SR 包生成时刻)的 NTP 时间。这个直接从传过来的参数 ctx 中就可以获得:image.gif

image.png

其次,我们要计算当前时刻,应该对应的 RTP 的时间戳是多少。根据最后一个发送的 RTP 包的时间戳 last_rtp_timestamp_ 和它的采集时刻的系统时间 last_frame_capture_time_ms_,和当前媒体流的时间戳的每 ms 增长速率 rtp_rate,以及从 last_frame_capture_time_ms_ 到当前时刻的时间流逝,就可以算出来。注意,last_rtp_timestamp_ 是媒体流的原始时间戳,不是经过随机偏移的 RTP 包时间戳,所以最后又累加了偏移量 timestamp_offset_。其中最后一个发送的 RTP 包的时间信息是通过下面的函数进行更新的:image.png

音视频同步的计算

因为同一台机器上音频流和视频流的本地系统时间是一样的,也就是系统时间对应的 NTP 格式的时间也是一样的,是在同一个坐标系上的,所以可以把 NTP 时间作为横轴 X,单位是 ms,而把 RTP 时间戳的值作为纵轴 Y,画在一起。下图展示了计算音视频同步的原理和方法,其实很简单,就是使用最近的两个 SR 点,两点确定一条直线,之后给任意一个 RTP 时间戳,都可以求出对应的 NTP 时间,又因为视频和音频的 NTP 时间是在同一基准上的,所以就可以算出两者的差值。image.png

上图以音频的两个 SR 包为例,确定出了 RTP 和 NTP 对应关系的直线,然后给任意一个 rtp_a,就算出了其对应的 NTP_a,同理也可以求任意视频包 rtp_v 对应的 NTP_v 的时间点,两个的差值就是时间差。


下面是 WebRTC 里面计算直线对应的系数 rate 和偏移 offset 的代码:

image.gifimage.png

在 WebRTC 中计算的是最新收到的音频 RTP 包和最新收到的视频 RTP 包的对应的 NTP 时间,作为网络传输引入的不同步时长,然后又根据当前音频和视频的 JitterBuffer 和播放缓冲区的大小,得到了播放引入的不同步时长,根据两个不同步时长,得到了最终的音视频不同步时长,计算过程在 StreamSynchronization::ComputeRelativeDelay() 函数中,之后又经过了 StreamSynchronization::ComputeDelays() 函数对其进行了指数平滑等一系列的处理和判断,得出最终控制音频和视频的最小延时时间,分别通过 syncable_audio_->SetMinimumPlayoutDelay(target_audio_delay_ms)syncable_video_->SetMinimumPlayoutDelay(target_video_delay_ms) 应用到了音视频的播放缓冲区。


这一系列操作都是由定时器调用 RtpStreamsSynchronizer::Process() 函数来处理的。


另外需要注意一下,在知道采样率的情况下,是可以通过一个 SR 包来计算的,如果没有 SR 包,是无法进行准确的音视频同步的

image.png

WebRTC 中实现音视频同步的手段就是 SR 包,核心的依据就是 SR 包中的 NTP 时间和 RTP 时间戳。最后的两张 NTP 时间-RTP 时间戳 坐标图如果你能看明白(其实很简单,就是求解出直线方程来计算 NTP),那么也就真正的理解了 WebRTC 中音视频同步的原理。如果有什么遗漏或者错误,欢迎大家一起交流!


「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。

image.png

相关文章
|
3月前
|
网络协议 开发工具 C#
RTSP协议探究和RTSP播放器技术实现
RTSP播放器可广泛应用于对延迟要求比较高的场景下,比如协同操控相关的智能机器人或无人机、实时视频监控、远程视频会议、网络电视等。通过控制信令实现对流媒体数据的远程控制和传输管理。
132 0
|
11天前
|
编解码 vr&ar 图形学
Unity下如何实现低延迟的全景RTMP|RTSP流渲染
随着虚拟现实技术的发展,全景视频逐渐成为新的媒体形式。本文详细介绍了如何在Unity中实现低延迟的全景RTMP或RTSP流渲染,包括环境准备、引入依赖、初始化客户端、解码与渲染、优化低延迟等步骤,并提供了具体的代码示例。适用于远程教育、虚拟旅游等实时交互场景。
19 2
|
3月前
|
编解码 网络协议 开发工具
Android平台如何实现多路低延迟RTSP|RTMP播放?
本文档详细介绍了大牛直播SDK在Android平台上实现RTSP与RTMP流媒体播放及录像功能的技术细节。早在2015年,SDK的第一版就已经支持了多实例播放,并且通过简单的实例封装就能轻松实现。文档中提供了代码示例,展示了如何开启播放、停止播放以及开始和停止录像等功能。此外,SDK还提供了丰富的配置选项,例如设置录像目录、文件大小限制、转码选项等。总结部分列出了该SDK的关键特性,包括但不限于高稳定性和低延迟的播放能力、多实例支持、事件回调、硬解码支持、网络状态监控以及复杂的网络环境处理等。这些功能使得SDK能够应对各种应用场景,特别是在对延迟和稳定性有极高要求的情况下表现优异。
|
5月前
|
Web App开发 JavaScript 前端开发
如何使用 WebRTC 获取摄像头视频
WebRTC 是一个强大的工具,能够帮助开发者在不安装任何第三方软件的情况下,在浏览器中实现实时视频和音频通信。通过简单的 HTML 和 JavaScript 代码,你可以轻松创建一个视频捕捉页面,为用户提供实时视频流。这种技术不仅适用于视频聊天应用,还可以扩展到各种实时交互场景中,如远程教学、在线协作等。
74 0
|
6月前
|
存储 算法 API
音视频同步的方法:深入探索基于FFmpeg的音视频同步策略(三)
音视频同步的方法:深入探索基于FFmpeg的音视频同步策略
257 1
|
6月前
|
编解码 UED
音视频同步的方法:深入探索基于FFmpeg的音视频同步策略(一)
音视频同步的方法:深入探索基于FFmpeg的音视频同步策略
909 1
|
6月前
|
传感器 机器学习/深度学习 编解码
音视频同步的方法:深入探索基于FFmpeg的音视频同步策略(二)
音视频同步的方法:深入探索基于FFmpeg的音视频同步策略
436 1
|
6月前
|
存储 编解码 C++
C++ 音视频原理
C++ 音视频原理
|
6月前
|
存储 缓存 算法
ffmpeg 音视频同步进阶 剖析:ffmpeg音视频同步中特殊情况处理策略
ffmpeg 音视频同步进阶 剖析:ffmpeg音视频同步中特殊情况处理策略
163 0
|
6月前
|
存储 编解码 容器
什么是 MKV 视频格式及其工作原理
对于希望创建、共享和享受多媒体内容的视频爱好者来说,MKV 视频格式是一种可靠且多功能的选择。 其兼容性、功能和质量使其成为休闲用户和行业专业人士的宝贵选择。
218 0