关于多路语音混音的思考与实现

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


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值