简介
在上一篇文章中 微雪ESP32-P4开发板评测【五】基于ESP-ADF的音频MP3的解码和播放 我们已经实现了ADF对音频MP3的解码和播放,本章节我们将研究如何使用SPIFFS文件系统,结合分区的方式将音频数据写到Flash的分区内,然后通过ADF的音频流的方式来进行音频的播放。 由于实验的效果和 评测【四】和 评测 【五】效果相同,所以不再额外添加视频文件。
一、要使用 spiffs 首先要在cmake list中增加对spiffs的依赖。

二、在项目的根目录下创建一个spiffs的文件夹
并且将MP3的音频文件拷贝进去。
三、修改分区表,增加对SPIFFS的分区支持 (由于我的MP3文件只有2.3MB,所以我的分区划分了4MB大小)
# ESP32-P4 Partition Table for 16MB Flash # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 0x5F0000, spiffs, data, spiffs, 0x600000, 0x400000,
需要注意的是SPIFFS的分区尽量稍微多划分一点。

不过我们的这个Flash一共是有16MB,所以也并不在乎这一点大小了。
四、在Cmakelist中增加对分区flash的支持
# Build SPIFFS image from spiffs/ directory and flash into the "spiffs" partition
spiffs_create_partition_image(spiffs ${CMAKE_CURRENT_LIST_DIR}/spiffs FLASH_IN_PROJECT)五、编写代码
#include "audio_event_iface.h" #include "spiffs_stream.h" #include "esp_spiffs.h"
在主程序中引入上述的三个头文件,其中第一个主要是处理一些音频的播放事件,比如说音频播放完毕、或者开始播放等,如果不需要的话则可以不引入。第二个则是ESP-ADF提供的SPIFFS流的函数定义,其主要作用就是支持将spiffs中的音频数据以数据流的方式进行读取。 第三个文件则是基础的esp_spiffs的驱动。
然后在主程序中首先定义我们音频的路径
const char *file_path = "/spiffs/1.mp3";
和新增一个事件的回掉句柄
audio_event_iface_handle_t evt;
以及音频的处理句柄
audio_element_handle_t file_stream;
之后便是初始化文件系统
ESP_LOGI(TAG, "[ 1.1 ] Mount SPIFFS");
esp_vfs_spiffs_conf_t spiffs_conf = {
.base_path = "/spiffs",
.partition_label = NULL,
.max_files = 5,
.format_if_mount_failed = true,
};
ESP_ERROR_CHECK(esp_vfs_spiffs_register(&spiffs_conf));在上述的这一段代码执行完毕之后 SPIFFS已经被挂载完毕。 可以通过下述的代码来查看具体的容量信息。
// 打印 SPIFFS 分区信息
size_t total = 0, used = 0;
esp_err_t ret = esp_spiffs_info(spiffs_conf.partition_label, &total, &used);
if (ret == ESP_OK)
{
ESP_LOGI(TAG, "SPIFFS: total=%zu KB, used=%zu KB, free=%zu KB",
total / 1024, used / 1024, (total - used) / 1024);
}
else
{
ESP_LOGW(TAG, "Failed to get SPIFFS info");
}效果如下所示

接着便是根据文件的路径来创建一个音频流,用于后续播放(路径为上述我们定义的spiffs/1.mp3)
ESP_LOGI(TAG, "[2.1] Create SPIFFS stream reader"); spiffs_stream_cfg_t spiffs_cfg = SPIFFS_STREAM_CFG_DEFAULT(); spiffs_cfg.type = AUDIO_STREAM_READER; file_stream = spiffs_stream_init(&spiffs_cfg); audio_element_set_uri(file_stream, file_path); ESP_LOGI(TAG, "[2.2] Create mp3 decoder");
还记得上一篇文章中我们是直接播放的MP3文件,所以上一章节中对于MP3的播放,在pipeline中只有两个。即只有MP3到I2S
audio_pipeline_register(pipeline, mp3_decoder, "mp3"); audio_pipeline_register(pipeline, i2s_stream_writer, "i2s");
但是本章节我们的流程为 首先从SPIFFS 到 MP3 然后再到I2S 从而驱动音频播放。所以要增加额外的流水线步骤。增加第一级输入。
audio_pipeline_register(pipeline, file_stream, "file"); audio_pipeline_register(pipeline, mp3_decoder, "mp3"); audio_pipeline_register(pipeline, i2s_stream_writer, "i2s");
最后便是音频的播放和事件的监听处理 (这一部分不重要)
ESP_LOGI(TAG, "[2.5] Link it together file->mp3->i2s");
const char *link_tag[3] = {"file", "mp3", "i2s"};
audio_pipeline_link(pipeline, link_tag, 3);
ESP_LOGI(TAG, "[2.6] Set up event listener for end-of-stream");
audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
evt = audio_event_iface_init(&evt_cfg);
audio_pipeline_set_listener(pipeline, evt);
ESP_LOGI(TAG, "[ 3 ] Start audio_pipeline from SPIFFS: %s", file_path);
audio_pipeline_run(pipeline);
ESP_LOGI(TAG, "[ 4 ] Playing from SPIFFS (wait for completion)");
while (1)
{
audio_event_iface_msg_t msg;
esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
if (ret != ESP_OK)
{
continue;
}
if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
msg.cmd == AEL_MSG_CMD_REPORT_STATUS &&
(int)msg.data == AEL_STATUS_STATE_FINISHED)
{
ESP_LOGI(TAG, "Playback finished");
break;
}
}
ESP_LOGI(TAG, "[ 5 ] Stopping pipeline");
audio_pipeline_stop(pipeline);
audio_pipeline_wait_for_stop(pipeline);
audio_pipeline_terminate(pipeline);
audio_pipeline_remove_listener(pipeline);
audio_event_iface_destroy(evt);
ESP_LOGI(TAG, "[ 6 ] Done");
vTaskDelay(portMAX_DELAY);
}总结
上文主要打通了通过 SPIFFS - > 音频解码器 (MP3)-> I2S的全部流程,从而实现了音频的播放。 如果我们现在使用的是其他的文件系统,则需要检查ESP-ADF中是否支持对应的Stream传输,如果支持的话只需要更换pipeline中的第一个文件系统的输入即可从而可以实现各个文件系统的音频输入和播放。 下一篇文章我们将研究如果驱动和挂载SD卡。然后从SD卡中读取音频文件然后输入到pipeline中进行播放
代码已经提交到Github
我要赚赏金
