真无线降噪耳机的响应时间一直是个痛点,用户切换降噪模式的时候经常能感觉到明显的延迟。经过一轮深入的性能调优,我把响应时间从原来的800毫秒降到了150毫秒,降幅达到80%。记录一下整个调优过程。
先交代一下背景。我所在的团队在做一款面向大众市场的真无线降噪耳机,主打高性价比,目标是把主动降噪(ANC)功能下放到千元以内的价位段。产品的整体方案已经跑通了,降噪效果也达到了预期,但在用户测试的时候发现了一个问题:切换降噪模式的时候,响应太慢了。
具体来说,用户点击耳机上的触控键切换降噪模式(比如从关闭切到降噪,或者从降噪切到通透模式),从点击到听到效果变化,中间有大概800毫秒的延迟。这个延迟说长不长说短不短,但用户能明显感觉到"按了之后等了一下才变",体验很不好。竞品的响应时间大概在200毫秒左右,我们的800毫秒确实差了不少。
于是我接了这个性能调优的任务,目标是把响应时间降到200毫秒以内。经过大概两周的努力,最终降到了150毫秒,超额完成目标。下面记录一下整个调优过程。
一、性能分析:先搞清楚时间花在哪里了
调优的第一步不是上来就改代码,而是先做性能分析,搞清楚时间到底花在了哪里。没有数据的调优就是瞎调,很可能改了半天发现改的地方根本不是瓶颈。
我在代码的关键路径上加了时间戳日志,从用户点击触控键开始,到最终音频输出发生变化结束,把每一个环节的耗时都打出来。然后做了几十次测试,取平均值,得到了下面这个耗时分布:
- 触控事件检测和去抖:50毫秒
- 事件上报到主控制线程:30毫秒
- 模式切换逻辑处理:100毫秒
- ANC参数加载和计算:250毫秒
- 音频路径重新配置:200毫秒
- 音频流切换和淡入淡出:170毫秒
加起来正好是800毫秒左右。从这个分布可以看出,耗时主要集中在三个地方:ANC参数加载和计算(250ms)、音频路径重新配置(200ms)、音频流切换和淡入淡出(170ms)。这三个加起来占了总耗时的77.5%,是调优的重点。
其他环节比如触控检测、事件上报、模式逻辑处理,加起来才180毫秒,优化空间不大,而且有些是硬件决定的,改不了。所以调优的策略很明确:重点优化三个大头,其他能优化就优化,不能优化就保持原样。
二、第一个优化点:ANC参数预加载
先看最大的耗时项:ANC参数加载和计算,250毫秒。
我去看了一下这部分的代码,发现每次切换模式的时候,系统都会从Flash里读取对应的ANC参数文件,然后做一次完整的参数计算和校验。ANC参数是一组滤波器系数,每个模式(关闭、降噪、通透、风噪抑制等)对应一组参数,存在Flash的不同区域。
每次切换都要重新从Flash读参数、重新计算,这显然是浪费。因为ANC参数是固定的,不会变,完全可以在系统启动的时候就把所有模式的参数都加载到内存里,切换的时候直接用内存中的参数,不需要再读Flash和重新计算。
我做了一个简单的改动:在系统初始化的时候,把所有ANC模式的参数一次性加载到内存中的一个数组里。切换模式的时候,直接从数组里取出对应模式的参数,跳过Flash读取和参数计算的步骤。
改完之后测试,这部分的耗时从250毫秒降到了20毫秒,降幅达到92%。因为只是从内存数组里取一组数据,几乎不花时间。剩下的20毫秒是参数写入DSP寄存器的时间,这个是硬件操作,没法再优化了。
这个优化非常简单,但效果非常明显。原因是原来的代码写得比较"直白",没有考虑性能优化,每次都做完整的加载流程。这种问题在嵌入式开发中很常见,因为资源有限,很多时候先跑通再说,性能优化留到后面。这次调优就是把这些"先跑通"的地方一个个优化掉。
三、第二个优化点:音频路径预配置
第二个大的耗时项是音频路径重新配置,200毫秒。
我们的耳机用的是一颗集成了DSP的蓝牙音频SoC,音频路径是通过软件配置的。不同的降噪模式对应不同的音频路径:关闭模式下,麦克风信号不经过ANC处理,直接混音输出;降噪模式下,麦克风信号经过ANC滤波器处理后再混音输出;通透模式下,麦克风信号经过通透处理(放大和均衡)后再混音输出。
每次切换模式的时候,代码都会先关闭当前的音频路径,然后重新配置新的音频路径,最后再打开。这个过程涉及到多个硬件模块的启停和配置,包括ADC、DAC、DSP处理单元、混音器等,每个模块的配置都需要一定的时间,加起来就是200毫秒。
我分析了一下,发现不同模式之间的音频路径其实有很多共同的部分。比如ADC和DAC在所有模式下都是开启的,变化的只是DSP处理单元的配置和混音器的路由。原来的代码不管这些,每次都全部关闭再全部重新配置,做了很多无用功。
优化的思路是:只重新配置变化的部分,不变的部分保持不动。具体来说:
- ADC和DAC在所有模式下都保持开启,不做关闭重启的操作。
- DSP处理单元的配置只更新变化的滤波器系数,不重新初始化整个单元。
- 混音器的路由切换用原子操作一次性完成,不需要先关后开。
这个优化比第一个复杂一些,因为涉及到底层硬件驱动的修改,需要仔细验证,确保不会引入爆音或者杂音的问题。我花了大概三天时间改代码和调试,中间遇到了几次爆音的问题,最后通过在切换点加一个很短的静音(5毫秒)解决了。
改完之后测试,这部分的耗时从200毫秒降到了40毫秒,降幅80%。剩下的40毫秒主要是DSP滤波器系数更新和硬件稳定的时间,这个是物理限制,没法再降了。
四、第三个优化点:优化淡入淡出策略
第三个大的耗时项是音频流切换和淡入淡出,170毫秒。
切换降噪模式的时候,为了避免突然的音量变化导致用户听到"咔哒"声或者爆音,代码会做一个淡入淡出的处理:先把当前的音频流淡出(音量逐渐降到零),然后切换到新的音频流,再淡入(音量逐渐升到正常)。
原来的淡出和淡入时间各设了80毫秒,中间还有10毫秒的静音间隔,加起来就是170毫秒。这个时间设置得比较保守,是为了确保切换的时候完全听不到爆音。但实际上,80毫秒的淡出淡入有点太长了,用户能明显感觉到音量先变小再变大,有一种"呼吸感",体验也不好。
我做了几组对比测试,分别试了50ms、30ms、20ms、10ms的淡出淡入时间,看哪个长度既能避免爆音,又能让用户感觉不到明显的音量变化。
测试结果是:
- 50ms:完全没有爆音,但能感觉到音量变化
- 30ms:完全没有爆音,几乎感觉不到音量变化
- 20ms:极少数情况下有轻微爆音,大部分时候没问题
- 10ms:经常有爆音,不可接受
最终我选择了30毫秒的淡出淡入时间,而且把淡出和淡入做成了交叉淡入淡出(crossfade),也就是新的音频流淡入的同时旧的音频流淡出,两个过程重叠进行,而不是先淡出再淡入。这样总耗时就是30毫秒,而不是60毫秒。
交叉淡入淡出的实现稍微复杂一点,因为需要同时处理两路音频流的混音。但我们的DSP硬件支持多路混音,所以实现起来不算太难。主要的工作是在软件层做好两路音频的同步和音量曲线的计算。
改完之后测试,这部分的耗时从170毫秒降到了30毫秒,降幅82%。而且用户体验也更好了,因为切换的时候音量变化更小,几乎感觉不到过渡的过程。
五、其他小优化
除了上面三个主要的优化点,我还做了一些小的优化,虽然每个的效果不大,但加起来也有几十毫秒的提升。
第一个是触控事件去抖时间的优化。原来的触控去抖时间是50毫秒,这个时间是为了防止误触。但我分析了一下触控芯片的输出,发现它本身已经有硬件去抖了,软件层再做50毫秒的去抖有点多余。我把软件去抖时间降到了20毫秒,测试了几百次没有出现误触的情况。这样省了30毫秒。
第二个是事件上报机制的优化。原来的触控事件是通过消息队列上报给主控制线程的,消息队列的处理周期是10毫秒,而且事件的优先级比较低,有时候会被其他高优先级任务延迟。我把模式切换事件的优先级提高了,并且改成了直接回调的方式,不需要经过消息队列。这样事件上报的时间从30毫秒降到了10毫秒,省了20毫秒。
第三个是模式切换逻辑的优化。原来的模式切换逻辑里有一些冗余的判断和计算,比如每次切换都会重新计算当前的电量状态、重新检查蓝牙连接状态等,但这些信息在短时间内是不会变的。我把这些计算改成了缓存的方式,只在信息真正变化的时候才更新。这样模式切换逻辑的处理时间从100毫秒降到了50毫秒,省了50毫秒。
这些小优化加起来一共省了100毫秒。虽然每个都不大,但性能调优就是这样,一个百分点一个百分点地抠,最后加起来就是很大的提升。
六、优化结果和验证
所有优化做完之后,我重新测了一下响应时间,结果是150毫秒左右,比原来的800毫秒降了81.25%,超额完成了200毫秒以内的目标。
新的耗时分布是:
- 触控事件检测和去抖:20毫秒
- 事件上报到主控制线程:10毫秒
- 模式切换逻辑处理:50毫秒
- ANC参数加载和计算:20毫秒
- 音频路径重新配置:40毫秒
- 音频流切换和淡入淡出:10毫秒(交叉淡入淡出的重叠部分)
加起来150毫秒。
当然,性能优化不能只看数字,还要验证功能是否正常,有没有引入新的问题。我做了以下几方面的验证:
第一是功能测试。所有降噪模式的切换都测试了一遍,包括关闭、轻度降噪、深度降噪、通透模式、风噪抑制,每个模式之间互相切换,确保切换后效果正确,没有出现模式错乱的情况。
第二是音频质量测试。切换的时候有没有爆音、杂音、音量突变。我用专业的音频设备录制了切换过程的音频,分析了波形和频谱,确认没有异常。同时也做了主观听感测试,找了几个同事试听,大家都反馈切换很顺滑,听不到任何异常声音。
第三是稳定性测试。连续切换模式1000次,看有没有出现死机、重启或者异常的情况。测试结果是1000次切换全部正常,没有出现任何问题。又做了长时间开机测试(24小时),期间随机切换模式,也没有出现问题。
第四是功耗测试。性能优化不能以牺牲功耗为代价,尤其是耳机这种电池容量很小的设备。我测了一下优化前后的功耗,发现优化后的功耗反而略有下降,因为预加载和预配置减少了运行时的计算量,Flash读取的次数也减少了。这是一个意外的惊喜。
第五是用户体验测试。找了10个用户做盲测,让他们分别用优化前和优化后的版本切换模式,问他们能不能感觉到区别。10个用户里有9个说优化后的版本"反应更快了"、"按了就变,没有延迟感"。剩下1个说感觉差不多。这个结果说明优化的效果用户是能感知到的,不是只有实验室里的数字变化。
七、一些调优的心得
这次调优做下来,有一些心得想分享一下。
第一,性能调优一定要先做测量,再做优化。没有数据的调优就是瞎猜,很可能花了很多时间改的地方根本不是瓶颈。我这次一开始就加了时间戳日志,把每个环节的耗时都量化了,然后针对性地优化耗时最大的几个环节,效率很高。如果上来就凭感觉改,可能改了半天发现效果不大。
第二,很多性能问题的根源是"先跑通再说"的代码。这次优化的几个点,比如ANC参数每次重新加载、音频路径每次全部重启,都是典型的"先跑通"写法。这种写法在功能验证阶段没问题,但到了性能优化阶段就需要一个个改掉。所以写代码的时候就要有性能意识,哪怕是先跑通,也要知道哪些地方是可以优化的,留好优化的空间。
第三,嵌入式系统的性能优化要充分利用硬件特性。比如这次的交叉淡入淡出,就是利用了DSP硬件支持多路混音的特性。如果硬件不支持,软件实现交叉淡入淡出会很耗CPU,可能就不划算了。所以做嵌入式开发,一定要深入了解你用的芯片的硬件能力,很多性能问题可以通过硬件特性来解决,比纯软件优化高效得多。
第四,性能优化要做权衡,不能只看一个指标。比如这次淡入淡出时间的优化,我不是一味地追求最短时间,而是在爆音风险和响应速度之间找了一个平衡点。30毫秒是既能保证没有爆音,又能让用户感觉不到延迟的最佳值。如果为了追求更短的时间用10毫秒,虽然响应更快了,但会有爆音,用户体验反而更差。
第五,优化完之后一定要做充分的验证。性能优化很容易引入新的问题,尤其是涉及到底层硬件和音频处理的改动。这次优化我花在验证上的时间比花在改代码上的时间还多,但这是值得的。如果没有充分验证就上线,出了问题再回滚,成本更高。
八、写在最后
这次性能调优把真无线降噪耳机的模式切换响应时间从800毫秒降到了150毫秒,降幅80%,用户体验有了明显的提升。整个过程虽然花了两周时间,但收获很大,不仅解决了具体的问题,还对嵌入式音频系统的性能优化有了更深的理解。
性能调优是一件很有成就感的事情。当你看到一个数字从800变成150,当你听到用户说"这个反应快多了",那种满足感是写普通功能代码给不了的。而且性能调优很锻炼人,它要求你对系统有深入的理解,从应用层到驱动层到硬件层,每一层都要搞清楚,才能找到最优的解决方案。
如果你也在做嵌入式开发或者音频相关的工作,希望这篇文章能给你一些启发。如果有什么问题或者不同的看法,欢迎在评论区交流。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录