文中提到的代码引用自 libwebrtc M96 版本 https://github.com/aggresss/libwebrtc/tree/M96

0x00 前言

WebRTC 音频和视频分别通过不同 RTP stream 传输,而 RFC 3550 Section 5.1 中明确说明 “The initial value of the timestamp SHOULD be random”,即两个不同的 RTP stream 之间不能直接通过 RTP 的 timestamp 进行同步。所以 RFC 3550 Section 6.4.1 中的 RTCP SR 维护了 RTP timestamp 与 NTP Time 的映射关系,在接收端通过与该 RTP Stream 关联的 RTCP SR 估算出墙上时钟 (wall clock) 后再进行音频和视频的同步。本文通过对 libwebrtc M96 中音频和视频同步的实现进行分析,进而讨论经过 SFU 转发后的音视频同步需要考量的因素。


0x01 libwebrtc 音视频同步原理

1.1 墙上时钟估算

RTCP SR 定义中与 NTP Timestamp 和 RTP Timestamp 相关的结构如下所示:

        0                   1                   2                   30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
header |V=2|P|    RC   |   PT=SR=200   |             length            |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|                         SSRC of sender                        |+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
sender |              NTP timestamp, most significant word             |
info   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|             NTP timestamp, least significant word             |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|                         RTP timestamp                         |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|                     sender's packet count                     |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|                      sender's octet count                     |+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+...

libwebrtc 在 modules/rtp_rtcp/source/rtcp_sender.ccRTCPSender::BuildSR 实现了 NTP timestamp 与 RTP timestamp 的计算过程,可通过伪代码表示为:

ntp_timestamp = now_ms;
rtp_timestamp = last_send_rtp_timestamp + (now_ms - last_send_ntp_timestamp) * clock_rate / 1000;

通过分析RTCP SR 中 rtp 与 ntp 时间戳的生成规则可以看出,两者之间在理想情况下是线性关系,如下图所示:

可以看出,只要两个不同时间的 SR 就能估算出每个 rtp timestamp 对应的 ntp timstamp, 但是收到网络抖动等影响,通过更多的 SR 进行线性拟合后会让 ntp timestamp 的估算更加精确。libwebrtc 中 system_wrappers/source/rtp_to_ntp_estimator.cc 实现了 ntp timestamp 的估算。

1.2 视频流与音频流同步关联

参考标准 WebRTC 1.0: Real-Time Communication Between Browsers 和 Media Capture and Streams ,在 MediaStream API 中找到了关于音视频同步的说明:

Each MediaStream can contain zero or more MediaStreamTrack objects. All tracks in a MediaStream are intended to be synchronized when rendered. This is not a hard requirement, since it might not be possible to synchronize tracks from sources that have different clocks. Different MediaStream objects do not need to be synchronized.

就是说在浏览器接口中一个 MediaStream 对象包含 0 或多个 MediaStreamTrack 对象,MediaStream 中的所有可同步的 MediaStreamTrack 对象在渲染时需要进行同步,不同的 MediaStream 之间不需要同步。
所有以上接口在 libwebrtc 中以 SDP 的形式被解析,通过分析 RFC 8830 可以看出,每个 m-line 中通过 a=msid 来标识 MediaStream 信息,格式为:

  // a=msid:<stream id> <track id>// msid-value = msid-id [ SP msid-appdata ]// msid-id = 1*64token-char ; see RFC 4566// msid-appdata = 1*64token-char  ; see RFC 4566

例如:

# First MediaStream - id is 47017fee-b6c1-4162-929c-a25110252400
m=audio 56500 UDP/TLS/RTP/SAVPF 96 0 8 97 98
a=msid:47017fee-b6c1-4162-929c-a25110252400 f83006c5-a0ff-4e0a-9ed9-d3e6747be7d9
m=video 56502 UDP/TLS/RTP/SAVPF 100 101
a=msid:47017fee-b6c1-4162-929c-a25110252400 b47bdb4a-5db8-49b5-bcdc-e0c9a23172e0
# Second MediaStream - id is 61317484-2ed4-49d7-9eb7-1414322a7aae
m=audio 56503 UDP/TLS/RTP/SAVPF 96 0 8 97 98
a=msid:61317484-2ed4-49d7-9eb7-1414322a7aae b94006c5-cade-4e0a-9ed9-d3e6747be7d9
m=video 56504 UDP/TLS/RTP/SAVPF 100 101
a=msid:61317484-2ed4-49d7-9eb7-1414322a7aae f30bdb4a-1497-49b5-3198-e0c9a23172e0

SDP 的解析过程详见 pc/webrtc_sdp.cc

在 libwebrtc 中需要同步的 stream 之间通过 sync_group 来关联,sync_group 的来源为 stream_id,config.sync_group = stream_ids[0];

call/call.cc 中的 Call::ConfigureSync 中实现了 audio_stream 和 video_stream 的关联逻辑。

1.3 音视频同步执行过程

音视频同步操作通常是视频同步到音频,即视频计算出渲染偏移时间同步到音频。
在 libwebrtc 中,音视频同步的基本操作对象是 AudioReceiveStreamVideoReceiveStream,两者都继承自 Syncable
通过 call/syncable.h 可以看到,Syncable 是一个纯虚类,以下是三个与音视频同步相关的纯虚函数:

  virtual bool GetPlayoutRtpTimestamp(uint32_t* rtp_timestamp, int64_t* time_ms) const = 0;virtual bool SetMinimumPlayoutDelay(int delay_ms) = 0;virtual void SetEstimatedPlayoutNtpTimestampMs(int64_t ntp_timestamp_ms, int64_t time_ms) = 0;

负责音视频同步的线程是 call 模块的 module_process_thread_,主要处理文件是 video/rtp_streams_synchronizer.cc
RtpStreamsSynchronizer 类包含以下成员:

  • StreamSynchronization
  • audio 和 video 的 Measurements
  • AudioReceiveStreamVideoReceiveStream 的指针:syncable_audio_syncable_video_

处理过程详见 RtpStreamsSynchronizer::Process()


0x02 SFU 转发后的音视频同步

2.1 SFU 中 RTCP SR 的时间戳管理

在 P2P 场景中,只有 Sender 和 Receiver,RTP 和 RTCP 都是从 Sender 直接传输到 Receiver ,而在 SFU 场景中,SFU为了适配一对多的转发需求,通常会增加一个中间层,即 Receive Stream 与 Send Stream 隔离,所以每个 Send Stream 的 Sequence 和 Timestamp 都会重新随机,RTCP 也会被隔离,Send Stream 根据发送情况重新生成 RTCP SR。

通常,SFU 会以本地时间为基准进行 NTP timestamp 与 RTP timestamp 计算,可通过伪代码表示为:

ntp_timestamp = now_ms;
rtp_timestamp = last_send_rtp_timestamp + (now_ms - last_send_ntp_timestamp) * clock_rate / 1000;

这种计算方式在简单传输的场景下,通常不会出现异常,如下图所示:


因为 audio 与 video 在同一个 PC 中,PC 的网络抖动同时作用在 audio 和 video 上,产生的延迟也相对同步,所以 Receiver A 在接收后通过 SFU 的 RTCP SR 计算的视频延迟偏移与 P2P 情况下的误差并不大。

下图演示了一个增加了一次转发,并且音频和视频在不同的传输通道中二次转发:


当音视频在不同的传输通道时,网络抖动与延迟的不同步会导致 Receiver 只以 SFU 到 Receiver 之间的 RTCP SR 计算视频延迟偏移产生较大误差,所以需要对上面 SFU 中生成 RTCP SR 的算法做一些改进,改进的思路为以 Sender 的 NTP timestamp 作为时间基准,每一级 SFU 以上一级的 SFU 作为时间基准,这样 Receiver 接收到的 RTCP SR 仍然是以 Sender 为时间基准,从而剪掉了 SFU 转发过程中每一级 SFU 独立生成 RTCP SR 而增加的误差。计算步骤如下:

  • ① SFU 接收到 Receive Stream 的 RTCP SR 后通知与其关联的 Send Stream;
  • ② Send Stream 接收到上游 Receive Stream 的 STCP SR 通知后,记录
    • last_sync_rtp_ts
    • last_sync_remote_ntp_ms
    • last_sync_local_ntp_ms
  • ③ rtp_timestamp 和 ntp_timestamp 计算伪代码:
    rtp_timestamp = last_sync_rtp_ts + (now_ms - last_sync_local_ntp_ms) * clock_rate() / 1000;
    ntp_timestamp = now_ms - last_sync_local_ntp_ms + last_sync_remote_ntp_ms;
    

注意:因为修改了 RTCP SR 的时间基准,所以 Send Stream 接收到 RTCP RR 时通过 LSR 计算 RTT 时也需要做相应的修改。

2.2 SFU 中音视频同步关联管理

下图为 SFU 的经典场景,一个 Receiver 通过一个 PC 可以订阅多个 Sender 的音视频:

通常 Receiver 的 Remote Description 会由 Receiver 或者 SFU 将多个 Sender 的 SDP 进行合成,在 Receiver 侧的表现为一个 PC 中包含多个 MediaStream,需要根据 msid 对属于不同 MediaStream 的 MediaStreamTrack 进行描述,以保证每个视频都能关联到正确的音频。


参考文档

  1. WebRTC音视频同步 · 言剑
  2. WebRTC 音视频同步原理与实现 · 阿里云视频云
  3. WebRTC音视频同步机制实现分析 · weizhenwei
  4. WebRTC笔记(三)音视频同步 · jiayayao
  5. WebRTC音视频同步分析 · lincai2018
  6. WebRTC研究:MediaStream概念以及定义 · 剑痴乎

WebRTC 音视频同步分析相关推荐

  1. WebRTC音视频同步详解

    WebRTC音视频同步详解 1 WebRTC版本 2 时间戳 2.1 视频时间戳 2.2 音频时间戳 2.3 NTP时间戳 2 延迟 3 同步 3.1 一张图看懂音视频同步 3.2 音视频相对延迟 3 ...

  2. Android 短视频 SDK 转场特效的音视频同步分析

    在短视频的应用场景中,经常存在用户拍摄的两个或者多个视频生成一个视频的需求,为了达到两个视频平滑过渡,就需要在两个视频中间添加转场效果. 由于导入视频的帧率.码率等参数都不一致,如何保证在添加完转场效 ...

  3. 音视频技术之ffplay源码分析-音视频同步

    音视频同步的目的是为了使播放的声音和显示的画面保持一致.视频按帧播放,图像显示设备每次显示一帧画面,视频播放速度由帧率确定,帧率指示每秒显示多少帧:音频按采样点播放,声音播放设备每次播放一个采样点,声 ...

  4. Android IOS WebRTC 音视频开发总结(八十七)-- WebRTC中丢包重传NACK实现分析

    Android IOS WebRTC 音视频开发总结(八十七)-- WebRTC中丢包重传NACK实现分析 本文主要介绍WebRTC中丢包重传NACK的实现,作者:weizhenwei ,文章最早发表 ...

  5. ffplay分析 (音视频同步:主时钟为音频)

    <ffplay的数据结构分析> <ffplay分析(从启动到读取线程的操作)> <ffplay分析(视频解码线程的操作)> <ffplay分析(音频解码线程的 ...

  6. ffmpeg源码分析_ffmpeg音视频同步的几种策略

    在前面的文章中,我们介绍了播放器的视频渲染及音频渲染的相关知识,这些都是单独进行的,一旦在现实开发中将视频及音频结合在一起播放就会出现音视频不同步的问题. 下面我们就来分析一下如何解决音视频同步的问题 ...

  7. 用Excel分析音视频同步

    声明:     这里主要介绍如何运用Excel来分析音视频是否同步,希望可以对大家有所帮助. 介绍:     学习音视频就一定要知道做音视频同步,而现在我们来分析音视频同步的工具也是有的,比如easy ...

  8. FFplay源码分析-音视频同步1

    本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8 FFplay 源码分析系列以一条简单的命令开始,ffplay -i a.mp4.a.mp4下载链接:百度网盘,提取 ...

  9. ffplay源码分析:音视频同步

    1. 音视频同步 音视频同步的目的是为了使播放的声音和显示的画面保持一致.视频按帧播放,图像显示设备每次显示一帧画面,视频播放速度由帧率确定,帧率指示每秒显示多少帧:音频按采样点播放,声音播放设备每次 ...

最新文章

  1. linux的套接口和管道
  2. 反弹端口 HTTP代理 HTTP隧道技术
  3. 6 HBase java API访问HBase数据库
  4. QString 与中文问题
  5. 【飞鸽传书】飞鸽传书2011绿色版
  6. 信息学奥赛一本通(2017:【例4.2】输出偶数)
  7. 131. 分割回文串
  8. Debian从光盘apt-get
  9. 几种数据库快速csv入库方式整理
  10. python使用内置函数方法和桶排序方法实现随机数去重、排序输出
  11. 智鼎测评--行测相关
  12. 微信支付商家转账到零钱功能使用教程
  13. 安卓开发中的 “Android高手” ,需要具备哪些技术?
  14. Hadoop LZO压缩配置
  15. numpy save load
  16. 51单片机控制TB6600驱动器驱动42步进电机
  17. Mysql 给字符串类型字段 加索引方法
  18. Ubuntu红外相机SDK/驱动安装(optris PI 400i / PI 450i)
  19. [PHP] 算法-请找出带环链表的环的入口结点的PHP实现
  20. vite+element-plus项目基础搭建

热门文章

  1. Windows Terminal + WSL2 + CENTOS 配置Windows命令终端
  2. 必须吹吹自己,太厉害了!-简直不敢相信,面试拼多多我只用了15天就成功拿下offer,
  3. java 佛祖保佑_佛祖保佑 永无bug 注释模板设置详解(仅供娱乐)
  4. 【墨尘】变态心理学(北京大学)
  5. 餐桌 (Standard IO)
  6. No tests found for given includes: [xxx.xxx.testList](filter.includeTestsMatching)
  7. [python]打日语
  8. 智慧书-永恒的处世经典格言:201-240
  9. 虚拟带库 Vistor + TSM 安装 (在家折腾了一个周末)
  10. 华为使用计算机投屏要打开什么,华为Mate20手机怎么投屏到电脑上呢