esp-adf框架笔记 1 概述

这个笔记的只是用于个人对于学习esp-adf的记录,如有错误纯属正常

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 应用场景包括:智能音箱、语音对讲机、语音播报机,以及其他音频类解决方案,如语音故事机和点读机。

esp-adf API介绍

此文来自对

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)

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>