在最近的项目开发中涉及到一个伴奏和类似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个样本合成,合成后的声音会有吵杂的感觉,需要继续找到新的算法支持。