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

本文介绍了一种用于在线K歌场景的音频混合算法,该算法能够有效避免因波形叠加导致的爆音问题,并通过动态调整增益保持输出音量稳定。

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


下表列出推荐的主要硬件模块型号,并说明选型理由(课堂环境、稳定性、易采购工程实现便利性)。 PLC(控制核心) 推荐型号:Siemens S7-1200 系列,CPU1214C DC/DC/DC(或 CPU1212/CPU1215 可根据 I/O 数量调整)。 理由:支持高速计数/高速输入、内置以太网,编程方便,工业可靠,便于扩展。 扩展 I/O / 输入终端 推荐:Siemens SM1223 数字量输入/输出模块(若需要额外点位);或者使用 DIN 导轨式通用输入端子排 + 光耦隔离板。 理由:工业兼容性模块化扩展。 学生抢答按钮(终端) 推荐型号:Schneider Electric XB4 系列工业按钮(防尘、按键长寿命);或普通防水瞬动按键(带 LED 灯)。 数量:按需(例如 16 路输入对 16 名学生)。 提示输出(蜂鸣器 / 指示灯 / 显示) 指示灯:堆叠式信号柱灯(如 PATLITE 或 ABB)或单独 LED 指示模块。 蜂鸣器:12V DC 有源蜂鸣器或继电器驱动的蜂鸣器。 语音采集设备 推荐麦克风(课堂):Shure SM58(动圈) × 若干(耐用、抗噪); 若需高质量:电容话筒如 Rode NT1(须 +48V 幻象电源); 音频接口:Focusrite Scarlett 2i2 或 Behringer UMC204HD(USB 音频接口)用于把模拟音频传至上位机。 若需覆盖多个座位:话筒阵列 + 小型混音器(Behringer Xenyx 系列)或采用多通道音频接口(Focusrite Scarlett 4i4)。 上位机(PC) 运行语音识别、流利度分析数据库。通过以太网或 RS485 PLC 通信。 通信模块 首选:以太网(CAT5e/CAT6)直连 PLC 上位机(S7 通信或 Modbus TCP)。 备用:RS485 转换器(MAX485) Modbus RTU(若上位机需串口通信)。 电源 PLC 电源:24V DC 直流稳压电源(例如 MeanWell 24V/5A DIN 导轨电源)。 麦克风接口:话筒电源/混音器自带或独立 +48V 幻象供电(若使用电容麦克风)。 指示灯/按钮:可用 12V 或 24V,依所选部件确定。 输入隔离防抖电路(建议用) 光耦隔离模块(如 ILDxx 系列或自行设计 PC817 光耦电路); RC 去抖电路、施密特触发器(74HC14)或专用去抖芯片。 接线端子、DIN 导轨外壳、熔断器、浪涌保护器 端子排、保险丝、接地端子、EMI 滤波器等。 3 各模块连接方式总体线路架构 3.1 总体连线说明(分层) 感知层(学生端):抢答按钮 → 输入调理板(去抖 + 光耦) → PLC 数字量输入。 控制层(PLC):接收按钮输入、判定先后、锁定抢答者、控制提示输出(蜂鸣器/指示灯)、通过以太网向上位机发送抢答事件编号。 采集层(语音):当 PLC 通知上位机或触发输入后,上位机指令音频接口开始录音(或硬件触发音频录制),语音数据存储到本地数据库并进行流利度分析。 展示层(教师端):上位机 UI 显示抢答结果、评分历史记录。 3.2 物理连接(主要线缆) 抢答按钮到 PLC:每路按钮使用屏蔽双绞线,屏蔽层接系统地;信号线连接到 PLC 数字输入端(24V DC)。每路应串联防抖 RC 并并联光耦输入。 PLC 到上位机:CAT5e 网线(RJ45)直连(Modbus TCP 或 S7 协议),或使用 RS485(两芯双绞线)。 麦克风到音频接口:XLR 线缆(平衡线)连接麦克风混音器/音频接口;音频接口通过 USB 上位机相连。 指示灯/蜂鸣器到 PLC:PLC 数字量输出驱动继电器/固态继电器或直接驱动 12/24V 指示灯(视输出模块能力)。 4 电路设计(详细) 下面给出关键电路的设计细节:按钮输入调理、光耦隔离、去抖、指示输出驱动、麦克风前端采集音频接口连接、PLC 到上位机通信电路。以工程实现为标准给出器件参数建议。 4.1 抢答按钮输入电路(单路示意) 目标:将教室按键的机械抖动噪声抑制,同时实现 PLC 的电气隔离。 元件(单路) 按键(常开) — StudentButton 上拉/下拉电阻(R1) — 10 kΩ 去抖电容(C1) — 0.1 μF(100 nF) 串联限流电阻(R2) — 1 kΩ 光耦(U1) — PC817 或高速光耦(工业级推荐) 施密特触发器(可选) — 74HC14(用于整形) 24V DC 供电(来自 PLC 电源) 电路说明(文本版示意) lua 复制 编辑 学生按键 ---||----+---- R2(1k) ---|> 光耦 LED (输入侧) C1 | +--- R1(10k) --- GND 光耦晶体管(输出侧) -> PLC DI (经过适当的上拉/下拉和保护) 当按钮闭合时,输入侧 LED 通电,光耦导通,输出侧产生干净的数字信号送入 PLC。 在输入侧并联 C1 R1 形成 RC 去抖,输出侧如有需要再用 74HC14 做整形。 关键点 光耦提供电气隔离,避免学生端大范围接地噪声进入 PLC。 所有长线建议采用屏蔽双绞线,屏蔽层接系统地(接地点统一)。 对于需要计时精度的抢答器,应保证去抖时间短(例如 <20 ms),同时需在 PLC 程序内实现软件级别的最小按下间隔先到锁定逻辑以避免竞争条件。 4.2 去抖先到判定(硬件 + PLC 程序配合) 硬件去抖:RC(10k + 0.1µF)去抖 + 74HC14 整形。 PLC 内部:使用高速计数器/时间戳记录每一路输入的触发时刻(以 ms 为单位);判定最小时间戳为“第一按下”,并立即将其他输入屏蔽直至复位。 4.3 指示输出驱动电路(示意) PLC 输出通常为开放集电极或晶体管输出,若直接驱动 12V/24V 指示灯或蜂鸣器需注意驱动电流限制。 推荐驱动方式:PLC DO -> 中继/固态继电器 -> 指示灯/蜂鸣器。 若使用 LED 指示灯,可在输出端串联限流电阻(若为指示灯模块则无需)。 pgsql 复制 编辑 PLC DO --- Driver Relay --- +24V ----> 指示灯 | 蜂鸣器(12V/24V) 4.4 麦克风音频采集电路 动圈麦克风(Shure SM58)连接(常见可靠) XLR(平衡)线接入混音器或音频接口(Mic In)。 动圈麦无需幻象电源(+48V)。 电容麦克风(如使用高级方案) 需要 +48V Phantom,混音器或音频接口需提供幻象电源。 若使用课堂麦克风阵列,建议使用音频混音器将多个麦克风混合或通过多通道音频接口采集。 采集链路 rust 复制 编辑 麦克风(XLR) -> 平衡线 -> 混音器/话放(preamp) -> USB 音频接口 -> 上位机(录音软件/识别程序) 关键点: 使用平衡线(XLR)和差分输入可显著降低电磁干扰。 在课堂中建议将麦克风靠近学生口,或使用小型指向性麦克风以减少环境噪声。 如果期望基于“单个学生语言流利度”做评估,需保证每次录音仅包含该学生的语音(可用会议流程:PLC 判定后仅打开该学生座位的麦克风通道,或上位机对录音做语音分段并标注参者)。 4.5 PLC—上位机通信电路 优选以太网(工业以太网): PLC RJ45(内置) 上位机 RJ45 通过 CAT5e 直连或交换机连接。 协议:Modbus TCP 或 S7 协议(取决于 PLC 型号上位机软件支持)。 备用 RS485: PLC RS485 接口(若有) -> RS485 双绞线 -> 上位机 RS485 转 USB(或串口)适配器。 使用终端电阻(120 Ω)差分传输,可抗干扰。 6 电磁兼容(EMC)、接地安全措施 屏蔽走线 使用屏蔽双绞线连接长距离信号(按钮、感应器);屏蔽层一端接地(建议接在控制柜一侧)。 电源信号线分开布线,避免大电流线平行走线超过 30 cm。 接地 系统采用单点接地策略(控制柜为公共接地点),上位机 PLC 共用接地母线,避免地环路。 抢答终端若分布较远,应考虑局部接地隔离。 隔离 输入端使用光耦隔离模块以降低地电位差噪声耦合。 PLC 输出若驱动大功率部件(如蜂鸣器/信号灯),通过中继或固态继电器隔离。 浪涌保护 在电源输入端以太网接口部署浪涌保护器(SPD)和熔断器,保护设备免受雷击或瞬态电压损伤。 断路保险 每一路关键电源使用适当额定熔断器或断路器
最新发布
09-19
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值