直播场景的音视频同步很多情况基本都是以音频为基准,视频去同步音频,从而达到音视频同步的目的,本文从 RTP 和 RTCP 协议出发讲解 WebRTC 接收端音视频同步流程
视频数据包在发送时都会打上 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()));
音频时间戳以采样点总数递增,和系统时间戳无关,该时间戳和 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_);
音频和视频 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 目标延迟,计算方法后续文章给出)
在音视频能正常流畅播放的前提下,计算二者的延迟大小,有以下几种场景
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 可以体验一下,有问题随时交流
Badwin