WebRTC 音视频同步
WebRTC 音视频同步
直播场景的音视频同步很多情况基本都是以音频为基准,视频去同步音频,从而达到音视频同步的目的,本文从 RTP 和 RTCP 协议出发讲解 WebRTC 接收端音视频同步流程
视频 Rtp 包时间戳
视频数据包在发送时都会打上 rtp 时间戳,详见 video_stream_encoder.cc set_timestamp
// 获取视频采集 ntp 时间戳
int64_t capture_ntp_time_ms;
if (video_frame.ntp_time_ms() > 0) {
capture_ntp_time_ms = video_frame.ntp_time_ms();
} else if (video_frame.render_time_ms() != 0) {
capture_ntp_time_ms = video_frame.render_time_ms() + delta_ntp_internal_ms_;
} else {
capture_ntp_time_ms = current_time_ms + delta_ntp_internal_ms_;
}
incoming_frame.set_ntp_time_ms(capture_ntp_time_ms);
// 将 ntp 时戳转换为 Rtp Timestamp
const int kMsToRtpTimestamp = 90;
incoming_frame.set_timestamp(
kMsToRtpTimestamp * static_cast<uint32_t>(incoming_frame.ntp_time_ms()));
音频 Rtp 包时间戳
音频时间戳以采样点总数递增,和系统时间戳无关,该时间戳和 RTCP 和Sender Report 会有一个对应关系
// 编码 10 ms 数据
// 音频编码模块内部会自动重传样
audio_frame->timestamp_ = _timeStamp;
// 编码,编码完成后自动触发打包和发送方法
if (audio_coding_->Add10MsData(*audio_frame) < 0) {
RTC_DLOG(LS_ERROR) << "ACM::Add10MsData() failed.";
return;
}
// 时间戳生成规则,以采样点总数递增,注意此处和系统时间是没有关系的
_timeStamp += static_cast<uint32_t>(audio_frame->samples_per_channel_);
RTCP 生成包规则
音频和视频 RTP 包发送时仅仅发送自身打包时的相对时间戳,需要通过 SR(Sender Report)携带时间基准来进行音视频同步,也就是 SR 会定时发送视频 RTP 时戳对应的 NTP 时间戳以及音频 RTP 时间戳对应的 NTP 时间戳,二者的 NTP 时间戳都是系统时间,基准是相同的
接收端处理原则
接收端先利用 SR 包将音视频 RTP 的时间戳统一到当前系统时间戳(计算逻辑其实很简单,由于二者是线性关系,使用最小二乘法算出斜率即可)
rtp_streams_synchronizer2.cc UpdateDelay
stream_synchronization.cc ComputeRelativeDelay
// 音频时间同步到当前系统时间更新
absl::optional<Syncable::Info> audio_info = syncable_audio_->GetInfo();
if (!audio_info || !UpdateMeasurements(&audio_measurement_, *audio_info)) {
return;
}
// 视频时间同步到当前系统时间更新
int64_t last_video_receive_ms = video_measurement_.latest_receive_time_ms;
absl::optional<Syncable::Info> video_info = syncable_video_->GetInfo();
if (!video_info || !UpdateMeasurements(&video_measurement_, *video_info)) {
return;
}
// 计算出以当前系统时间为基准的音频采集时间
int64_t audio_last_capture_time_ms;
if (!audio_measurement.rtp_to_ntp.Estimate(audio_measurement.latest_timestamp,
&audio_last_capture_time_ms)) {
return false;
}
// 计算出以当前系统时间为基准的视频采集时间
int64_t video_last_capture_time_ms;
if (!video_measurement.rtp_to_ntp.Estimate(video_measurement.latest_timestamp,
&video_last_capture_time_ms)) {
return false;
}
音视频各自正常播放
-
进行音视频同步前需要先确保音视频各自能单独正常流畅播放,需要二者先流畅播放后才考虑同步。由于音频和视频有自己各自的网络延迟,也就是音视频各自流畅播放的延迟是不相同的,在该延迟的基础上二者能正常播放;(WebRTC 内部叫 target_delay 目标延迟,计算方法后续文章给出)
-
在音视频能正常流畅播放的前提下,计算二者的延迟大小,有以下几种场景
- 视频延迟小、音频延迟大;此时需要增加视频延迟以保持二则同步
- 音频延迟小、视频延迟大;此时需要增加音频延迟以保持二则同步
- 此处增加的延迟在 WebRTC 叫 extra_delay
stream_synchronization.cc ComputeDelays
// 首先计算出最后一对音视频包的相对延迟,最后一对音视频包往往能表现后续音视频同步的附加延迟趋势,正数表示视频慢于音频
// Positive diff means that video_measurement is behind audio_measurement.
*relative_delay_ms =
video_measurement.latest_receive_time_ms -
audio_measurement.latest_receive_time_ms -
(video_last_capture_time_ms - audio_last_capture_time_ms);
if (*relative_delay_ms > kMaxDeltaDelayMs ||
*relative_delay_ms < -kMaxDeltaDelayMs) {
return false;
}
通过当前视频延迟和当前音频延迟之差,加上最后一对音视频包的相对延迟能够确定当前音视频的总体延迟,该延迟最终会以其他延迟附加到音频或者视频延迟之中,(注:WebRTC 并不是一次性把所有延迟附加到音视频延迟中,每次都会限定一个阈值 80ms、也就是音视频每次进行音视频同步时附加的延迟不超过 80 ms
)
// Calculate the difference between the lowest possible video delay and the
// current audio delay.
int current_diff_ms =
current_video_delay_ms - current_audio_delay_ms + relative_delay_ms;
avg_diff_ms_ =
((kFilterLength - 1) * avg_diff_ms_ + current_diff_ms) / kFilterLength;
if (abs(avg_diff_ms_) < kMinDeltaMs) {
// Don't adjust if the diff is within our margin.
return false;
}
至此音视频同步的大致流程结束,WebRTC 音视频演示 Demo 可以体验一下,有问题随时交流