4、多路音频采集:多路麦克风阵列同步采集、回声参考信号采集、A2B总线音频采集
好,咱们接着聊音频采集这块。说实话,在车载音频系统里,采集往往比播放更让人头疼。播放嘛,数据流是单向的,你只管往DAC里喂数据就行。但采集不一样——你得同时伺候好几路信号,还要保证它们之间的同步关系。我当年第一次调MTK8676的多麦阵列时,就被同步问题折腾得够呛。
4.1 多路麦克风阵列同步采集
先说说麦克风阵列。现在的智能座舱,少说4个麦,多的能到8个甚至12个。这些麦要用来做语音唤醒、声源定位、降噪处理。你想想看,如果每个麦采集的时间点对不齐,那声源定位就全乱套了。
MTK8676内部有多个I2S接口,每个接口可以挂载2个麦克风(左右声道)。我个人习惯把麦克风分成两组:
- 前排阵列:通常4个麦,挂在I2S0和I2S1上
- 后排阵列:通常2-4个麦,挂在I2S2上
关键点来了——同步。MTK8676的I2S模块支持主从模式。我建议把所有麦克风接口都配置成从模式,统一由芯片内部的一个主时钟驱动。这样所有I2S的LRCLK和BCLK都是同源的,采样点自然就对齐了。
核心配置要点:
- 所有I2S接口使用同一个MCLK(主时钟)
- LRCLK频率必须一致(比如48kHz)
- BCLK频率根据采样率和位深计算(48kHz×32bit×2ch = 3.072MHz)
- 启用硬件FIFO,避免DMA传输抖动
代码层面,我一般这样配置I2S采集接口:
// 以I2S0为例,配置为从模式、48kHz、32bit
struct mtk_i2s_config i2s_cfg;
i2s_cfg.mode = I2S_MODE_SLAVE;
i2s_cfg.sample_rate = 48000;
i2s_cfg.data_width = 32;
i2s_cfg.channels = 2;
i2s_cfg.mclk_div = 256; // MCLK = 48k * 256 = 12.288MHz
mtk_i2s_init(I2S_PORT_0, &i2s_cfg);
mtk_i2s_init(I2S_PORT_1, &i2s_cfg); // 同样的配置
嗯,这里要注意一个坑。我曾经遇到过一个问题:明明所有I2S配置都一样,但采集到的数据就是有几十个sample的偏移。后来发现是PCB走线长度不一致导致的。解决办法是在驱动层做软件对齐——采集到数据后,先做互相关运算,找到偏移量,然后统一裁剪对齐。
我的经验:如果硬件走线无法保证等长,可以在DMA中断里加一个同步标记。每次中断时记录一个时间戳,然后根据时间戳对齐各路数据。这个方法虽然多消耗一点CPU,但效果很稳定。
4.2 回声参考信号采集
回声消除(AEC)是车载语音的另一个老大难问题。说白了,就是喇叭放出来的声音又被麦克风收进去了,导致语音识别出错。要解决这个问题,除了算法本身,采集端必须提供回声参考信号。
回声参考信号是什么?就是喇叭实际播放出去的音频数据。注意,不是你想播放的数据,而是经过DAC、功放、喇叭之后真正发出来的声音。但实际项目中我们没法在喇叭端挂一个采集探头,所以通常的做法是:
- 数字参考:直接从音频DSP的播放路径上截取数据
- 模拟参考:在功放输出端加一个衰减电路,再送回ADC采集
MTK8676的方案更倾向于数字参考。芯片内部有一个回环通路,可以把播放数据直接路由到采集路径上。配置起来很简单:
// 启用回声参考通路
mtk_audio_loopback_enable(LOOPBACK_SRC_PLAYBACK, LOOPBACK_DST_CAPTURE);
// 设置参考信号增益(避免饱和)
mtk_audio_loopback_set_gain(-6); // -6dB
但这里有个细节——延迟匹配。回声参考信号和麦克风采集信号之间会有延迟差。为什么?因为参考信号走的是数字通路,而麦克风信号经过了ADC、模拟前端等环节。我建议在驱动层做一个延迟补偿:
// 估算麦克风路径延迟(单位:sample)
int mic_delay = 48; // 48kHz下约1ms
int ref_delay = 8; // 数字回环延迟较小
// 在采集数据时,将参考信号延迟对齐
audio_buffer_align(ref_buf, mic_buf, ref_delay - mic_delay);
注意:延迟补偿的精度直接影响AEC效果。我曾经在一个项目里因为延迟差了2个sample,导致回声消除后还有残留。后来用扫频信号实测了延迟,才把问题解决。建议量产前用标准测试信号验证延迟值。
4.3 A2B总线音频采集
最后说说A2B。A2B(Automotive Audio Bus)是汽车音频的专用总线,用一根双绞线就能传多路音频和控制信号。现在很多高端座舱会在后排、头枕、低音炮等位置挂A2B节点。
MTK8676本身没有原生A2B接口,但可以通过I2S转A2B桥接芯片来扩展。常用的桥接芯片有AD2428、AD2435等。我一般这样连接:
- MTK8676的I2S3作为A2B主节点接口
- 桥接芯片配置为I2S从模式
- A2B总线上的子节点(如后排麦克风)通过总线供电和传数据
采集流程是这样的:
- A2B主节点通过I2S接口接收MTK8676的时钟
- 主节点轮询各个子节点,收集音频数据
- 数据通过I2S回传给MTK8676
- MTK8676的驱动层解析数据,按节点ID分发到不同音频通道
代码层面,A2B的配置通常由桥接芯片的驱动完成。MTK这边只需要配置好I2S接口:
// 配置I2S3用于A2B数据交互
struct mtk_i2s_config a2b_i2s_cfg;
a2b_i2s_cfg.mode = I2S_MODE_MASTER; // MTK做主,提供时钟
a2b_i2s_cfg.sample_rate = 48000;
a2b_i2s_cfg.data_width = 32;
a2b_i2s_cfg.channels = 4; // A2B可以传4路音频
mtk_i2s_init(I2S_PORT_3, &a2b_i2s_cfg);
A2B采集的关键点:
- 同步:A2B总线有自己的同步机制,但需要和MTK的I2S时钟对齐。我建议用MTK的MCLK作为A2B主节点的参考时钟。
- 带宽:A2B总线的带宽有限,48kHz/32bit下最多传16路音频。别超了。
- 供电:A2B总线可以给子节点供电,但要注意总功耗。我遇到过子节点太多导致电压跌落的问题。
说实话,A2B这块我踩过不少坑。有一次调试后排麦克风,死活采集不到数据。查了半天,发现是A2B主节点的固件版本不对,导致子节点枚举失败。从那以后,我每次做A2B项目都会先确认桥接芯片的固件版本。
避坑指南:A2B总线的子节点地址不能冲突。我曾经因为两个节点用了相同地址,导致数据错乱。建议在系统启动时做一次地址扫描,确保所有节点地址唯一。
好了,多路音频采集这块就聊到这儿。总结一下:麦克风阵列要同步,回声参考要补偿延迟,A2B要关注时钟对齐和节点管理。下一章咱们聊聊音频路由和混音的具体实现。