首页 / 资讯中心 / 文章详情

杰理平台蓝牙音箱人声消除功能开发实战

杰理平台蓝牙音箱人声消除功能开发实战 ★ FEATURED ARTICLE
上个月接了一个蓝牙音箱的固件需求客户的原话很简短加一个人声消除要能随时开关。我一开始以为这就是调个音效开关的事真做起来才发现从算法原理到SDK挂载再到量产细节牵扯的东西有一大串。项目用的是杰理平台的蓝牙音频芯片SDK对音频DSP的控制还算开放加自定义效果链并不难但前提是你得先搞清楚人声为什么能被消掉以及开关到底在切什么。这篇文章写给两类人一类是要在杰理平台上做卡拉OK、人声消除这类功能的嵌入式音频开发者另一类是产品定义阶段还没想明白交互的人。我按实际开发的时间线来写从需求拆解、算法原理、SDK接入、调试踩坑到量产确认每一步都带上能直接落地的细节希望能帮你少走几趟弯路。1. 需求先想明白人声消除开关到底给谁用1.1 为什么一定要可开关而不是一直开着人声消除这个功能站在算法角度就是把混音里的中置内容按比例干掉。但在音乐欣赏的语境里这是破坏性处理——歌曲会失去原有的立体感和人声感染力听久了还会产生一种空洞感。所以正规产品里没人会把这种算法默认常开更不会有人想在听梁静茹的时候把她的声音抽走。需求方要开关本质上是想要两种听音模式。模式A正常立体声音乐怎么混的就怎么放模式B卡拉OK模式人声抽掉用户跟着伴奏唱。这两个模式之间切换要轻最好一颗按键或者App里一个按钮就能办到而且切换瞬间不能有让人皱眉的杂音。容易忽略的一点是模式B不光是消人声还得保证伴奏听感能接受。如果算法把鼓和贝斯也一并干掉切过去之后用户会觉得歌坏了。所以开关背后其实是两套输出路径在切换——干声路径和湿声路径开关切的是这两条路径的权重而不是简单地把某个处理模块置零。明白这一点后面设计状态机时才不会走偏。1.2 交互方式决定固件结构按键、App命令还是语音我这边的产品定了两种交互音箱顶面一颗麦字按键短按切换模式手机App通过蓝牙透传下发命令也能切。按键是普通GPIO软件要处理防抖、长按/短按区分。这块逻辑我建议跟音效模块彻底解耦单独做一个功能开关管理器后续加新功能时能复用。状态也不要只定义成开/关两个值。我一开始就用枚举typedef enum { VOCAL_MODE_NORMAL 0, VOCAL_MODE_CANCEL, } vocal_mode_e;如果后续要加减弱人声和完全消除两个档位这个枚举可以直接扩展成带强度的模式。我就是因为初期留了这个口子后来需求加第三档时只改了一小片代码没有动到框架。App命令走蓝牙透传通道格式自定义0x5A 0xA5 0x01 0x00末字节0x00切正常0x01切消除解析完直接调模式切换函数。这里要留意多端同步按键切了模式App界面要知道App切了模式按键状态也要更新。我在固件里做了模式变更回调任何一端发起切换都会把新模式广播给所有界面端。提示交互端的逻辑和音频处理端的逻辑别混在一起。音频回调里只读全局状态按键和App解析只管写状态两边靠一个标志位和一个函数隔开。这样调试时才能快速定位是命令没到还是处理没生效。2. 算法原理拆解为什么左右声道相减能去掉人声2.1 立体声混音里的居中人声绝大多数商业歌曲在立体声混音时主唱都会被放在声场正中间也就是左右声道各给一份完全相同的信号等幅同相。其他乐器根据需要摆到两侧。这是人声消除能成立的根基——如果主唱声像偏到一边去了那这个算法从原理上就不成立。所谓居中数学上就是左声道和右声道都包含了一个相同的分量C。如果我们做左减右这个C就被抵消成零而左右不完全相同的侧向元素会保留下来。所以最基础的思路很简单中心信号 M (LR)/2侧向信号 S (L-R)/2。消人声的本质是中心抑制而不是把中心内容整个抹掉。2.2 L-R相减的局限低音去哪儿了最粗暴的做法是直接拿左右声道相减当输出。我最早在PC上用工具试听效果非常两极人声确实薄了但鼓组的低频、贝斯、还有混音里同样居中的和声全都没了。一首歌变得又薄又干完全没法用来伴奏。问题出在居中不是人声的特权。贝斯和底鼓为了让单声道设备也有足够的存在感混音时几乎必然居中。不分青红皂白消掉中间等于把歌曲骨架抽走了。这也是很多网上流传的人声消除方案听感差的根本原因——它们只做了最简单的一步减法没做任何频段权衡。2.3 嵌入式平台上的实用变体分频段处理与中心抑制我最后用的方案是一个分频段的中心抑制器不算先进但在嵌入式上能稳定跑。要点有三个低频段120Hz以下完全不处理LR混入输出保住贝斯和底鼓中频段120Hz到6kHz左右是主唱主要存在的频段做中心抑制用系数控制削减深度高频段6kHz以上也做中心抑制但深度比中频浅因为高频乐器通常偏侧过度削减会损失空气感。分频用的滤波器是固定系数的双二阶Biquad系数提前算好放在表格里避免运行时现算三角函数。中心抑制部分没有用完整FFT而是用时域近似频段分离后把 (L-R) 信号与该频段原始信号按比例混合。削减深度做成可配置参数默认在 -18dB 到 -24dB 之间。太浅比如-6dB人声还剩一半太深-30dB以下伴奏发干发硬。最后这个参数是配合App调试页动态调整的比反复烧录改代码高效得多。得提前说一句这个方案对主唱居中、伴奏立体的普通流行歌效果好但对以下场景基本无能为力——主唱加了很强的立体声混响或延迟、人声被做了宽声像处理、录音本身是单声道、Live现场版人声和和声重叠太严重。这些是物理限制不是代码缺陷需求阶段必须跟客户对齐期望值否则验收全是坑。3. 杰理SDK里的接入点从配置到开关的完整链路3.1 先找到效果链人声消除模块挂在哪杰理的音频框架里音乐播放的PCM数据流会经过一条效果链EQ、环绕、各种sound effect都挂在这条链上。要把自研算法挂进去第一步是找到这条链的注册/解注册入口。不同SDK版本的接口名差异比较大方法却一致先看SDK自带的音效Demo是怎么把效果模块挂进数据流的照着那个模式把自己写的处理回调注册一次。回调里传进来的帧数据帧长由DAC采样率和音频任务调度周期共同决定不要假设它是固定值要去读SDK的配置宏。我这边实际挂载顺序是解码输出 → 人声消除模块 → EQ → 音量/DAC。为什么放EQ前面因为消除算法会显著改变频谱结构放到EQ后面会让现有EQ曲线的听感目标对不上。如果你的SDK效果链顺序不可调那就要做好EQ参数重新校准的准备这个工作量不比写算法小。3.2 开关状态机不只是一根GPIO的事前面说的功能开关管理器展开职责有三件按键防抖与模式去重已经处于消除模式时再按一次不触发重复切换App命令与按键事件的优先级协调同时来以最后生效的为准模式切换时通知音效模块做渐变并把状态广播给界面指示LED、提示音。按键防抖用标准做法10ms轮询一次连续两次读到有效电平才确认一次按下长按和短按用计时窗口区分。这些逻辑单独放一个文件跟音频回调完全隔离。音频回调里只读一个全局标志位不做任何状态判断和按键处理。这样就算按键扫描被高优先级任务抢占音频路径也不会跟着抖动模式切换的稳定性会好很多。3.3 切换瞬间防爆音淡入淡出还是交叉渐变这是整段开发里最容易被低估的一步。直接把湿声路径使能或失能听感上大概率出啪声。原因是PCM波形在切换瞬间产生不连续点这个断点被DAC放大成爆音。我试过两种方案。第一种是给处理后的数据加20到30ms线性淡入淡出实现简单效果还行但切换反应速度看起来略慢。第二种是干湿交叉渐变切换期间输出 干声×(1-t) 湿声×tt在50ms内从0线性滑到1。这个方案切换过程更自然没有明显的音量塌陷感。我最终用第二种核心逻辑static uint16_t s_fade_t; /* 0x0000 ~ 0x1000渐变进度 */ static uint8_t s_switch_busy; static void vocal_wet_dry_process(int16_t *l, int16_t *r, uint16_t samples) { uint16_t i; if (!s_switch_busy) { return; } for (i 0; i samples; i) { int32_t dry_l l[i], dry_r r[i]; int32_t wet_l dry_l; /* 实际工程里wet由算法模块填好 */ int32_t wet_r dry_r; l[i] (int16_t)((dry_l * (0x1000 - s_fade_t) wet_l * s_fade_t) 12); r[i] (int16_t)((dry_r * (0x1000 - s_fade_t) wet_r * s_fade_t) 12); } s_fade_t S_FADE_STEP; /* 根据帧长和期望渐变时间折算 */ if (s_fade_t 0x1000) { s_fade_t 0x1000; s_switch_busy 0; } }细节在这里渐变期间湿声路径要已经处于目标模式否则渐变完听到的还是旧效果。我在状态机里定义了目标模式和当前生效模式两个变量切换流程是设置目标模式 → 算法模块预热新参数 → 开启渐变 → 渐变结束把当前生效模式更新为目标模式。注意如果产品要求切换速度很快比如200ms内必须完成交叉渐变还要配合缓冲区的帧对齐。有些SDK在效果链切换时会丢弃一帧数据那一帧的缺失甚至比渐变更容易被听出来这一点得在联调时拿示波器或录音实测确认。3.4 状态掉电保存VM区还是直接放弃记忆产品上电后要不要恢复上次的模式我们定义的是恢复。杰理SDK通常提供VMValue Memory机制可以存少量自定义数据。模式值建议单独一个VM key1字节就够别跟其他用户配置混在同一个结构体里。模式切换频繁中途如果写坏不至于把整份用户配置搞坏。写入时机也要讲究不要每次切换都立刻写Flash频繁写会磨损而且有些方案里VM写操作会阻塞主流程。我的做法是切换时先改RAM状态延时5秒后再落VM。用户快速来回切换时只落一次盘体验和寿命都兼顾。4. 实测与调试人声残留、低音失踪和爆音的三场硬仗4.1 用试音曲目建立验收标准调试主观听感功能最怕你说不行我说还行。我做的第一件事不是调参数而是建了一个试音歌单并且写了两页验收标准核心几条流行女声钢琴人声应基本消失钢琴保留完整摇滚/电吉他重鼓低音保留量大于70%人声辨识度明显下降男女对唱/和声允许轻微残留不作为主要验收项纯乐器曲目切换后没有明显劣化这是最容易漏掉的回归项单声道老歌人声消除不生效可以接受但不得出现明显金属声或相位失真。这份清单后来帮了大忙因为测试组和音频工程师的还行完全不是一个标准。没有量化预期之前沟通效率低得惊人有了这份东西至少争论时能指着一行字说这条没过。4.2 人声残留的查因思路第一次Demo出来评测组反馈人声还剩一半。我没有急着改削减深度先怀疑算法链路根本没生效——在固件里把模块注册进效果链跟它真正处理到数据是两码事。排查方法很土但有效在算法模块入口和出口各打一个串口打印或断点确认出口数据和入口数据确实发生了差异。接着用一个左右相位相反的1kHz正弦波跑一遍看输出是否按预期抵消。这套流程下来基本能把算法没跑和算法跑得不对区分开。确认链路没问题之后再去动削减深度这个参数方向就清楚了。4.3 低音被误删的补偿方案低音丢失的问题在有分频段方案后解决了一大半但120Hz这个分频点卡得不够好。分频点太高鼓的冲击感回来了但人声的低频部分也没了分频点太低贝斯根音被削。最后改成双分频点斜率可调60Hz以下完全不处理保留极低频冲击60~150Hz混合比例从全保留平滑过渡到中心抑制150Hz以上正常中心抑制。这比一刀切分频顺耳得多系数表在PC端算好烧进Flash。顺带说一句这个过渡带的设计直接决定鼓点实不实贝斯沉不沉值得花半天时间反复听。我当时临时改了六七套过渡带参数最后选的那套不是数据上最优的却是听感上最稳的。4.4 开关切换有杂音时序和渐变的调试爆音问题的排查过程我印象最深。第一次听到啪声我以为是增益突变把淡入淡出加上后啪声还在。后来用逻辑分析仪抓I2S输出和GPIO切换的时间关系才发现根因在DAC静音管脚和数据处理之间的时序竞争——效果链已经切换了但DAC还处于静音释放的中间态输出先静音一小段时间再突然出声听感就是咔。解决方式是把DAC静音时序和模式切换解耦切换效果链时DAC保持已就绪状态不再联动静音。这个坑值得写出来因为很多明明加了渐变还是爆音的问题根因根本不在效果链里而是外围时序在捣乱。排查这类问题逻辑分析仪或者至少要有带时基的录音对比别全靠耳朵猜。4.5 资源占用CPU和内存的账要提前算这类蓝牙音频芯片的算力够用但不是无限够用。我的消除模块最终占用内存上双路帧缓冲加中间滤波状态约4KBCPU在48kHz采样率下约占8%的处理余量。单看不觉得多但和已有EQ、蓝牙协议栈叠加后如果产品还要做低延迟透传模式就非常紧张。建议动手前先做一张资源预算表把当前固件各功能CPU占比列出来给新功能留足余量。我一开始没做功能跑通后想加双模蓝牙自动切换CPU直接超了回头优化滤波器结构又花了两天。如果早做预算这个优化应该在写代码阶段就完成。5. 量产落地采样率矩阵、EQ联动与交付测试清单5.1 采样率、通路、格式的完整矩阵测试产品最终有多条音频通路蓝牙A2DP、本地USB/TF播放、AUX输入各通路的采样率和数据格式不一定相同。我测试时发现一个典型问题消除模块在48kHz下调通的参数切到44.1kHz后高频分频点变了人声残留感明显变强。原因是滤波系数固定没有按采样率重算。解法是把分频点和削减深度做成按采样率索引的参数表初始化时根据当前采样率加载。处理回调里也要监听采样率切换事件一旦检测到变化重新初始化滤波状态否则会有一小段杂音。这个矩阵测试不能省尤其AUX输入通常走硬件通路跟蓝牙解码走的不是同一个数据源处理方式也不完全一样。5.2 与EQ、低音增强、音量曲线的互相影响人声消除会改变频段能量分布接入现有EQ后可能出两种奇怪听感一是EQ低频本身已经加了增益消除模块又放大了低频整体低音浑浊二是音量很小的时候中心抑制后的伴奏显得特别散声音发虚。我的处理低频段的全保留比例做成跟随总音量的动态值音量低时稍微多保留一点中低频避免声音单薄。补偿量很小1到2dB但听感差异非常明显。EQ部分则和音频团队约定消除模式下单独出一套EQ预设不复用正常模式的曲线。这样虽然多占了一点Flash但两种模式各自的听感都能调到舒服状态。5.3 功耗与代码体积睡眠唤醒后如果模式状态恢复错用户会以为功能坏了。一定要在产测和老化测试里加入断电重启后模式保持的用例。代码体积方面我的滤波系数表和渐变逻辑加起来不到4KB Flash如果你的Flash紧张把滤波器系数用定点Q15格式存储能省不少空间代价是阅读代码时没那么直观。功耗这部分容易被忽略但消除模式如果额外开了效果链芯片的DSP负载提高电流会明显多几十毫安。对插电的桌面音箱无所谓对便携蓝牙音箱就是大事。我建议在消除模式常开的情况下做一轮完整的功耗测试别只看峰值。5.4 给测试组的一份试音清单最后交付测试组的清单每一项后面都写了预期结果和允许偏差范围。这是把主观验收变成可重复的客观步骤测试员照着跑就行不用靠个人听感拍板。主要验收项测试场景操作预期结果允许偏差流行歌曲-蓝牙短按模式键主唱基本消失伴奏完整歌词首尾轻微残留摇滚歌曲-TF播放切换暂停低音鼓点保留人声抽离电吉他高频略变歌曲播放中反复切换连按10次无爆音、无音量突变单次轻微咔哒声可接受断电重启切换后立即断电恢复上次模式恢复默认模式可接受单声道收音机任意模式无明显金属声或失真人声消除可不生效最后说句实在话人声消除这种功能技术实现只占一小半需求对齐和听感验收才是大头。我这次最大的体会是——动手写算法之前先跟产品、跟客户把什么样算消干净哪些歌允许消不干净说清楚。不然你辛苦调出来的一版参数在别人耳朵里可能完全不是一回事。如果你的产品也要上这个功能建议从我这篇的试音清单开始先跑一轮再定算法深度别让代码白写。
阅读完成 · 觉得有帮助?
咨询建站