ESP-ADF入门——从play_mp3_control入门adf

什么是ESP-ADF?

ESP-ADF是乐鑫基于自家的SDK——esp-idf开发的音频开发框架

创建第一个esp-adf工程

本文主要为了方便大家对esp-adf的了解,抛弃了官方的流程。将esp-adf作为idf工程的组件创建esp-adf的最简工程,并从中分析adf的使用方法。本章默认大家已经装好了esp-idf,如果有不会安装的可以看我的视频。如有不合理之处欢迎大家雅正。

如何优雅的下载github上的仓库并且下载子模块

我自己在gitee上上传了一个自己常用的脚本,原理是递归换源。把github换成国内源,这样方便我们去下载仓库及其子模块。话不多说,来看看如何下载esp-adf。

# 下载我的换源脚本
git clone https://gitee.com/geekheart/github_download_tools.git
# 使用脚本下载esp-adf及其子模块
python github_download_tools/gdt.py https://github.com/espressif/esp-adf.git -s

这样一来我们的esp-adf下载好了,esp-adf实际上自带一个esp-idf,这个是它推荐的版本,如果你目前的idf能够编译,那就不需要这个idf。

创建第一个adf工程

# 复制出最基础的例程
cp -r esp-adf/examples/get-started/play_mp3_control ./
# 进入例程文件夹中
cd play_mp3_control 
# 复制工程所需的全部组件
cp -r ../esp-adf/components/* ./components

adf使用有三种方法:

  1. adf的components放入idf的components里
  2. adf的components放入自己的components里
  3. 添加adf的components绝对路径为环境变量ADF_PATH

1,2两种方法需要删除掉工程文件夹下CMakeLists.txt中的include($ENV{ADF_PATH}/CMakeLists.txt)这一行

通过export.sh激活idf环境,然后idf.py build就编译完成我们的工程了。

分析play_mp3_control 工程

抛开components不看先看看主代码

/* Play mp3 file by audio pipeline
   with possibility to start, stop, pause and resume playback
   as well as adjust volume

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include "esp_log.h"
#include "audio_element.h"
#include "audio_pipeline.h"
#include "audio_event_iface.h"
#include "audio_mem.h"
#include "audio_common.h"
#include "i2s_stream.h"
#include "mp3_decoder.h"
#include "esp_peripherals.h"
#include "periph_touch.h"
#include "periph_adc_button.h"
#include "periph_button.h"
#include "board.h"

static const char *TAG = "PLAY_FLASH_MP3_CONTROL";

static struct marker
{
    int pos;              // 已读长度
    const uint8_t *start; // 开始指针
    const uint8_t *end;   // 结束指针
} file_marker;

// low rate mp3 audio
extern const uint8_t lr_mp3_start[] asm("_binary_music_16b_2c_8000hz_mp3_start");
extern const uint8_t lr_mp3_end[] asm("_binary_music_16b_2c_8000hz_mp3_end");

// medium rate mp3 audio
extern const uint8_t mr_mp3_start[] asm("_binary_music_16b_2c_22050hz_mp3_start");
extern const uint8_t mr_mp3_end[] asm("_binary_music_16b_2c_22050hz_mp3_end");

// high rate mp3 audio
extern const uint8_t hr_mp3_start[] asm("_binary_music_16b_2c_44100hz_mp3_start");
extern const uint8_t hr_mp3_end[] asm("_binary_music_16b_2c_44100hz_mp3_end");

/**
 * @brief 轮询三个音频文件
 *
 */
static void set_next_file_marker()
{
    static int idx = 0;

    switch (idx)
    {
    case 0:
        file_marker.start = lr_mp3_start;
        file_marker.end = lr_mp3_end;
        break;
    case 1:
        file_marker.start = mr_mp3_start;
        file_marker.end = mr_mp3_end;
        break;
    case 2:
        file_marker.start = hr_mp3_start;
        file_marker.end = hr_mp3_end;
        break;
    default:
        ESP_LOGE(TAG, "[ * ] Not supported index = %d", idx);
    }
    if (++idx > 2)
    {
        idx = 0;
    }
    file_marker.pos = 0;
}

/**
 * @brief 音频回调函数
 *
 * @param el 音频对象
 * @param buf 读取音频指针
 * @param len 读取音频长度
 * @param wait_time 等待时间
 * @param ctx 用户传递数据
 * @return int 下一步读取长度
 */
int mp3_music_read_cb(audio_element_handle_t el, char *buf, int len, TickType_t wait_time, void *ctx)
{
    int read_size = file_marker.end - file_marker.start - file_marker.pos; // 剩余未读长度
    if (read_size == 0)                                                    // 如果剩余长度为0,则返回读取完毕
    {
        return AEL_IO_DONE;
    }
    else if (len < read_size) // 如果剩余长度还足够则还读len长度,如果不够则读剩余长度
    {
        read_size = len;
    }
    memcpy(buf, file_marker.start + file_marker.pos, read_size); // 更新下一段读取的部分
    file_marker.pos += read_size;                                // 跟新接下来要读取的位置
    return read_size;                                            // 返回下一段读取的长度
}

void app_main(void)
{
    audio_pipeline_handle_t pipeline;                      // 设置管道对象
    audio_element_handle_t i2s_stream_writer, mp3_decoder; // 设置管道数据,i2s流,mp3流

    // 设置level等级
    esp_log_level_set("*", ESP_LOG_WARN);
    esp_log_level_set(TAG, ESP_LOG_INFO);

    ESP_LOGI(TAG, "[ 1 ] Start audio codec chip");
    audio_board_handle_t board_handle = audio_board_init();                                         // codec等模块初始化
    audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_BOTH, AUDIO_HAL_CTRL_START); // 初始化

    int player_volume;
    audio_hal_get_volume(board_handle->audio_hal, &player_volume); // 获取播放器音量

    ESP_LOGI(TAG, "[ 2 ] Create audio pipeline, add all elements to pipeline, and subscribe pipeline event");
    audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG(); // 设置默认的管道参数
    pipeline = audio_pipeline_init(&pipeline_cfg);                       // 创建管道

    ESP_LOGI(TAG, "[2.1] Create mp3 decoder to decode mp3 file and set custom read callback");
    mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();        // 设置默认的mp3解析流
    mp3_decoder = mp3_decoder_init(&mp3_cfg);                        // 初始化MP3解析流
    audio_element_set_read_cb(mp3_decoder, mp3_music_read_cb, NULL); //  此 API 允许应用程序为管道中的第一个 audio_element 设置读取回调,以允许管道与其他系统交互。每次音频元素需要处理数据时都会调用回调。

    ESP_LOGI(TAG, "[2.2] Create i2s stream to write data to codec chip");
    i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT(); // 设置默认的i2s流参数
    // i2s_cfg.type = AUDIO_STREAM_WRITER; // 设置i2s类型
    i2s_stream_writer = i2s_stream_init(&i2s_cfg); // 初始化i2s流

    ESP_LOGI(TAG, "[2.3] Register all elements to audio pipeline");
    audio_pipeline_register(pipeline, mp3_decoder, "mp3");       // 管道中注册mp3流
    audio_pipeline_register(pipeline, i2s_stream_writer, "i2s"); // 管道中注册i2s流

    ESP_LOGI(TAG, "[2.4] Link it together [mp3_music_read_cb]-->mp3_decoder-->i2s_stream-->[codec_chip]");
    const char *link_tag[2] = {"mp3", "i2s"};
    audio_pipeline_link(pipeline, link_tag, 2); // 按照link_tag的顺序链接数据流

    ESP_LOGI(TAG, "[ 3 ] Initialize peripherals");
    esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG(); // 设置默认外设参数
    esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);   // 初始化外设参数

    ESP_LOGI(TAG, "[3.1] Initialize keys on board");
    audio_board_key_init(set); // 初始化adc按钮

    ESP_LOGI(TAG, "[ 4 ] Set up  event listener");
    audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG(); // 设置默认事件监听参数
    audio_event_iface_handle_t evt = audio_event_iface_init(&evt_cfg); // 初始化事件监听器

    ESP_LOGI(TAG, "[4.1] Listening event from all elements of pipeline");
    audio_pipeline_set_listener(pipeline, evt); // 监听管道的事件

    ESP_LOGI(TAG, "[4.2] Listening event from peripherals");
    audio_event_iface_set_listener(esp_periph_set_get_event_iface(set), evt); // 监听外设事件

    ESP_LOGW(TAG, "[ 5 ] Tap touch buttons to control music player:");
    ESP_LOGW(TAG, "      [Play] to start, pause and resume, [Set] to stop.");
    ESP_LOGW(TAG, "      [Vol-] or [Vol+] to adjust volume.");

    ESP_LOGI(TAG, "[ 5.1 ] Start audio_pipeline");
    set_next_file_marker();       // 切换第一个音频
    audio_pipeline_run(pipeline); // 运行管道

    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.source == (void *)mp3_decoder && 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, "[ * ] Receive music info from mp3 decoder, sample_rates=%d, bits=%d, ch=%d",
                     music_info.sample_rates, music_info.bits, music_info.channels);
            // audio_element_setinfo(i2s_stream_writer, &music_info); // 设置音频信息
            i2s_stream_set_clk(i2s_stream_writer, music_info.sample_rates, music_info.bits, music_info.channels); // 通过music信息初始化i2s参数
            continue;
        }

        // 监听外设事件
        if ((msg.source_type == PERIPH_ID_TOUCH || msg.source_type == PERIPH_ID_BUTTON || msg.source_type == PERIPH_ID_ADC_BTN) && (msg.cmd == PERIPH_TOUCH_TAP || msg.cmd == PERIPH_BUTTON_PRESSED || msg.cmd == PERIPH_ADC_BUTTON_PRESSED))
        {
            // adc按钮对应功能
            if ((int)msg.data == get_input_play_id())
            {
                ESP_LOGI(TAG, "[ * ] [Play] touch tap event");
                audio_element_state_t el_state = audio_element_get_state(i2s_stream_writer); // 获取音频状态
                switch (el_state)
                {
                case AEL_STATE_INIT:
                    ESP_LOGI(TAG, "[ * ] Starting audio pipeline");
                    audio_pipeline_run(pipeline); // 运行管道
                    break;
                case AEL_STATE_RUNNING:
                    ESP_LOGI(TAG, "[ * ] Pausing audio pipeline");
                    audio_pipeline_pause(pipeline); // 暂停管道
                    break;
                case AEL_STATE_PAUSED:
                    ESP_LOGI(TAG, "[ * ] Resuming audio pipeline");
                    audio_pipeline_resume(pipeline); // 恢复管道
                    break;
                case AEL_STATE_FINISHED:
                    ESP_LOGI(TAG, "[ * ] Rewinding audio pipeline");
                    audio_pipeline_reset_ringbuffer(pipeline);             // 重置环形缓冲区
                    audio_pipeline_reset_elements(pipeline);               // 重置管道链接元素
                    audio_pipeline_change_state(pipeline, AEL_STATE_INIT); // 改变管道状态
                    set_next_file_marker();                                // 切换音频
                    audio_pipeline_run(pipeline);                          // 运行管道
                    break;
                default:
                    ESP_LOGI(TAG, "[ * ] Not supported state %d", el_state);
                }
            }
            else if ((int)msg.data == get_input_set_id())
            {
                ESP_LOGI(TAG, "[ * ] [Set] touch tap event");
                ESP_LOGI(TAG, "[ * ] Stopping audio pipeline");
                break;
            }
            else if ((int)msg.data == get_input_mode_id())
            {
                ESP_LOGI(TAG, "[ * ] [mode] tap event");
                audio_pipeline_stop(pipeline);             // 停止管道
                audio_pipeline_wait_for_stop(pipeline);    // 等待管道停止
                audio_pipeline_terminate(pipeline);        // 终止管道
                audio_pipeline_reset_ringbuffer(pipeline); // 重置环形缓冲区
                audio_pipeline_reset_elements(pipeline);   // 重置管道链接元素
                set_next_file_marker();                    // 切换音频
                audio_pipeline_run(pipeline);              // 运行管道
            }
            else if ((int)msg.data == get_input_volup_id())
            {
                ESP_LOGI(TAG, "[ * ] [Vol+] touch tap event");
                player_volume += 10;
                if (player_volume > 100)
                {
                    player_volume = 100;
                }
                audio_hal_set_volume(board_handle->audio_hal, player_volume); // 设置音量
                ESP_LOGI(TAG, "[ * ] Volume set to %d %%", player_volume);
            }
            else if ((int)msg.data == get_input_voldown_id())
            {
                ESP_LOGI(TAG, "[ * ] [Vol-] touch tap event");
                player_volume -= 10;
                if (player_volume < 0)
                {
                    player_volume = 0;
                }
                audio_hal_set_volume(board_handle->audio_hal, player_volume); // 设置音量
                ESP_LOGI(TAG, "[ * ] Volume set to %d %%", player_volume);
            }
        }
    }

    ESP_LOGI(TAG, "[ 6 ] Stop audio_pipeline");
    audio_pipeline_stop(pipeline);                          // 停止管道
    audio_pipeline_wait_for_stop(pipeline);                 // 等待管道停止完毕
    audio_pipeline_terminate(pipeline);                     // 结束管道
    audio_pipeline_unregister(pipeline, mp3_decoder);       // 卸载mp3流
    audio_pipeline_unregister(pipeline, i2s_stream_writer); // 卸载i2s流

    /* Terminate the pipeline before removing the listener */
    audio_pipeline_remove_listener(pipeline); // 移除管道事件监听

    /* Make sure audio_pipeline_remove_listener is called before destroying event_iface */
    audio_event_iface_destroy(evt); // 销毁事件监听

    /* Release all resources */
    audio_pipeline_deinit(pipeline);         // 注销管道
    audio_element_deinit(i2s_stream_writer); // 注销i2s流
    audio_element_deinit(mp3_decoder);       // 注销mp3流
}

分析

这里表现的东西非常多,我们从两个方面去看,一个是音频,一个是事件状态机。
音频方面的主要思想就是以pipeline管道形式为主,所有的数据都是以stream的形式从管道的一头流向另一头。
这里的核心部分就是i2s_stream_writer(i2s流)和mp3_decode(mp3流)。数据是从我们编译进去的mp3数组,进入mp3_music_read_cb回调函数中,通过回调函数进行音频的分段读取,然后通过管道的连接,流向了i2s,最终从codec芯片输出经过功放最后就是喇叭出来的音乐。
事件状态机方面是通过audio_event_iface_handle_t这个对象去监听外设事件和音频的管道事件。从事件上来看,音频监听的是mp3解析头的内容,解析出来后会产生AEL_MSG_CMD_REPORT_MUSIC_INFO事件,监听到msg.source_type产生这个事件后,通过audio_element_getinfo获取音频信息,然后便可以设置i2s的相关参数。外设事件监听,这里主要是初始化了一个adc_btn,事件则是会发出PERIPH_ID_ADC_BTN,不同的是adc事件是有msg.data的内容,这里主要是分辨出不同的按钮。以便执行不同内容的东西。外设这里可以从esp_peripherals文件夹下查看,支持了很多有意思的东西,这些支持的外设都有和音频互斥,在程序中保证了其线程的安全性。

吐槽

这里有几个点,我觉得是多此一举的举措,第一就是在设置默认i2s参数时,有个修改i2s类型为AUDIO_STREAM_WRITER的举措,这里看了一下底层默认的配置就是AUDIO_STREAM_WRITER,这里操作,笔者认是多此一举了。
另外,在打印音频信息的时候,又设置了一遍信息,这里未做任何修改,所以也很迷惑,估计是为了展示有这个api可以调用。

分析components

除了我i之前为了方便分析把adf的components全部引入工程之外,其例程自带的一个components是my_board。这里用于介绍如何在自己的板子上对接adf的内容。

CMakeLists.txt入手分析

set(COMPONENT_REQUIRES)
set(COMPONENT_PRIV_REQUIRES audio_sal audio_hal esp_dispatcher esp_peripherals display_service)

if(CONFIG_AUDIO_BOARD_CUSTOM)
message(STATUS "Current board name is " CONFIG_AUDIO_BOARD_CUSTOM)
list(APPEND COMPONENT_ADD_INCLUDEDIRS ./my_board_v1_0 ./my_codec_driver)
set(COMPONENT_SRCS
./my_board_v1_0/board.c
./my_board_v1_0/board_pins_config.c
./my_codec_driver/new_codec.c
)
endif()

register_component()

IF (IDF_VERSION_MAJOR GREATER 3)
idf_component_get_property(audio_board_lib audio_board COMPONENT_LIB)
set_property(TARGET ${audio_board_lib} APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${COMPONENT_LIB})

ELSEIF (IDF_VERSION_MAJOR EQUAL 3)
set_property(TARGET idf_component_audio_board APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES  $<TARGET_PROPERTY:${COMPONENT_TARGET},INTERFACE_INCLUDE_DIRECTORIES>)

ENDIF (IDF_VERSION_MAJOR GREATER 3)

这里看一下CMakeLists,其实adf的CMakeLists写法更趋近于cmake的写法,而idf的那种写法则是调用了idf封装好的编译指令,如果想更接近原生的cmake语法,建议还是用adf这种。
这里if(CONFIG_AUDIO_BOARD_CUSTOM)就需要我们的menuconfig的Audio HAL必须要设置到Custom audio board,才能进行编译。我们可以依据此基础之上进行修改,比如我们想修改成自己的codec芯片,则需要对接好audio_hal_func_t里的所有回调函数(为啥my_board里的my_codec_driver是空的?除此之外我发现es7481也是空的,官方这么偷懒嘛),然后再在audio_board_codec_init里注册好audio_hal,就完成了codec的驱动对接。再比如adc按钮这里,我们对接好periph_adc_button_init的参数然后esp_periph_start它就对接完毕了。

  • 10
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
引用: 在Ubuntu 20.04.4 LTS操作系统中,我已经安装了ESP-idf开发环境并使用了一段时间。现在我想安装esp-adf音频开发框架,但遇到了问题。可以在https://***/index.html找到相关文档。 引用: LinkGUI™ Air E32是一个开发平台,基于ESP32 WROVER芯片组,可以实现完美的图形交互体验。它将GUI系统嵌入到乐鑫开源的ESP-ADF/ESP-IDF开发环境中,提供了丰富的UI交互功能。LinkGUI™ Air E32平台由ESP32 WROVER芯片、LCD、按键、T卡插槽、喇叭插槽、麦克风和扩展接口组成。它的SDK以基于ESP-ADF的LinkGUI图形系统为核心,与ESP-ADF开发环境无缝连接,实现了WIFI/BT/音频和高级图形交互的功能。 根据您的问题,您想了解在Ubuntu操作系统上使用esp-adf和lvgl。首先,esp-adf是一个音频开发框架,用于在ESP32芯片上开发音频应用程序。它提供了丰富的音频功能和示例,可以实现音频播放、录制、语音识别等功能。 而lvgl是一个开源的图形库,用于创建嵌入式设备上的用户界面。它提供了丰富的图形元素和交互功能,可以实现按钮、滑块、进度条、列表等常见的UI元素。 在Ubuntu操作系统上,您可以按照ESP-ADF文档中提供的步骤安装和配置esp-adf开发环境。然后,您可以使用lvgl库开发具有图形界面的应用程序。您可以参考lvgl的官方文档和示例来学习如何使用lvgl库进行UI设计和交互。 总结起来,esp-adf是一个音频开发框架,lvgl是一个图形库。您可以在Ubuntu操作系统上使用esp-adf开发音频应用程序,并结合lvgl库创建图形界面。希望这能帮助到您。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Ubuntn环境下安装ESP 音频框架时出现Failed to resolve component ‘jsmn‘.问题(已解决)](https://blog.csdn.net/qq_41601311/article/details/124293303)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [基于ESP-ADF的图形系统](https://blog.csdn.net/skdev/article/details/120765588)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值