ESP32 ADF element,pipeline和event是什么 如何处理音频数据

本文深入剖析ADF音频处理框架,涵盖element、event和pipeline三大核心组件。element作为基础单元,承担音频数据处理职责;event机制确保状态监测与信息传递;pipeline则串联各组件,形成高效音频处理流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

请参考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

事件是用来通知用户elementpipeline的运行状态的,基于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;

如何初始化一个event呢,先初始化一个event配置结构体,用它来初始化一个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_tringbuf_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);

这个函数完成的主要内容是将elementringbuff_itemringbuff连接起来。
通过函数_pipeline_rb_linked(pipeline, el, first, last);实现。

这个函数创建了一个ringbuffringbufff_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的详细逻辑可以大概如下所示:
在这里插入图片描述
如果还是觉得复杂,这里有一个简化的逻辑图帮助你
在这里插入图片描述

辛苦打字来之不易,点个赞支持下呗。
在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值