WebRTC 音视频同步

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;
  }

音视频各自正常播放

  1. 进行音视频同步前需要先确保音视频各自能单独正常流畅播放,需要二者先流畅播放后才考虑同步。由于音频和视频有自己各自的网络延迟,也就是音视频各自流畅播放的延迟是不相同的,在该延迟的基础上二者能正常播放;(WebRTC 内部叫 target_delay 目标延迟,计算方法后续文章给出)

  2. 在音视频能正常流畅播放的前提下,计算二者的延迟大小,有以下几种场景


  • 视频延迟小、音频延迟大;此时需要增加视频延迟以保持二则同步
  • 音频延迟小、视频延迟大;此时需要增加音频延迟以保持二则同步
  • 此处增加的延迟在 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 可以体验一下,有问题随时交流

Badwin

上一篇:OOA、OOD、OOP


下一篇:[javascript] ie下audio不支持一些媒体类型