这个笔记的只是用于个人对于学习esp-adf的记录,如有错误纯属正常
ESP-ADF 在 ESP-IDF(乐鑫物联网开发框架,广泛运用于 ESP32 的 SDK)的基础上开发而成,具有高度的灵活性,既可作为一整套应用方案,面向配网、OTA (Over The Air) 等各类应用场景,亦可作为开发平台,供开发人员搭建各类定制化应用场景。
ESP-ADF 具有一系列丰富的功能特色,涵盖编解码器、发送端和接收端音频流、管线化支持、唤醒词引擎,以及其他各类服务和控制等。
乐鑫音频开发框架:
支持音频格式:MP3、AAC、WAV、OGG、AMR、TS、OPUS、SPEEX 等
支持 EQ、Mixer、Resample 等音效处理功能
多音频播放来源:HTTP、HLS (HTTP Live)、SD 卡、Bluetooth A2DP/HFP
支持多媒体交互:DLNA、Airplay、微信和 Internet radio 等
云端语音接入:Alexa、DuerOS、Turing、IFLYTEK、TmallGenie、RooBo 等
ESP-ADF 应用场景包括:智能音箱、语音对讲机、语音播报机,以及其他音频类解决方案,如语音故事机和点读机。
此文来自对
https://docs.espressif.com/projects/esp-adf/zh_CN/latest/api-reference/index.html
乐鑫关于esp-adf的说明文档整理而来,由于本人英文水平有限特此将文档整理出来为了以后学习使用。在这其中也添加了我个人学习esp-adf的心得。
1. 整体框架介绍
根据上面图可以看出,esp-adf 底层是调用esp-idf框架,esp-idf是乐鑫最早推出的基于esp32的物联网软件框架。
esp-adf是面向对象的思想实现的,整个框架分层处理,保证每层都有相对的独立性。每个具体功能都定义成一个模块,最后为上层应用提供统一的接口。
在esp-adf中最低层应该是硬件的驱动和功能的实现,包括:音频3款音频编解码器芯片驱动(ESP8388,ES8374,ZL38063)然后就是诸如SD卡,LED,wifi等软件的基于esp-idf的重新封装。 在这之上则是软件个功能的实现(软件编解码MP3,wav,amr等,DLNA的协议,REcorder,Player )等。在这之上就是element(元素),stream(数据流),pipeline(管道),然后在这上面就是软件层具体的协议实现了包括baidu DueroOS,amazon Alexa等。
2. esp-adf运行机制
esp-adf运行主要是基于pipeline运行,每个pipeline中最基本的运行单元就是element,每个element之间靠stream传送音频数据。 这个是esp-adf提供的例程play_mp3的运行示意图。
其中MP3 decoder 就是一个element,而I2S stream就是一个stream。
我查看源码其中pipeline是通过链表方式实现的,他没有具体的功能操作,只是将pipeline中的内容链接在一起。 而element 和 streamm 是基于freeRTOS的task实现的,也就是说当你开始运行一个pipeline时,他是同时启动了几个 freeRTOS任务函数,每个任务通过freertos自带的队列、信号量,互斥体等机制实现数据的传输和消息的传递。
3. esp-adf 支持的elements和stream
从图中可以看到esp-adf 现在支持的音频数据流有i2s,http,fatfs,raw,spifss。
* i2s是通过操作i2s接口控制硬件编解码器的。
* http是通过http协议将音频数据发送到远程服务器中。
* fatfs则是实现了fatfs文件系统,一般是用于操作SD卡读写音频文件。
* raw则是一种数据传输流,本身没有功能,只是负责将音频传送到下一个element。
* spifss是一种基于flash的文件系统,可以通过它对flash以为文件系统方式操对音频文件进行读写。
实际在源码中还有另外两种数据流algorithm stream和tone stream。algorithm stream是回声处理,唤醒词处理加入到里面数据流,tone stream则是另外一种flash操作方法。
4. 例程分析
先通过一个最简单的例程,来说明一下esp-adf的工作机制,以esp-adf/exampele/get-started/play_mp3 为例,他实现的功能就是播放一段flash中mp3格式的音频文件。
/*定义一个pipeline管道*/
audio_pipeline_handle_t pipeline;
/*定义两个音频元素,一个用于mp3解码,一个用于将音频数据通过i2s传送硬件播放*/
audio_element_handle_t i2s_stream_writer, mp3_decoder;
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();
/*使能编解码器*/
audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_BOTH, AUDIO_HAL_CTRL_START);
ESP_LOGI(TAG, "[ 2 ] Create audio pipeline, add all elements to pipeline, and subscribe pipeline event");
/*将pipeline设置成默认配置*/
audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
/*初始化pipeline*/
pipeline = audio_pipeline_init(&pipeline_cfg);
mem_assert(pipeline);
ESP_LOGI(TAG, "[2.1] Create mp3 decoder to decode mp3 file and set custom read callback");
/*将mp3_decoder 配置成默认配置*/
mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
/*初始化 mp3 解码器*/
mp3_decoder = mp3_decoder_init(&mp3_cfg);
audio_element_set_read_cb(mp3_decoder, mp3_music_read_cb, NULL);
ESP_LOGI(TAG, "[2.2] Create i2s stream to write data to codec chip");
/*配置i2s_stream */
i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
i2s_cfg.type = AUDIO_STREAM_WRITER;
i2s_cfg.i2s_config.sample_rate = 48000;
/*初始化 i2s_stream*/
i2s_stream_writer = i2s_stream_init(&i2s_cfg);
ESP_LOGI(TAG, "[2.3] Register all elements to audio pipeline");
/*将mp3_decoder 添加到pipeline 中*/
audio_pipeline_register(pipeline, mp3_decoder, "mp3");
/*将i2s_stream 添加到pipeline 中*/
audio_pipeline_register(pipeline, i2s_stream_writer, "i2s");
ESP_LOGI(TAG, "[2.4] Link it together [mp3_music_read_cb]-->mp3_decoder-->i2s_stream-->[codec_chip]");
/*将两个elements链接在一起*/
audio_pipeline_link(pipeline, (const char *[]) {"mp3", "i2s"}, 2);
ESP_LOGI(TAG, "[ 3 ] Set up event listener");
/*创建监听事件,用于监听pipeline中的标志变化*/
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, "[3.1] Listening event from all elements of pipeline");
audio_pipeline_set_listener(pipeline, evt);
ESP_LOGI(TAG, "[ 4 ] Start audio_pipeline");
/*运行pipeline*/
audio_pipeline_run(pipeline);
/*audio_pipeline_run执行之后,同时开始运行两个audio_element 任务,mp3_decode将读取到mp3音频传输给i2s_steam ,i2s_stream 通过控制编辑码芯片将音频播放出来*/
5. ringbuf 和event interface
event interface
在上一个例程中出现了新的类型 audio_event_iface_handle_t 音频事件接口
audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
audio_event_iface_handle_t evt = audio_event_iface_init(&evt_cfg);
audio_pipeline_set_listener(pipeline, evt);
这个数据类型的作用是监听pipeline运行的的发生事件。消息事件是通过消息队列实现的,使用audio_event_iface_init(&evt_cfg);
完成创建队列,然后通过audio_pipeline_set_listener(pipeline, evt); 将队列指针与pipeline中的队列指针关联,以达到通过获取这个消息队列内容就可以获取整个pipeline的目的。在实际使用过程中,在主循环中循环调用 audio_event_iface_listen获取当前pipeline的消息。
while(1){
...
audio_event_iface_msg_t msg;
esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
...
}
ringbuffer
ringbuffer是一种环形缓冲区,这种缓冲区不仅用作数据缓冲同样也用于连接 audio element ,没element 向ringbuffer请求数据时都会导致ringbuffer任务阻塞,直到ringbufer中的数据可以使用这个任务才可以继续执行。
6. 结束
之前的esp-idf教程是想到那写到那根本没有学习路径可言(之后还是会这样),因此决定写esp-adf笔记之前立下flag,以防写着写着忘记了(也是提醒自己不要放弃)。
esp-adf概述介绍
根据官方提供的文档撰写api 参考手册
找出几个比较有代表性的例程分析相关代码
自己实现一个esp-adf 项目(具体还没想好) 在这个计划之外还会时不时的上传当前使用esp-adf的一些心得体会。
欢迎关注我的个人网站:zwww.zcxbb.com
知乎专栏:物联网开发入门 - 知乎 (zhihu.com)