esp32-s3播放wav破音问题,开发工具是IDF5.5 @windows 10

yunfeihan
Posts: 1
Joined: Wed Aug 27, 2025 2:22 pm

esp32-s3播放wav破音问题,开发工具是IDF5.5 @windows 10

Postby yunfeihan » Wed Aug 27, 2025 2:39 pm

我在windows 10下,使用vscode安装配置了IDF5.5插件,模组使用的是esp32-s3的 wroom模组, 解码使用的是Max93857,gain引脚通过上拉vdd选择的是6db。

遇到的问题时,使用同一块电路板的从SD卡播放相同wav文件的情况下:
1. 使用IDF5.5编译的程序在 播放wav时,会有严重的破音,只要音乐中有部件音量稍大,就会破音。使用ffmpeg工具将音乐音量改到原来的40%以下时,破音会消除,但此时音量太小了听着不舒服。 正常音量的mp3文件转换成wav格式后均破音,在电脑、手机上播放均不破音。

2.使用arduino 2.3.6 ,使用乐鑫官方的ESP_I2S.h头文件提供的播放功能完全正常,大音量的情况下,完全没有破音。arudino下即使将gain从硬件上配置成12db的增益也完全没有破音。

这就奇怪了,相同的电路板,在arduino下编译运行正常,在IDF5.5之下编译的固件就不行了!!! :( :(
以下是mpeg输出的音频信息:

Code: Select all

[aist#0:0/pcm_s16le @ 000001dfe3e70d40] Guessed Channel Layout: stereo
Input #0, wav, from 'fc.wav':
  Metadata:
    encoder         : Lavf61.7.100
  Duration: 00:04:45.74, bitrate: 1411 kb/s
  Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, stereo, s16, 1411 kb/s
以下是我的IDF程序以及arduino程序。
IDF程序:

Code: Select all


#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <dirent.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "driver/gpio.h"
#include "esp_check.h"
#include "sdkconfig.h"
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"

#define EXAMPLE_STD_BCLK_IO1 16 // I2S bit clock io number
#define EXAMPLE_STD_WS_IO1 17   // I2S word select io number
#define EXAMPLE_STD_DOUT_IO1 18 // I2S data out io number
#define EXAMPLE_STD_DIN_IO1 6   // I2S data in io number
#define SDMODE 8
#define EXAMPLE_BUFF_SIZE 10240

#define PIN_NUM_MISO 39
#define PIN_NUM_MOSI 40
#define PIN_NUM_CLK 41
#define PIN_NUM_CS 42
#define SPI_DMA_CHAN SPI_DMA_CH_AUTO

#define MOUNT_POINT "/sdcard"

static i2s_chan_handle_t tx_chan; // I2S tx channel handler

#define TAG "i2s_std_example"

// 打印目录内容的函数实现
void list_directory_contents(const char *path)
{
    DIR *dir;
    struct dirent *entry;

    // 打开目录
    dir = opendir(path);
    if (dir == NULL)
    {
        perror("无法打开目录");
        printf("请检查路径是否正确或是否有权限。\n");
        return;
    }

    printf("\n目录 '%s' 中的文件和子目录:\n", path);
    printf("----------------------------------------\n");
    // 读取目录内容
    while ((entry = readdir(dir)) != NULL)
    {
        // entry->d_name 包含文件名或目录名
        // entry->d_type 可以用来判断类型 (DT_DIR 目录, DT_REG 普通文件等)
        // 为了简化,这里只打印名称
        printf("%s\n", entry->d_name);
    }
    printf("----------------------------------------\n");

    // 关闭目录
    closedir(dir);
}
static void i2s_example_write_task(void *args)
{
    char songs[4][30] = {"/sdcard/yueliang.wav", "/sdcard/rlgt.wav",  "/sdcard/NFC.wav","/sdcard/njohn.wav"};
    int idx = 0;
    while (1)
    {
        uint16_t *w_buf = (uint16_t *)calloc(1, EXAMPLE_BUFF_SIZE);
        assert(w_buf); // Check if w_buf allocation success

    
        size_t w_bytes = EXAMPLE_BUFF_SIZE;


        FILE *file;
        printf("Opening file: %s\n", songs[idx]);
        file = fopen(songs[idx], "rb");
        if (file == NULL)
        {
            printf("Error opening file\n");
            vTaskDelay(pdMS_TO_TICKS(2000));
            idx++;
            continue;
        }

        fseek(file, 44, SEEK_SET);

        // 循环读取文件内容
        size_t readed = 0;
        // 预加载数据
        readed = fread(w_buf, 1, EXAMPLE_BUFF_SIZE, file);
        if (readed > 0)
        {
            ESP_ERROR_CHECK(i2s_channel_preload_data(tx_chan, w_buf, readed, &w_bytes));
        }
        printf("预加载数据--》 Readed  %d bytes,   I2S writed  %d bytes\n", readed, w_bytes);
        // 没能把读到的数据全部写入DMA
        if (w_bytes < readed)
        {
            fseek(file, w_bytes+44, SEEK_SET);
        }

        /* Enable the TX channel */
        ESP_ERROR_CHECK(i2s_channel_enable(tx_chan));

        while ((readed = fread(w_buf, 1, EXAMPLE_BUFF_SIZE, file)) > 0)
        {
            // printf("Readed  %d bytes\n", readed);
            ESP_ERROR_CHECK(i2s_channel_write(tx_chan, w_buf, readed, &w_bytes, 1000));
            printf("Readed  %d bytes,   I2S writed  %d bytes\n", readed, w_bytes);
            //vTaskDelay(pdMS_TO_TICKS(1000));
        }

        ESP_ERROR_CHECK(i2s_channel_disable(tx_chan));

        fclose(file);
        free(w_buf);
        vTaskDelay(pdMS_TO_TICKS(1000));
        idx++;
        if(idx>=4){
            idx = 0;
        }
    }

}

static void i2s_example_init_std_simplex(void)
{
    
    i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
    ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL));
  
   
    i2s_std_config_t tx_std_cfg = {
        .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(44100),
        .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
        .gpio_cfg = {
            .mclk = I2S_GPIO_UNUSED, 
            .bclk = EXAMPLE_STD_BCLK_IO1,
            .ws = EXAMPLE_STD_WS_IO1,
            .dout = EXAMPLE_STD_DOUT_IO1,
            .din = I2S_GPIO_UNUSED,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false,
            },
        },
    };
    
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_chan, &tx_std_cfg));
}

void confgSdMode(void)
{
    // 1. 配置 GPIO
    gpio_config_t io_conf = {
        // 禁用中断
        .intr_type = GPIO_INTR_DISABLE,
        // 设置为输出模式
        .mode = GPIO_MODE_OUTPUT_OD, // <--- 关键:设置为开漏输出模式
        // 设置要配置的 GPIO 引脚
        .pin_bit_mask = (1ULL << SDMODE),
        // 禁用下拉
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        // 启用上拉 (可选,但通常建议,以防外部没有上拉)
        // 注意:GPIO 内部上拉能力有限,对于 I2C 等总线,通常需要外部上拉电阻。
        .pull_up_en = GPIO_PULLUP_DISABLE,
    };
    // 应用配置
    esp_err_t err = gpio_config(&io_conf);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to configure GPIO: %s", esp_err_to_name(err));
        return;
    }

    ESP_LOGI(TAG, "GPIO %d configured as Open-Drain output.", SDMODE);

    // --- 关键操作 ---
    // 2. 设置输出电平为高 (1)
    // 对于开漏输出,写 1 会使引脚进入高阻态 (Hi-Z),由外部上拉电阻拉高电平。
    err = gpio_set_level(SDMODE, 1);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to set GPIO level: %s", esp_err_to_name(err));
        return;
    }
    ESP_LOGI(TAG, "GPIO %d set to HIGH (Open-Drain Hi-Z state).", SDMODE);
}

void configSDCard(void)
{

    esp_err_t ret;

    esp_vfs_fat_sdmmc_mount_config_t mount_config = {
        .format_if_mount_failed = false,
        .max_files = 5,
        .allocation_unit_size = 16 * 1024};
    sdmmc_card_t *card;
    const char mount_point[] = MOUNT_POINT;
    ESP_LOGI(TAG, "Initializing SD card");

    ESP_LOGI(TAG, "Using SPI peripheral");

    sdmmc_host_t host = SDSPI_HOST_DEFAULT();
    host.slot = SPI3_HOST;

    spi_bus_config_t bus_cfg = {
        .mosi_io_num = PIN_NUM_MOSI,
        .miso_io_num = PIN_NUM_MISO,
        .sclk_io_num = PIN_NUM_CLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 4000,
    };

    ret = spi_bus_initialize(host.slot, &bus_cfg, SDSPI_DEFAULT_DMA);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to initialize bus.");
        return;
    }

    // This initializes the slot without card detect (CD) and write protect (WP) signals.
    // Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
    sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
    slot_config.gpio_cs = PIN_NUM_CS;
    slot_config.host_id = host.slot;

    ESP_LOGI(TAG, "Mounting filesystem");
    ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card);

    if (ret != ESP_OK)
    {
        if (ret == ESP_FAIL)
        {
            ESP_LOGE(TAG, "Failed to mount filesystem. "
                          "If you want the card to be formatted, set the CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.");
        }
        else
        {
            ESP_LOGE(TAG, "Failed to initialize the card (%s). "
                          "Make sure SD card lines have pull-up resistors in place.",
                     esp_err_to_name(ret));
        }
        return;
    }
    ESP_LOGI(TAG, "Filesystem mounted");
}
void app_main(void)
{

    confgSdMode();
    configSDCard();
    vTaskDelay(pdMS_TO_TICKS(200));

    i2s_example_init_std_simplex();

    /* Step 3: Create writing and reading task, enable and start the channels */
    // xTaskCreate(i2s_example_read_task, "i2s_example_read_task", 4096, NULL, 5, NULL);
    xTaskCreate(i2s_example_write_task, "i2s_example_write_task", 4096, NULL, 5, NULL);
}

下面是正常的arduino程序:

Code: Select all

//输出音频用的I2S接口
const uint8_t LRCLK = 17, BCLK = 16, DIN = 18, SDMODE = 8;

I2SClass I2S;


SPIClass *sdspi = NULL;
uint8_t keyPressed = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  delay(2000);
  Serial.println("Hello s3!");

  sdspi = new SPIClass(HSPI);
  sdspi->begin(SD_CLK, SD_MISO, SD_MOSI, SD_CS);
  if (!SD.begin(SD_CS, *sdspi)) {
    Serial.println("Card Mount Failed");
    return;
  }
  
  setupAudio();
}

void setupAudio() {
  pinMode(SDMODE, OUTPUT_OPEN_DRAIN);
  digitalWrite(SDMODE, 1);

  I2S.setPins(BCLK, LRCLK, DIN, -1, -1);  //SCK, WS, SDOUT, SDIN, MCLK
  I2S.begin(I2S_MODE_STD, 44100, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO);
}

void loop() {
  // put your main code here, to run repeatedly:

  delay(200);
  // if(keyPressed){
  //  readKey();
  // }


  if (!SD.begin(SD_CS,*sdspi)) {
    Serial.println("Card Mount Failed");
    SD.end();
    delay(1000);
    return;
  }
  String fname = "/FCOLD.wav";
  File file = SD.open(fname, "rb");
  if (!file) {
    Serial.print("Failed to open audio file:");
    Serial.println(fname);
    SD.end();
    delay(1000);
    return;
  }

  Serial.println("opened audio file.");

  uint8_t *buffer = (uint8_t *)malloc(BUFFER_SIZE);
  size_t bytes_read;
  bool playing = true;
  while (playing && (bytes_read = file.read(buffer, BUFFER_SIZE)) > 0) {
    Serial.print(".");
    I2S.write(buffer, bytes_read);
  }
  Serial.println();
  free(buffer);
  file.close();
  SD.end();
}


Who is online

Users browsing this forum: No registered users and 3 guests