当前位置:网站首页>[005] [ESP32开发笔记] ADF基本框架
[005] [ESP32开发笔记] ADF基本框架
2022-06-09 04:32:00 【柯西的彷徨】
1 ADF整体框架

esp-adf主要是基于pipeline运行,每个pipeline中最基本的运行单元就是element,每个element都由一个ringbuffer连接,每个element之间靠stream传送音频数据(stream∈element)。
例如,将MP3解码器和I2S流两个元素添加进管道,解码器的输入是MP3文件数据流,I2S流将解码后的音频输出到音频解码器芯片(Codec chip):
pipeline通过链表管理,即每个元素通过链表连接在一起,element和stream基于FreeRTOS的任务实现,即运行pipeline同时会启动了几个任务,基于队列、信号量、互斥体等机制实现数据的传输和消息的传递。
pipeline是adf实现音频处理的基础。可以将
pipeline看作是流水线。音频数据从一头进,从另一头出。element是流水线上的工人,负责加工音频数据。event是监听流水线上所有工人的情况,可用户以通过msg得知。每个element都是一个任务。
esp-adf 支持的elements和stream:
- i2s是通过操作i2s接口控制硬件编解码器的。
- http是通过http协议将音频数据发送到远程服务器中。
- fatfs则是实现了fatfs文件系统,一般是用于操作SD卡读写音频文件。
- raw则是一种数据传输流,本身没有功能,只是负责将音频传送到下一个element。
- spifss是一种基于flash的文件系统,可以通过它对flash以为文件系统方式操对音频文件进行读写。
源码中还有另外两种数据流algorithm stream和tone stream。algorithm stream是回声处理,唤醒词处理加入到里面数据流,tone stream则是另外一种flash操作方法。
2 音频元素
每个解码器decoder、编码器encoder、滤波器filter、输入流input stream以及输出流output stream都属于音频元素。
主要功能为获取输入的数据,对其进行处理,然后输出到下一个,每个元素作为单独的任务运行,为了能够监听控制从输入、处理阶段到输出的数据,有7种可用的回调函数:open、seek、process、close、destroy、read 和 write。例如MP3 解码器正在使用打开、处理、关闭和销毁回调函数。
2.1 元素状态
typedef enum {
AEL_STATE_NONE = 0,
AEL_STATE_INIT = 1,
AEL_STATE_INITIALIZING = 2,
AEL_STATE_RUNNING = 3,
AEL_STATE_PAUSED = 4,
AEL_STATE_STOPPED = 5,
AEL_STATE_FINISHED = 6,
AEL_STATE_ERROR = 7
} audio_element_state_t;
2.2 元素动作指令
typedef enum {
AEL_MSG_CMD_NONE = 0,
// AEL_MSG_CMD_ERROR = 1,
AEL_MSG_CMD_FINISH = 2,
AEL_MSG_CMD_STOP = 3,
AEL_MSG_CMD_PAUSE = 4,
AEL_MSG_CMD_RESUME = 5,
AEL_MSG_CMD_DESTROY = 6,
// AEL_MSG_CMD_CHANGE_STATE = 7,
AEL_MSG_CMD_REPORT_STATUS = 8,
AEL_MSG_CMD_REPORT_MUSIC_INFO = 9,
AEL_MSG_CMD_REPORT_CODEC_FMT = 10,
AEL_MSG_CMD_REPORT_POSITION = 11,
} audio_element_msg_cmd_t;
2.3 元素执行的当前事件状态
typedef enum {
AEL_STATUS_NONE = 0,
AEL_STATUS_ERROR_OPEN = 1,
AEL_STATUS_ERROR_INPUT = 2,
AEL_STATUS_ERROR_PROCESS = 3,
AEL_STATUS_ERROR_OUTPUT = 4,
AEL_STATUS_ERROR_CLOSE = 5,
AEL_STATUS_ERROR_TIMEOUT = 6,
AEL_STATUS_ERROR_UNKNOWN = 7,
AEL_STATUS_INPUT_DONE = 8,
AEL_STATUS_INPUT_BUFFERING = 9,
AEL_STATUS_OUTPUT_DONE = 10,
AEL_STATUS_OUTPUT_BUFFERING = 11,
AEL_STATUS_STATE_RUNNING = 12,
AEL_STATUS_STATE_PAUSED = 13,
AEL_STATUS_STATE_STOPPED = 14,
AEL_STATUS_STATE_FINISHED = 15,
AEL_STATUS_MOUNTED = 16,
AEL_STATUS_UNMOUNTED = 17,
} audio_element_status_t;
2.4 音频信息
typedef struct {
int sample_rates; /*!< 采样率 Hz */
int channels; /*!< 音频通道数,单通道为1,立体声为2 */
int bits; /*!< 位宽(8, 16, 24, 32 bits) */
int bps; /*!< 比特率 */
int64_t byte_pos; /*!< 元素当前位置(unit: bytes) */
int64_t total_bytes; /*!< 元素的总字数数 */
int duration; /*!< 元素的持续时间 (可选) */
char *uri; /*!< URI (可选) */
esp_codec_type_t codec_fmt; /*!< 音乐格式 (可选) */
audio_element_reserve_data_t reserve_data; /*!< 该值保留给用户使用 (可选) */
} audio_element_info_t;
默认的音频信息取值:
#define AUDIO_ELEMENT_INFO_DEFAULT() {
\ .sample_rates = 44100, \ .channels = 2, \ .bits = 16, \ .bps = 0, \ .byte_pos = 0, \ .total_bytes = 0, \ .duration = 0, \ .uri = NULL, \ .codec_fmt = ESP_CODEC_TYPE_UNKNOW \ }
2.5 元素管理句柄
在
audio_element.c中定义
struct audio_element {
/* Functions/RingBuffers */
el_io_func open;
ctrl_func seek;
process_func process;
el_io_func close;
el_io_func destroy;
io_type_t read_type;
union {
ringbuf_handle_t input_rb;
io_callback_t read_cb;
} in;
io_type_t write_type;
union {
ringbuf_handle_t output_rb;
io_callback_t write_cb;
} out;
audio_multi_rb_t multi_in;
audio_multi_rb_t multi_out;
/* Properties */
volatile bool is_open;
audio_element_state_t state;
events_type_t events_type;
audio_event_iface_handle_t iface_event;
audio_callback_t callback_event;
int buf_size;
char *buf;
char *tag;
int task_stack;
int task_prio;
int task_core;
xSemaphoreHandle lock;
audio_element_info_t info;
audio_element_info_t *report_info;
bool stack_in_ext;
audio_thread_t audio_thread;
/* PrivateData */
void *data;
EventGroupHandle_t state_event;
int input_wait_time;
int output_wait_time;
int out_buf_size_expect;
int out_rb_size;
volatile bool is_running;
volatile bool task_run;
volatile bool stopping;
};
typedef struct audio_element *audio_element_handle_t;
element其实是一个freertos的任务,拥有优先级、线程栈等信息,每个任务都会执行callback open -> [loop: read -> process -> write] -> close.读取前一个element的数据,处理,再写入输出buff,传递给下一个element。
前七个元素都是不同阶段的回调函数,tag是每个element的身份证,pipeline凭借tag来识别element,同时还定义了out_buff的长度。
根据audio_element_cfg_t配置的参数来初始化一个element对象,返回element对象句柄:
audio_element_handle_t audio_element_init(audio_element_cfg_t *config);
3 音频流
负责获取音频数据,然后处理后将数据发送出去的Audio Element ,称为Audio Stream。
支持以下流类型:
示例:audio_element_handle_t http_stream_reader, i2s_stream_writer, aac_decoder;
4 音频事件
音频事件用于管道中的音频元素之间建立通信,事件API基于FreeRTOS 消息队列messagequeue构建的,实现了“监听器”来监听传入的消息,并通过回调函数通知它们。
4.1 事件句柄
struct audio_event_iface {
QueueHandle_t internal_queue;
QueueHandle_t external_queue;
QueueSetHandle_t queue_set;
int internal_queue_size;
int external_queue_size;
int queue_set_size;
audio_event_iface_list_t listening_queues;
void *context;
on_event_iface_func on_cmd;
int wait_time;
int type;
};
typedef struct audio_event_iface *audio_event_iface_handle_t;
其中audio_event_iface_list_t宏定义如下:
typedef STAILQ_HEAD(audio_event_iface_list, audio_event_iface_item) audio_event_iface_list_t;
STAILQ_HEAD声明 名为audio_event_iface_list,数据类型为audio_event_iface_item的单链表,在queue.h中定义如下:
#define STAILQ_HEAD(name, type) \ struct name {
\ struct type *stqh_first;/* first element */ \ struct type **stqh_last;/* addr of last next element */ \ }
audio_event_iface_item 数据类型如下:
typedef struct audio_event_iface_item {
STAILQ_ENTRY(audio_event_iface_item) next; // 指向下一个event_item的指针
QueueHandle_t queue; // 消息队列数据类型
int queue_size; // 消息队列大小
int mark_to_remove; //
} audio_event_iface_item_t;
4.2 事件配置结构体
typedef struct {
int internal_queue_size; /*!< 事件内部队列的大小(可选) */
int external_queue_size; /*!< 事件外部队列的大小(可选) */
int queue_set_size; /*!< 设置事件队列的大小(可选) */
on_event_iface_func on_cmd; /*!< 事件到达时,监听器的回调函数 */
void *context; /*!< 上下文将传递给回调函数 */
TickType_t wait_time; /*!< 检查事件队列的超时时间 */
int type; /*!< 来自audio_event_iface_msg_t source_type */
} audio_event_iface_cfg_t;
默认的事件配置:
#define DEFAULT_AUDIO_EVENT_IFACE_SIZE (5)
#define AUDIO_EVENT_IFACE_DEFAULT_CFG() {
\ .internal_queue_size = DEFAULT_AUDIO_EVENT_IFACE_SIZE, \ .external_queue_size = DEFAULT_AUDIO_EVENT_IFACE_SIZE, \ .queue_set_size = DEFAULT_AUDIO_EVENT_IFACE_SIZE, \ .on_cmd = NULL, \ .context = NULL, \ .wait_time = portMAX_DELAY, \ .type = 0, \ }
4.3 监听事件(iface)的初始化
audio_event_iface_handle_t audio_event_iface_init(audio_event_iface_cfg_t *config)
{
audio_event_iface_handle_t evt = audio_calloc(1, sizeof(struct audio_event_iface));
evt->queue_set_size = config->queue_set_size;
evt->internal_queue_size = config->internal_queue_size;
evt->external_queue_size = config->external_queue_size;
evt->context = config->context;
evt->on_cmd = config->on_cmd;
evt->type = config->type;
if (evt->queue_set_size) {
evt->queue_set = xQueueCreateSet(evt->queue_set_size);
}
if (evt->internal_queue_size) {
evt->internal_queue = xQueueCreate(evt->internal_queue_size, sizeof(audio_event_iface_msg_t));
}
if (evt->external_queue_size) {
evt->external_queue = xQueueCreate(evt->external_queue_size, sizeof(audio_event_iface_msg_t));
}
STAILQ_INIT(&evt->listening_queues);
return evt;
[...]
}
创建两个消息队列,其大小通过事件配置结构体audio_event_iface_cfg_t配置,然后初始化audio_event_iface_list_t的单链表。
之后event对象将作为监听器listener,监听pipeline中所有element的通知。
5 音频管道
音频管道组成:输入流->编解码->适配->输出流
功能:
- 用于链接元素
- 负责将消息从元素任务转发到应用程序
struct audio_pipeline {
audio_element_list_t el_list; // 音频元素单链表
ringbuf_list_t rb_list; // 环形缓冲区单链表
audio_element_state_t state;
xSemaphoreHandle lock;
bool linked;
audio_event_iface_handle_t listener;
};
typedef STAILQ_HEAD(audio_element_list, audio_element_item) audio_element_list_t;
typedef STAILQ_HEAD(ringbuf_list, ringbuf_item) ringbuf_list_t;
STAILQ_HEAD是个宏定义,创建一个名为name,数据类型为type的链表:
#define STAILQ_HEAD(name, type) \ struct name {
\ struct type *stqh_first;/* 第一个元素 */ \ struct type **stqh_last;/* 下一个元素的地址 */ \ }
audio_element_item数据类型(包含元素句柄audio_element_handle_t)
typedef struct audio_element_item {
STAILQ_ENTRY(audio_element_item) next; // next指针域
audio_element_handle_t el; // 当前的element
bool linked; // 表示是否连接到pipeline
bool kept_ctx; //
audio_element_status_t el_state; // 元素状态
} audio_element_item_t;
ringbuf_item数据类型(包含环形缓冲区句柄ringbuf_handle_t和元素句柄audio_element_handle_t)
typedef struct ringbuf_item {
STAILQ_ENTRY(ringbuf_item) next; // next指针域
ringbuf_handle_t rb; // 当前的ringbuff
audio_element_handle_t host_el; // 当前ringbuff所属的元素
bool linked; // 表示是否连接到pipeline
bool kept_ctx;
} ringbuf_item_t;
5.1 环形缓冲区

ringbuffer是一种环形缓冲区,用于:
- 连接 audio element
- 作数据缓冲
缓冲区中没有元素时,向ringbuffer请求数据时都会导致ringbuffer任务阻塞,直到ringbufer中的数据可以使用这个任务才可以继续执行。
管道、元素和环形缓冲区大致关系如下:

5.2 主要函数
5.2.1 audio_pipeline_register
esp_err_t audio_pipeline_register(audio_pipeline_handle_t pipeline, audio_element_handle_t el, const char *name);
根据audio_element_handle_t创建一个element_item,并将element_item插入到element_list末尾,同时设置tag。
5.2.2 audio_pipeline_link
esp_err_t audio_pipeline_link(audio_pipeline_handle_t pipeline, const char *link_tag[], int link_num);
根据已经注册到管道中的元素名称name(audio_pipeline_register),将元素用环形缓冲区ringbuffer连接起来。
audio_pipeline_link函数内部调用:
_pipeline_rb_linked(audio_pipeline_handle_t pipeline, audio_element_handle_t el, bool first, bool last)
该函数创建一个ringbuff_handle和ringbufff_item,将ringbuff_handle传递给ringbufff_item,并将ringbufff_item添加到ringbuff_list,设置ringbuff_item的host_el(当前ringbuff所属的元素)为el(当前元素),并将ringbuff传递给el。
_pipeline_rb_linked函数内部主要内容:
//判断el的位置并将rb传递给el
audio_element_set_input_ringbuf(el, rb);
audio_element_set_output_ringbuf(el, rb);
//将rb_item插入rb_list队尾
STAILQ_INSERT_TAIL(&pipeline->rb_list, rb_item, next);
5.2.3 audio_pipeline_set_listener
esp_err_t audio_pipeline_set_listener(audio_pipeline_handle_t pipeline, audio_event_iface_handle_t evt);
函数会将pipeline中element_list从表头至表尾取出element_item,并将其指向的element_handle对象中的iface_event事件成员对象的external_queue,插入到listener的event_list中的event_item中的queue。
简单来说,就是将pipeline中所有的element的事件消息队列插入listener中,这样listener就可接收element中的全部消息。
整个pipeline的逻辑大概如下所示:

简化逻辑图:

6 注意事项
- 同一个元素不能同时被链接到不同的管道(因为都挂在了各自的链表上,直接通过地址来修改)
- 灵活管道操作方法(参考例程):
audio_pipeline_breakup_elements(pipeline, NULL); // NULL打散所有元素,但它们及其连接的ringbuffer将被保留
// 重新链接AEL_STATE_PAUSED
audio_pipeline_relink(pipeline, (const char *[]) {
"fm_http", "aac", "i2s"}, 3);
- 监听来自管道中所有元素的事件必须是链接(Link)的元素, 若仅register而没link则不会监听
参考:
END
边栏推荐
- StepN分析
- Internet behavior networking
- Openinfra Foundation launched the "targeted fund" program to promote successful open source governance experience
- 我的创作纪念日
- Wechat applet: (exception) expected begin_ Object but was string at line 1 column 1 path $solution and analysis process
- Rigidbody2d SweepTest Rigidbody2D. Cast but for rotation rotation
- 测试网站搭建+渗透+审计之第三篇Swagger接口渗透测试
- openGL_01-创建窗口
- (7) Attribute binding
- 17billion parameters, 28 open test sets SOTA, the industry's largest unified visual multi task model
猜你喜欢

OpenInfra基金会发起“定向基金”计划,推行成功开源治理经验

number-precision--使用/实例

(7)属性绑定

golang---並發runtime包

Golang --- comparison operation of various types of variables

JVM面试

Question bank and answers of G3 boiler water treatment examination in 2022

API 网关 Apache APISIX 在 AWS Graviton3 上的安装与性能测试

rigidbody2d 围绕某固定点point进行旋转rotate

National information security competition for college students (ciscn) -reverse- recurrence (part)
随机推荐
170亿参数,28项公开测试集SOTA,行业最大的视觉多任务统一大模型来了
"Diwen Cup" skill competition between teachers and students of Electrical Engineering Department of Zibo technician college was successfully held
opcv图像二值化处理
(9)分支循环结构
Audio power amplifier circuit (used voice scheme circuit record)
View local public IP
Rigidbody2d rotate around a fixed point rotate
Mmdet detection box font color modification
MMdet修改检测框字体大小、位置、颜色、填充框
2022 safety officer-a certificate examination questions and online simulation examination
openGL_05 Shader的简单应用
OpenInfra基金会发起“定向基金”计划,推行成功开源治理经验
API Gateway Apache apisix Installation and Performance Test on AWS graviton3
openGL_02-点线面三角形
(2) V-cloak instruction
Devon 2K high resolution smart screen releases 4 new products
lua 运算符
2022年高处安装、维护、拆除特种作业证考试题库及模拟考试
How to calculate the rarity of NFT?
(5) Bidirectional data binding