开始前适用开发板:ESP32-S3 Korvo · 开发框架:ESP-IDF
项目概述
本教程将介绍如何使用ESP32 Korvo开发板创建一个简单的MP3播放器。该播放器具有以下功能:
- 从SPIFFS文件系统读取并播放MP3文件
- 通过LVGL图形库实现用户界面
- 支持播放/暂停、上一曲/下一曲和音量调节
硬件需求
- ESP32 Korvo开发板
- 显示屏(本项目使用280x240分辨率的LCD屏)
- 触摸屏(CST816S触摸控制器)
- 音频编解码器
软件架构
项目主要由以下几个部分组成:
- 主程序(app_main.c) - 初始化系统并启动应用
- 音频处理(audio.c) - 负责MP3文件的扫描、解码和播放
- 用户界面(ui_audio.c) - 使用LVGL创建播放器界面
- 音频硬件配置(app_speech.c) - 配置I2S和音频编解码器
- LVGL端口(ksdiy_lvgl_port.c) - 将LVGL与ESP32的LCD和触摸屏连接
详细教程
第一步:了解项目结构
首先,让我们了解项目的主要文件及其功能:
- app_main.c: 程序入口,初始化各个模块
- audio.c/audio.h: MP3文件扫描和播放功能
- ui_audio.c/ui_audio.h: 播放器用户界面
- app_speech.c/app_speech.h: 音频硬件初始化
- ksdiy_lvgl_port.c/ksdiy_lvgl_port.h: LVGL与硬件的连接层
- CMakeLists.txt: 项目构建配置
- idf_component.yml: 项目依赖组件
第二步:理解主程序流程
app_main.c是程序的入口点,它完成以下任务:
void app_main(void)
{
// 1. 初始化NVS(非易失性存储)
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// 2. 初始化SPIFFS文件系统(用于存储MP3文件)
ESP_LOGI(TAG, "Initializing SPIFFS");
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs",
.partition_label = "storage",
.max_files = 2,
.format_if_mount_failed = true};
ret = esp_vfs_spiffs_register(&conf);
if (ret != ESP_OK) {
// 错误处理...
return;
}
// 3. 显示SPIFFS中的文件列表
SPIFFS_Directory("/spiffs/");
// 4. 初始化LVGL图形库
ksdiy_lvgl_port_init();
if (ksdiy_lvgl_lock(0))
{
// 5. 启动音频播放器UI
ui_audio_start();
ksdiy_lvgl_unlock();
}
// 6. 启动MP3播放器
ESP_ERROR_CHECK(mp3_player_start("/spiffs"));
}
第三步:SPIFFS文件系统
SPIFFS是一个轻量级的文件系统,用于在闪存上存储文件。在本项目中,我们使用它来存储MP3文件。
// 初始化SPIFFS
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs", // 挂载点
.partition_label = "storage", // 分区标签
.max_files = 2, // 最大同时打开文件数
.format_if_mount_failed = true // 如果挂载失败则格式化
};
SPIFFS_Directory函数用于列出SPIFFS中的所有文件:
static void SPIFFS_Directory(char *path)
{
DIR *dir = opendir(path);
assert(dir != NULL);
while (true)
{
struct dirent *pe = readdir(dir);
if (!pe)
break;
ESP_LOGI(__FUNCTION__, "d_name=%s d_ino=%d d_type=%x", pe->d_name, pe->d_ino, pe->d_type);
}
closedir(dir);
}
第四步:音频处理模块
audio.c文件负责MP3文件的扫描、解码和播放。主要功能包括:
- 文件扫描:扫描SPIFFS中的所有文件
- MP3解码:使用Helix MP3解码器解码MP3文件
- 音频播放控制:播放、暂停、上一曲、下一曲等功能
关键函数:
// 启动MP3播放器
esp_err_t mp3_player_start(char *file_path)
{
// 创建音频任务
BaseType_t ret_val = xTaskCreatePinnedToCore(
(TaskFunction_t)audio_task,
(const char *const)"Audio Task",
(const uint32_t)4 * 1024,
(void *const)file_path,
(UBaseType_t)configMAX_PRIORITIES - 1,
(TaskHandle_t *const)NULL,
(const BaseType_t)0);
return (pdPASS == ret_val) ? ESP_OK : ESP_ERR_NO_MEM;
}
// 音频任务
static void audio_task(void *pvParam)
{
// 1. 扫描音频文件
char *base_path = (char *)pvParam;
audio_file_scan(base_path);
// 2. 初始化音频硬件
Codec_I2S_init();
// 3. 创建音频控制事件队列
audio_event_queue = xQueueCreate(4, sizeof(audio_event_t));
// 4. 获取第一首歌曲的名称
char full_name[256] = {0};
strcpy(full_name, base_path);
char *file_name = audio_get_name_from_index(audio_index, full_name);
// 5. 开始播放音乐
while (vTaskDelay(1), NULL != file_name)
{
// 播放MP3文件
esp_err_t ret_val = aplay_mp3(full_name);
// 播放下一首
if (ESP_OK == ret_val)
{
audio_index++;
if (audio_index >= audio_count)
{
audio_index = 0;
}
}
// 获取下一首歌曲的文件名
file_name = audio_get_name_from_index(audio_index, strcpy(full_name, base_path));
}
}
MP3解码函数aplay_mp3使用Helix MP3解码器将MP3文件解码为PCM数据,然后通过I2S发送到音频编解码器:
static esp_err_t aplay_mp3(const char *path)
{
// 1. 初始化MP3解码器
HMP3Decoder mp3_decoder = MP3InitDecoder();
// 2. 打开MP3文件
FILE *fp = fopen(path, "rb");
// 3. 处理ID3标签
mp3_id3_header_v2_t tag;
if (sizeof(mp3_id3_header_v2_t) == fread(&tag, 1, sizeof(mp3_id3_header_v2_t), fp))
{
if (memcmp("ID3", (const void *)&tag, sizeof(tag.header)) == 0)
{
// 跳过ID3标签
int tag_len = ((tag.size[0] & 0x7F) << 21) + ... ;
fseek(fp, tag_len - sizeof(mp3_id3_header_v2_t), SEEK_SET);
}
else
{
// 不是ID3标签,回到文件开头
fseek(fp, 0, SEEK_SET);
}
}
// 4. 开始MP3解码循环
do
{
// 处理音频事件(暂停、切换等)
if (pdPASS == xQueueReceive(audio_event_queue, &audio_event, 0))
{
// 处理事件...
}
// 读取数据
// ...
// 查找MP3同步字
int offset = MP3FindSyncWord(read_buf, MAINBUF_SIZE);
if (offset >= 0)
{
// 解码MP3帧
int mp3_dec_err = MP3Decode(mp3_decoder, &read_ptr, &bytes_left, (int16_t *)output, 0);
// 获取帧信息
MP3GetLastFrameInfo(mp3_decoder, &frame_info);
// 如果采样率改变,重新配置I2S时钟
if (sample_rate != frame_info.samprate)
{
// 重新配置音频编解码器
// ...
}
// 将解码后的数据写入音频编解码器
esp_codec_dev_write(output_dev_, (void *)output, output_size);
}
} while (true);
}
第五步:用户界面
ui_audio.c文件使用LVGL库创建播放器界面,包括播放/暂停按钮、上一曲/下一曲按钮和音量滑块:
void ui_audio_start(void)
{
// 1. 创建播放/暂停按钮
lv_obj_t *btn_play_pause = lv_btn_create(lv_scr_act());
lv_obj_align(btn_play_pause, LV_ALIGN_CENTER, 0, 40);
lv_obj_set_size(btn_play_pause, 50, 50);
lv_obj_set_style_radius(btn_play_pause, 25, LV_STATE_DEFAULT);
lv_obj_add_flag(btn_play_pause, LV_OBJ_FLAG_CHECKABLE);
lv_obj_t *label_play_pause = lv_label_create(btn_play_pause);
lv_label_set_text_static(label_play_pause, LV_SYMBOL_PAUSE);
lv_obj_center(label_play_pause);
lv_obj_set_user_data(btn_play_pause, (void *)label_play_pause);
lv_obj_add_event_cb(btn_play_pause, btn_play_pause_cb, LV_EVENT_VALUE_CHANGED, NULL);
// 2. 创建上一曲按钮
lv_obj_t *label_prev = lv_label_create(lv_scr_act());
// ...
// 3. 创建下一曲按钮
lv_obj_t *label_next = lv_label_create(lv_scr_act());
// ...
// 4. 创建音量滑块
lv_obj_t *volume_slider = lv_slider_create(lv_scr_act());
// ...
// 5. 创建歌曲标题标签
lv_obj_t *lab_title = lv_label_create(lv_scr_act());
// ...
// 6. 创建歌曲列表下拉菜单
lv_obj_t *music_list = lv_dropdown_create(lv_scr_act());
// ...
// 7. 注册音频回调函数
audio_callback_register(audio_callback, (void *)music_list);
}
按钮回调函数:
// 播放/暂停按钮回调
static void btn_play_pause_cb(lv_event_t *event)
{
lv_obj_t *btn = (lv_obj_t *)event->target;
lv_obj_t *lab = (lv_obj_t *)btn->user_data;
if (lv_obj_has_state(btn, LV_STATE_CHECKED))
{
lv_label_set_text_static(lab, LV_SYMBOL_PLAY);
audio_pause(); // 暂停音频
}
else
{
lv_label_set_text_static(lab, LV_SYMBOL_PAUSE);
audio_play(); // 播放音频
}
}
// 上一曲/下一曲按钮回调
static void btn_prev_next_cb(lv_event_t *event)
{
bool is_next = (bool)event->user_data;
if (is_next)
{
audio_play_next(); // 播放下一曲
}
else
{
audio_play_prev(); // 播放上一曲
}
}
// 音量滑块回调
static void volume_slider_cb(lv_event_t *event)
{
lv_obj_t *slider = (lv_obj_t *)event->target;
int volume = lv_slider_get_value(slider);
esp_codec_dev_set_out_vol(output_dev_, volume); // 设置音量
}
第六步:音频硬件初始化
app_speech.c文件负责初始化I2S和音频编解码器:
void Codec_I2S_init(void)
{
// 1. 创建I2S通道
i2s_chan_config_t chan_cfg = {
.id = I2S_NUM_0,
.role = I2S_ROLE_MASTER,
.dma_desc_num = 6,
.dma_frame_num = 240 * 3,
.auto_clear_after_cb = true,
.auto_clear_before_cb = false,
.intr_priority = 0,
};
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
// 2. 配置I2S标准模式
i2s_std_config_t std_cfg = {
.clk_cfg = {
.sample_rate_hz = (uint32_t)16000,
.clk_src = I2S_CLK_SRC_DEFAULT,
.ext_clk_freq_hz = 0,
.mclk_multiple = I2S_MCLK_MULTIPLE_256
},
.slot_cfg = {
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
.slot_mode = I2S_SLOT_MODE_STEREO,
// ...
},
.gpio_cfg = {
.mclk = AUDIO_I2S_GPIO_MCLK,
.bclk = AUDIO_I2S_GPIO_BCLK,
.ws = AUDIO_I2S_GPIO_WS,
.dout = AUDIO_I2S_GPIO_DOUT,
// ...
}
};
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
// 3. 初始化音频编解码器
// ...
}
第七步:LVGL端口
ksdiy_lvgl_port.c文件负责将LVGL与ESP32的LCD和触摸屏连接:
void ksdiy_lvgl_port_init(void)
{
// 1. 创建LVGL互斥锁
lvgl_mux = xSemaphoreCreateMutex();
// 2. 初始化I2C(用于触摸屏)
i2c_init();
// 3. 初始化SPI(用于LCD)
// ...
// 4. 初始化LCD
// ...
// 5. 初始化触摸屏
// ...
// 6. 初始化LVGL
lv_init();
// 7. 注册显示驱动
// ...
// 8. 注册触摸驱动
// ...
// 9. 创建LVGL任务
// ...
}
项目依赖
项目依赖于以下组件(在idf_component.yml中定义):
dependencies:
chmorgan/esp-libhelix-mp3: "^1.0.3" # MP3解码器
espressif/esp_audio_codec: "^2.0.2" # 音频编解码器
espressif/esp32-camera: "^2.0.13" # 摄像头支持
idf: ">=4.4" # ESP-IDF版本
lvgl/lvgl: "~8.3.0" # LVGL图形库
esp_lcd_touch_cst816s: "^1.0" # 触摸屏驱动
espressif/esp-sr: "^1.9.0" # 语音识别
espressif/esp_codec_dev: "^1.3.1" # 编解码器设备
esp_io_expander_tca9554: "^2.0.0" # IO扩展器
如何使用
- 将MP3文件放入
spiffs文件夹中 - 构建并烧录项目到ESP32 Korvo开发板
- 使用触摸屏控制播放器:
- 点击中间按钮播放/暂停
- 点击左右按钮切换上一曲/下一曲
- 使用底部滑块调节音量
总结
本教程介绍了如何使用ESP32 Korvo开发板创建一个简单的MP3播放器。通过使用LVGL图形库、Helix MP3解码器和ESP-IDF框架,我们实现了一个功能完整的音频播放器,包括文件扫描、MP3解码、音频播放和用户界面。
希望本教程能帮助你理解ESP32音频应用的开发流程,并为你的项目提供参考。