简介
在上文中我们已经实现了很多种类的音频的播放形式,比如说从SD卡加载音频进行播放、或者是从微雪ESP32-P4开发板评测【六】使用SPIFFS文件系统结合ESP-ADF实现音频流读取并且播放。那么本章节将介绍如何通过HTTP流的方式,使其开发板可以播放在线音频的播放。
视频效果如下
1- 准备工作,我这里下载了一个Keil的注册机音乐,并且将其转码成了MP3的格式。

2- 然后使用Python的http server代理了这个文件夹

3- 然后检查是否可以直接通过浏览器打开这个音频文件。

可以正常播放,并且记录上上述的URL
4- 使用ESP-IDF创建一个ADF的项目,选择Pipline_http_mp3
需要注意的是,这个项目是仅仅对乐鑫的开发板支持的,如果我们想要在微雪的这款开发板上运行的话,需要一点小小的修改即:修改BSP的驱动。
5- 我们将原本我们已经移植好的BSP的音频驱动拷贝到当前的项目下。
同时在上述menuconfig中来选择我们自己的开发板。并且修改wifi的账号和密码。
6 - 在idf组件管理器的yml中增加上ES8311的配置。

7 - 替换原本的播放链接为我们自定义的链接,然后进行编译和烧录

8 - 检查控制台的输出情况。
已经开始播放经典的Keil注册机音乐啦!
下述代码是通过AI修改的对播放的控制,经过测试功能无误!
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "sdkconfig.h"
#include "audio_element.h"
#include "audio_pipeline.h"
#include "audio_event_iface.h"
#include "audio_common.h"
#include "http_stream.h"
#include "i2s_stream.h"
#include "mp3_decoder.h"
#include "esp_peripherals.h"
#include "periph_wifi.h"
#include "board.h"
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0))
#include "esp_netif.h"
#else
#include "tcpip_adapter.h"
#endif
static const char *TAG = "HTTP_MP3_EXAMPLE";
/* 播放控制命令枚举 */
typedef enum
{
PLAY_CMD_PLAY = 0,
PLAY_CMD_PAUSE = 1,
PLAY_CMD_RESUME = 2,
PLAY_CMD_STOP = 3,
PLAY_CMD_SET_URL = 4,
} play_command_t;
/* 播放控制消息结构体 */
typedef struct
{
play_command_t cmd;
void *data; /* 用于存储URL等数据 */
} play_control_msg_t;
/* 全局变量 */
static QueueHandle_t play_control_queue = NULL;
static TaskHandle_t play_task_handle = NULL;
static TaskHandle_t control_task_handle = NULL;
/* 播放任务 - 处理音频管道和控制命令 */
static void play_task(void *pvParameters)
{
audio_pipeline_handle_t pipeline;
audio_element_handle_t http_stream_reader, i2s_stream_writer, mp3_decoder;
audio_event_iface_handle_t evt;
play_control_msg_t msg;
int is_running = 0;
ESP_LOGI(TAG, "[ 1 ] 启动音频编解码芯片");
audio_board_handle_t board_handle = audio_board_init();
audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_DECODE, AUDIO_HAL_CTRL_START);
ESP_LOGI(TAG, "[2.0] 创建播放音频管道");
audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
pipeline = audio_pipeline_init(&pipeline_cfg);
mem_assert(pipeline);
ESP_LOGI(TAG, "[2.1] 创建HTTP流来读取数据");
http_stream_cfg_t http_cfg = HTTP_STREAM_CFG_DEFAULT();
http_stream_reader = http_stream_init(&http_cfg);
ESP_LOGI(TAG, "[2.2] 创建I2S流向编解码芯片写入数据");
i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
i2s_cfg.type = AUDIO_STREAM_WRITER;
i2s_stream_writer = i2s_stream_init(&i2s_cfg);
ESP_LOGI(TAG, "[2.3] 创建MP3解码器来解码MP3文件");
mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
mp3_decoder = mp3_decoder_init(&mp3_cfg);
ESP_LOGI(TAG, "[2.4] 将所有元素注册到音频管道");
audio_pipeline_register(pipeline, http_stream_reader, "http");
audio_pipeline_register(pipeline, mp3_decoder, "mp3");
audio_pipeline_register(pipeline, i2s_stream_writer, "i2s");
ESP_LOGI(TAG, "[2.5] 链接http_stream-->mp3_decoder-->i2s_stream-->[编解码芯片]");
const char *link_tag[3] = {"http", "mp3", "i2s"};
audio_pipeline_link(pipeline, &link_tag[0], 3);
ESP_LOGI(TAG, "[2.6] 设置默认URI");
audio_element_set_uri(http_stream_reader, "http://192.168.1.148:8000/videoplayback.mp3");
/* 设置事件监听器 */
ESP_LOGI(TAG, "[ 3 ] 设置事件监听器");
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);
/* 主播放控制循环 */
BaseType_t xResult;
while (1)
{
/* 检查是否有新的控制命令,不阻塞 */
xResult = xQueueReceive(play_control_queue, &msg, 0);
if (xResult == pdPASS)
{
ESP_LOGI(TAG, "[ 控制 ] 收到命令: %d", msg.cmd);
switch (msg.cmd)
{
case PLAY_CMD_PLAY:
if (!is_running)
{
ESP_LOGI(TAG, "[ 控制 ] 开始播放");
audio_pipeline_run(pipeline);
is_running = 1;
}
break;
case PLAY_CMD_PAUSE:
if (is_running)
{
ESP_LOGI(TAG, "[ 控制 ] 暂停播放");
audio_pipeline_pause(pipeline);
is_running = 0;
}
break;
case PLAY_CMD_RESUME:
if (!is_running)
{
ESP_LOGI(TAG, "[ 控制 ] 继续播放");
audio_pipeline_resume(pipeline);
is_running = 1;
}
break;
case PLAY_CMD_STOP:
if (is_running)
{
ESP_LOGI(TAG, "[ 控制 ] 停止播放");
audio_pipeline_stop(pipeline);
audio_pipeline_wait_for_stop(pipeline);
is_running = 0;
}
break;
case PLAY_CMD_SET_URL:
if (msg.data)
{
if (is_running)
{
audio_pipeline_stop(pipeline);
audio_pipeline_wait_for_stop(pipeline);
is_running = 0;
}
ESP_LOGI(TAG, "[ 控制 ] 设置新URL: %s", (char *)msg.data);
audio_element_set_uri(http_stream_reader, (char *)msg.data);
free(msg.data);
}
break;
default:
ESP_LOGW(TAG, "[ 控制 ] 未知命令: %d", msg.cmd);
break;
}
}
/* 处理音频事件 */
if (is_running)
{
audio_event_iface_msg_t audio_msg;
esp_err_t ret = audio_event_iface_listen(evt, &audio_msg, 100);
if (ret == ESP_OK)
{
if (audio_msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
audio_msg.source == (void *)mp3_decoder &&
audio_msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO)
{
audio_element_info_t music_info = {0};
audio_element_getinfo(mp3_decoder, &music_info);
ESP_LOGI(TAG, "[ * ] 音乐信息 - 采样率=%d, 比特数=%d, 通道数=%d",
music_info.sample_rates, music_info.bits, music_info.channels);
i2s_stream_set_clk(i2s_stream_writer, music_info.sample_rates,
music_info.bits, music_info.channels);
}
/* 当播放结束 */
if (audio_msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
audio_msg.source == (void *)i2s_stream_writer &&
audio_msg.cmd == AEL_MSG_CMD_REPORT_STATUS &&
(((int)audio_msg.data == AEL_STATUS_STATE_STOPPED) ||
((int)audio_msg.data == AEL_STATUS_STATE_FINISHED)))
{
ESP_LOGW(TAG, "[ * ] 播放完成");
is_running = 0;
}
}
}
else
{
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
/* 清理资源 */
audio_pipeline_stop(pipeline);
audio_pipeline_wait_for_stop(pipeline);
audio_pipeline_terminate(pipeline);
audio_pipeline_unregister(pipeline, http_stream_reader);
audio_pipeline_unregister(pipeline, i2s_stream_writer);
audio_pipeline_unregister(pipeline, mp3_decoder);
audio_pipeline_remove_listener(pipeline);
audio_event_iface_destroy(evt);
audio_pipeline_deinit(pipeline);
audio_element_deinit(http_stream_reader);
audio_element_deinit(i2s_stream_writer);
audio_element_deinit(mp3_decoder);
vTaskDelete(NULL);
}
/* 控制输入任务 - 模拟按键输入,可替换为实际的GPIO/按键检测 */
static void control_task(void *pvParameters)
{
play_control_msg_t cmd_msg;
int count = 0;
ESP_LOGI(TAG, "[ 控制任务 ] 已启动 - 每30秒模拟用户输入");
vTaskDelay(5000 / portTICK_PERIOD_MS); /* 等待WiFi连接 */
while (1)
{
count++;
switch (count)
{
case 1:
/* 30秒后:开始播放 */
ESP_LOGI(TAG, "[ 控制任务 ] 发送播放命令");
cmd_msg.cmd = PLAY_CMD_PLAY;
cmd_msg.data = NULL;
xQueueSend(play_control_queue, &cmd_msg, 0);
break;
case 4:
/* 120秒后:暂停 */
ESP_LOGI(TAG, "[ 控制任务 ] 发送暂停命令");
cmd_msg.cmd = PLAY_CMD_PAUSE;
cmd_msg.data = NULL;
xQueueSend(play_control_queue, &cmd_msg, 0);
break;
case 6:
/* 180秒后:继续 */
ESP_LOGI(TAG, "[ 控制任务 ] 发送继续命令");
cmd_msg.cmd = PLAY_CMD_RESUME;
cmd_msg.data = NULL;
xQueueSend(play_control_queue, &cmd_msg, 0);
break;
case 10:
/* 300秒后:停止 */
ESP_LOGI(TAG, "[ 控制任务 ] 发送停止命令");
cmd_msg.cmd = PLAY_CMD_STOP;
cmd_msg.data = NULL;
xQueueSend(play_control_queue, &cmd_msg, 0);
count = 0; /* 重置计数器重新循环 */
break;
}
vTaskDelay(30000 / portTICK_PERIOD_MS); /* 每30秒检查一次 */
}
vTaskDelete(NULL);
}
void app_main(void)
{
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES)
{
// NVS分区已截断,需要被擦除
// 重试nvs_flash_init
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0))
ESP_ERROR_CHECK(esp_netif_init());
#else
tcpip_adapter_init();
#endif
esp_log_level_set("*", ESP_LOG_WARN);
esp_log_level_set(TAG, ESP_LOG_DEBUG);
ESP_LOGI(TAG, "[ 0 ] 创建播放控制队列");
play_control_queue = xQueueCreate(10, sizeof(play_control_msg_t));
if (play_control_queue == NULL)
{
ESP_LOGE(TAG, "创建播放控制队列失败");
return;
}
ESP_LOGI(TAG, "[ 1 ] 启动并等待Wi-Fi网络");
esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);
periph_wifi_cfg_t wifi_cfg = {
.wifi_config.sta.ssid = CONFIG_WIFI_SSID,
.wifi_config.sta.password = CONFIG_WIFI_PASSWORD,
};
esp_periph_handle_t wifi_handle = periph_wifi_init(&wifi_cfg);
esp_periph_start(set, wifi_handle);
periph_wifi_wait_for_connected(wifi_handle, portMAX_DELAY);
ESP_LOGI(TAG, "[ 2 ] 创建播放任务");
xTaskCreate(play_task, "play_task", 8192, NULL, 5, &play_task_handle);
ESP_LOGI(TAG, "[ 3 ] 创建控制任务");
xTaskCreate(control_task, "control_task", 4096, NULL, 4, &control_task_handle);
ESP_LOGI(TAG, "[ 4 ] 所有任务创建成功");
/* 主任务可以持续监控或进行其他操作 */
while (1)
{
vTaskDelay(5000 / portTICK_PERIOD_MS);
/* 这里可以添加额外的监控逻辑 */
}
/* 清理资源 - 该代码通常不会执行,除非任务出错 */
esp_periph_set_stop_all(set);
esp_periph_set_destroy(set);
}完整代码附件
总结
使用ESP-ADF播放音频非常的方便,中间处理操作这个框架其实已经帮我们给处理好了。 所以并不需要额外的音频解码和编码等,对于不同的开发板而言只需要按照对应的音频驱动芯片来按照ADF的规范创建一个BSP即可,即可对源代码0修改的来完成我们想要的功能。 原本还是有一篇使用SD卡读取文件播放的,但是那篇文章不知道在哪里了。 如果能找到的话,我就把它发出来。找不到的话这个应该是音频播放的最后一篇了。接下来我们就该去研究研究语音识别相关的内容了。
我要赚赏金
