WebRTC中混音流程分析

混音分为服务端混音和客户端混音两种,服务端混音是为了节省带宽。哪为什么客户端也要混音呢?哪是国为声卡同一时刻只能播放一路
语音,当你的客户端有多路接收语音时,如果你不先混音,而是每一路都直接住声卡送的话,容易会造成声音越来越延时。

WebRTC中目前只有客户端混音,混音具体实现在webrtc/modules/audio_conference_mixer目录中
想知道一个C++模块如何使用,我们一般会先看看这个模块的头文件,在audio_conference_mixer模块的include目录下有两个头文件
audio_conference_mixer.h 和 audio_conference_mixer_defines.h,我们先来看看audio_conference_mixer_defines.h,在这个文件
中定义了两个类
MixerParticipant:被混音方,负责给混音器提供原始语音数据
AudioMixerOutputReceiver :混音后的语音接收方

在audio_conference_mixer.h文件中定义了 AudioConferenceMixer 类,它继承于Module类,说明这个类的对象可以被WebRTC的工作线
程周期性的调用。在这个类定义中,

被混音方的添加和删除用SetMixabilityStatus(MixerParticipant* participant, bool  mixable)方法来实现,这个方法中
第一个参数是被混音对象指针,第二个参数是是否能被混音。

添加接收方使用RegisterMixedStreamCallback(AudioMixerOutputReceiver* receiver),参数代表接收者对象
另外我们还发现这两个方法
// Module functions
int64_t TimeUntilNextProcess() override = 0;
void  Process() override = 0;

从上面几个方法,我们就可能想象出这个混音模块大概的工作流程,WebRTC工作线程先调用混音模块的TimeUntilNextProcess方法,先确认是否到了处理
这个模块的时间,如果还没有到时间,就直接处理下一个模块 ,如果到了时间,就调用这个模块的Process方法,在Process方法中,会从所有的等待混音对象
中分别取出一个音频帧,再把所有的音频帧叠加起来,合成一个音频帧,再送给混音接收对象。

为了验证我们的想法,我们先在模块中搜索TimeUntilNextProcess和Process方法,发现这两个方法的具体实现在 AudioConferenceMixerImpl 类中
AudioConferenceMixerImpl .cc 182行AudioConferenceMixerImpl:: TimeUntilNextProcess 方法中,我们可以知道这这个模块以10ms的周期被
调用,这和WebRTC的音频采样周期刚好符合。

我们先来分析一下AudioConferenceMixerImpl:: Process :下面我会以中文的方式把Process的流程写出来,大家再对照代码自己再体会一下。
void  AudioConferenceMixerImpl: rocess() {
1,更新 _timeScheduler ,作用是确保以近似10ms的间隔去音频缓冲队列取数据来混音
2,定义了三个 AudioFrameList 音频帧队列, mixList rampOutList additionalFramesList ,这三个队列分别是当前需要混音的音频队列,上次已经被混音,
      这次需要被移除队列,附加的音频队列(在WebRTC中,主要用于文件播放器)
3,分别取数据填充这三个队列,
UpdateToMix(&mixList, &rampOutList, &mixedParticipantsMap,&remainingParticipantsAllowedToMix);
GetAdditionalAudio(&additionalFramesList);
4,把这三个队列混音
      MixFromList(mixedAudio, mixList);
      MixAnonomouslyFromList(mixedAudio, additionalFramesList);
               MixAnonomouslyFromList(mixedAudio, rampOutList);
        5,  调用WebRTC中的AudioProcessing模块处理混音后的音频帧,调整音量 LimitMixedAudio(mixedAudio);
        6,通知混音接收方 _mixReceiver->NewMixedAudio
        7,清除数据
}

Process()方法结构还比较清晰,理解起来不太难。UpdateToMix方法主要作用是从众多候选者中选出声音最大的三个,从这三个候选者中取出音频,再混音。
因为同时混太多路,容易发生重叠,造成语音不清楚。下面我们来分析UpdateToMix方法,UpdateToMix方法代码比较长,第一次看有点费力。

void  AudioConferenceMixerImpl::UpdateToMix(
    AudioFrameList* mixList,
    AudioFrameList* rampOutList,
    std::map< int , MixerParticipant*>* mixParticipantList,
    size_t* maxAudioFrameCounter)  const  {
const  size_t mixListStartSize = mixList->size();  //一直为0,不知道是不是bug
AudioFrameList activeList;   //声音最大的用户,最多三个
ParticipantFrameStructList passiveWasNotMixedList; //虽然没有说话,但是因为当前用户少于3,也要被混音
ParticipantFrameStructList passiveWasMixedList; //上一次已经被混音了,这一次没有说话的用户
for{
1,循环从候选用户取出音频帧
2,如果当前总用户数少于3,设置标志位 mustAddToPassiveList ,即使这个音频帧不是活动的,也可以被混音
3,检测上一次是不是已经被混音,设置标志位 wasMixed
if(音频帧是活动的){
下面代码是检测出最大的三个用户出来
}else{
if(上次被混音过){
添加到 passiveWasMixedList
}else if(用户总数小于3,必需要被混音){
添加 RampIn ,类似图片切换淡入淡出效果
添加到 passiveWasNotMixedList
}else{
直接释放不处理
}
}
}
}

这里需要单独说明的是,WebRTC处理被混音用户进入和退出时加了特效,要不然就会太生硬了,具体实现在 audio_frame_manipulator .cc文件中。当用户第一次进入时,会添加RampIn效果,当用户退出时,会加入RampOut效果。为了避免混音后的音量忽大忽小,调用了AudioProcessing模块的AGC功能,这样就可能保证音频的音量保持稳定。

两个音频帧的混音具体实现在
void  MixFrames( AudioFrame * mixed_frame,  AudioFrame * frame,  bool  use_limiter) {
   assert (mixed_frame-> num_channels_  >= frame-> num_channels_ );
   if  (use_limiter) {
     // Divide by two to avoid saturation in the mixing.
    // This is only meaningful if the limiter will be used.
     *frame  >>=  1 ;
  }
   if  (mixed_frame-> num_channels_  > frame-> num_channels_ ) {
     // We only support mono-to-stereo.
     assert (mixed_frame-> num_channels_  ==  &&
           frame-> num_channels_  ==  1 );
     AudioFrameOperations ::MonoToStereo(frame);
  }

  *mixed_frame  +=  *frame;
}
从*mixed_frame  +=  *frame;这一行我们可以看出AudioFrame类重定义了+=运算符,我们转到AudioFrame类去看看两个音频帧是如何混合在一起的

inline  AudioFrame& AudioFrame:: operator +=( const  AudioFrame& rhs) {
...

for  (size_t i =  0 ; i < samples_per_channel_ * num_channels_; i++) {
                 int32_t wrap_guard =
                               static_cast (data_) +  static_cast(rhs.data_);
                          data_ = ClampToInt16(wrap_guard);
}
}

从上面可以看出,两个音频帧的混合非常简单,只要把对应的音频数据相加就行。
ClampToInt16这个方法是为了防止两个short类型数据相加的结果溢出。我们转到这个方法看看是如何实现的。
inline int16_t ClampToInt16(int32_t input) {
  if (input < -0x00008000) {
    return -0x8000;
  } else if (input > 0x00007FFF) {
    return 0x7FFF;
  } else {
    return static_cast(input);
  }
}
这个算法也很简单,一目了然

到此,WebRTC中的混音模块算是分析完了,本文是根据WebRTC56版本代码写的,其它版本可能会有不同的地方。


原文:http://www.rtc8.com/forum.php?mod=viewthread&tid=33&extra=page%3D1

你可能感兴趣的:(webrtc)