麦克风与音频采集:I2S 是怎么回事
- 搞懂为什么语音 AI 硬件几乎不用普通模拟麦,而用 I2S 数字麦
- 弄清 I2S 是什么、它和 I2C 完全不是一回事
- 看懂 I2S 数字麦(如 INMP441)和功放(如 MAX98357)怎么接到 ESP32
- 看懂一段读麦克风数据的配置思路,知道哪些引脚和参数要你自己核
你已经看懂了硬件接大模型那条链路:唤醒 → ASR → LLM → TTS。但那一节是站在"文字"这一层讲的——板子发文字、拿文字。真要做一个会听会说的东西,绕不开最物理的一关:声音怎么进到芯片里,又怎么从芯片出去。这就是这一节的事。
把它想成给硬件装"耳朵和嘴"。耳朵负责把空气里的振动变成芯片能处理的数据,嘴负责把数据变回能听见的声音。这两件事,在 AI 硬件里几乎都走同一条总线:I2S。先把它讲透,后面接麦克风、接喇叭才不会一头雾水。
为什么不用那种最常见的模拟麦克风
你大概见过那种三只脚的小麦克风模块(驻极体 + 一个 MAX9814 之类的放大芯片),输出一根模拟电压线。它便宜、好接,但做语音 AI 基本不选它。原因就两点,但都很要命。
- 信噪比差。模拟信号在杜邦线上走一段,就会蹭上各种电噪声——电源纹波、Wi-Fi 干扰、板子上其他芯片的串扰。声音本身才几十毫伏,噪声一上来,人耳能忍,但 ASR 模型会被搞糊涂。
- 多一道模数转换,多一处损失。模拟麦的电压最后还得喂给 ESP32 的 ADC 转成数字。ESP32 自带的 ADC 精度和线性度都很一般,转出来的音频质量经不起细看。
数字麦克风(I2S MEMS 麦,典型如 INMP441)把"采集 + 模数转换"做进了麦克风芯片内部,直接吐数字信号。好处是直接的:
- 转换在麦克风壳子里就完成了,模拟段极短,几乎不给噪声可乘之机。
- 出来就是数字,沿着 I2S 走多远都不失真——传的是 0 和 1,不是怕干扰的电压。
- 接线干净、量产一致性好,这也是为什么市面上的语音模组、智能音箱基本清一色用它。
一句话:做语音 AI,从一开始就用 I2S 数字麦,别在模拟麦上浪费时间。 省下的调试功夫,够你把后面三步链路调通了。
I2S 到底是什么(顺便澄清:它不是 I2C)
I2S 全称 Inter-IC Sound,是一条专门传数字音频流的总线。注意它和 I2C 只差一个字母,但完全是两码事,别搞混:
- I2C 是给传感器、屏幕这类设备发"读写寄存器"的小命令的,一问一答、数据量小、慢。
- I2S 是给音频流量身定的,要的是连续、稳定、按固定节拍把一个个采样点不停地搬运——音频是个永不停歇的数据流,慢一拍就卡顿。
I2S 一般用三根信号线,名字你会反复见到,先记住它们各管什么:
- SCK / BCLK(位时钟):每传一个二进制位就跳一下,是整条总线的节拍器。
- WS / LRCLK(字/声道选择):标记现在传的是左声道还是右声道。它跳一个完整周期,就对应一个采样点。
- SD / DOUT / DIN(数据线):真正的音频数据在这根线上走。麦克风往外吐叫 SD/DOUT,喇叭功放往里收叫 DIN。
理解这三根的分工,下面接线就不是死记,而是"知道每根线在干嘛"。
接线示意:麦克风进、喇叭出
I2S MEMS 麦克风和功放都对供电敏感,务必按模块标注的电压供电(INMP441 多为 3.3V,喇叭功放有的要 5V)。接错电压或正负接反,轻则不工作、重则烧片。上电前对着模块丝印再核一遍 VDD/GND,别凭印象插。
下面的引脚是示意,具体脚号以你手上的模块丝印和 ESP32 官方文档为准——不同开发板、不同模组的可用引脚不一样,下面的 GPIO 号是占位,照搬不一定对。
输入:INMP441 数字麦 → ESP32
INMP441 ESP32(引脚号为占位,需按你的板子核对)
VDD ─────────── 3.3V
GND ─────────── GND
SCK ─────────── GPIO_xx (I2S 位时钟 BCLK)
WS ─────────── GPIO_xx (I2S 字选择 LRCLK)
SD ─────────── GPIO_xx (I2S 数据,麦克风往外吐)
L/R ─────────── GND (接地=用左声道,接 VDD=右声道)
输出:MAX98357 I2S 功放 → 喇叭
MAX98357 ESP32
VIN ─────────── 5V(看模块标注,有的 3.3V 也行)
GND ─────────── GND
BCLK ─────────── GPIO_xx (位时钟,可和麦共用,也可独立)
LRC ─────────── GPIO_xx (字选择)
DIN ─────────── GPIO_xx (I2S 数据,往功放里送)
+/- ─────────── 喇叭两根线
麦克风的 L/R 脚决定它占左还是右声道,接地通常就走左声道、单声道采集足够。功放这边 DIN 是"进",方向和麦克风的 SD"出"正好相反——一个往芯片送数据,一个从芯片要数据。
示意代码:配置 I2S 读麦克风的思路
下面这段是参考实现,不是复制就能跑的成品。它演示的是"怎么把 I2S 配成接收模式、然后读出一段音频"的骨架。引脚号、API 细节请以 ESP-IDF 官方 I2S 文档和你用的库版本为准——ESP-IDF 不同版本的 I2S API 改过,新旧写法不一样,照抄旧教程很容易对不上。
// 示意:配置 I2S 为接收(读麦克风),思路演示,引脚和 API 需按你的板子/库核对
#include <driver/i2s.h> // 注意:新版 ESP-IDF 用 driver/i2s_std.h,API 不同
#define I2S_WS GPIO_xx // 占位:字选择(LRCLK),换成你接的脚
#define I2S_SCK GPIO_xx // 占位:位时钟(BCLK)
#define I2S_SD GPIO_xx // 占位:数据线(麦克风的 SD)
void setupMic() {
i2s_config_t cfg = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), // 主机、接收
.sample_rate = 16000, // 采样率,语音常用 16kHz
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // 位深,下面解释
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 只取左声道(对齐 L/R 接地)
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.dma_buf_count = 4, // DMA 缓冲块数
.dma_buf_len = 256 // 每块采样数
};
i2s_pin_config_t pins = {
.bck_io_num = I2S_SCK,
.ws_io_num = I2S_WS,
.data_out_num = I2S_PIN_NO_CHANGE, // 不发,只收
.data_in_num = I2S_SD // 数据从这根脚进来
};
i2s_driver_install(I2S_NUM_0, &cfg, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pins);
}
void readMic() {
int32_t buf[256]; // INMP441 每个采样是 24/32 位,用 int32 存
size_t bytesRead = 0;
i2s_read(I2S_NUM_0, buf, sizeof(buf), &bytesRead, portMAX_DELAY);
// buf 里就是一段原始音频采样了。下一步:算电平、编码、或上传 ASR
}
这段只解决"把声音读进来"。真正把麦克风、喇叭、唤醒、ASR/TTS 串成一个能用的语音助手,是整套工程的事——完整可跑的实现见实战项目里的小智旗舰,那里有正经的引脚定义、流式处理和内存管理。别指望这一段骨架能直接长成一个会聊天的音箱。
采样率和位深:两个绕不开的概念
读音频前,你得先告诉 I2S "按多细的颗粒度采"。两个参数决定这件事,配错了声音就不对。
- 采样率:一秒钟采多少个点,单位赫兹。语音识别通常 16kHz 就够(人声主要能量在 8kHz 以下,按采样定理 16kHz 能覆盖)。音乐才需要 44.1kHz 这种高采样。采样率越高,数据量越大、越占内存和带宽——做语音别盲目堆高,16kHz 是甜点。
- 位深:每个采样点用多少位来表示振幅,决定动态范围(最响和最轻之间能分多少档)。16 位是语音的常用值。注意一个坑:INMP441 实际吐的是 24 或 32 位的数据,但有效位往往在高位,你常常要把读到的
int32右移若干位才拿到真正的振幅——这一步漏了,声音会要么爆音、要么几乎听不见。
记住一组好用的起步值:16kHz 采样、16 位有效位、单声道。语音场景下这套又省内存又够用,先用它跑通,有特殊需求再调。
为什么要 OPUS 编码
读进来的原始音频(PCM)很占地方:16kHz、16 位、单声道,一秒就是 32KB。你要把它流式传给云端 ASR,这个体积扛不住——ESP32 内存小、上行带宽也不富裕。
OPUS 是一种为语音和实时传输设计的音频压缩格式,能把音频压到原来的几分之一甚至更小,而且专为低延迟、抗丢包优化,正合"边录边传"的语音链路。所以正经的语音硬件里,麦克风读出的 PCM 通常先 OPUS 编码、再上传,到了服务端再解码识别;TTS 回来的音频也常是压缩的,板子解码后才送进功放。
这一步属于"把音频接进网络链路"的环节,细节(编码库、流式打包、和 ASR 服务对接)放到语音识别与合成那节专门讲。这里你只要建立一个意识:原始音频不直接上云,中间要压一道,OPUS 是语音场景的默认选择。
故障排查:声音不对,按这个查
第一次接 I2S 麦克风,听不到声音或一片噪声是常态。照这张表查:
| 现象 | 最可能的原因 | 怎么办 |
|---|---|---|
| 完全没数据 / 全是 0 | 接线错或供电没到 | 核对 VDD/GND、三根信号线是否接到你代码里配的脚 |
| 读到数据但全是噪声 | 位深没右移,或 L/R 脚没接 | INMP441 要把 int32 右移取有效位;确认 L/R 脚接了地或电源 |
| 声音很轻、几乎听不见 | 有效位取错,或增益问题 | 检查右移位数;离麦克风近点测试 |
| 声音断断续续 / 卡顿 | DMA 缓冲太小或读取跟不上 | 调大 dma_buf_count/dma_buf_len,确保读取循环够快 |
| 喇叭无声 | 功放供电/DIN 方向错 | 功放是 DIN(进),别接成 DOUT;核对功放供电电压 |
| 编译报 I2S 函数未定义 | ESP-IDF 版本 API 不匹配 | 新版用 i2s_std.h 的新 API,对照你的版本文档改 |
排查总原则和调网络一样:一次只动一个变量。先确认有数据进来,再看数据对不对,最后才管编码和上传。
动手挑战
别只看,把"耳朵"亲手装上:
- 先证明声音进来了:按上面的思路配好 I2S 接收,在
readMic()里把读到的采样取绝对值求个平均,当作"音量电平"用串口打印出来。对着麦克风说话、拍手,看数字会不会跳——能跳,就说明声音真进到芯片里了。 - 再做个简易电平条:把那个电平值映射成串口里打印的一串
#号(越响越长),相当于一个文字版的音量表。这一步跑通,你就把"采集"这一环彻底攥在手里了。
卡住了?把你的 I2S 配置代码、串口打印的数据、还有你用的模块型号一起发给 AI,让它帮你对引脚、查右移位数。
小结 · 你现在掌握了什么
- 你知道了语音 AI 为什么用 I2S 数字麦(如 INMP441)而不是模拟麦:信噪比和数字化这两笔账。
- 你能说清 I2S 是什么、三根信号线(SCK/WS/SD)各管什么,以及它和 I2C 完全不是一回事。
- 你看懂了麦克风进、喇叭出怎么接,以及读 I2S 数据的配置骨架——并知道引脚和 API 要按自己的板子核。
- 你建立了采样率、位深、OPUS 编码这几个概念,知道语音场景该用哪组起步值。
这是给硬件装"耳朵和嘴"的第一步。把采集这一环跑通,AI 硬件那条语音链路你就握住了最物理的源头。完整工程怎么落地,去 L4 这一级 往下走、或直接拆实战项目。
下一步:声音能进来了,但板子不能一直把每句话都上传——它得先听到那句"你好"才醒来。去学唤醒词,给你的硬件装上一个永远在线、却只为暗号睁眼的"耳朵开关"。