什么是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 pipelinewith possibility to start, stop, pause and resume playbackas well as adjust volumeThis example code is in the Public Domain (or CC0 licensed, at your option.)Unless required by applicable law or agreed to in writing, thissoftware is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES ORCONDITIONS 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它就对接完毕了。

ESP-ADF入门——从play_mp3_control入门adf相关推荐

  1. Python从入门到精通 - 入门篇 (下)

    上一讲回顾:Python从入门到精通 - 入门篇 (上) 接着上篇继续后面两个章节,函数和解析式. 4 函数 Python 里函数太重要了 (说的好像在别的语言中函数不重要似的).函数的通用好处就不用 ...

  2. python快速编程入门课后简答题答案-编程python入门 编程python入门课后习题

    编程python入门 编程python入门课后习题 米粒妈咪课堂小编整理了填空.选择.判断等一些课后习题答案,供大家参考学习. 第一章 一.填空题 Python是一种面向对象的高级语言. Python ...

  3. adf开发_了解ADF生命周期中的ADF绑定

    adf开发 在这篇文章中,我将重点介绍ADF绑定层,并探讨当最初从浏览器请求带有一些数据的ADF页面时它如何工作. Oracle ADF提供了自己的JSF生命周期扩展版. 实际上,ADF扩展了标准的J ...

  4. 了解ADF生命周期中的ADF绑定

    在本文中,我将重点介绍ADF绑定层,并探讨当最初从浏览器请求带有某些数据的ADF页面时,它如何工作. Oracle ADF提供了自己的JSF生命周期扩展版. 实际上,ADF扩展了标准JSF生命周期实现 ...

  5. 半小时入门MATLAB编程入门基础知识:

    https://learnxinyminutes.com/docs/zh-cn/matlab-cn/ 半小时入门MATLAB编程入门基础知识: % 以百分号作为注释符 %{ 多行注释 可以 这样 表示 ...

  6. python编程入门指南-编程入门指南

    编程入门指南 ----------------------------------------------- 编程入门指南 v1.5 --- https://zhuanlan.zhihu.com/p/ ...

  7. flink入门_Flink从入门到放弃-入门篇

    大数据成神之路: 点我去成神之路系列目录^_^ Java高级特性增强-集合 Java高级特性增强-多线程 Java高级特性增强-Synchronized Java高级特性增强-volatile Jav ...

  8. Apache NIFI入门(读完即入门)

    Apache NIFI入门(读完即入门) 编辑人(全网同名):酷酷的诚 邮箱:zhangchengk@foxmail.com 我将在本文中介绍: 什么是ApacheNIFI,应在什么情况下使用它,理解 ...

  9. 推荐系统从入门到接着入门

    本文转载自:推荐系统从入门到接着入门-张小磊(北京交通大学 计算机科学与技术博士在读) 推荐系统从入门到接着入门 一.前言 二.简介 搜索引擎 推荐引擎 三.所属领域 四.会议介绍 五.推荐系统分类 ...

最新文章

  1. 前端中的this,指的是什么?
  2. Kubernetes实战[1]: 基于kubernetes构建Docker集群环境实战
  3. JDK1.8源码(三)——java.lang.String 类
  4. nodejs新建服务器
  5. service mesh istio-0.8安装测试
  6. SQL学习笔记之存储过程的编写
  7. Vue 调试工具 vue-devtools 安装及使用
  8. 关于TCP协议的几个问题
  9. python count函数用法 comm_Python学习第六天课后练习案例 (主要针对的内容是python函数的定义和使用)...
  10. 牛客网编程题07--提取不重复的整数
  11. [WildPackets.OmniPeek].OmniPeek.4.0.1
  12. 利用syslinux制作Dos、WinPE、Slax Linux集成u盘
  13. vim编辑器及目录结构
  14. linux硬盘支持fat32,Linux下,挂载windows管理格式的FAT32/NTFS 硬盘
  15. 上海南京路步行街向全球征集标识Logo及吉祥物设计
  16. [LOJ6198] 谢特(sam+字典树合并)
  17. 【视频】主成分分析PCA降维方法和R语言分析葡萄酒可视化实例|数据分享
  18. C#(三十二)之Windows绘图
  19. 都23年了你还记得渐进式框架是什么意思吗
  20. Android点选下拉列表框选项,获取选项内容

热门文章

  1. spectre13 matlab,惠普全新幽灵系列Spectre 13
  2. 中信计算机管理中心,2019广东省中信银行信用卡中心总部IT系统运营管理岗社会招聘...
  3. 用Excel来将一行分隔成多行
  4. RPA自动办公02——Uibot界面元素选择
  5. 中国各省人口,面积排名
  6. 免费好用的短视频去水印工具
  7. MFC程序最小化到托盘
  8. youtube 可以下载一些英语
  9. ffmpeg绿幕抠图原理解析
  10. Python用百度AI识别车牌号教程(超详细)