2. 播放内置WAV音频文件

任务目标

在ESP32-S3上通过I2S接口播放存储在Flash中的WAV音频文件。本案例使用乐鑫官方esp32-s3-eye开发板(板载ES8311音频编解码器),演示从Flash读取WAV文件数据并经由I2S输出音频的完整最小实现。

环境与依赖

  • ESP-IDF v5.x(需启用CONFIG_IDF_TARGET_ESP32S3=y)
  • 开发板:ESP32-S3-EYE(I2S0连接ES8311,使用内置PA)
  • 音频文件:将8kHz/16bit/单声道WAV文件放入main目录下,命名为audio.wav,通过组件管理器或idf.py create-project后手动添加

实现原理

ESP32-S3的I2S外设支持从Flash直接读取音频数据。本案例将WAV文件作为嵌入式二进制资源编译进固件,运行时通过esp_i2s驱动将PCM数据连续写入I2S总线。ES8311编解码器配置为从模式,I2S提供主时钟与位时钟。

关键完整代码

// main.c
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/i2s_std.h"
#include "driver/gpio.h"
#include "esp_heap_caps.h"

// 嵌入WAV文件(需在CMakeLists.txt中添加:target_add_binary_data(main "audio.wav" BINARY))
extern const uint8_t audio_start[] asm("_binary_audio_wav_start");
extern const uint8_t audio_end[] asm("_binary_audio_wav_end");

#define I2S_NUM         I2S_NUM_0
#define SAMPLE_RATE     8000
#define SAMPLE_BITS     16
#define DMA_BUF_SIZE    1024

// ESP32-S3-EYE ES8311引脚
#define I2S_SCLK        41
#define I2S_LRCK        42
#define I2S_DOUT        15
#define I2S_MCLK        16
#define PA_GPIO         48

static void i2s_init(void) {
    i2s_std_config_t std_cfg = {
        .clk_cfg = {
            .sample_rate_hz = SAMPLE_RATE,
            .clk_src = I2S_CLK_SRC_DEFAULT,
            .mclk_multiple = I2S_MCLK_MULTIPLE_256
        },
        .slot_cfg = {
            .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
            .slot_bit_width = I2S_SLOT_BIT_WIDTH_16BIT,
            .slot_mode = I2S_SLOT_MODE_MONO,
            .slot_mask = I2S_STD_SLOT_LEFT,
            .ws_pol = false,
            .bit_shift = true
        },
        .gpio_cfg = {
            .mclk = I2S_MCLK,
            .bclk = I2S_SCLK,
            .ws = I2S_LRCK,
            .dout = I2S_DOUT,
            .din = I2S_GPIO_UNUSED,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false
            }
        }
    };

    i2s_chan_config_t chan_cfg = {
        .id = I2S_NUM,
        .role = I2S_ROLE_MASTER,
        .dma_desc_num = 6,
        .dma_frame_num = DMA_BUF_SIZE,
        .auto_clear = true
    };

    i2s_chan_handle_t tx_chan;
    i2s_new_channel(&chan_cfg, &tx_chan, NULL);
    i2s_channel_init_std_mode(tx_chan, &std_cfg);
    i2s_channel_enable(tx_chan);

    // 使能PA功放
    gpio_config_t pa_cfg = {
        .pin_bit_mask = (1ULL << PA_GPIO),
        .mode = GPIO_MODE_OUTPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE
    };
    gpio_config(&pa_cfg);
    gpio_set_level(PA_GPIO, 1);
}

void app_main(void) {
    i2s_init();

    size_t wav_size = audio_end - audio_start;
    // 跳过WAV文件头(44字节标准PCM头)
    const uint8_t *pcm_data = audio_start + 44;
    size_t pcm_size = wav_size - 44;

    ESP_LOGI("WAV", "PCM data size: %d bytes", pcm_size);

    i2s_chan_handle_t tx_chan;
    i2s_channel_get_handle(I2S_NUM, &tx_chan);

    size_t bytes_written = 0;
    while (bytes_written < pcm_size) {
        size_t chunk = (pcm_size - bytes_written) > DMA_BUF_SIZE * 2 ? 
                       DMA_BUF_SIZE * 2 : (pcm_size - bytes_written);
        size_t written = 0;
        i2s_channel_write(tx_chan, pcm_data + bytes_written, chunk, &written, portMAX_DELAY);
        bytes_written += written;
    }

    // 播放完成后等待DMA排空
    vTaskDelay(pdMS_TO_TICKS(100));
    ESP_LOGI("WAV", "Playback finished");
}

CMakeLists.txt 配置

idf_component_register(SRCS "main.c"
                       INCLUDE_DIRS ".")
target_add_binary_data(${COMPONENT_LIB} "audio.wav" BINARY)

运行与验证

  1. 准备音频:使用ffmpeg或Audacity生成8kHz/16bit/单声道WAV文件,时长建议3~5秒,文件大小控制在100KB以内。
  2. 将文件命名为audio.wav放入main目录。
  3. 编译烧录:idf.py build flash monitor
  4. 验证标准:
    • 串口输出日志包含PCM data size: xxx bytes,且数值与WAV文件数据段大小一致(文件总字节数减44)。
    • 日志出现Playback finished后,开发板音频输出插孔(或板载喇叭)能听到清晰、无爆音的音频内容。
    • 若听不到声音,检查PA引脚电平是否为高(可用万用表测GPIO48对地电压约3.3V),以及ES8311的I2C配置是否与板卡匹配。

常见问题

  • 播放速度异常:确认WAV文件采样率与代码中SAMPLE_RATE一致。若WAV为44100Hz而代码配置8000Hz,播放会变慢约5.5倍。
  • 只有噪声:检查WAV是否为16bit线性PCM格式,不支持压缩格式(如ADPCM)。
  • 编译报错找不到audio.wav:确认文件确实位于main目录且文件名拼写完全一致(区分大小写)。