/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp_check.h"
#include "es8311.h"
#include "example_config.h"
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
#include "mp3dec.h"
static const char *TAG = "i2s_es8311_mp3";
static const char err_reason[][30] = {"input param is invalid",
"operation timeout"
};
static i2s_chan_handle_t tx_handle = NULL;
static i2s_chan_handle_t rx_handle = NULL;
// SD卡相关变量
static sdmmc_card_t *card;
static esp_err_t sdcard_init(void)
{
esp_err_t ret;
// 配置SD卡GPIO
gpio_set_pull_mode(SD_CLK_IO, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(SD_CMD_IO, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(SD_D0_IO, GPIO_PULLUP_ONLY);
// 配置FAT文件系统
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 5,
.allocation_unit_size = 16 * 1024
};
// 配置SDMMC主机
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
// 配置SD卡引脚
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
slot_config.width = 1;
slot_config.clk = SD_CLK_IO;
slot_config.cmd = SD_CMD_IO;
slot_config.d0 = SD_D0_IO;
slot_config.d1 = SD_D1_IO;
slot_config.d2 = SD_D2_IO;
slot_config.d3 = SD_D3_IO;
// 挂载文件系统
ret = esp_vfs_fat_sdmmc_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 format_if_mount_failed = true.");
} 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 ret;
}
ESP_LOGI(TAG, "SD card mounted successfully");
return ESP_OK;
}
static esp_err_t es8311_codec_init(void)
{
/* Initialize I2C peripheral */
#if !defined(CONFIG_EXAMPLE_BSP)
const i2c_config_t es_i2c_cfg = {
.sda_io_num = I2C_SDA_IO,
.scl_io_num = I2C_SCL_IO,
.mode = I2C_MODE_MASTER,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 100000,
};
ESP_RETURN_ON_ERROR(i2c_param_config(I2C_NUM, &es_i2c_cfg), TAG, "config i2c failed");
ESP_RETURN_ON_ERROR(i2c_driver_install(I2C_NUM, I2C_MODE_MASTER, 0, 0, 0), TAG, "install i2c driver failed");
#else
ESP_ERROR_CHECK(bsp_i2c_init());
#endif
/* Initialize es8311 codec */
es8311_handle_t es_handle = es8311_create(I2C_NUM, ES8311_ADDRRES_0);
ESP_RETURN_ON_FALSE(es_handle, ESP_FAIL, TAG, "es8311 create failed");
const es8311_clock_config_t es_clk = {
.mclk_inverted = false,
.sclk_inverted = false,
.mclk_from_mclk_pin = true,
.mclk_frequency = EXAMPLE_MCLK_FREQ_HZ,
.sample_frequency = EXAMPLE_SAMPLE_RATE
};
ESP_ERROR_CHECK(es8311_init(es_handle, &es_clk, ES8311_RESOLUTION_16, ES8311_RESOLUTION_16));
ESP_RETURN_ON_ERROR(es8311_sample_frequency_config(es_handle, EXAMPLE_SAMPLE_RATE * EXAMPLE_MCLK_MULTIPLE, EXAMPLE_SAMPLE_RATE), TAG, "set es8311 sample frequency failed");
ESP_RETURN_ON_ERROR(es8311_voice_volume_set(es_handle, EXAMPLE_VOICE_VOLUME, NULL), TAG, "set es8311 volume failed");
ESP_RETURN_ON_ERROR(es8311_microphone_config(es_handle, false), TAG, "set es8311 microphone failed");
#if CONFIG_EXAMPLE_MODE_ECHO
ESP_RETURN_ON_ERROR(es8311_microphone_gain_set(es_handle, EXAMPLE_MIC_GAIN), TAG, "set es8311 microphone gain failed");
#endif
return ESP_OK;
}
static esp_err_t i2s_driver_init(void)
{
#if !defined(CONFIG_EXAMPLE_BSP)
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM, I2S_ROLE_MASTER);
chan_cfg.dma_desc_num = 8;
chan_cfg.dma_frame_num = 512;
chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle));
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(EXAMPLE_SAMPLE_RATE),
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
.gpio_cfg = {
.mclk = I2S_MCK_IO,
.bclk = I2S_BCK_IO,
.ws = I2S_WS_IO,
.dout = I2S_DO_IO,
.din = I2S_DI_IO,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false,
},
},
};
std_cfg.clk_cfg.mclk_multiple = EXAMPLE_MCLK_MULTIPLE;
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_cfg));
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));
ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));
#else
ESP_LOGI(TAG, "Using BSP for HW configuration");
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(EXAMPLE_SAMPLE_RATE),
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
.gpio_cfg = BSP_I2S_GPIO_CFG,
};
std_cfg.clk_cfg.mclk_multiple = EXAMPLE_MCLK_MULTIPLE;
ESP_ERROR_CHECK(bsp_audio_init(&std_cfg, &tx_handle, &rx_handle));
ESP_ERROR_CHECK(bsp_audio_poweramp_enable(true));
#endif
return ESP_OK;
}
#if CONFIG_EXAMPLE_MODE_MUSIC
static void i2s_music(void *args)
{
esp_err_t ret = ESP_OK;
size_t bytes_read, bytes_write;
FILE *mp3_file = NULL;
HMP3Decoder mp3_decoder = NULL;
MP3FrameInfo mp3_frame_info;
int decode_ret;
int bytes_left = 0;
uint8_t *read_ptr = mp3_input_buffer; // 输入缓冲区指针
short pcm_output[PCM_OUTPUT_BUFFER_SIZE / 2]; // MP3Decode输出为short类型
// 初始化SD卡(SPI模式,仅使用DAT0)
if (sdcard_init() != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize SD card");
goto exit;
}
// 打开MP3文件
mp3_file = fopen(MOUNT_POINT "/music.mp3", "rb");
if (!mp3_file) {
ESP_LOGE(TAG, "Failed to open MP3 file");
goto exit;
}
ESP_LOGI(TAG, "MP3 file opened successfully");
// 初始化MP3解码器
mp3_decoder = MP3InitDecoder();
if (!mp3_decoder) {
ESP_LOGE(TAG, "Failed to initialize MP3 decoder");
goto exit;
}
// 预读取一部分数据到输入缓冲区
bytes_read = fread(mp3_input_buffer, 1, MP3_INPUT_BUFFER_SIZE, mp3_file);
if (bytes_read == 0) {
ESP_LOGE(TAG, "Failed to read MP3 file");
goto exit;
}
bytes_left = bytes_read;
// 启用I2S发送通道
// ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));
ESP_LOGI(TAG, "Starting MP3 playback");
while (1) {
// 确保输入缓冲区有足够数据(至少保留一个帧的可能空间)
if (bytes_left < 1024) { // 减小阈值,避免缓冲区溢出
// 移动剩余数据到缓冲区开头
memmove(mp3_input_buffer, read_ptr, bytes_left);
// 从文件补充新数据
bytes_read = fread(mp3_input_buffer + bytes_left, 1,
MP3_INPUT_BUFFER_SIZE - bytes_left, mp3_file);
if (bytes_read == 0) {
if (feof(mp3_file)) {
ESP_LOGI(TAG, "End of MP3 file reached");
break;
} else {
ESP_LOGE(TAG, "Error reading MP3 file");
goto exit;
}
}
bytes_left += bytes_read;
read_ptr = mp3_input_buffer; // 重置指针到缓冲区开头
}
// 解码MP3帧(使用MP3Decode替代MP3DecodeFrame)
// 参数说明:
// - 解码器句柄:mp3_decoder
// - 输入缓冲区指针的指针:&read_ptr(函数会修改指针位置)
// - 剩余字节数的指针:&bytes_left(函数会修改剩余字节数)
// - 输出PCM缓冲区:pcm_output
// - 是否使用固定大小缓冲区:0(自动适应)
decode_ret = MP3Decode(mp3_decoder, &read_ptr, &bytes_left, pcm_output, 0);
// 处理解码结果
if (decode_ret != ERR_MP3_NONE) {
// 错误处理:打印错误信息并尝试跳过错误数据
ESP_LOGW(TAG, "MP3 decode error: %d", decode_ret);
if (decode_ret == ERR_MP3_INDATA_UNDERFLOW) {
// 输入数据不足,继续读取文件(循环会自动补充数据)
continue;
} else if (decode_ret == ERR_MP3_INVALID_FRAMEHEADER) {
// 无效帧头,跳过1字节继续尝试
read_ptr++;
bytes_left--;
continue;
} else {
// 严重错误,终止播放
ESP_LOGE(TAG, "Fatal decode error: %d", decode_ret);
goto exit;
}
}
// 获取当前帧信息(采样率、声道数等)
MP3GetLastFrameInfo(mp3_decoder, &mp3_frame_info);
// 检查采样率是否匹配(可选,不匹配可能导致播放速度异常)
if (mp3_frame_info.samprate != EXAMPLE_SAMPLE_RATE) {
ESP_LOGW(TAG, "MP3 sample rate (%d) != configured rate (%d)",
mp3_frame_info.samprate, EXAMPLE_SAMPLE_RATE);
}
// 计算PCM数据大小:单帧采样数 × 声道数 × 每个采样的字节数(16位=2字节)
size_t pcm_size = mp3_frame_info.outputSamps * mp3_frame_info.nChans * sizeof(short);
// 发送PCM数据到I2S(ES8311)
ret = i2s_channel_write(tx_handle, pcm_output, pcm_size, &bytes_write, portMAX_DELAY);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "[music] I2S write failed: %s", err_reason[ret == ESP_ERR_TIMEOUT]);
goto exit;
}
if (bytes_write != pcm_size) {
ESP_LOGW(TAG, "[music] Incomplete write: %d/%d bytes", bytes_write, pcm_size);
}
}
exit:
// 清理资源
if (mp3_decoder) {
MP3FreeDecoder(mp3_decoder); // 使用MP3FreeDecoder释放解码器
}
if (mp3_file) {
fclose(mp3_file);
}
esp_vfs_fat_sdcard_unmount(MOUNT_POINT, NULL);
ESP_LOGI(TAG, "Playback finished");
vTaskDelete(NULL);
}
#endif
void app_main(void)
{
gpio_reset_pin(GPIO_NUM_3);
gpio_set_direction(GPIO_NUM_3, GPIO_MODE_OUTPUT);
gpio_set_level(GPIO_NUM_3, 1);
printf("i2s es8311 mp3 player example start\n-----------------------------\n");
/* Initialize i2s peripheral */
if (i2s_driver_init() != ESP_OK) {
ESP_LOGE(TAG, "i2s driver init failed");
abort();
} else {
ESP_LOGI(TAG, "i2s driver init success");
}
/* Initialize i2c peripheral and config es8311 codec by i2c */
if (es8311_codec_init() != ESP_OK) {
ESP_LOGE(TAG, "es8311 codec init failed");
abort();
} else {
ESP_LOGI(TAG, "es8311 codec init success");
}
if(rx_handle){
i2s_channel_disable(rx_handle);
}
#if EXAMPLE_PA_CTRL_IO >= 0
/* Enable PA by setting the PA_CTRL_IO to high, because the power amplifier on some dev-kits are disabled by default */
gpio_config_t gpio_cfg = {
.pin_bit_mask = (1ULL << EXAMPLE_PA_CTRL_IO),
.mode = GPIO_MODE_OUTPUT,
};
ESP_ERROR_CHECK(gpio_config(&gpio_cfg));
ESP_ERROR_CHECK(gpio_set_level(EXAMPLE_PA_CTRL_IO, 1));
#endif
#if CONFIG_EXAMPLE_MODE_MUSIC
/* Play MP3 from SD card */
xTaskCreate(i2s_music, "i2s_music", 2048 * 6, NULL, 8, NULL);
#else
/* Echo the sound from MIC in echo mode */
xTaskCreate(i2s_echo, "i2s_echo", 8192, NULL, 5, NULL);
#endif
}
在此源码基础上修改
最新发布