请参考adf/compoments/audio_pipeline目录下的源文件。
pipeline是adf实现音频处理的基础。可以将pipeline看出是流水线。音频数据从一头进,从另一头出。element是流水线上的工人,负责加工音频数据。event是监听流水线上所有工人的情况,可用户以通过msg得知。每个element都是一个任务。
一、element
首先看audio_element.h
,主要是对element的很多参数的定义:
element
的状态定义:
/**
* @brief Audio element state
*/
typedef enum {
AEL_STATE_NONE = 0,
AEL_STATE_INIT,
AEL_STATE_RUNNING,
AEL_STATE_PAUSED,
AEL_STATE_STOPPED,
AEL_STATE_FINISHED,
AEL_STATE_ERROR
} audio_element_state_t;
element动作指令
反映element当前执行的动作:
/**
* Audio element action command, process on dispatcher
*/
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;
element当前动作的状态,注意要与element的状态区分
/**
* Audio element status report
*/
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;
?两个状态state和status?百度下来意思都差不多,先不管了
element informations
里包含的音频信息,就是采样率,通道,位宽,byte_pos:当前处理的字节位置,codec_fmt:音频的格式,uri
:是音频数据的地址。默认配置如下:
/**
* @brief Audio Element informations
*/
typedef struct {
int sample_rates; /*!< Sample rates in Hz */
int channels; /*!< Number of audio channel, mono is 1, stereo is 2 */
int bits; /*!< Bit wide (8, 16, 24, 32 bits) */
int bps; /*!< Bit per second */
int64_t byte_pos; /*!< The current position (in bytes) being processed for an element */
int64_t total_bytes; /*!< The total bytes for an element */
int duration; /*!< The duration for an element (optional) */
char *uri; /*!< URI (optional) */
esp_codec_type_t codec_fmt; /*!< Music format (optional) */
audio_element_reserve_data_t reserve_data; /*!< This value is reserved for user use (optional) */
} 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 \
}
element
结构体在audio_element.c中定义
可以看出 element包含一个任务来着
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;
};
配置element的结构体:用来创建一个element。
从这个结构体可知,element其实是一个freertos的任务,拥有优先级等。每个任务都会执行callback open -> [loop: read -> process -> write] -> close.
读取前一个element的数据,处理,再写入输出buff,传递给下一个element。
前七个元素都是不同阶段的回调函数,tag是每个element的身份证,pipeline凭借tag来识别element,同时还定义了out_buff的长度。
/**
* @brief Audio Element configurations.
* Each Element at startup will be a self-running task.
* These tasks will execute the callback open -> [loop: read -> process -> write] -> close.
* These callback functions are provided by the user corresponding to this configuration.
*
*/
typedef struct {
el_io_func open; /*!< Open callback function */
ctrl_func seek; /*!< Seek callback function */
process_func process; /*!< Process callback function */
el_io_func close; /*!< Close callback function */
el_io_func destroy; /*!< Destroy callback function */
stream_func read; /*!< Read callback function */
stream_func write; /*!< Write callback function */
int buffer_len; /*!< Buffer length use for an Element */
int task_stack; /*!< Element task stack */
int task_prio; /*!< Element task priority (based on freeRTOS priority) */
int task_core; /*!< Element task running in core (0 or 1) */
int out_rb_size; /*!< Output ringbuffer size */
void *data; /*!< User context */
const char *tag; /*!< Element tag */
bool stack_in_ext; /*!< Try to allocate stack in external memory */
int multi_in_rb_num; /*!< The number of multiple input ringbuffer */
int multi_out_rb_num; /*!< The number of multiple output ringbuffer */
} audio_element_cfg_t;
初始化函数
根据audio_element_cfg_t
配置的参数来初始化一个element对象。
audio_element_handle_t audio_element_init(audio_element_cfg_t *config);
其他的函数大都是设置或读取element
的部分成员。
二、event
事件是用来通知用户element
和pipeline
的运行状态的,基于freertos的队列实现。
event有event
本身和msg
消息
1、event msg
cmd:是消息的命令类型;
/**
* Event message
*/
typedef struct {
int cmd; /*!< Command id */
void *data; /*!< Data pointer */
int data_len; /*!< Data length */
void *source; /*!< Source event */
int source_type; /*!< Source type (To know where it came from) */
bool need_free_data; /*!< Need to free data pointer after the event has been processed */
} audio_event_iface_msg_t;
2、event
事件结构体定义如下:
主要成员有两个队列,一个队列集与他们的尺寸,还有一个audio_event_iface_list_t
类型的listening_queues
。
/**
* Audio event structure
*/
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;
};
audio_event_iface_list_t
是啥?看定义:
typedef STAILQ_HEAD(audio_event_iface_list, audio_event_iface_item) audio_event_iface_list_t;
而STAILQ_HEAD()
是声明一个单链表,这个定义在queue.h
中介绍,queue.h
中还定义了其他对单链表的宏,比如插入链表尾等。
/*
* Singly-linked Tail queue declarations.
*/
#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_list_t
是以event_item
为成员的单链表的数据类型,listening_queues
是以event_item
为成员的单链表。
再看看event_item
包括啥:指向下一个item的指针,一个队列和队列尺寸。
重点是队列。到这里可以看出listening_queues其实就是一个队列单链表。
typedef struct audio_event_iface_item {
STAILQ_ENTRY(audio_event_iface_item) next;
QueueHandle_t queue;
int queue_size;
int mark_to_remove;
} audio_event_iface_item_t;
如何初始化一个even
t呢,先初始化一个even
t配置结构体,用它来初始化一个event
对象。
默认只配置了队列的大小,on_cmd,context都是空的,type=0;
/**
* Event interface configurations
*/
typedef struct {
int internal_queue_size; /*!< It's optional, Queue size for event `internal_queue` */
int external_queue_size; /*!< It's optional, Queue size for event `external_queue` */
int queue_set_size; /*!< It's optional, QueueSet size for event `queue_set`*/
on_event_iface_func on_cmd; /*!< Function callback for listener when any event arrived */
void *context; /*!< Context will pass to callback function */
TickType_t wait_time; /*!< Timeout to check for event queue */
int type; /*!< it will pass to audio_event_iface_msg_t source_type (To know where it came from) */
} audio_event_iface_cfg_t;
#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, \
}
再来看看如何初始化一个event
:初始化函数中创建了两个队列和一个队列集,大小是刚刚config配置的,然后初始化了一条audio_event_iface_list_t
的单链表。此时的单链表是空的,也就是没有队列。后面这个event
将作为listener
,监听pipeline
中所有element
的通知。
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));
AUDIO_MEM_CHECK(TAG, evt, return NULL);
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));
AUDIO_MEM_CHECK(TAG, evt->internal_queue, goto _event_iface_init_failed);
}
if (evt->external_queue_size) {
evt->external_queue = xQueueCreate(evt->external_queue_size, sizeof(audio_event_iface_msg_t));
AUDIO_MEM_CHECK(TAG, evt->external_queue, goto _event_iface_init_failed);
} else {
ESP_LOGD(TAG, "This emiiter have no queue set,%p", evt);
}
STAILQ_INIT(&evt->listening_queues);
return evt;
三、pipeline
pipeline
翻译成管道,但我觉得翻译成流水线好像也不错,它就是一条流水线,element
就是流水线上的工人,流水线将工人的工作连接起来,而event
就是流水线上的管理人员,负责收集流水线上员工的工作情况,并通过msg
将情况向老板(用户)反馈,老板通过msg中的数据调整流水线。
首先看pipeleine结构体,el_list,rb_list是两条单链表,具体接下来看
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;
};
audio_element_list_t
和ringbuf_list_t
是一个类型定义:
typedef STAILQ_HEAD(ringbuf_list, ringbuf_item) ringbuf_list_t;
typedef STAILQ_HEAD(audio_element_list, audio_element_item) audio_element_list_t;
而STAILQ_HEAD()是一个宏定义,它的作用是创建一个名为name,数据类型为type的链表,所以以上ringbuf_list_t创建了一个名为ringbuf_list,数据类型为ringbuf_item的单链表,audio_element_list_t同理。
/*
* Singly-linked Tail queue declarations.
*/
#define STAILQ_HEAD(name, type) \
struct name { \
struct type *stqh_first;/* first element */ \
struct type **stqh_last;/* addr of last next element */ \
}
audio_element_item_t 是个单链表成员的结构体 next指向下个element,el是当前的element,linked表示是否连接到pipeline。
typedef struct audio_element_item {
STAILQ_ENTRY(audio_element_item) next;
audio_element_handle_t el;
bool linked;
bool kept_ctx;
audio_element_status_t el_state;
} audio_element_item_t;
ringbuf_item_t 也是单链表成员的结构体,将element对应的ringbuff连接起来,next指向下一个element的ringbuff,rb是当前的ringbuff,host_el是当前ringbuff所属的element
typedef struct ringbuf_item {
STAILQ_ENTRY(ringbuf_item) next;
ringbuf_handle_t rb;
audio_element_handle_t host_el;
bool linked;
bool kept_ctx;
} ringbuf_item_t;
大致关系如下:
再来看主要的函数:
esp_err_t audio_pipeline_register(audio_pipeline_handle_t pipeline, audio_element_handle_t el, const char *name);
这个函数将根据element_handle
生成一个element_item
,并将item插入element_list队尾,同时设置tag。
esp_err_t audio_pipeline_link(audio_pipeline_handle_t pipeline, const char *link_tag[], int link_num);
这个函数完成的主要内容是将element
与ringbuff_item
,ringbuff
连接起来。
通过函数_pipeline_rb_linked(pipeline, el, first, last);
实现。
这个函数创建了一个ringbuff
和ringbufff_item
,将ringbuff
传递给ringbufff_item
并将ringbufff_item
添加到ringbuff_list
,设置ringbuff_item的host_el为el,并将ringbuff传递给el。
//判断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);
3
esp_err_t audio_pipeline_set_listener(audio_pipeline_handle_t pipeline, audio_event_iface_handle_t listener)
这个函数会将pipeline中element_list链表中从头到尾取出element_item并将其指向的element的event中的external_queue插入到listener的event_list中的event_item中的queue。这个过程有点难懂。简单说就是:
将pipeline中所有的element的事件消息队列插入listener中,这样listener就可接收element中的全部消息。
所以整个pipeline的详细逻辑可以大概如下所示:
如果还是觉得复杂,这里有一个简化的逻辑图帮助你
辛苦打字来之不易,点个赞支持下呗。