ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RK3399上ES7210+ES8156打造四路麦克风阵列音频方案

RK3399上ES7210+ES8156打造四路麦克风阵列音频方案 1. 从一块板载Codec到双芯片方案为什么非要折腾ES7210加ES8156我手上的RK3399开发板预装的是Android 10默认音频方案用了一颗RT5651负责耳机和板载麦克风。但项目需求是做一个本地语音交互设备除了日常通话还得同时采集四路模拟麦克风阵列做声源定位并对播放通道的底噪和动态范围有明确要求。RT5651是二进二出的小Codec模拟输入只有一对差分MIC和一对LINEIN无论怎么复用管脚都凑不出四路同时采样的能力所以只能重新选采集前端。当时对比过几颗物料ES7243、ES8388、TLV320AIC3106还有CS4265等等。单看采集的话ES7243很便宜但它是单声道ADC四路就得拼四颗I2C地址还得做片选电路板面积和BOM成本一下子就上去了。而ES7210这颗ADC原生就是4通道支持TDM输出一颗芯片吃四路模拟输入非常对路。播放端我们本来可以用板载RT5651但它和ES7210挂在不同的I2S总线上上层要同时管两张卡调度、混音、策略配置都会更麻烦搞不好还会出现录音卡和播放卡采样率乱套的问题。所以我的选择是采集端用ES7210播放端加一颗ES8156立体声DAC两颗芯片一起挂在同一条I2S总线上让系统把它们识别成同一张声卡。ES8156是ESS的立体声低功耗DAC带耳机放大器信噪比数据也够看正常情况下驱动一颗DAC比驱动完整Codec简单得多因为它不涉及ADC方向DAPM路径少了一半。标题里说的“增加一款声卡设备”本质上就是让RK3399的Linux内核和Android音频框架认出一张由ES7210加ES8156组成的复合声卡然后分别验证录音和放音。这个方案对熟悉RK平台的工程师来说其实挺常规的真正麻烦的地方在于RK3399的I2S控制器既可以当主机也可以当从机ES7210既支持主模式也支持从模式两边一旦配合不好就会遇到“驱动加载成功但录音全是0x00”这种棘手问题。后面我会把时钟配置、DTS节点、内核适配、Android层打通这几个环节挨个拆开讲。2. 硬件拓扑和I2S链路先想清楚时钟谁给谁再谈配置2.1 I2S总线分配与管脚复用RK3399上有多组I2S控制器一般I2S0被HDMI音频占着I2S1接板载CodecI2S2用于扩展。我的做法是把ES7210和ES8156并联在I2S2上。两片芯片的I2C地址呈现在同一条I2C总线上采样率等参数由同一块主控统一配置。硬件连接上有几点必须提前确认MCLK来源ES7210和ES8156都需要主时钟MCLK由RK3399的I2S2_MCLK管脚供给。这个脚在设备树里要选对pinmux很多板子上MCLK和GPIO功能复用如果默认被设成了GPIO口I2S2就会不工作。I2C地址冲突ES7210的I2C地址由AD0引脚决定ES8156也有类似的地址选择脚。设计时要确认两颗芯片的默认地址不是同一个否则后挂的设备I2C probe会失败。复位时序两颗芯片都有复位脚最好都由GPIO控制。上电时先拉低保持一段时间再释放要确保主控的I2C初始化晚于复位释放否则寄存器写入会被芯片的复位动作清掉。2.2 主从模式的选择逻辑这里建议ES7210和ES8156都设成从机模式RK3399作为I2S主机。如果让Codec当主机RK3399的I2S控制器配置成从机代码层面也能跑通但调试复杂得多因为主从时钟的相位容差和首帧对齐全得靠引脚时序保证一不留神就是“偶发性咔哒声”。RK3399输出BCLK和LRCLK两颗芯片从设备分别采样。BCLK频率由采样率、通道数、位宽共同决定。比如48kHz、32bit、2通道时BCLK等于48k×32×2也就是3.072MHz如果是TDM八通道BCLK可能要到12.288MHz。RK3399的I2S2最大支持的BCLK一般够用但要确认下CLK tree有没有能力从24MHz晶振上分出准确的12.288MHz。这个阶段如果分频算不对声音速度会变快或变慢。我用一个表格整理下主从选择的考量方便新手对照模式组合时钟源调试难度稳定性使用建议RK3399主Codec从RK3399输出BCLK/LRCLK/MCLK低高相位可控推荐首选Codec主RK3399从外部晶振或Codec产生时钟中高中受晶振精度影响特殊板卡才会用两颗Codec互相级联复杂需要相同MCLK高低不建议量产不推荐3. 驱动侧的三板斧内核配置、设备树和ALSA注册3.1 内核驱动使能ES7210和ES8156在主线或Rockchip官方内核里有时没有现成的驱动文件因此一个快速做法是先确认你的内核里有没有sound/soc/codecs/es7210.c和es8156.c。如果没有就从厂商BSP包同步或者到ESS提供的Linux驱动移植过来。驱动文件拷进内核后要改sound/soc/codecs/Kconfig和Makefile增加对应的编译选项。以ES7210为例Kconfig里加入类似这样的内容config SND_SOC_ES7210 tristate ESS ES7210 CODEC depends on I2C然后将驱动配置为y或m重新编译内核。这里我建议直接编入内核而不是用module因为RK平台上modprobe加载顺序有时候会和机器驱动不匹配。编入内核之后驱动probe时机由DTS中i2c子节点的扫描决定更可控。3.2 设备树节点怎么挂在i2c2节点下面挂两颗codec以RK3399的I2S2为例DTS片段如下i2c2 { status okay; es7210: es721040 { compatible ess,es7210; reg 0x40; clocks cru SCLK_I2S2_8CH; clock-names mclk; reset-gpios gpio3 RK_PB4 GPIO_ACTIVE_HIGH; #sound-dai-cells 0; }; es8156: es815618 { compatible ess,es8156; reg 0x18; clocks cru SCLK_I2S2_8CH; clock-names mclk; reset-gpios gpio3 RK_PB5 GPIO_ACTIVE_HIGH; #sound-dai-cells 0; }; }; i2s2 { status okay; rockchip,clk-trcm 1; pinctrl-names default; pinctrl-0 i2s2_8ch_mclk, i2s2_8ch_bus; };rockchip,clk-trcm这个属性等于1表示让RK3399的I2S时钟输出给外部。如果缺少这句I2S2可能只产生BCLK和LRCLK而不输出MCLK导致ES7210和ES8156因为没有主时钟而完全不工作。在pinctrl中也需要把I2S2的MCLK脚配置成复用为GPIO或I2S功能否则MCLK拉不出来。3.3 为什么需要machine driver设备树里只有codec节点还不够ALSA还要求有一个“machine driver”把CPU DAI、Codec DAI、DMA缓冲区、音频链路绑在一起。RK平台上常见的做法是使用通用的simple-audio-card。在DTS中可以这样描述sound_es7210_es8156 { compatible simple-audio-card; simple-audio-card,name es7210-es8156; simple-audio-card,format i2s; simple-audio-card,mclk-fs 256; simple-audio-card,cpu { sound-dai i2s2; }; simple-audio-card,codec { sound-dai es7210; }; };ES7210负责录音方向ES8156负责播放方向simple-audio-card也可以定义多个codec挂在同一个cpu下具体的dai-link组合可以看内核版本的支持情况。如果使用厂商提供的machine驱动就直接在rockchip_audio配置里添加新声卡名然后把dai-link按录音和播放拆开。有一点要提醒mclk-fs表示MCLK频率对采样率的倍数。ES7210在32bit四通道TDM模式下MCLK等于256×采样率会比较安全48kHz对应12.288MHz。如果你的驱动里把这个值设成128可能出现ES7210无法锁定LRCLK的问题。4. Android层封装让应用能直接用而不是停在ALSA裸设备4.1 从内核ALSA到tinyalsa的验证路径在Android里做音频调试第一件事是不要急着去改上层策略先用tinyalsa工具验证底层硬件是否通了。RK3399的Android系统里通常内置了tinycap和tinyplay分别用来录音和放音。如果你手里没有这两个工具可以推到/data/local/tmp下用adb执行。先查看ALSA设备节点cat /proc/asound/cards正常情况下会看到类似0 [rockchiphdmi ]: rockchip_hdmi - rockchip-hdmi 1 [es7210es8156 ]: simple-audio_card - es7210-es8156如果声卡名是一串乱掉的名字或者es7210es8156没出现说明machine driver和设备树还没对齐需要回到DTS去排查。然后查看录音节点和播放节点cat /proc/asound/card1/pcm0p/info # 播放 cat /proc/asound/card1/pcm0c/info # 录音确认信息无误后开始录音测试tinycap /data/local/tmp/test_rec.wav -D 1 -d 0 -c 4 -r 16000 -b 16 -T 5这里-D 1指定card编号-d 0指定pcm设备-c 4表示四通道-r 16000是采样率-b 16是位深。录完用tinyplay回放如果OK说明底层硬件链路正常。先用PCM原始数据测别用WAV文件因为WAV头文件里带采样率信息如果和实际配置不一致会干扰判断。4.2 AudioPolicy和mixer_paths的修改底层ALSA通了以后Android音频框架不一定能自动认识这张新卡。Android系统依赖audio_policy_configuration.xml来分配音频输入输出设备mixer_paths.xml来初始化Codec寄存器。RK平台的这些文件一般在/vendor/etc/目录下。我的做法是把新声卡的所有输入源配置成.input。如果产品只用到麦克风阵列采集和扬声器播放则把输入输出都绑定到ES7210ES8156这张卡audioPolicyConfiguration globalConfiguration speaker_drc_enabledfalse/ modules module nameprimary halVersion3.0 attachedDevices itemSpeaker/item itemBuilt-In Mic/item /attachedDevices mixPorts mixPort nameprimary output rolesource profile name formatAUDIO_FORMAT_PCM_16_BIT samplingRates16000,48000 channelMasksAUDIO_CHANNEL_OUT_STEREO/ /mixPort mixPort nameprimary input rolesink profile name formatAUDIO_FORMAT_PCM_16_BIT samplingRates16000,48000 channelMasksAUDIO_CHANNEL_IN_STEREO/ /mixPort /mixPorts route type namemix/ sink nameprimary input devicePorts devicePort tagNameBuilt-In Mic typeAUDIO_DEVICE_IN_BUILTIN_MIC rolesink/ /devicePorts /sink /route /module /modules /audioPolicyConfigurationsamplingRates里我写的是16000和48000如果你的产品用44.1kHz为主那这里也要把采样率加进去。ES7210和ES8156在驱动里如果支持多采样率切换上层配置和底层必须对齐否则会出现“上层请求48k实际驱动配置44.1k”这种离谱的失配。mixer_paths.xml方面ES7210和ES8156很多寄存器控制项会暴露成ALSA Control比如Capture MIC Path、ADC PGA Gain、DAC Volume等。上电初始化时最好在mixer_paths里把这些路径设置好不然后期Audioserver起来后path没有正确组的应用录音音量可能默认是静音。4.3 让Audioserver默认走新声卡如果系统里同时存在HDMI、板载Codec和新声卡Android会优先选择它认为的“默认输出设备”。最简单的方法是在audio_policy_configuration.xml里把新声卡对应的输出设备设为默认或者通过setprop来指定但这种方式在reboot后会失效。产品化阶段建议直接改XML中的defaultOutputDevice不要依赖android.intent这些上层配置来动态切。为了保证测试用的应用不被Android音频焦点干扰推荐在项目阶段用低频底层测试软件直接调用AAudio或OpenSL ES打开AUDIO_DEVICE_OUT_SPEAKER设备同时配合底层的tinyplay结果来比对。这样可以区分问题到底是在HAL层还是应用层。5. 实际测试中的血泪教训和音质问题排查5.1 录音全零数据查了一整天才发现是TDM slot没配对ES7210支持标准I2S和TDM两种模式。因为要传四通道所以肯定走TDM。调驱动的时候设备树或驱动里要把TDM slot数配置好。RK3399的I2S2可以配置成8个slot我用了slot 0-3传ES7210的四通道slot 4-7没有使用。代码里对应的格式配置是SND_SOC_DAIFMT_TDM | slot_width 32。当天遇到的问题是录音文件能生成时长正常但用Audacity打开看波形全是一条直线。用命令读回放PCM文件cat /proc/asound/card1/pcm0c/sub0/sram_bytes xxd /data/local/tmp/test_rec.wav | head看到全是0x00。这种“有数据但无内容”的现象基本可以判定是I2S时序或TDM时隙错位。后来对比芯片手册发现我把ES7210的TDM配置寄存器写成了2通道模式等于它只占前面两个slot而后面的通道根本没人驱动内核又按四通道去DMA取数自然取回来的都是空数据。把ADC通道数和TDM slot配置改成一致后波形立刻正常。5.2 ES8156放音有底噪根源在电平和PGA设置放音测试刚开始还行但一静音就听到明显的“嘶嘶”底噪。用示波器看ES8156输出管脚发现尾音部分存在高频纹波。ES8156的数字增益和模拟增益分开配置我当时把DAC数字音量寄存器拉满了但模拟输出级的增益又设得很高两级放大一叠加本底噪声被放大得很明显。解决方法比较朴素先调到中低增益用耳朵听阈值然后看芯片手册推荐的典型值。ES8156这类DAC芯片输出功率按喇叭和耳机负载来算耳机通道增益设在0dB附近一般比较好。另外RK3399的I2S和ES8156之间的BCLK线如果走线太长没有包地也会引入噪声。我的板子上ES8156离SoC比较远最后在MCLK和BCLK上各串了22Ω电阻噪声明显下降。5.3 采样率切换时爆音需要注意静音时序应用从48kHz切到16kHz录音时偶发爆音。这个问题在Android上层不容易复现因为系统一般不会频繁切采样率但我用脚本循环测试时必现。分析下来是切换采样率时I2S的BCLK频率在瞬间不对齐DAC端产生pop音。解决思路是先控制ES8156静音再切换采样率切换完成后再取消静音。在驱动层面可以这样处理在soc_dai_ops的trigger回调里加上静音控制的判断static int es8156_trigger(struct snd_pcm_substream *substream, int cmd) { switch (cmd) { case SNDRV_PCM_TRIGGER_START: regmap_update_bits(es8156-regmap, ES8156_REG_DAC_VOL, 0xF0, 0x00); break; case SNDRV_PCM_TRIGGER_STOP: regmap_update_bits(es8156-regmap, ES8156_REG_DAC_VOL, 0xF0, 0x80); break; } return 0; }5.4 录音与播放同时开启时的时间漂移问题在双工测试中同时开启录音和放音长时间运行后录音数据量略大于播放数据量也就是所谓的时钟漂移。原因是ES7210和ES8156虽然都由RK3399的I2S2提供时钟但录音和播放走的是同一I2S控制器下的两个DMA通道DMA的中断调度和音频FIFO的读写节拍不完全同步。大部分声卡都有这个问题程度轻重不同。短期内可以在Android层加软件重采样或buffer补偿来处理严格的方案是使用统一的音频时钟域也就是让ES7210和ES8156都从同一颗晶振取MCLK并且配合驱动代码做buffer对齐。RK3399这种SoC级别的控制器在实际产品里靠的是上层AudioFlinger做偏移补偿所以不用太过焦虑只要漂移在一个可接受范围内就行。5.5 快速定位工具集以最小代价确认硬件问题当底噪、无声音、爆音这些问题混在一起时建议先排除硬件连接问题再做驱动排查。我习惯在调试早期用这些手段工具/命令作用典型场景i2cdetect -y 2查看I2C设备地址确认ES7210/ES8156是否被系统识别cat /sys/kernel/debug/asoc/components查看ASoC组件注册情况判断codec驱动是否probe成功tinypcminfo -D 1查看PCM设备的格式信息确认采样率、通道数是否和预期一致/sys/kernel/debug/clk/clk_summary查看MCLK频率状态确认SCLK_I2S2是否输出正确时钟示波器量MCLK/BCLK/LRCLK观察I2S波形排查时钟相位和幅值问题如果i2cdetect能看见设备但驱动probe还是失败优先看I2C的clock-frequency是不是设太高了一般400kHz没问题但如果总线走线寄生电容偏大100kHz更稳。ES7210A和ES7210B对于I2C时序的要求略有不同具体看芯片的勘误表我当时就遇到过ES7210B把I2C总线拉死的情况后来把频率降到100kHz解决。6. 这套方案在产品化阶段还要注意什么整张声卡在开发板上跑通只是第一步真正量产时还有几个边角问题比你想的更影响体验。这颗ES8156如果同时驱动喇叭和耳机DAC输出路径必须要做耳机检测和功放控制联动否则耳机插入瞬间容易听到明显的开关机噪音。ES7210的4路模拟输入如果全部接麦克风模拟电源AVDD的纹波会直接耦合到录音数据里供电不够干净时用软件滤波效果很有限该加LDO的地方省不得。另外不要以为DTS里配置好了Android系统升级后配置还能保持。RK3308/RK3399这类平台固件里经常出现内核单独更新但/vendor分区的so库和XML没同步的现象导致录放功能偶发异常。产品发布前最好打包验证一次整包升级确认声卡编号不会因为启动顺序变化而Swap。Alsa声卡的命名和索引在每次启动时可能变化应用层如果依赖固定card id会踩大坑。建议在底层把文件节点用by-name符号链接暴露出来或者在HAL层通过声卡name去匹配不要裸用数字索引。测试这块也说一句不要只测16bit/48kHz这一组参数ES7210和ES8156的多采样率切换、暂停恢复、长时间稳定性都要纳入自动化测试。我遇到过反复播放几小时后DAC输出偶发一个左声道音节丢一半的情况这是I2S的FIFO在系统负载高时出现欠载了最终靠调整DMA的burst size解决。类似问题必须靠长时间压测才能暴露。7. 最后补一个实用小脚本一键验证声卡状态平台开发过程中每次切换内核或改动DTS后都要重新确认声卡状态建议把下面这段脚本放到板子里快速输出关键信息#!/system/bin/sh echo sound card list cat /proc/asound/cards echo i2c detect (i2c2) i2cdetect -y 2 echo asoc components cat /sys/kernel/debug/asoc/components echo clock summary cat /sys/kernel/debug/clk/clk_summary | grep i2s2 echo record 3s test tinycap /data/local/tmp/test.wav -D 1 -d 0 -c 4 -r 48000 -b 32 -T 3 echo play test tinyplay /data/local/tmp/test.wav -D 1 -d 0-b 32位深在部分老版本tinyalsa里可能不支持先查一下tinypcminfo确认格式再相应调整。如果录制出来的wav文件大小不对优先检查通道数和位深的匹配4通道32bit、3秒、48kHz的文件大小理论上等于4×4×48000×3字节也就是2.304MB和这个数差距过大就说明PCM配置有问题。这套方案从驱动到应用层全部打通前前后后大概花了我两个完整工作日。ES7210和ES8156的组合对于多Mic阵列加独立播放通道的产品形态来说确实是一个成本和性能都比较合适的方案希望这些经验能帮你少走几步弯路。
返回列表