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_ == 2 &&
           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<int32_t>(data_) + static_cast<int32_t>(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<int16_t>(input);
  }
}
這個算法也很簡單,一目瞭然

到此,WebRTC中的混音模塊算是分析完了,本文是根據WebRTC56版本代碼寫的,其它版本可能會有不同的地方。

原文:http://www.rtc8.com/forum.php?mod=viewthread&tid=33&extra=page%3D1
--------------------- 
作者:老衲不出家 
來源:CSDN 
原文:https://blog.csdn.net/tanningzhong/article/details/77968090 
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章