设为首页收藏本站

安徽论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 21189|回复: 0

WebRTC音视频同步分析

[复制链接]

77

主题

512

回帖

1017

积分

金牌会员

Rank: 6Rank: 6

积分
1017
发表于 2022-3-26 10:29:59 | 显示全部楼层 |阅读模式
网站内容均来自网络,本站只提供信息平台,如有侵权请联系删除,谢谢!
对于视频点播还是实时视频开发,音视频同步是一个必要的环节。
目录
一、音视频同步原理
二、点播、直播视频播放器
三、实时视频
四、WebRTC音视频同步源码分析
五、总结



  • 一、音视频同步原理
一般来说,音视频同步就是视频同步到音频。视频在渲染的时候,每一帧视频根据与音频的时间戳对比,来调整立即渲染还是延迟渲染。比如有一个音频序列,他们的时间戳是A(0, 20, 40, 60,80,100, 120...),视频序列V(0, 40, 80, 120...)。音画同步的步骤如下:
1)取一帧音频A(0),播放。取一帧视频V(0),视频帧时间戳与音频相等,视频立即渲染。
2)取一帧音频A(20),播放。取一帧视频V(40),视频帧时间戳大于音频,视频太早,需要等待。
3)取一帧音频A(40),播放。取出视频,还是上面的V(40),视频帧时间戳与音频相等(真实场景中不一定完全相等,他们之间差值的绝对值在一个帧间隔时间内也可以认为是相同的时间戳),视频立即渲染。
对于视频播放器和实时视频,他们的同步原理如上面描述的一样,逃不开时间戳的对齐,只是在实现的时候可能有些差异。


  • 二、点播、直播视频播放器
对于初学视频播放器的新手还是有多年音视频开发经验的人,都可以参照ffmpeg源码目录下的ffplay.c来了解音视频同步原来。本文不会对此展开讲。


  • 三、实时视频
像我们生活中接触到的微信视频、视频会议都是实时视频的范畴。视频采集到对方观看的延迟一般最多在400ms。为了更快的传输速度,一般都会采用udp实现。但是由于udp传输是不可靠的,数据包容易丢失和乱序,所以在使用UDP的时候,会增加丢包重传、重排序的逻辑。这时就会引入了jitterbuffer的逻辑。实时视频的音画同步原理还是(一)中讲的,只是实现手段上是通过音视频两个jitterbuffer的控制。
实时视频比较好的框架就是WebRTC,本文将结合WebRTC的源码分析音视频同步的原理。


  • 四、WebRTC音视频同步源码分析
WebRTC源码目录下video/stream_synchronization.cc实现了音视频同步
  1. void RtpStreamsSynchronizer::Process() {  RTC_DCHECK_RUN_ON(&process_thread_checker_);  last_sync_time_ = rtc::TimeNanos();  rtc::CritScope lock(&crit_);  if (!syncable_audio_) {    return;  }  RTC_DCHECK(sync_.get());  absl::optional 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 video_info = syncable_video_->GetInfo();  if (!video_info || !UpdateMeasurements(&video_measurement_, *video_info)) {    return;  }  /**  struct Info {    int64_t latest_receive_time_ms = 0;// 最新接收到rtp包的时刻    uint32_t latest_received_capture_timestamp = 0; //最新接收到的rtp包的时间戳    uint32_t capture_time_ntp_secs = 0; // 采集的ntp时间,秒的部分   (对端,从sr中获取得到,用于rtp至ntp的换算)    uint32_t capture_time_ntp_frac = 0; // 采集的ntp时间,非秒的部分 (对端,从sr中获取得到,用于rtp至ntp的换算)    uint32_t capture_time_source_clock = 0; // rtp时间戳          (对端,从sr中获取得到,用于rtp至ntp的换算)    int current_delay_ms = 0;  };  */  if (last_video_receive_ms == video_measurement_.latest_receive_time_ms) {    // No new video packet has been received since last update.    return;  }  int relative_delay_ms;  // Calculate how much later or earlier the audio stream is compared to video.  if (!sync_->ComputeRelativeDelay(audio_measurement_, video_measurement_,                                   &relative_delay_ms)) {    return;  }  TRACE_COUNTER1("webrtc", "SyncCurrentVideoDelay",                 video_info->current_delay_ms);  TRACE_COUNTER1("webrtc", "SyncCurrentAudioDelay",                 audio_info->current_delay_ms);  TRACE_COUNTER1("webrtc", "SyncRelativeDelay", relative_delay_ms);  int target_audio_delay_ms = 0;  int target_video_delay_ms = video_info->current_delay_ms;  // Calculate the necessary extra audio delay and desired total video  // delay to get the streams in sync.  if (!sync_->ComputeDelays(relative_delay_ms, audio_info->current_delay_ms,                            &target_audio_delay_ms, &target_video_delay_ms)) {    return;  }  syncable_audio_->SetMinimumPlayoutDelay(target_audio_delay_ms);  syncable_video_->SetMinimumPlayoutDelay(target_video_delay_ms);}
复制代码
为了对WebRTC的同步有比较好的理解,我们先看Process方法中最下面的两行代码。
  1. syncable_audio_->SetMinimumPlayoutDelay(target_audio_delay_ms);syncable_video_->SetMinimumPlayoutDelay(target_video_delay_ms);
复制代码
SetMinimumPlayoutDelay会传递一个最小playout_delay的值(target_xxx_delay_ms)下去,一直传递到音频或视频jitterbuffer上。告诉jitterbuffer:之后渲染的每一帧至少要延迟target_xxx_delay_ms时间才能输出,除非下次重新传递新的值。特别注意:现在WebRTC最新代码里,jitterbuffer使用了新的实现modules/video_coding/frame_buffer2.cc,而且,这里的target_xxx_delay_ms只是至少要延迟的时间,可能真实的延迟会略大于target_xxx_delay_ms。因为jitterbuffer中还会计算一个current_delay,它包括了jitterdelay、renderdelay和requiredDecodeTime的总和。所以jitterbuffer总的delay时间为:int actual_delay = std::max(current_delay_ms_, min_playout_delay_ms_);jitterbuffer的细节在本文中就不再扩展。
回过头来,有了SetMinimumPlayoutDelay如何控制音画同步呢?
原理是这样的:
1、如果音频播放比视频快,调大音频的target_audio_delay_ms或者调小target_video_delay_ms;
2、如果音频播放比视频慢了,调小音频的target_audio_delay_ms或者调大视频的target_video_delay_ms;
3、音视频处于同步状态,不作调整;
至于如何判断音视频谁快谁慢以及如何调整target_audio_delay_ms和target_video_delay_ms,我们继续查看代码。
  1. absl::optional 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 video_info = syncable_video_->GetInfo();if (!video_info || !UpdateMeasurements(&video_measurement_, *video_info)) {  return;}if (last_video_receive_ms == video_measurement_.latest_receive_time_ms) {  // No new video packet has been received since last update.  return;}
复制代码
UpdateMeasurements用于获取最新收到数据包的时间戳(latest_timestamp)和到达时刻(latest_receive_time_ms)以及更新RtpToNtpEstimator(用于换算rtp时间戳为ntp时间)。音频和视频分别记录在audio_measurement_和video_measurement_中。
接下来,
  1. int relative_delay_ms;// Calculate how much later or earlier the audio stream is compared to video.if (!sync_->ComputeRelativeDelay(audio_measurement_, video_measurement_,                                 &relative_delay_ms)) {  return;}
复制代码
ComputeRelativeDelay计算在网络传输上音频相对视频提早了多少毫秒relative_delay_ms。
  1. // Positive diff means that video_measurement is behind audio_measurement./// relative_delay_ms meams A - V.*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);
复制代码
上面一步只是算出了音频数据包比视频数据包网络传输上相对提早时间(命名上叫delay,其实是提早时间),接下来开始计算target_audio_delay_ms和target_video_delay_ms。
  1. int target_audio_delay_ms = 0;int target_video_delay_ms = video_info->current_delay_ms;// 初始化为视频当前的TargetDelay                                                    // 会被赋值给ComputeDelays中的current_video_delay_ms// Calculate the necessary extra audio delay and desired total video// delay to get the streams in sync.if (!sync_->ComputeDelays(relative_delay_ms, audio_info->current_delay_ms,                          &target_audio_delay_ms, &target_video_delay_ms)) {  return;}
复制代码
ComputeDelays会结合relative_delay_ms和音视频当前的target_delay_ms, 计算target_audio_delay_ms和target_video_delay_ms。
[code]bool StreamSynchronization::ComputeDelays(int relative_delay_ms,                                          int current_audio_delay_ms,                                          int* total_audio_delay_target_ms,                                          int* total_video_delay_target_ms) {  assert(total_audio_delay_target_ms && total_video_delay_target_ms);  int current_video_delay_ms = *total_video_delay_target_ms;  RTC_LOG(LS_VERBOSE)  base_target_delay_ms_      // We have no extra delay in VoiceEngine, increase the video delay.      // Note: diff_ms is negative; subtract the negative difference.      channel_delay_.extra_video_delay_ms -= diff_ms;  // X - (-Y) = X + Y.      channel_delay_.extra_audio_delay_ms = base_target_delay_ms_;    }  }  // Make sure that video is never below our target.  channel_delay_.extra_video_delay_ms =      std::max(channel_delay_.extra_video_delay_ms, base_target_delay_ms_);  int new_video_delay_ms;  if (channel_delay_.extra_video_delay_ms > base_target_delay_ms_) {    new_video_delay_ms = channel_delay_.extra_video_delay_ms;  } else {    // No change to the extra video delay. We are changing audio and we only    // allow to change one at the time.    new_video_delay_ms = channel_delay_.last_video_delay_ms;  }  // Make sure that we don't go below the extra video delay.  new_video_delay_ms =      std::max(new_video_delay_ms, channel_delay_.extra_video_delay_ms);  // Verify we don't go above the maximum allowed video delay.  new_video_delay_ms =      std::min(new_video_delay_ms, base_target_delay_ms_ + kMaxDeltaDelayMs);  int new_audio_delay_ms;  if (channel_delay_.extra_audio_delay_ms > base_target_delay_ms_) {    new_audio_delay_ms = channel_delay_.extra_audio_delay_ms;  } else {    // No change to the audio delay. We are changing video and we only    // allow to change one at the time.    new_audio_delay_ms = channel_delay_.last_audio_delay_ms;  }  // Make sure that we don't go below the extra audio delay.  new_audio_delay_ms =      std::max(new_audio_delay_ms, channel_delay_.extra_audio_delay_ms);  // Verify we don't go above the maximum allowed audio delay.  new_audio_delay_ms =      std::min(new_audio_delay_ms, base_target_delay_ms_ + kMaxDeltaDelayMs);  // Remember our last audio and video delays.  channel_delay_.last_video_delay_ms = new_video_delay_ms;  channel_delay_.last_audio_delay_ms = new_audio_delay_ms;  RTC_LOG(LS_VERBOSE)
免责声明
1. 本论坛所提供的信息均来自网络,本网站只提供平台服务,所有账号发表的言论与本网站无关。
2. 其他单位或个人在使用、转载或引用本文时,必须事先获得该帖子作者和本人的同意。
3. 本帖部分内容转载自其他媒体,但并不代表本人赞同其观点和对其真实性负责。
4. 如有侵权,请立即联系,本网站将及时删除相关内容。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表