【ESP32笔记】I2S的音频录制与播放

本节目标:分清 I2S 的三根信号线和三种时序模式,理解 PCM 与 PDM 的区别,能独立看懂 ESP-IDF 5.x 的「PDM 麦克风接收 + 标准 I2S 发送」代码,完成录音与回放实验

一、本节要解决的问题

前面几节打交道的都是「慢变量」:按键、温度、超声波,几百毫秒读一次完全够用。声音不行------要还原一段人声,每秒至少得采几千次,而且采样间隔必须均匀,差一个节拍就会失真。如果把这件事交给 CPU 用 ADC 一点点采,既要保证节拍,又要同时把数据喂给喇叭,很容易顾此失彼。

I2S 就是为音频设计的硬件串行总线:外设自己按位时钟搬数据,CPU 只需要准备一块内存。再配上数字麦克风和数字功放,整条链路可以做到没有一个模拟环节:

环节 模拟方案 I2S 数字方案
麦克风 模拟麦克风 + ADC 连续采样 PDM 数字麦克风,直接输出 1 bit 流
传输 模拟电压走线,容易串扰 三根数字线,抗干扰
功放 DAC + 模拟功放 数字功放芯片内置 DAC
CPU 负担 定时器 + ADC 中断逐点采样 DMA 搬完一块再处理

本节的两个例程是这条链路的两种落地方式:

  • audio_record:录 10 秒,存进内部 Flash 的 SPIFFS 分区(/spiffs/record.pcm),然后回放。
  • audio_record_sdcard:录 10 秒,按 WAV 格式存进 SD 卡(/sdcard/record.wav),然后回放。WAV 带文件头,拔下卡插电脑就能直接播。

二、硬件基础

2.1 引脚连接

用途 信号 GPIO 对端
I2S 发送 BCLK GPIO46 功放芯片位时钟
I2S 发送 LRCLK / WS GPIO9 功放芯片声道选择
I2S 发送 SDOUT GPIO8 功放芯片数据输入
PDM 接收 PDM_CLK GPIO3 麦克风时钟
PDM 接收 PDM_DATA GPIO42 麦克风数据
IO 扩展 I2C SDA / SCL GPIO10 / GPIO11 XL9555,地址 0x20
SD 卡 CS / MOSI / MISO / SCK GPIO4 / 5 / 7 / 6 TF 卡座(SPI 模式)

几个容易忽略的点:

  • 功放使能不在 SoC 上 。喇叭使能脚接在 XL9555 的 IO0_0 上,必须先把 XL9555 配置成输出再拉高,功放才工作。代码里 xl9555_ioconfig((~(SPK_EN_IO)) & 0xFFFF) 把除 IO0_0 外的引脚全设为输入,只有 IO0_0 是输出。
  • 没有用 MCLK 。i2s_std_config_t 里 .mclk = I2S_GPIO_UNUSED;功放只需要 BCLK、WS、DATA 三根线,主时钟由芯片内部自己恢复。
  • 麦克风只有两根线。PDM 麦克风不接收采样率参数,它只跟着 CLK 的节拍把 1 bit 数据推上 DATA;采样在麦克风内部完成。
  • I2S0 和 I2S1 分工 。ESP32-S3 的 PDM 模式只有 I2S0 支持,所以麦克风固定接 I2S_NUM_0;喇叭用标准模式,接 I2S_NUM_1。两个通道相互独立,可以同时工作。

三、知识点

3.1 三根信号线

信号 全称 作用
BCLK Bit Clock 位时钟,每个边沿搬一位
LRCLK Left/Right Clock,也叫 WS 声道选择,频率等于采样率
SD Serial Data 数据线,MSB 先行
MCLK Master Clock 主时钟,本实验不用
  • 主机(Master)负责产生 BCLK 和 LRCLK,从机(Slave)跟着节拍收发。例程里 ESP32-S3 是主机,麦克风和功放都是从机。
  • 一帧 = 一个采样周期(1/fs)。标准 I2S 的一帧固定包含左、右两个槽,单声道时只有一个槽有有效数据,另一个槽补 0。
  • 帧内位数 = 位宽 × 槽数。例如 16 bit × 2 槽 = 每帧 32 个 BCLK。

3.2 位宽与位深

课件用一张 32 bit 的时序图区分这两个概念:

  • 位宽(word length):传输时每个样本占用的时钟位数,属于接口设计参数,常见 16/24/32。
  • 位深(bit depth):样本量化后的有效精度,属于数据本身的参数。
  • 位宽大于位深时,多出来的低位补 0。例如位宽 32、位深 24:前 24 位是有效数据,后 8 位是 0。

注意:位深决定动态范围,位宽只决定传输时间。用 32 位宽传 16 位深的音频不会更清晰,只是每帧多花了一倍时钟。

3.3 三种时序模式

模式 ESP-IDF 宏 LRCLK 电平 数据延迟 对齐方式
标准(飞利浦) I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG 低=左,高=右 1 个 BCLK MSB 比 LRCLK 跳变晚 1 个 BCLK
左对齐 I2S_STD_MSB_SLOT_DEFAULT_CONFIG 高=左,低=右 无 MSB 与 LRCLK 跳变沿对齐
右对齐 I2S_STD_LSB_SLOT_DEFAULT_CONFIG 高=左,低=右 无 LSB 与 LRCLK 跳变沿对齐,高位补 0

补充说明:

  • 三种模式都是「BCLK 下降沿发送、上升沿采样」,差别只在 LRCLK 极性和数据相对帧边界的位置。
  • 发送方和接收方必须约定同一种模式,否则听到的是噪声或错位的声道。
  • 本实验的功放芯片按标准飞利浦模式接收,所以代码用 I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG。

3.4 采样率

  • 采样率 = 每秒钟采多少个点,常见 8 kHz、16 kHz、44.1 kHz、44 kHz、48 kHz。
  • LRCLK 的频率就是采样率:一帧对应一个采样点。
  • 采样率越高越接近原声,数据量也线性增长。语音用 16k~24k 足够,音乐要 44.1k/48k。
  • 奈奎斯特采样定理:采样率至少是信号最高频率的 2 倍。人声主要能量在 4 kHz 以下,8 kHz 采样就能听懂,24 kHz 更自然。
  • 例程取 24 kHz:语音清晰度和文件大小折中,10 秒单声道 16 bit 约 480 KB。

3.5 PCM 脉冲编码调制

课件给的链路是:麦克风 → PCM 调制 → 主控 → DAC → 喇叭。PCM 分三个步骤:

  1. 采样:按固定时间间隔记录模拟电压。
  2. 量化:把每个样本的幅度近似到最近的量化级。
  3. 编码:把量化级写成二进制。
  • 采样率决定时间分辨率,采样位数(位深)决定幅度分辨率。
  • PCM 是未经压缩的原始音频,文件里只有样本值,没有格式说明。播放时必须知道采样率、位宽、声道数,否则速度或音调会不对。
  • WAV 就是「44 字节文件头 + PCM 数据」:文件头把采样率、位宽、声道数写在最前面,播放器先读头再解码。

3.6 PDM 脉冲密度调制

  • PDM 只用 1 bit 表示幅度,用「1 的密度」表达大小:幅度越大,1 越密;静音时输出连续的 0 或 1 的平衡点。
  • 调制方法是 Δ-Σ(delta-sigma);解调时做低通/抽取滤波,把 1 bit 流还原成 PCM。
  • 优点:麦克风里只需要一个 1 bit 调制器,成本低、抗干扰;缺点是数据率极高(时钟常在 1~3 MHz),必须靠硬件抽取。
  • ESP32-S3 的 I2S 外设在 PDM 接收模式下内置抽取滤波器,配置好采样率后读出来就是 16 bit PCM,CPU 不参与解调。
  • PDM 的 DATA 线上是左右声道交替的 1 bit 流;单声道麦克风靠 SEL 引脚决定自己占左槽还是右槽,代码用 slot_mask 选择。

3.7 实验功能框图

课件最后一页给出的录音实验流程,用文字展开就是:

复制代码
PDM 麦克风 --PDM_CLK/PDM_DATA--> ESP32-S3 I2S0(PDM RX) --PCM--> 写文件
                                                                  |
                                          SPIFFS(内部 Flash) 或 SD 卡(WAV)
                                                                  |
喇叭 <--功放芯片<--I2S1(标准 TX)<--PCM 数据<--读文件

录音和回放是两个独立方向:录音走 I2S0 的 PDM 接收,回放走 I2S1 的标准发送。两个方向共用同一份 PCM 数据格式(16 bit、24 kHz、单声道),所以存下来的数据可以直接拿去播放。

四、代码分析

4.1 工程结构

复制代码
audio_record/
├── CMakeLists.txt
├── partitions_audio.csv      # 8 MB 的 audio(SPIFFS) 分区
├── sdkconfig                 # 目标 esp32s3,16 MB Flash,八线 PSRAM
└── main/
    ├── audio_record.c        # app_main + 录音/回放流程
    ├── nocodec_audio.c/.h    # I2S 初始化与读写封装
    └── xl9555.c/.h           # IO 扩展芯片驱动

audio_record_sdcard 的结构类似,多一个 format_wav.h(WAV 文件头结构体和宏),少一个 SPIFFS 分区表。

partitions_audio.csv 的内容:

复制代码
# Name,   Type, SubType, Offset,  Size, Flags
nvs,      data, nvs,     ,        0x6000,
phy_init, data, phy,     ,        0x1000,
factory,  app,  factory, ,        1M,
audio,    data, spiffs,  ,        8M,

应用分区只有 1 MB,剩下 8 MB 全给 SPIFFS 存录音。sdkconfig 里 CONFIG_PARTITION_TABLE_CUSTOM=y 指定了这张表。

4.2 引脚定义与主流程

c 复制代码
//I2S 主时钟
#define I2S_BCLK    GPIO_NUM_46
//I2S 声道选择线
#define I2S_WS      GPIO_NUM_9
//I2S 数据线
#define I2S_SDOUT   GPIO_NUM_8
//功放芯片使能引脚对应XL9555的IO0_0
#define SPK_EN_IO   IO0_0
//PDM麦克风的CLK
#define PDM_CLK     GPIO_NUM_3
//PDM麦克风数据引脚
#define PDM_DATA    GPIO_NUM_42
//采样率
#define SAMPLE_RATE     24000

app_main 的执行顺序是:配置 IO 扩展芯片 → 初始化喇叭 → 初始化麦克风 → 挂载文件系统 → 录音 10 秒 → 开功放 → 回放 → 关功放。

c 复制代码
void app_main(void)
{
    //XL9555初始化
    xl9555_init(XL9555_SDA,XL9555_SCL,GPIO_NUM_NC,NULL);
    //把除了IO0_0引脚外,都设置为输入
    xl9555_ioconfig((~(SPK_EN_IO))&0xFFFF);
    //初始化喇叭
    init_speaker(I2S_BCLK,I2S_WS,I2S_SDOUT,24000);
    //初始化PDM麦克风
    init_pdm_microphone(PDM_DATA,PDM_CLK,24000);
    //初始化spiff文件系统
    audio_spiffs_init();
    //开始录音10秒
    start_record(10);
    xl9555_pin_write(SPK_EN_IO,1);
    vTaskDelay(pdMS_TO_TICKS(100));
    //开始播放
    start_play();
    xl9555_pin_write(SPK_EN_IO,0);
    while(1)
    {
        vTaskDelay(pdMS_TO_TICKS(5000));
    }
}

注意 :init_speaker 和 init_pdm_microphone 的第 4 个参数直接写了 24000,而不是宏 SAMPLE_RATE。改采样率时如果只改宏,这两处还是 24 kHz,录音和回放就会不一致。这是一个复制代码时留下的坑。

4.3 喇叭:标准 I2S 发送

c 复制代码
void init_speaker(gpio_num_t bclk,gpio_num_t ws,gpio_num_t dout,uint32_t sample_rate)
{
    i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER);
    chan_cfg.auto_clear_after_cb = true;
    ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));
    i2s_std_config_t i2s_rx_cfg ={
        .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate),
        .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
        .gpio_cfg = {
            .mclk = I2S_GPIO_UNUSED,
            .bclk = bclk,
            .ws = ws,
            .dout = dout,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false,
            },
        },
    };
    i2s_rx_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_RIGHT;
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &i2s_rx_cfg));
    ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));
}

逐项解释:

  1. I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER):用通道 1、主机模式,其余参数(DMA 描述符数量、每帧字数)用默认值。
  2. auto_clear_after_cb = true:DMA 缓冲区下溢时自动填 0。回放结束或数据没跟上时,不会把旧数据反复送出去,避免「卡带」一样的杂音。
  3. i2s_new_channel(&chan_cfg, &tx_handle, NULL):第二个参数是发送句柄,第三个是接收句柄。只发送,接收传 NULL。
  4. I2S_STD_CLK_DEFAULT_CONFIG(sample_rate):时钟源默认用 PLL_160M,mclk_multiple 取 256,按采样率自动算分频。
  5. I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(16BIT, MONO):16 位、单声道、飞利浦标准时序。
  6. slot_mask = I2S_STD_SLOT_RIGHT:单声道时只把数据放在右槽,左槽补 0。
  7. .mclk = I2S_GPIO_UNUSED:功放不需要主时钟,省一根线。

代码痕迹 :这个函数里保存配置的变量叫 i2s_rx_cfg,其实是发送配置,是从接收例程复制过来的;不影响功能,但读代码时别被名字误导。

4.4 麦克风:PDM 接收

c 复制代码
void init_pdm_microphone(gpio_num_t dat,gpio_num_t clk,uint32_t sample_rate)
{
    i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
    ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_handle));

    i2s_pdm_rx_config_t pdm_rx_cfg = {
        .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(sample_rate),
        /* The default mono slot is the left slot (whose 'select pin' of the PDM microphone is pulled down) */
        .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
        .gpio_cfg = {
            .clk = clk,
            .din = dat,
            .invert_flags = {
                .clk_inv = false,
            },
        },
    };
    pdm_rx_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_RIGHT;
    ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle, &pdm_rx_cfg));
    ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));
}
  • 通道固定 I2S_NUM_0:ESP32-S3 只有 I2S0 支持 PDM 模式。i2s_new_channel(&chan_cfg, NULL, &rx_handle) 的第三参数是接收句柄。
  • I2S_PDM_RX_CLK_DEFAULT_CONFIG(sample_rate):PDM 时钟和抽取率都按采样率算好,改速度只改这一个入参。
  • I2S_PDM_RX_SLOT_DEFAULT_CONFIG(16BIT, MONO):硬件抽取后直接输出 16 bit PCM。
  • slot_mask = I2S_STD_SLOT_RIGHT:只取右槽。注意这里用的是标准模式的枚举 I2S_STD_SLOT_RIGHT,而字段属于 PDM 配置;两者数值碰巧相同,能编过,但类型不对。

4.5 录音:PDM → PCM → SPIFFS

c 复制代码
void start_record(uint32_t rec_time)
{
    int flash_wr_size = 0;
    //每次采样的数据长度(单位:字)
    const size_t read_size_word = 8192;
    //根据录音时间计算出需要录的长度(计算公式:采样率*录音时间 = 录音数据长度)
    const int flash_rec_time = SAMPLE_RATE*rec_time;

    FILE *f = fopen(AUDIO_MOUNT"/record.pcm", "w");
    if (f == NULL)
    {
        ESP_LOGE(TAG, "Failed to open file for writing");
        return;
    }
    int16_t* i2s_read_buff = (int16_t*)malloc(read_size_word*sizeof(int16_t));
    ESP_LOGI(TAG,"Start record");
    while (flash_wr_size < flash_rec_time)
    {
        int read_word = audio_read(i2s_read_buff,read_size_word);
        if (read_word)
        {
            //声音放大,可以去掉,放大是为了听得更清楚
            for(int i = 0;i<read_word;i++)
            {
                i2s_read_buff[i] = i2s_read_buff[i]<<1;
            }
            ESP_LOGI(TAG,"audio read word:%d",read_word);
            fwrite(i2s_read_buff, read_word*2, 1, f);
            flash_wr_size += read_word;
        }
        else
        {
            ESP_LOGI(TAG,"Read Failed!\n");
        }
    }
    ESP_LOGI(TAG, "Recording done!");
    fclose(f);
}
  • 计数单位是字 (int16_t,一个采样一个字),所以 flash_rec_time = 24000 × 10 = 240000 个字,写入时再乘 2 变成字节。
  • 每次读 8192 个字(16 KB),10 秒大约循环 29 次。
  • <<1 是把幅度翻倍,注释写明了只是为了听得更清楚,可以去掉。对这种简单左移要有警惕:超过 int16_t 范围会溢出翻转,形成爆音。
  • 文件里只有裸 PCM,没有文件头。总大小 240000 × 2 = 480000 字节。

4.6 回放:SPIFFS → I2S1 → 功放

c 复制代码
void start_play(void)
{
    const size_t write_size_byte = 8192;
    struct stat st;
    if(stat(AUDIO_MOUNT"/record.pcm",&st) == 0)
    {
        ESP_LOGI(TAG,"record.pcm filesize:%ld",st.st_size);
    }
    FILE *f = fopen(AUDIO_MOUNT"/record.pcm", "r");
    if(!f)
    {
        ESP_LOGI(TAG,"record.pcm open fail!");
        return;
    }
    ESP_LOGI(TAG,"Start play");
    uint8_t *i2s_write_buff = malloc(write_size_byte);
    if(!i2s_write_buff)
    {
        fclose(f);
        return;
    }
    size_t read_byte = 0;
    do
    {
        fread(i2s_write_buff,write_size_byte,1,f);
        audio_write((const int16_t*)i2s_write_buff,write_size_byte/2);
        read_byte += write_size_byte;
        vTaskDelay(pdMS_TO_TICKS(10));
    } while (read_byte < st.st_size);
    free(i2s_write_buff);
    fclose(f);
    ESP_LOGI(TAG,"Play done");
}
  • 每次从文件读 8192 字节,按 16 bit 拆成 4096 个字送进 I2S。
  • audio_write 内部调用 i2s_channel_write(..., 200),写完后返回实际写入的字数。
  • 每轮延时 10 ms,让 DMA 有机会把缓冲送到功放,避免喂得太快。
  • 回放前 xl9555_pin_write(SPK_EN_IO, 1) 打开功放,并延时 100 ms 等它上电稳定;回放结束后再拉低,省电也避免底噪。

4.7 SD 卡版:多一个 WAV 文件头

audio_record_sdcard 的录音流程和 SPIFFS 版基本一样,区别在存储和文件格式:

c 复制代码
uint32_t flash_rec_time = BYTE_RATE * rec_time;
const wav_header_t wav_header =
    WAV_HEADER_PCM_DEFAULT(flash_rec_time, BIT_SAMPLE, SAMPLE_RATE, 1);
...
FILE *f = fopen(SD_MOUNT_POINT"/record.wav", "a");
fwrite(&wav_header, sizeof(wav_header), 1, f);

WAV_HEADER_PCM_DEFAULT 把 WAV 的 44 字节头一次填好:

字段 值 含义
chunk_id RIFF 文件标识
chunk_size 数据长度 + 44 - 8 从本字段之后到文件尾的长度
chunk_format WAVE 格式类型
subchunk_id fmt 格式块
audio_format 1 PCM
num_of_channels 1 单声道
sample_rate 24000 采样率
byte_rate 48000 每秒字节数 = 24000 × 16 / 8
block_align 2 每个采样字节数
bits_per_sample 16 位深
data 块大小 480000 10 秒的 PCM 数据

其它差异:

  • SD 卡走 SPI:mount_sdcard() 用 SDSPI_HOST_DEFAULT(),把 CS/MOSI/MISO/SCK 接到 GPIO4/5/7/6,挂载点 /sdcard。
  • 这一版放大倍数更大:i2s_readraw_buff[i] << 2,相当于 4 倍。
  • xl9555 驱动的接口也换了一版:SPIFFS 版直接传 SDA/SCL,SD 卡版先 i2c_new_master_bus() 再把总线句柄传给 xl9555_init()。两份驱动是各自复制的,不能混用。

五、关键 API 速查

API 或宏 作用 头文件
i2s_new_channel 申请 I2S 通道,指定收发方向 driver/i2s_common.h
i2s_channel_init_std_mode 按标准 I2S 模式初始化 driver/i2s_std.h
i2s_channel_init_pdm_rx_mode 按 PDM 接收模式初始化 driver/i2s_pdm.h
i2s_channel_enable / i2s_channel_disable 启动 / 停止通道 driver/i2s_common.h
i2s_channel_read 从 RX 通道读 PCM 数据 driver/i2s_common.h
i2s_channel_write 向 TX 通道写 PCM 数据 driver/i2s_common.h
I2S_CHANNEL_DEFAULT_CONFIG 通道默认配置(DMA 数量、帧数) driver/i2s_common.h
I2S_STD_CLK_DEFAULT_CONFIG 标准模式时钟配置(采样率) driver/i2s_std.h
I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG 飞利浦时序、位宽、声道模式 driver/i2s_std.h
I2S_PDM_RX_CLK_DEFAULT_CONFIG PDM 接收时钟配置 driver/i2s_pdm.h
I2S_PDM_RX_SLOT_DEFAULT_CONFIG PDM 接收位宽、声道模式 driver/i2s_pdm.h
esp_vfs_spiffs_register 挂载 SPIFFS 分区 esp_spiffs.h
esp_vfs_fat_sdspi_mount 以 SPI 方式挂载 FAT 文件系统 esp_vfs_fat.h
xl9555_init / xl9555_ioconfig / xl9555_pin_write IO 扩展芯片初始化 / 方向 / 输出 xl9555.h

六、编译与现象验证

例程不在本工作区,先拉源码:

bash 复制代码
git clone https://gitee.com/vi-iot/esp32s3-board.git
cd esp32s3-board/audio_record

仓库 README 注明依赖 ESP-IDF v5.3.3 或 v5.4.2 :代码用了 driver/i2s_std.h、driver/i2s_pdm.h 和新的 driver/i2c_master.h,4.x 编不过。编译:

bash 复制代码
idf.py set-target esp32s3
idf.py build
idf.py -p COMx flash monitor

audio_record 用自定义分区表 partitions_audio.csv,sdkconfig 里已打开 CONFIG_PARTITION_TABLE_CUSTOM=y;audio_record_sdcard 用默认分区表。开发板需要是 16 MB Flash + PSRAM 的 ESP32-S3 板子,否则分区表放不下 8 MB 的 audio 分区。

6.1 audio_record 的预期日志

复制代码
I (xxx) main: Start record
I (xxx) main: audio read word:8192
...
I (xxx) main: Recording done!
I (xxx) main: record.pcm filesize:480000
I (xxx) main: Start play
I (xxx) main: Play done

10 秒 × 24000 样本 = 240000 个 16 bit 样本,正好 480000 字节。audio read word:8192 会出现约 29 次(240000 / 8192 ≈ 29.3)。

6.2 audio_record_sdcard 的预期日志

复制代码
I (xxx) pdm_rec_example: Initializing SD card
...
I (xxx) pdm_rec_example: Starting recording for 10 seconds!
I (xxx) pdm_rec_example: Recording done!
I (xxx) pdm_rec_example: Starting play wav

录完把 TF 卡拔下来插电脑,record.wav 应能直接用播放器打开。

相关推荐
qq_284274051 小时前
电动机控制与PLC入门:自耦减压启动、反接制动与速度继电器KS,及PLC诞生、定义、特点与分类(含考点)
笔记·学习·自动化·制造
LCG米1 小时前
STM32 HardFault故障定位实测:压栈帧寄存器解析、CFSR故障源判读与出错地址回溯
前端·javascript·stm32·单片机·嵌入式
Source.Liu1 小时前
【A11】服务器底层权限表笔记
运维·服务器·笔记
噜~噜~噜~2 小时前
钢琴入门-基础乐理:第1讲到第3讲笔记
笔记·钢琴入门
坤坤子吖2 小时前
Linux 线程概念与控制——线程创建、退出与等待
linux·开发语言·c++·笔记·学习
qq_284274052 小时前
间歇运动机构与机械设计概论:棘轮与槽轮机构运动系数、零件失效与强度刚度准则、结构工艺性清根及标准件(含考点)
笔记·学习·平面·自动化·制造
于眠牧北3 小时前
雷电模拟器9/14搭配charles抓取自带浏览器接口9/14
经验分享·笔记·git
码上观世界3 小时前
Agent 对话里的图片与视频处理
音视频