在最近的項目開發中涉及到一個伴奏和類似K歌的功能,最明顯的做法就是將播放器裏播放的聲音撲捉到緩衝區裏與麥克風的聲音做混合,然後編碼發送出去。這裏有個關鍵環節就是混音。因爲是音樂類的聲音混合,所以要求儘量保真。我看了數字信號處理方面關於波形混合的算法描述,其實就是兩個波形值線性相加得到新的波形就可以了。用符號描述:
Si= Bi + Pi; (i = 1 , 2, ,3 ...N, B表示背景音,P表示人聲)。
這個原理很簡單,但是在PC上,一個語音樣本值的範圍是-32767 ~ 32767,之間的值。如果背景和人聲疊加後,很有可能就會超過這個值,這樣只能取最大或者最小值,如此以來合成後的聲音發生了改變,這樣合成後的聲音就有爆音。達不到效果。我後來嘗試用:
Si= (Bi + Pi) / 2;
這樣可以得到最好的波形,但又引來另外一個問題,整個合成後的聲音播放出來聲音變小了。爲了讓波形儘量不變,但又維持儘量大的音量。後來我引入了一個調節參數f模型。具體算法描述如下:
假如人聲和背景音同時輸入M個語音塊,一個語音塊的N樣本。
1.初始化f = 1.0;
2.將第一個語音塊進行線性疊加。得到 S(1, 2, 3, ...N)波形序列,從S序列中找出絕對值最大的樣本數S(max).
3.計算本語音塊的調節參數 如果S(max) >32767則: f = S(max) / 32767,如果 S(max) <=32767; f用上次語音塊的f;
4.將S(1, 2, 3, ...., N)全部乘 f得到新的S樣本集合。
5.將f趨近於1.0操作 f = (1 - f) / 32 + f;
6.獲取下一個語音塊,重複第2步。
以下是基本的算法代碼示例:
class HeAudioMixer
{
....
bool mixer(const SrcAudioDataList& data_list, int32_t src_size, int16_t* dst, int32_t& dst_size);
....
protected:
float f_;
int32_t* values_;
int32_t audio_size_; //一個固定單元的AUDIO長度
};
bool HeAudioMixer::mixer(const SrcAudioDataList& data_list, int32_t src_size, int16_t* dst, int32_t& dst_size)
{
if(data_list.size() <= 0 || dst == NULL || src_size > dst_size
|| src_size == 0 || src_size > audio_size_){
return false;
}
int16_t* dst_pos = dst;
int16_t* src_pos = NULL;
int32_t mix_value = 0;
int32_t max_sample = 0;
register int32_t i = 0;
int32_t sample_num = data_list.size();
//如果只有一個樣本,無需混合,只需要賦值就行了
if(sample_num == 1){
src_pos = data_list[0];
for(i = 0; i < src_size; ++ i){
dst_pos[i] = src_pos[i];
}
f_ = 1.0;
}
else if(sample_num > 0){
for(i = 0; i < src_size; ++ i){ //統計樣本
mix_value = 0;
for(register int32_t v_index = 0; v_index < sample_num; ++ v_index){
src_pos = data_list[v_index];
if(src_pos != NULL)
mix_value += src_pos[i];
}
values_[i] = mix_value;
if(max_sample < abs(mix_value)) //求出一個最大的樣本
max_sample = abs(mix_value);
}
if(max_sample * f_ > VOLUMEMAX)
f_ = VOLUMEMAX * 1.0 / max_sample;
for(i = 0; i < src_size; ++ i)
dst_pos[i] = (int16_t)(values_[i] * f_);
//讓f_接近1.0
if(f_ < 1.0)
f_ = (1.0 - f_) / 32 + f_;
else if(f_ > 1.0)
f_ = 1.0;
dst_size = src_size;
}
return true;
}
綜述,以上是兩個樣本的混合,多個源語音樣本其實是一樣的計算。通過基本的線上測試,發現這個算法在合成方面可以做到基本不損失用戶體驗。總體效果還算不錯。如果超過5個樣本合成,合成後的聲音會有吵雜的感覺,需要繼續找到新的算法支持。