做MTK平台的Android音频问题最磨人的往往不是硬件而是改完配置之后系统那副“懒得理你”的样子。我收到过不少这样的case播放器在跑、进度条在跳、音量条也拉满了就是耳机、扬声器没有一丝声音甚至logcat里连一条error都找不到。这种“无声但不报错”的现象十有八九要往音频路径配置上找。这里的“路径”指的是从App的AudioTrack、AudioFlinger的MixerThread、AudioPolicyManager的设备选择到HAL层selectDevices再到ALSA声卡切换Codec/PA控件的一条完整链路。MTK平台在AOSP之上又叠加了厂商策略和一堆音频配置文件任何一个衔接点不一致最终结果都一样静音。这篇文章不绕弯子直接按我实际排查的顺序把“无声”问题从配置层面彻底翻一遍顺便把MTK平台音频路径配置里几个高频翻车点一起讲清楚。1. 为什么MTK平台的“无声”问题会集中在路径配置上1.1 要排查的不是“音量”而是“路由”很多人第一次查无声问题第一反应是音量、静音开关、音效算法但音量问题通常一眼就能在设置里发现。真正让人头疼的是“音频路由”问题。把Android音频路径想象成一套送快递的流水线App是下单的人AudioFlinger是分拣中心AudioPolicyManager是调度员HAL和ALSA声卡是快递车。配置好的路由表决定了声音这个“包裹”该送到扬声器、耳机、听筒还是蓝牙。路由表一旦填错包裹要么送错地址要么被丢在分拣中心系统还不会提示投递失败。MTK平台采用的Android音频整体架构和AOSP基本一致但在vendor层面存在大量自有实现和配置文件。以新项目为例平时最常动到的是这几类文件audio_policy_configuration.xmlframework可见的端口、路由、设备注册对应AudioPolicyManager的管理范围。audio_platform_info.xml部分项目也叫audio_platform_info_sku.xml完成framework端口到HAL设备实际名字的映射相当于调度单上的“具体地址”。mixer_paths.xmlHAL层执行某条路径时需要把哪几个声卡control设成什么值相当于快递员按什么顺序敲门。老MTK项目Android 7/8时代经常还有audio_device.xml、audio_customization这类专属配置Android 9之后多数项目已经收敛到vendor/etc/audio/下面。这三层配置相互独立又必须对齐。比如audio_policy_configuration.xml里注册了一个名叫Speaker的devicePortaudio_platform_info.xml里却映射到了SPKHAL执行时匹配不上系统不会报错只会不动作。很多“改了配置还是无声”的问题根因就是这种跨层名字不一致。1.2 无声但不报错的高频场景不同路径不同查法不是所有“无声”都同一个查法。我习惯先按现象把问题归类再决定从哪一层入手否则很容易在错误的方向上浪费几个小时。故障现象可能断点所在优先排查手段外放无声耳机正常Policy路由没选到Speaker或HAL没执行Speaker路径dumpsys audio确认当前设备查HAL log耳机无声外放正常devicePort映射、Headphone增益、功放控制查audio_platform_info.xml和mixer_paths听筒无声路由选错设备、听筒增益为0查policy device和tinymix控件通话时对方听不到我输入路由、mic bias、AEC回采配置查input route和mic相关tinymix只有某个App无声AudioFocus策略、usage映射问题查focus log和dumpsys audio_policyFM无声FM路径、耳机插拔检测、FM设备注册查FM devicePort和HAL route这类问题的共同规律是不是所有场景都无声而是某些场景能响、某些场景不响。把握住“什么场景能听到声音、什么场景听不到”就能把排查范围砍掉一大半。比如外放能响、耳机不响至少说明AudioFlinger到HAL主链路是通的问题大概率在耳机相关的路径映射或控件的开关顺序上这时候再一头扎进XML里翻方向才对。2. 动手排查前的收场工作日志和配置文件必须一把抓齐2.1 日志怎么抓才能在最短时间内定位很多人在无声问题上一上来就盯着一行log看这不是不行但效率太低。我习惯固定抓四类信息主log、audio dumpsys、PCM设备状态、tinymix控件值。而且一定要抓“复现前”和“复现后”两份尤其是tinymix只有对比才能看出路径上的控件到底有没有跟着切换。adb shell logcat -c adb shell logcat -v threadtime audio_main.log # 在这个窗口复现无声问题 adb shell dumpsys audio audio_dump.txt adb shell dumpsys media.audio_flinger audioflinger_dump.txt adb shell dumpsys media.audio_policy audiopolicy_dump.txt adb shell cat /proc/asound/pcm asound_pcm.txt adb shell tinymix -D 0 tinymix_0_before.txt # 复现后 adb shell tinymix -D 0 tinymix_0_after.txt这四类信息各有用途audio_main.log里重点看AudioPolicyManager、AudioFlinger、audio_hw_primary三个tag能第一时间判断上层有没有发出路由请求audio_dump.txt和audiopolicy_dump.txt会显示当前系统认为的active output device如果policy显示的是SPEAKER而实际没声音问题在下层/proc/asound/pcm确认声卡PCM流是否真的打开tinymix前后对比则直接反映HAL是否执行了预期的控件设置。有经验的同事还会把“正常设备”的同款log一起抓下来做对照。很多配置问题单独看log怎么都不像错一旦和正常机器的log逐行diff差异点立刻暴露。没有对照帧的抓log等于猜谜。2.2 先确认系统加载的配置文件到底是哪一份“我改了配置为什么没反应”是MTK平台一个特别常见的伪问题。改了一处XML后重新刷机发现现象不变多半不是没编进去而是系统实际加载的根本不是你改的那份。在Android 9之后音频配置会同时出现在system、vendor、product等多个分区同名文件还可能被覆盖。动态分区和overlay机制让这个问题更隐蔽。我每次排查第一步都先做两件事adb shell find / -name audio_policy_configuration.xml 2/dev/null adb shell md5sum /vendor/etc/audio_policy_configuration.xml然后和本地编译产物做对比。如果机器上实际加载的md5和本地build输出对不上优先解决“文件没进去”的问题别急着继续debug。另外注意检查一些缓存属性MTK部分项目会把音频策略做成预编译库配合persist.vendor.audio.*的运行时属性改完配置不生效时可以先用getprop扫一遍adb shell getprop | grep -i persist.vendor.audio adb shell getprop | grep -i audio.hal如果项目里允许remount我一般会这样验证文件路径是否正确adb root adb remount adb shell ls -l /vendor/etc/audio/ adb shell grep -rn Speaker /vendor/etc/audio/这一步能省掉后面大量弯路上的时间。3. 逐层定位从应用播放动作一路查到Codec3.1 第一道关确认AudioFlinger真的在“出声”拿到log之后先从最上层确认。复现播放时logcat里只要应用创建了AudioTrack并调用了start通常能看到类似AudioFlinger: start(4097)的输出。这里的含义是AudioFlinger接到了一个Track的启动请求并开始Mix。如果你能看到这个输出说明应用、AudioTrack、AudioFlinger这一整段是通的。接下来再用dumpsys确认MixerThread的状态adb shell dumpsys media.audio_flinger | grep -A 8 Thread type: MIXER我主要看两项一是线程当前是否处于ACTIVE状态二是Thread里有没有挂载活跃的Track且Track的状态不是STOPPED或PAUSED。如果这里显示一切正常但用户就是听不到声音那么问题就已经明确落在下层的路由或HAL上不需要再纠缠Framework层。3.2 第二道关AudioPolicyManager把声音派给了谁下一步看dumpsys media.audio_policy和logcat里的AudioPolicyManager日志。重点找几个关键字getDeviceForStrategy代表某种策略最终选择了哪个输出设备startOutput说明有输出流被真正启动setOutputDevices表示policy要求HAL把输出切到某设备。正常情况下播放音乐时日志里会出现strategy-music选择的device比如SPEAKER或者WIRED_HEADSET。如果这里出现的device和预期不符比如播放外放时policy选到了耳机问题就在audio_policy_configuration.xml的路由配置。理解这段配置有两个关键一是必须存在一条从mixPort到devicePort的route二是在route里写的tagName必须和HAL层认识的名字一致。典型配置结构如下module nameprimary halVersion3.0 devicePorts devicePort tagNameSpeaker typeAUDIO_DEVICE_OUT_SPEAKER rolesink/ /devicePorts mixPorts mixPort nameprimary output rolesource/ /mixPorts routes route typemix sinkSpeaker sourcesprimary output/ /routes /module这里最容易踩的坑是新增或修改设备时devicePort的tagName和HAL侧能识别的字符串不一致。Policy层可能已经创建了路由对象但HAL不认这个设备名selectDevices的时候不会真正执行任何动作外部表现就是“什么都不报错但也没声音”。3.3 第三道关HAL和声卡层有没有把配置落到控件上Framework和Policy都正常后第三道关看HAL实际执行情况。MTK的HAL log一般会打印select_devices、out_set_parameters、set_output_devices这类关键词有的版本里还能看到具体的route名称。如果HAL日志里能看到路由已经切到了Speaker或Headphone但还是无声就要把目光转向声卡控制层面。用tinymix这条路验证最直接adb shell tinymix | grep -iE Speaker|SPK|Headphone|HP|PA|Amp|Volume对比复现前后的控件值。正常情况应该是打开外放时Speaker PA或Amp Enable这类控件从Off变成On音量控件从0变成预期数值。如果控件值完全没变化说明HAL的执行逻辑或mixer_paths根本没走到如果控件值变了但依然无声那才是真正需要怀疑硬件、功放时序、Codec驱动的时候。实际工作中我遇到过一次特别典型的顺序问题route已经执行了Codec控件也动了但声音就是出不来。最后发现是mixer_paths里的执行顺序不对PA功放的Enable写在了音源通路建立之前等于先开门再放货物货物还是被挡在门外。把两条配置顺序对调后声音立刻恢复正常。这种问题光靠读配置很难发现只有靠前后tinymix的diff加上时间线对照才能揪出来。4. 现场复盘三个让我改到凌晨的配置错误4.1 案例一改了一个overlay外放全线静音有一次接手的项目里产品定义要把设备名字从Speaker_Normal统一成Speaker。开发在audio_policy_configuration.xml里改了devicePort的tagName但HAL侧、audio_platform_info.xml和mixer_paths里仍然是Speaker_Normal。结果框架层successfully把路由对象建好了HAL却不匹配整个外放静态。排查的时候最具有迷惑性的点是dumpsys audio显示当前的输出设备确实是Speakerlogcat里没有任何报错。我一度怀疑是功放驱动的问题直到对比HAL日志发现select_devices压根没有被正常触发才意识到是设备名对不上。后来我做配置改动前都会先全局grep一遍这个名字的所有出现位置grep -rn Speaker_Normal device/ vendor/ --include*.xml确保devicePort tagName、route source/sink、audio_platform_info.xml里的映射、HAL里的设备枚举字符串四处同步。这种问题在Git合并时特别容易出现两边分支各自改了一半合出来就是路径悬空。4.2 案例二耳机插入正常、外放关闭但耳机里什么也没有另一个高频Bug是耳机插入后系统能识别到耳机甚至外放都已经停止了但耳机里依然无声。这类case看现象会觉得系统已经“发现”耳机了很容易往驱动方向查。可实际多数情况是耳机路径上的控件根本没有完整执行。我遇到过的问题是mixer_paths里耳机功放的供电控制错误地放进了Speaker路径。每次切换外放的时候Speaker路径会把耳机功放一并关掉。之后即使系统切回耳机耳机路径只设置了音量控件没有重新把耳机功放打开结果就是耳机彻底哑掉。这类问题的核心逻辑是一条完整音频路径通常由多个path组合而成比如Headphone、Headphone PA、Headphone Volume它们之间有严格的顺序和归属关系。乱放一处或者合并代码时不小心把一个控件从A路径挪到B路径都会导致类似的“部分场景无声”。排查时如果发现dumpsys audio显示设备是WIRED_HEADSETHAL也执行了Headphone route但tinymix里面耳机功放是Off就赶紧回头查mixer_paths里控件归属是不是被其他路径篡改了。4.3 案例三新加了一个输入设备主麦克风反而丢了MTK平台上新增音频设备也是无声问题高发点。为了做双mic降噪或语音唤醒项目里加了一个新的input devicePort但只改了audio_policy_configuration.xml没同步audio_platform_info.xml和HAL的supported devices列表。结果系统在做录音或通话时policy按新配置选择了新设备HAL却根本没有这个设备的路径实现直接把主mic的路由也给冲掉了。这种问题最坑的地方在于logcat里可能会有Unsupported device之类关键字但也可能完全没有表现却是从“新增功能不工作”变成“原功能也坏了”。我现在的习惯是凡是涉及音频设备的增删改必然把这几处文件一起列进git提交里并强制自己在本地做一次tinycap录音回放验证adb shell tinycap /data/local/tmp/test_rec.wav -D 0 -d 0 -c 2 -r 48000 -b 16 -T 3 adb pull /data/local/tmp/test_rec.wav如果主mic和副mic的录音都能正常回放再提代码。哪怕只是改一个名字也要走一遍这个流程因为“名字不一致”不会在编译期暴露只会出现在运行期的无声现场。5. 改完配置后怎么确认自己真的改对了5.1 一份我常年使用的音频配置自检清单这几年的实践经验让我养成了固定动作每次改完音频路径配置先在本地把这张清单过一遍再交给测试能省掉大量往返。确认改的是实际加载文件md5sum要和编译产物一致。XML能正常解析在编译机的SDK里用xmllint --noout检查一遍别让一个标签错误卡住整个reboot。路由闭环从devicePort到mixPort的route必须存在且tagName在全局grep中只有一种写法。policy层选对设备用dumpsys media.audio_policy确认复现时选择的device是预期设备。HAL层执行到位log中的select_devices或setOutputDevices出现且route名是预期值。控件变化符合预期复现前后tinymix的diff中预期的功放和音量控件发生了预期变化。实际出声用tinyplay直接播放一个标准wav确认链路有真实音频输出。这套清单轮完要么已经找到问题要么问题已经被缩小到很小范围内不会再出现“不知道卡在哪层”的迷茫状态。5.2 必用的tinyalsa工具组和命令在MTK平台调试音频tinyalsa工具组是效率最高的武器。它不依赖framework可以绕过上层直接操作声卡用来划分问题边界特别方便。常用工具tinypcminfo查看PCM设备支持的路数、采样率、格式。tinyplay直接播放wav文件验证输出通路。tinycap直接录音到文件验证输入通路。tinymix查看和修改声卡控件值。一个很实用的排查思路是先用tinyplay播放一个标准wav。如果tinyplay都没声音问题几乎可以确定在HAL/驱动/硬件层不用再去翻Framework和policy如果tinyplay有声音但App放不出声音那问题就在上层路由或策略配置。这个二分法能直接把排查范围砍掉一半比无头苍蝇一样翻日志高太多。adb push test.wav /data/local/tmp/ adb shell tinyplay /data/local/tmp/test.wav如果项目userdebug里没有tinyalsa工具建议在本地编译时把tinyalsa塞进vendor镜像长期调试会方便很多。5.3 回归测试哪些case最容易暴露配置问题音频是手机里回归频率最高的功能之一因为路径相互牵连。改一条Speaker的配置可能影响耳机、FM甚至通话。我通常至少回归以下场景场景操作预期结果外放播放Music App或tinyplaySpeaker路径有声dumpsys显示SPEAKER耳机插拔插拔3.5mm耳机设备状态切换外放/耳机声音互切通话切换拨号后切换免提/听筒路由在Receiver和Speaker间正确切换录音回放tinycap录音10秒后回放主mic、副mic都能收声FM播放插耳机打开FMFM路径有声且HEADSET设备状态正确蓝牙A2DP连接蓝牙音箱播放音频切换到A2DP输出多App抢音频焦点音乐播放中进游戏焦点恢复后音乐能继续出声尤其是“插拔耳机”和“通话切换”这两项每次必测。它们牵动的路由链最长中间任何一个设备名不匹配、控件归属错乱都会在这两个场景里先爆发。6. 经验教训与避坑手记关于MTK平台的无声问题最后分享几条这些年花钱买来的教训。不要一上来就怀疑驱动或硬件。只要设备还能开机、还能正常识别声卡优先假设是配置问题。先用tinyplay把framework与HAL分开定位能帮你省下一大半时间。抓log一定要有对照。只有问题机器的log很多配置错误是看不出异常的。准备一台状态正常的同版本机器抓同场景的log做diff。很多模糊问题在diff下无所遁形。改配置前先留下原始快照。我用tinymix导出过一份“golden state”每次改配置前导出before改完导出after全程只允许预期控件发生变化。一旦出现意外改动就说明其他路径被干扰了。批量替换XML时格外小心。一个设备名可能同时出现在devicePort tagName、route的sink/source、audio_platform_info.xml的映射、HAL的枚举字符串里。用编辑器全局替换看似爽快实际经常只改了一半最后还是要花几小时去对名字。厂商预编译库不是不能动但要先确认它依赖什么。MTK部分音频策略以二进制库形式下发运行时只读。遇到改配置不生效的情况先查persist.vendor.audio.*属性和分区挂载别一头扎进反编译的坑。最后说一条最本能的建议遇到无声问题先别急着看代码。先把当前机器上完整的tinymix导出和音频配置打包存成“golden baseline”。这个小习惯几年下来帮我省下的时间和返工成本比任何花哨的调试技巧都多。