WebRTC 音视频同步分析

文中提到的代码引用自 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                   3
        0 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 时间戳的生成规则可以看出,两者之间在理想情况下是线性关系,如下图所示:
WebRTC 音视频同步分析
可以看出,只要两个不同时间的 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 BrowsersMedia 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;

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

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

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

WebRTC 音视频同步分析
当音视频在不同的传输通道时,网络抖动与延迟的不同步会导致 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 的音视频:
WebRTC 音视频同步分析
通常 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】demo 测试 peerconnection_server 和 peerconnection_client


下一篇:srs更改端口号导致webrtc播放异常