这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 微雪ESP32-P4开发板评测【七】使用ESP-ADF实现HTTP音频流播放

共1条 1/1 1 跳转至

微雪ESP32-P4开发板评测【七】使用ESP-ADF实现HTTP音频流播放

工程师
2026-01-20 15:05:07     打赏

简介

在上文中我们已经实现了很多种类的音频的播放形式,比如说从SD卡加载音频进行播放、或者是从微雪ESP32-P4开发板评测【六】使用SPIFFS文件系统结合ESP-ADF实现音频流读取并且播放。那么本章节将介绍如何通过HTTP流的方式,使其开发板可以播放在线音频的播放。

视频效果如下


1- 准备工作,我这里下载了一个Keil的注册机音乐,并且将其转码成了MP3的格式。

image.png

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

image.png

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

image.png

可以正常播放,并且记录上上述的URL

4- 使用ESP-IDF创建一个ADF的项目,选择Pipline_http_mp3

image.png需要注意的是,这个项目是仅仅对乐鑫的开发板支持的,如果我们想要在微雪的这款开发板上运行的话,需要一点小小的修改即:修改BSP的驱动。

5- 我们将原本我们已经移植好的BSP的音频驱动拷贝到当前的项目下。image.png

同时在上述menuconfig中来选择我们自己的开发板。并且修改wifi的账号和密码。

6 - 在idf组件管理器的yml中增加上ES8311的配置。

image.png

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

image.png

8 - 检查控制台的输出情况。image.png

已经开始播放经典的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);
}


完整代码附件

pipeline_http_mp3.zip


总结

使用ESP-ADF播放音频非常的方便,中间处理操作这个框架其实已经帮我们给处理好了。 所以并不需要额外的音频解码和编码等,对于不同的开发板而言只需要按照对应的音频驱动芯片来按照ADF的规范创建一个BSP即可,即可对源代码0修改的来完成我们想要的功能。 原本还是有一篇使用SD卡读取文件播放的,但是那篇文章不知道在哪里了。 如果能找到的话,我就把它发出来。找不到的话这个应该是音频播放的最后一篇了。接下来我们就该去研究研究语音识别相关的内容了。




关键词: ESP-IDF     ESP-ADF     HTTP stre    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]