ARTICLE DETAIL

资讯详情

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

RK3399移植ES7210+ES8156:音频驱动、设备树与录音播放全流程

RK3399移植ES7210+ES8156:音频驱动、设备树与录音播放全流程 做RK3399平台音频方案的朋友多半会遇到类似需求Android系统里除了默认的HDMI、耳机声卡之外还要再挂一路模拟麦克风阵列或者额外的立体声扬声器、功放。最近我在RK3399平台上把ES7210 ADC加ES8156 DAC这一组合当成“一款声卡设备”完整跑通了录音和播放都验证过这篇文章就从驱动配置、设备树、音频路由到录音/播放测试的整个链路做一次复盘。内容不玄乎都是实操里能直接拿来用的东西。先说结论ES7210是一颗4通道24位ADC适合做模拟驻极体麦克风阵列采集ES8156是一颗立体声DAC适合接功放或耳机做播放。两者组合起来正好覆盖了“多麦克风拾音 立体声输出”的典型交互场景在RK3399这种经常被拿来做带屏音箱、视频会议终端、智能交互设备的平台上应用空间很直接。1. 为什么是ES7210ES8156方案选型背后的门道1.1 这颗ADC/DAC组合能解决什么问题ES7210常见于智能音箱的多麦克风阵列板卡上4路模拟输入可以直接挂4颗模拟麦省掉外部ADC转换的麻烦。ES8156则是一颗低功耗立体声DAC带耳机输出和线路输出能力在语音交互设备里负责把TTS、提示音、多媒体声音放出去。这两个芯片组合在一起能同时覆盖“听”和“说”的两端。在RK3399平台上很多方案默认只做HDMI音频或者蓝牙音频板上没有模拟音频通路。一旦产品需要本地麦克风阵列或者独立喇叭输出就绕不开“增加声卡设备”这件事。ES7210ES8156比单独用一颗编解码芯片更灵活尤其是4通道ADC结构比常见双声道codec天然多出两路麦克风输入方便做波束成形、声源定位这类算法。1.2 在RK3399上做音频方案的几条路线如果你要在RK3399或其他Rockchip平台增加声卡一般有三条路线选一颗主流通用codec比如WM8960、RT5651、ES8316这类芯片自带ADCDAC驱动在内核里通常已经打包好设备树配一下就能用。使用USB声卡这种方式最简单Linux内核的USB Audio Class驱动很成熟但延迟和稳定性受USB协议影响不适合严肃的低延迟录音场景。像这次一样把独立的ADC和DAC组合起来通常是为特定硬件形态服务例如“4路模拟麦 2路喇叭输出”这样就需要分别移植两个codec驱动再把它们接到同一个或不同I2S控制器上。ES7210ES8156属于第三种。两条芯片虽然各自是codec但拿来做“一个组合声卡”完全没问题。内核里两个芯片都有主线驱动sound/soc/codecs/es7210.c、es8156.c省去很多自己写驱动的底层工作关键是理解怎么把它们挂到设备树和machine驱动上。1.3 前提条件内核源码与编译环境开始之前确认手头SDK的内核版本。我这边用的是Rockchip官方发布的Linux 4.4内核Android版本是Android 10。不同SDK版本的内核代码结构会有差异但ASoC框架和Rockchip的I2S节点配置思路基本一致。编译环境需要注意两点一是最好用跟SDK配套的交叉编译链二是有干净的out目录避免不同版本的驱动对象混在一起。驱动代码改动后很多时候只需要重编内核模块但如果把驱动编进内核就得跟着boot.img一起烧录。为了方便调试我会先把相关驱动设为模块m验证完再改回y或者直接保持模块方式配合init脚本加载。2. 驱动移植从内核配置到设备树2.1 确认内核是否自带Codec驱动打开内核源码先看sound/soc/codecs目录下有没有es7210.c和es8156.c。Rockchip的不少SDK已经集成了ES7210驱动因为这类芯片在很多带麦阵列的公板上出现过。如果目录里没有需要从上游Linux内核或者其他BSP中获取对应驱动文件放进来补上Kconfig和Makefile条目。确认驱动文件存在后检查对应的config项。比如在sound/soc/codecs/Kconfig里搜索“ES7210”常见配置项是CONFIG_SND_SOC_ES7210和CONFIG_SND_SOC_ES8156。缺了就在menuconfig里加上Device Drivers - Sound card support - ALSA for SoC audio support - CODEC drivers - Everest Semiconductor ES7210 driver - Everest Semiconductor ES8156 driver同时需要确保Rockchip I2S控制器驱动的配置项已经打开一般是CONFIG_SND_SOC_ROCKCHIP_I2S以及machine驱动对应的配置项。我建议先用simple-audio-card跑通链路它只需要CONFIG_SND_SOC_SIMPLE_CARD。这样能最大程度减少干扰项把问题范围控制在codec本身。2.2 使能Kconfig与Makefile如果SDK自带的Kconfig/Makefile里已经有这两个驱动直接开启即可。缺少的话在sound/soc/codecs/Makefile里追加两行snd-soc-es7210-objs : es7210.o snd-soc-es8156-objs : es8156.o obj-$(CONFIG_SND_SOC_ES7210) snd-soc-es7210.o obj-$(CONFIG_SND_SOC_ES8156) snd-soc-es8156.oKconfig里也要有对应的menuconfig条目否则menuconfig里看不到。大多数情况下Rockchip BSP已经补齐了这些我这边只是确认并置为y或m。置为y的好处是烧录后直接可用不用关心模块依赖顺序坏处是每次改代码都要重编内核。调试阶段我还是推荐m单独编译生成es7210.ko、es8156.ko然后用insmod加载效率高很多。需要特别留意的是ES7210和ES8156的I2C地址在驱动里通常有默认值但硬件上可以通过地址引脚配置。如果发现设备树里reg值和实际探测结果对不上先量一下I2C引脚上下拉再对照datasheet确认地址。各芯片的I2C地址常见配置可能是0x40/0x42ES7210和0x10/0x12ES8156但这不是绝对标准必须以硬件设计和驱动源码里的i2c_device_id或of_match_table为准。2.3 设备树节点I2C、复位引脚与MCLK设备树是整个移植过程中最容易出问题的环节。RK3399有多个I2C控制器和I2S控制器先确认你的ES7210和ES8156挂在哪个I2C总线上比如I2C1。两个芯片也可以挂同一条I2C总线只要地址不冲突。以下是典型的设备树片段i2c1 { status okay; es7210: es721040 { compatible everest,es7210; reg 0x40; reset-gpios gpio4 RK_PA5 GPIO_ACTIVE_LOW; mclk-fs 256; #sound-dai-cells 0; }; es8156: es815610 { compatible everest,es8156; reg 0x10; reset-gpios gpio4 RK_PA6 GPIO_ACTIVE_LOW; mclk-fs 256; #sound-dai-cells 0; }; };reset-gpios不是必须的但如果硬件设计了复位引脚最好在驱动里或者通过GPIO子系统拉起来。很多“codec读不到”的问题本质是芯片处于复位状态或者I2C地址本来就对不上。可以在设备树阶段先不接reset引脚量一下RESET脚的默认电平确保是高电平再继续。MCLK的配置也很关键。RK3399的I2S控制器会给codec提供主时钟一般要求是采样率的256倍或512倍。比如采样率44.1kHz时MCLK用11.2896MHz采样率48kHz时用12.288MHz。mclk-fs 256表示MCLK频率 256 * fs驱动会据此计算和设置时钟。如果设置不对codec内部锁相环可能锁定不了音频全是杂音或者完全无声。2.4 用simple-audio-card搭起DAI Link接下来是最核心的machine层配置。ES7210负责录音ES8156负责播放两个codec都接到同一个I2S0控制器上时可以分别定义两个dai-link在设备树中这样呈现sound { compatible simple-audio-card; simple-audio-card,name es7210-es8156; simple-audio-card,format i2s; simple-audio-card,mclk-fs 256; simple-audio-card,dai-link0 { format i2s; cpu { sound-dai i2s0; }; codec { sound-dai es8156; }; }; simple-audio-card,dai-link1 { format i2s; cpu { sound-dai i2s0; }; codec { sound-dai es7210; }; }; };这样做的效果是在ALSA层面会出现一个名为“es7210-es8156”的声卡内部可能拆成多个PCM设备一个用来播放走ES8156另一个或两个用来录音走ES7210。实际要看simple-audio-card对同一CPU DAI上多个dai-link的处理方式有的内核版本会把两个link合并到同一PCM设备的不同substream有的会生成独立PCM设备。因此测试前用cat /proc/asound/pcm确认设备编号很重要。如果你觉得simple-audio-card的可控性不够Rockchip的machine驱动也有rockchip,multicodecs这类方案可以一个dai-link下挂多个codec。只是配置会更复杂需要确认I2S的TDM模式是否打开保证两个芯片的数据线不会互相干扰。我们这次先用simple-audio-card跑通后面再根据实际需求换到更复杂的machine驱动。3. 录音与播放的音频通路设计3.1 RK3399 I2S控制器与Codec的匹配逻辑RK3399有多个I2S控制器I2S0常用于多媒体音频I2S1/2/3可以作为备用。每个I2S控制器在设备树里是一个节点对应ALSA层的一个或多个PCM设备。CPU侧负责产生BCLK、LRCK和MCLKcodec侧负责编解码数据。ES7210和ES8156与I2S控制器连接时必须保证以下几点BCLK和LRCK的极性一致。大多数codec默认是I2S标准格式BCLK低电平空闲LRCK低电平左声道设备树里format i2s就表示标准I2S模式。MCLK频率要在codec支持的范围内。ES7210和ES8156一般支持256fs/512fs个别模式下也支持128fs。如果I2S控制器输出的主时钟偏离太多录音和播放都会异常。数据线不能接反。ES7210的ADC数据输出到I2S的RXES8156的DAC数据输入来自I2S的TX。硬件上如果RX/TX接反驱动里再怎么调都白搭。3.2 理解ASoC三要素Codec、DAI、Machine这块还是得掰开讲一下因为很多人栽在概念混乱上。Linux音频框架ASoC由三部分组成Codec驱动管理音频编解码芯片本身包括I2C寄存器读写、音量控制、ADC/DAC通路开关比如es7210.c和es8156.c。DAI驱动管理I2S控制器负责时钟生成和帧格式设置比如rockchip i2s驱动。Machine驱动把Codec和DAI绑定起来决定哪条I2S接到哪个codec声音路由怎么走比如simple-audio-card。调试声卡时经常遇到“声卡已注册但没有声音”的问题这时候就要逐个排查Codec驱动是否正常初始化、DAI层的时钟是否拉起来、Machine的路由是否配置正确。三者的关系可以理解成Codec是“喇叭”DAI是“功放”Machine是“接线师傅”。有一个没配合好声音就出不来。3.3 左右声道与TDM模式的参数思考ES7210是4通道ADC在I2S标准模式下实际只有2个通道左/右要使用4通道需要配置为TDM模式。TDM模式允许在一条I2S总线上分时传输多路数据比如4个slot每个slot对应一个通道。设备树声卡节点里的format如果只是i2s可能只会启用前两个通道。如果产品确实需要4路麦克风需要在codec驱动里确认是否支持TDM slot配置同时在I2S控制器驱动里使能TDM。很多RK3399 BSP的I2S驱动已经支持dai-tdm-slot-num这类属性i2s0 { status okay; pinctrl-names default; pinctrl-0 i2s0_8ch_bus; dai-tdm-slot-num 4; dai-tdm-slot-width 32; };这里slot-width设成32位是因为很多codec使用32bit slot传输24bit有效数据。这样4通道占满一条I2S总线的8根时隙配合ES7210的TDM输出模式就能在一条数据线上传完4路PCM。不过要注意ES8156是立体声DAC播放时通常不需要4通道TDM直接走标准I2S即可。如果播放和录音共用同一条I2S总线且总线配置成了4slot TDMES8156可能无法正确解析数据。这种场景下要么分两条I2S控制器要么播放时也让ES8156工作在TDM模式并只解析前两个slot具体要看ES8156的寄存器是否支持。这也是为什么很多产品实际会把ES7210挂I2S0用于录音ES8156挂I2S1用于播放两个PCM设备互不干扰音质也更有保证。3.4 Android侧路由怎么接在Linux驱动层面做好声卡注册后Android系统里还涉及音频通路路由。默认情况下Android会去枚举所有声卡然后通过AudioPolicy选择默认输入/输出设备。但如果设备上同时有HDMI、蓝牙、USB声卡等多路音频Android未必会自动选择你新加的ES7210/ES8156声卡。常见做法是在Android的audio_policy_configuration.xml里新增一个音频输出设备比如devicePort tagNameSpeaker typeAUDIO_DEVICE_OUT_SPEAKER rolesource profile name formatAUDIO_FORMAT_PCM_16_BIT samplingRates48000 channelMasksAUDIO_CHANNEL_OUT_STEREO/ /devicePort然后在mixPort里把声卡对应的ALSA设备映射过去。比如ES8156对应的PCM设备是hw:0,0ES7210对应hw:1,0就需要在audio policy里把它们分别配置为output和input。如果只是想快速验证驱动先用tinyalsa工具绕过Android策略层在adb shell里直接测录音和播放是最简单也最直接的验证方式。4. 编译烧录与测试验证4.1 内核编译与模块烧写我第一次移植时先把驱动编进内核省事结果每次改一个寄存器配置都要重烧boot.img非常浪费时间。后来改成模块方式编译出es7210.ko、es8156.ko和对应的machine驱动模块开发效率高很多。编译内核make ARCHarm64 rockchip_defconfig make ARCHarm64 menuconfig # 勾选需要的配置 make ARCHarm64 rk3399.img -j8如果选了模块在kernel源码目录执行make ARCHarm64 modules -j8生成的.ko文件在sound/soc/codecs/、sound/soc/soc-generic等目录下。用adb push到板子上的/vendor/lib/modules或者直接insmod路径adb push es7210.ko /data/local/tmp/ adb shell insmod /data/local/tmp/es7210.ko模块加载顺序一般是先加载codec驱动再加载machine驱动。如果codec驱动没加载machine驱动在解析设备树时找不到对应节点声卡就注册不出来。用modprobe能省去手动处理依赖的麻烦前提是把.ko安装到系统的modules目录并执行depmod。4.2 查看声卡注册情况驱动加载成功后第一件事就是看声卡有没有注册。ADB进入shell执行cat /proc/asound/cards正常会看到类似0 [es7210es8156 ]: es7210-es8156 - es7210-es8156 es7210-es8156如果没有出现看dmesgdmesg | grep -i es7210 dmesg | grep -i es8156 dmesg | grep -i asoc常见错误是failed to find I2C device from device tree或者ASoC: failed to instantiate card。这类信息直接指向设备树节点或I2C地址问题顺着查很快能定位。确认声卡注册后再看PCM设备cat /proc/asound/pcm输出会列出类似00-00: es7210es8156 : es7210-es8156 : playback 1 : capture 1这里playback对应ES8156capture对应ES7210。如果只看到playback或只看到capture说明另一个codec没有被正确绑定排查方向就是dai-link配置是否遗漏。4.3 用tinyalsa完成录音验证录音测试我强烈推荐用tinyalsa工具集比直接用Audacity到板子上抓声音要轻量得多。先把tinycap、tinyplay、tinypcminfo这些工具推到板子上adb push tinycap /data/local/tmp/ adb push tinyplay /data/local/tmp/ adb push tinypcminfo /data/local/tmp/ adb shell chmod 755 /data/local/tmp/tiny*用tinypcminfo确认设备格式adb shell /data/local/tmp/tinypcminfo -D 0 -d 0重点看支持格式、通道数、采样率。如果ES7210被识别为四通道设备最好录制四通道PCM验证所有麦克风通路。录音命令示例adb shell /data/local/tmp/tinycap /data/record.wav -D 0 -d 0 -c 4 -b 24 -r 16000 -T 5参数含义-D声卡编号-d设备编号-c通道数-b位深-r采样率-T录音秒数。录制完把文件拉回来adb pull /data/record.wav ./record.wav用Audacity或者Cool Edit打开看四个声道是否都有波形。如果只有部分声道有波形检查麦克风接线和ES7210的内部通道使能寄存器如果全都没有波形优先怀疑I2S时钟或者TDM配置。4.4 用tinyalsa完成播放验证播放测试前准备一个测试音频文件比如44100Hz、16bit、双声道、1kHz正弦波的wav文件放在/data/local/tmp/。播放命令adb shell /data/local/tmp/tinyplay /data/local/tmp/test.wav -D 0 -d 0播放时用示波器或万用表量ES8156的模拟输出脚是否有电压变化也可以直接接耳机或功放听。如果无声先检查ES8156的DAC power、输出mixer路由是否打开音量是否调到0了。用tinycontrol或者直接通过tinyplay播放的同时查看声卡控件adb shell tinymix -D 0tinymix能列出所有属于声卡0的ALSA控件包括DAC Volume、ADC PGA、Mux选择等。把音量类控件调到合理范围adb shell tinymix -D 0 DAC Volume 100注意不同驱动导出的控件名不一样以tinymix -D 0实际显示为准。4.5 在Android上做基本录音放音驱动验证通了之后再从Android上层走一遍流程。最简单的验证方式是使用系统自带的录音机或第三方录音App选“麦克风”通道录一段音再播放出来。如果不方便装App可以用adb shell里Android自带的MediaRecorder相关命令但不同版本不一定有现成CLI我习惯直接用tinyalsa验证Android上层验证等音频策略配置完成后再做。对于需要做语音识别的产品还要注意采样率对齐。ES7210支持较高的采样率但Android音频策略可能默认16kHz或48kHz如果codec的PLL没有配置好录音数据会拉长或变短识别率会急剧下降。碰到这种情况重点查MCLK频率和采样率对应关系。5. 实战中的坑与排查方法5.1 I2C通信不稳定codec读不到我遇到最多的问题就是I2C读不到codec报i2c transfer error。原因通常是I2C地址配置错或者RESET引脚一直拉低导致芯片未启动。排查方法很简单用i2cdetect扫描总线确认设备地址确实存在。命令示例i2cdetect -y 1。对照数据手册确认地址引脚电平ES7210的I2C地址有时通过AD0/AD1引脚区分。检查reset-gpios对应引脚是否在设备树里被正确占用别和其他外设冲突。还有一个低级错误是设备树里reg值用了十进制导致读地址错位。比如8位I2C地址0x40写成64驱动里如果用7位地址匹配就匹配不上了。我习惯统一用hex写reg。5.2 声卡注册了但录音数据全零声卡注册成功但录音文件打开全是直线全零这种情况很常见。重点怀疑以下方向Microphone偏置电压没开。驻极体麦克风需要提供偏置电压如果ES7210的MICBIAS没使能麦克风不工作录下来的就是零。ADC输入通路没有使能。ES7210内部可能有mic input mux和PGA需要用tinymix把对应开关打开。I2S数据时序不对。特别是TDM模式下slot offset设置错了导致有效数据落到无效slot上PCM缓冲里全是0。通道数、位深不匹配。比如录音时指定-c 2但实际ES7210配置成4通道TDM某些驱动会在数据格式转换时清空数据。针对最后一点我用tinypcminfo查看设备实际宣称的格式后再调整tinycap参数能很快排除参数不匹配的问题。5.3 播放有杂音或无声播放杂音多半是MCLK频率或PLL配置不干净。比如48kHz采样率时MCLK用了12.288MHz但驱动里配成了11.2896MHzcodec输出信号会有细微频偏听感上就是音调不对或“沙沙”杂音。这种问题用示波器看MCLK频率最直观没有示波器的话用cat /sys/kernel/debug/clk/clk_summary | grep i2s看当前I2S时钟树状态。播放无声常见原因是DAC的power管理问题。有些驱动默认把DAC置于省电模式播放前需要写寄存器唤醒并能听到pop音。如果tinymix里找不到明显的DAC volume控件干脆在驱动里把DAC相关init寄存器数组全部确认一遍重点看有没有把output mixer设为静音。另外ES8156和ES7210如果共用同一个I2S控制器但数据引脚模式配置不一致也可能导致播放时数据被错误路由。比如I2S0的RX/TX是独立引脚但有的板卡会短接这种硬件问题不如用软件排查快出现播放无声而录音正常时先量一下TX数据引脚是否有波形。5.4 多块声卡共存时的设备选择如果板子上有其他声卡比如HDMI声卡、USB声卡那么新注册的ES7210ES8156不一定是card0可能是card1、card2。Android上层如果硬编码了card编号会导致找不到设备。我在这个项目里遇到的坑是Android AudioPolicy默认把card0当成外放而我新加的声卡是card1结果每次播放都从HDMI走了板载喇叭没声音。解决办法有两个在audio_policy_configuration.xml里把新声卡对应的设备端口映射好并提高优先级。让驱动把声卡注册顺序提前。比如在设备树里调整sound节点和I2C节点的顺序很多情况下card编号是按驱动初始化顺序排列的早注册的就是card0。现代Android其实推荐用USB Audio等动态探测方式但RK3399这类嵌入式平台还是以静态配置为主。多声卡场景下规则是先用tinyalsa确认硬件层没问题再回到Android层解决路由问题别两边同时挂。5.5 调试利器dmesg、tinymix、debugfs最后推荐一套调试组合拳能覆盖90%的音频问题dmesg | grep -i asoc看ASoC框架的初始化日志包括codec绑定、DAI link创建、PCM注册。dmesg | grep -i es7210看codec驱动私有日志很多驱动里有ioread/iowrite的debug打印。tinymix -D 0看控件列表用tinymix -D 0 控件名 值改寄存器能快速验证通路。/proc/asound/card0/codec#0这种debugfs文件可以看到codec寄存器快照有些驱动会导出来。cat /proc/asound/pcm确认PCM流方向防止录音和播放设备号搞混。调试音频驱动时不要急着改Android上层。先把ALSA这一层完全打通保证用tinycap/tinyplay能正常录音播放再进入Android应用验证否则问题叠加起来会非常难定位。说回这次EP7210ES8156整体移植我觉得最值得记住的一点是先把I2C、时钟、数据引脚三件事确认清楚再谈驱动和路由。硬件上少一条线、错一个地址后面软件再怎么调都是浪费时间。我自己的习惯是拿到板子第一件事先量I2C地址、量RESET电平、量MCLK波形确认无误后再去改设备树。这套流程帮我避开了绝大多数“怎么调都没声音”的尴尬。另外调试阶段尽量用模块方式加载驱动别整天烧整个boot.img改代码到验证的周期压缩到一分钟以内心情会好很多。
返回列表