ARTICLE DETAIL

资讯详情

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

RK3399平台ES7210+ES8156声卡移植与调试全记录

RK3399平台ES7210+ES8156声卡移植与调试全记录 接手这块板子的时候项目需求听起来很简单RK3399 Android 平台上要多挂一套声卡ES7210 做录音ES8156 做播放系统起来之后录音、播放能正常跑通。但真做起来才发现这活儿从内核驱动、设备树、machine driver一路牵扯到 Android 的 HAL 层和 mixer 路由配置。这篇文章把这次 ES7210ES8156 声卡移植与测试的完整过程、关键细节和踩坑记录整理出来给正在搞 RK3399 或类似瑞芯微平台的 BSP、音频驱动工程师做个参考。这套方案在智能会议终端、远场语音交互设备里很常见ES7210 是一颗四通道 ADC专门用来接麦克风阵列做远场拾音ES8156 是一颗立体声 DAC负责播放提示音和本地音频。两者分开部署比传统单颗 codec 在麦克风通道数、增益调节、底噪控制上灵活得多。整个调试链路从内核启动时的 I2C 探测到 tinyplay/tinycap 底层录音放音再到 Android 上层 App 调用任一层出问题都会导致功能异常。下面按我的实际操作顺序来复盘。1. 方案背景与整体架构拆解1.1 ES7210 和 ES8156 这两颗芯片分别负责什么ES7210 是 Everest Semiconductor 的四通道音频 ADC支持 I2S/TDM 输出采样率覆盖 8kHz 到 192kHz信噪比标称能做到 102dB 左右。它常见于四麦克风阵列的采集场景比如会议麦克风、智能音箱的远场语音模块。四路模拟输入可以接四颗 MEMS 麦克风或模拟麦克风通过 TDM 模式把四个通道的数据依次送出。控制接口是 I2C芯片的采样率、通道映射、PGA 增益、MIC bias 都可以通过寄存器配置。ES8156 则是同厂的立体声 DAC支持 I2S/TDM 输入内部带耳机放大器或线路输出比较适合做设备播放端的音频输出。控制接口同样是 I2CACL/DRC、动态范围控制、EQ 这类音效功能也都有只是实际嵌入式项目里大部分时候只用它的基础 DAC 通路。ES8156 的模拟输出可以直接接喇叭功放或者接耳机接口。这套组合和常见的单颗音频 codec 最大的区别是采集和回放在物理上是两颗独立芯片各管一摊互不占用资源。如果你用 ES8316 或 WM8960 这类二合一 codec通常只有两路麦克风输入想做成四麦阵列就得外挂模拟开关或专用 ADC电路和驱动都会更绕。ES7210 ES8156 则直接把采集通道拉到四路且 ADC 的底噪、串扰指标比普通 codec 内置 ADC 更好回放部分也独立非常适合理清“拾音归拾音、播放归播放”的产品形态。1.2 为什么选择 RK3399 平台做这套声卡方案RK3399 在智能设备里的存量很大双路 A72 加四路 A53 的性能对于 Android 系统和音频处理算法绰绰有余。它的 I2S 接口资源也比较丰富I2S0 支持多声道 TDM 模式可以同时承载播放和录音两条数据流正好匹配 ES7210 四路采集加 ES8156 立体声回放的需求。另一个原因是 Android 系统对音频设备的抽象层已经非常成熟只要底层 ALSA 驱动把声卡节点注册好HAL 层就能自动探测到新的 ALSA 设备再通过 mixer 控件和路径配置文件把路由关系理顺。相比 Linux 桌面端Android 上做声卡移植的最大工作量反而不在内核驱动本身而是要让音频策略文件、混音路由和声卡节点对齐这一步很多人容易忽略。1.3 这套方案要用到哪些软件层次从系统架构来看声卡设备真正跑起来涉及四个层面内核层ES7210、ES8156 两个 codec 驱动RK3399 I2S 控制器驱动以及一个把它们绑定在一起的 machine driver。设备树层I2C 地址、I2S 引脚、时钟频率、codec 之间的连接关系、DAPM 路由都在这里。ALSA 用户空间层/proc/asound 下的声卡节点、tinymix 看到的 kcontrol 控件、tinyplay/tinycap 这些测试工具。Android 音频 HAL 层audio_policy_configuration.xml 里的输入输出设备定义、audio_platform_info.xml 的声卡映射、mixer_paths.xml 的通路和音量化配置。任何一个层面差异都会导致功能异常。比如内核驱动起来了但 Android 不认识这张卡App 打开录音还会直接报错或者 HAL 认识声卡了但 mixer_paths 里没把 ADC PGA 打开录出来的全是静音。所以整个移植过程不能只盯着内核代码每一层都要验证。2. 硬件连接与设备树配置实操2.1 引脚和信号链路的连接要点先理清硬件上的连接关系这是后续所有调试的基础。常规接法是这样的ES7210 的 I2C 接 RK3399 某个 I2C 控制器例如 I2C4四路模拟输入 MIC1~MIC4 接麦克风阵列。数字音频接口 DOUT 接 RK3399 I2S0 的 RXD 引脚因为采集方向是芯片输出、SoC 接收BCLK 和 LRCK 则由 RK3399 I2S0 提供ES7210 工作在从机模式。除此之外ES7210 还需要一路 MCLK 主时钟一般从 RK3399 的 codec 时钟或外部晶振获取。ES8156 的 I2C 控制线可以和 ES7210 挂在同一条 I2C 总线上只要地址不冲突就行。数字音频接口 DIN 接 RK3399 I2S0 的 TXD 引脚同样由 RK3399 提供 BCLK 和 LRCK。模拟输出接功放或耳机。reset 引脚通常由 GPIO 控制驱动 probe 的时候拉一下让芯片复位到默认状态。两路 codec 共用同一组 I2S0 时钟但数据方向不同播放走 TXD录音走 RXD互不干扰。这里有个很容易踩的坑如果 ES7210 和 ES8156 接到的是不同的 I2S 控制器注意下面的坑如果 ES7210 和 ES8156 接到的是不同的 I2S 控制器那么两边的 BCLK/LRCK 会由不同的时钟域产生录音和播放同时进行时容易出现时钟不同步的问题。对于 AEC 回声消除这类需要参考信号的应用这是致命的。所以尽量让两颗芯片挂在同一个 I2S 控制器的 TX 和 RX 上。2.2 设备树节点和 codec 驱动加载准备好硬件之后就开始改设备树。以 Linux 4.4 或 4.19 内核为例先在 I2C 节点下增加两颗 codeci2c4 { status okay; /* ADDR 引脚决定的 I2C 地址实际以 datasheet 为准 */ es7210: es721040 { compatible everest,es7210; reg 0x40; clocks cru SCLK_I2S0_8CH; clock-names mclk; assigned-clocks cru SCLK_I2S0_8CH; assigned-clock-rates 12288000; pinctrl-names default; }; es8156: es815648 { compatible everest,es8156; reg 0x48; clocks cru SCLK_I2S0_8CH; clock-names mclk; assigned-clocks cru SCLK_I2S0_8CH; assigned-clock-rates 12288000; }; };这里的 compatible 字符串要和驱动里的 of_match_table 严格对应ES7210 和 ES8156 两个驱动一般在内核里都注册成了标准 codec driverprobe 时读取 reg 得到 I2C 地址。时钟部分要根据硬件实际连接的 MCLK 频率来配如果接了 24.576MHz 晶振就配 24576000如果从 SoC 输出就配置对应的 clock-parent。然后要把 I2S0 节点配置成开启状态并确认引脚复用正确i2s0 { status okay; #sound-dai-cells 0; rockchip,clk-trcm 1; pinctrl-names default; pinctrl-0 i2s0_8ch_bus; };rockchip,clk-trcm 1这个属性很关键它的作用是让 TX 和 RX 使用同一组时钟不会出现录音一个时钟、播放一个时钟的错位问题。如果内核版本比较老这个属性可能叫别的名字需要看自己内核里的 I2S 控制器驱动支持哪些属性。2.3 machine driver 如何把两个 codec 串成一张声卡RK3399 要挂外部 codec通常还需要一个 machine driver 来定义声卡及 dai_link。为什么不像有些 i.MX 平台那样直接用 simple-audio-card因为 simple-audio-card 在单颗 codec 场景下好用ES7210 和 ES8156 是两颗芯片一个负责 capture、一个负责 playback用 simple-audio-card 描述起来非常别扭。实践中更可控的方案是自己写一个 platform machine driver或者在内核里找现成的对照模板比如 rockchip 平台常见rk3399_es7210_es8156.c这类文件里面核心要干三件事第一定义声卡名称比如 “RK_ES7210_ES8156”。这个名字会直接出现在 /proc/asound/cards 里Android HAL 层的 audio_platform_info.xml 也靠它做声卡匹配。第二定义两个 snd_soc_dai_link一个 playback 链路指向 i2s0 和 es8156一个 capture 链路指向 i2s0 和 es7210然后指定 init 函数和 ops在 init 里设置好 codec 的 DAI 格式I2S 还是 DSP/TDM 模式。第三配置 DAPM 路由把 codec 里的Mic Bias、ADC PGA、DAC这些 widget 和声卡层面的事件连接起来。这个不配的话可能出现“播放有数据但没声音”“录音有电平但全是噪音”这种看似诡异的问题。DAPM 路由本质上是在控制音频链路的电源开关链路没打通信号就出不来。整个 machine driver 写好后注册成 platform_driver这个声卡才算在内核里立住。3. 编译烧录与声卡底层验证3.1 内核配置和编译注意事项改完代码之后先把内核选项核对一遍。需要确认以下几个 config 都打开了I2S 控制器驱动一般是 CONFIG_SND_SOC_ROCKCHIP_I2S。ES7210 codec 驱动一般是 CONFIG_SND_SOC_ES7210。ES8156 codec 驱动一般是 CONFIG_SND_SOC_ES8156。machine driver不同类型的平台名字不一样常见的是 CONFIG_SND_SOC_ROCKCHIP_ES7210_ES8156。如果用 menuconfig 勾选位置在Device Drivers - Sound card support - Advanced Linux Sound Architecture - ALSA for SoC audio support下面。编译时注意交叉编译工具链要和内核版本匹配RK3399 一般是 aarch64 架构编译命令类似make ARCHarm64 rockchip_defconfig然后再make ARCHarm64 rk3399-xxx.img。烧录时只需要替换 boot 分区里的 kernel image不用重刷整个系统。不过有些方案把内核放在 resource 分区或单独 kernel 分区具体看项目烧录脚本。烧完之后先不急着进 Android开机后直接看串口或 adb shell 进到底层先确认cat /proc/asound/cards能不能看到新声卡。3.2 用 tinymix 检查 Audio 控件和路由通路声卡节点出来之后下一步就是看控件。执行tinymix不带参数会列出这张声卡上所有 kcontrol。常见的控件包括 ES7210 的ADC PGA Gain、MIC Bias、ADC MuxES8156 的DAC Volume、DAC Mux等等。第一次执行时不要急着去改先整体看一遍有哪些控件心里有个底。如果一个控件都没有说明 codec 驱动的 probe 或 DAPM widget 注册有问题优先查 dmesg。如果只有一半控件可能是两个 codec 中有一个没正常上电或者 I2C 读取失败。打开录音通路的基本操作是这样用 tinymix 把对应声卡的 Capture Mux 切到线路输入把该打开的 ADC PGA 设置成合适增益再把 MIC Bias 打开最后用tinymix Capture Switch 1确保不是静音状态。播放通路则要确认 DAC Mux 切到 I2S 输入DAC Volume 不要是 0所有涉及 muted 的开关都要打开。为了方便后续测试我通常会把常用的一组通路设置整理成一份 shell 脚本每次重启后在 adb shell 里执行一遍省得反复敲 tinymix 命令。3.3 底层录音播放tinyplay 和 tinycap 的正确用法底层通路验证优先级很高因为 Android 上层还没介入这个时候出问题基本都能确定是内核、设备树或驱动的问题。先用 tinyplay 播放一个 wav 文件tinyplay /data/test.wav -D card -d device-D指定声卡编号比如-D 0表示 card0-d指定设备编号一般 PCM 播放设备是 0。如果执行后没有报错且喇叭能出声说明播放链路从 I2S 到 DAC 到功放基本是通的。录音再用 tinycaptinycap /data/test_rec.wav -D card -d device -c 4 -r 16000 -b 16 -T 5这里-c 4表示录四个通道和 ES7210 的四路输入对应-r 16000是采样率-T 5是录 5 秒。录完的文件用 adb pull 拉到电脑上用 Audacity 或 Python 的 wave 模块直接看波形和频谱。我第一次测这套设备时tinycap 录出来的文件大小正常但四个通道全是整齐的直流偏置几乎没有有效信号。后来排查才发现是 MIC Bias 没开模拟麦克风根本没有工作点ADC 量化出来只有底噪和偏置。这类问题在底层用 tinymix 打开对应开关就能解决。3.4 采样率、位深和 TDM 通道对齐ES7210 是四通道 ADC在 TDM 模式下会占用四个 slot。而 ES8156 是双声道 DAC在 TDM 模式下默认占用两个 slot。如果两个 codec 挂在同一条 I2S 上需要保证下行数据发送到 ES8156 的 slot 和上行数据从 ES7210 读取的 slot 都正确配置。I2S 模式是另一种情况LRCK 高电平对应左声道低电平对应右声道ES7210 的通道 1 和 2 会分别映射到左右声道通道 3、4 则要继续通过扩展的 TDM slot 输出。实际操作中需要根据机器驱动里的snd_soc_dai_set_tdm_slot()配置来决定。比如让 ES8156 只接收 slot 0 和 1 的数据让 ES7210 在 slot 0 到 3 上输出四个通道。如果 slot 没对齐典型表现是播放左右声道声音一样但内容互换或者录音时通道错位1 通道声音录到了 3 通道上。还有位深问题。ES7210 和 ES8156 都支持 16/24/32bitAndroid 默认的路由配置经常是 16bit但底层机器驱动设置的 DAI format 可能是 24bit 或 32bit两边不匹配会出现音量异常、噪声、或者只有某几个通道有声音。建议底层调试阶段统一成 16bit先把通路跑通再逐步加位深。4. Android 层配置与上层功能验证4.1 声卡映射与 audio_policy 配置底层声卡通了之后Android HAL 不一定自动认识它。Android 在启动时会扫描 /proc/asound/cards并根据 HAL 库里的判断逻辑选择使用哪张声卡。大部分平台 HAL 会有固定的声卡优先策略比如audio_platform_info.xml里强制指定了某个 scard index 或 card name 给 speaker、mic 等设备使用。实际配置时先在/vendor/etc/或/system/etc/下找到audio_platform_info.xml把声卡名加进去让 HAL 知道这张卡对应的是哪种输出/输入设备audio_platform_info acdb_audio_device namespeaker card_nameRK_ES7210_ES8156/ /audio_platform_info如果平台 HAL 不是这种字符串匹配方式还需要在代码层面指定声卡索引那就得改 HAL 的源码。总之目标只有一个让上层播放/录音调用到新增声卡的 PCM 设备而不是拿这张卡当摆设。audio_policy_configuration.xml里的模块配置也需要过一遍。通常 HAL 的音频模块名是固定的比如primary、usb、a2dp如果你把声卡挂在 primary 模块下那么输出设备需要声明 mixPort、devicePort确保路由引擎能把 AudioTrack 的流导向这张卡的 playback PCM 设备录音也同理。一个常见问题声卡节点在 /proc/asound 里已经存在但dumpsys audio里看不到对应输出设备App 播放声音也没有实际输出。这种情况大概率是 policy 配置里没有把 HAL 模块的输出设备和该声卡关联起来或者 mixer_paths.xml 里没有对应声卡的 route 配置导致 HAL 打开 PCM 设备后没有设置必要的 kcontrol。4.2 mixer_paths.xml 和路由配置mixer_paths.xml是 Android 上音频路由最核心的配置文件。它定义了“在使用某个设备时需要把哪些 kcontrol 设置成什么值”。以 speaker 设备为例播放提示音时除了打开底层 PCM 流还要把 DAC 音量调到合理值、把 DAC Mux 切到正确的输入源、解除静音这些都可以在mixer_paths.xml的 path 里描述。path namespeaker节里可以写多个ctl nameXXX valueYYY /项每一项对应一个 tinymix 控件。配置好之后HAL 在打开输出设备时会自动按顺序设置这些控件不需要 App 关心底层细节。录音路径同理比如path namemic里面要设置 ADC PGA Gain、MIC Bias、Capture Mux 等。这套配置的好坏直接影响用户体验比如回声消除时要屏蔽某个麦克风、会议平板上要开启 AGC 还是关掉 AGC都靠这里管理。注意修改 mixer_paths.xml 后要重启 audioserver可以执行adb shell stop adb shell start或者直接重启系统否则 HAL 仍然持有旧的路由配置。4.3 App 层录音播放验证底层和 HAL 都配置完之后选一个自带的录音 App 或者简单写个小 Demo 来验证。Android 6.0 以上录音需要动态申请 RECORD_AUDIO 权限如果直接跑第三方测试 App 没有弹权限授权框很可能录出来的文件就是空的这和声卡本身没有关系。验证播放更简单随便用系统音乐播放器或者adb shell settings put system播放一个音频文件听声音是否正常、音量调节是否线性。我这里有个经验底层测试通过之后优先用一个自己写的简单 APK只做 AudioTrack 循环播放和 AudioRecord 采集方便定位问题在哪个调用层面。拿一个功能完整的商业 App 来测一旦没声音你可能分辨不清是权限问题、焦点问题、路由问题还是声卡本身问题。4.4 录音/播放同时进行的特殊验证这套 ES7210ES8156 方案如果只做单边测试很容易掩盖问题。远场语音产品比如会议音箱经常要同时录音和播放——人说话音箱放音乐算法要实时获取参考信号做回声消除。这时候必须验证一个关键场景播放音乐的同时启动录音录到的文件里能否同时包含用户的声音和播放的音乐参考。如果底层 I2S0 被配置成 TX 和 RX 使用不同时钟域或者声卡驱动里没有做 clock 同步播放和录制的采样率会漂移录出来的文件听感会有回声消除后残留的机器人声。这时检查 dmesg 里有没有clk mismatch相关报错确认设备树里rockchip,clk-trcm 1这个属性有没有真正生效。我在实际项目中还遇到过一个比较隐蔽的问题ES7210 的 MIC3、MIC4 在 TDM 模式下需要额外设置通道映射寄存器否则虽然四个通道都有数据但通道 3/4 数据其实是通道 1/2 的拷贝。排查方法就是分别对每路麦克风讲话看录下来的四个通道波形是否只有对应通道有明显信号。5. 问题排查与调试技巧实录5.1 声卡节点没出现 / I2C 探测失败开机后cat /proc/asound/cards看不到新增声卡是最常见的问题。第一步不要看驱动代码先查dmesg | grep -i es7210和dmesg | grep -i es8156看驱动有没有被 probe。如果是 I2C 探测失败一般会有类似es7210 4-0040: ASoC: failed to probe component的日志。这时候先用 i2cdetect 扫描 I2C 总线上有哪些设备i2cdetect -y 4如果总线上扫不到 0x40 或 0x48 地址说明硬件没接好或者 codec 没有正常上电。优先量一下芯片电源和 reset 引脚是否处于正常状态。如果 i2cdetect 能扫到但驱动 probe 还是失败再看设备树里 reg 是否写错以及 codec 驱动里是否有等待外部时钟稳定这类时序要求。有个容易忽略的细节我遇到过代码树上同时存在两个版本的 ES7210 驱动一个支持旧版寄存器映射一个支持新版默认编进内核的那个恰好和硬件版本不匹配导致读寄存器全是错误值。所以别只信 dmesg 的 probe 成功日志还要手动用 i2cget 读几个关键寄存器对比 datasheet 确认寄存器映射正确。5.2 播放没声音 / 声音异常播放链路无声的排查顺序我一般是这样的先确认 PCM 设备打开成功tinyplay 不报错。再用 tinymix 看 DAC 相关控件比如DAC Mute、DAC Volume确保不是驱动默认静音。然后量 BCLK、LRCK、MCLK 三路时钟确认 I2S 信号到达了 ES8156 引脚。如果时钟正常量 ES8156 的模拟输出看有没有波形。最后检查功放和喇叭接线。有杂音则复杂一些。一种常见杂音是 I2S 位宽不匹配导致的沙沙声比如驱动配置 32bit slots 而 ES8156 实际只认 24bit 有效数据。另一种是电源纹波耦合ES8156 的 AVDD 如果直接用的 DC-DC没有加足够的 LC 滤波耳机或喇叭里会有明显底噪。还有一种情况是 MCLK 频率和采样率不呈整数倍关系比如用 24.576MHz MCLK 跑 44.1kHz 采样率时钟没法整除就会出现周期性爆音。5.3 录音静音 / 声音小 / 通道错位录音有问题先区分是硬件还是软件。最简单的方法拿信号发生器或手机播放一个 1kHz 正弦波靠近麦克风用 tinycap 录音拉到电脑上看频谱和波形。如果完全没波形优先查 MIC Bias 和 ADC 是否开启。ES7210 的 MICBIAS 如果不打开驻极体麦克风是没有任何输出的。如果波形存在但幅度极小把 PGA 增益调大几档再看同时注意不要调到接近饱和失真的位置。通道错位是 TDM 配置问题的高发区。ES7210 四个通道对应哪些 slotES8156 播放数据对应哪些 slot需要根据机器驱动或 codec 驱动里实际设置的参数去核对。如果驱动用的是默认 TDM slot 0-3而 ES7210 硬件接线上第一个麦克风从第四通道进来那录出来的人声就会跑到第三个或第四个通道上。5.4 调试工具和排查套路总结针对 RK3399 Android 音频调试我习惯准备一套工具组合i2cdetect和i2cget查 I2C 设备是否在线、寄存器值是否合理。tinymix查所有 kcontrol手动设置控件来验证某段通路。tinyplay和tinycap绕过 HAL 直接操作 ALSA 设备验证底层通路。cat /proc/asound/cards和cat /proc/asound/pcm看声卡注册情况和 PCM 设备列表。dmesg看内核驱动报错。dumpsys media.audio_flingerAndroid 层音频流和设备状态。示波器/逻辑分析仪量 MCLK、BCLK、LRCK、I2S_DATA是定位硬件问题最直接的手段。调试顺序也有讲究先底层后上层先单端后同时。底层 tinyplay 能出声、tinycap 能录到清晰信号之后再去改 Android HAL 配置否则一旦上层有问题你会上下两层来回猜。还有一个习惯我强烈建议每次改完设备树或驱动先抓一份 dmesg 完整保存下来改之前和改之后对比很多问题就是靠这种对比日志快速定位的。尤其是涉及到 codec 驱动 probe 时序、时钟通知链、DAPM 上下电这些环节光看含义模糊的报错根本猜不出是哪里出了问题。几点个人体会这次 RK3399 Android 平台增加 ES7210ES8156 声卡的移植任务从底层 I2C 探测、设备树修改、machine driver 编写到 Android 层音频策略配置和 App 功能验证整个流程走下来最大的感受是这种多 codec 方案比单颗 codec 复杂一个量级并不是“把自己驱动编进去就完事”而是要保证从 codec 引脚到 Android 录音 App 的整条链路每个环节都对齐。底层工具和 dmesg 日志是最可靠的伙伴遇到问题先在 ALSA 层验证别急着改上层。最后再分享一个小技巧如果你要适配的平台上自制 machine driver 从零写比较费劲先在内核里搜一下有没有现成的sound/soc/rockchip/rk3399_es7210_es8156.c或类似文件很多 SDK 里已经带了这套模板基于现成文件改 slot 配置、改引脚、改声卡名比自己从 snd_soc_dai_link 开始写要稳得多。等整个链路跑通之后回头再看这套方案的设计逻辑就会明白为什么厂商要拿两颗独立芯片来做采集和回放了。
返回列表