【ESP-ADF】元素,事件,消息指令,状态的理解

        ESP-ADF框架其实是利用了Freertos,将所有音视频领域可能用上的东西都统一定义了变量类型,方便玩家利用这个框架随意组装出mp3+i2s或wav+DAC或https+实体按键+i2s等各种灵活组合产品。前提是理解好这个框架,才能随心所欲组装。我花了好长时间实践理解,分享一下。

touch控制播放音乐

一、框架的形象理解

ESP-ADF官方网址如下:

      乐鑫音频应用开发指南 — ADF 音频应用开发框架 文档 (readthedocs-hosted.com)

        这个框架有很多概念需要理清,elements, pipeline, event, board, peripherals,实际上源代码就是对这几类抽象概念进行了定义。

1.1 Elements

        其他都可有可无,但elements和Pipeline一定要有,是组成ESP-ADF项目可运作的最小系统。Element好比工厂里的其中一个车间,这个车间完成这条串行流水线的一个工序,每个车间都是独立而完整的个体,有一整套自己的抓取物品、加工物品、输出物品、信息汇报的流程操作,对应的是audio_element结构体定义中,open、seek、process、close这些方法。这个比喻的物品或半成品就是音频信息码。物品进来车间A以A特有的方法加工,根据下个车间B的要求产出半成品,然后上pipeline运输线路,传输给下一个车间B进行处理。所以element A的输出就是element B的输入,对应audio_element结构体定义中read、write数据的方法。audio_element结构体定义在audio_element.c文件中。

        Element与其他概念最大区别是,它一定是涉及音视频信息流处理的个体。从外界获取音视频信息流的element A可以是SD卡的FatFs信息流,HTTP流等,经过处理后获取.mp3、.wac、.aac等格式的音视频数据,这个element可以是叫http_stream。第二个环节的element B一般是解码器,其实是各种数据格式的解码程序API,比如mp3 decoder(乐鑫隐藏了mp3_decoder底层代码我们看不到,只留了个接口),将.mp3格式的数据帧消化后以另一种格式吐出给下一个element C。第三个环节的element C一般是要面向具备解码&功放功能的设备输出数据,这个环节输出的格式一般是I2S(PCM)、PDM、TDM等格式,在ADF里面这个element叫i2s_stream_writer。

        Element创建的代码一般是这样:

    audio_element_handle_t mp3_decoder;   
    mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
    mp3_decoder = mp3_decoder_init(&mp3_cfg);

        先用cfg配置好参数,再用audio_element_handle_t 设立element句柄,用这个句柄来Initialize mp3。至此,分配好mp3 element的内存空间和默认的使用方法。i2s_stream_writer等其他element的创建过程大同小异。

1.2 Pipeline

        讲完elements再讲pipeline会体现出整个框架从0到1的建立过程。pipeline是工厂里的流水传输线,负责将上一个车间产出的半成品以及状态信息运输给下一个车间。pipeline的底层是Freertos,用了ringbuff环形缓冲消息队列、事件组这两项Freertos功能。对pipeline来说,每个element都是一个任务。消息队列负责将mp3音频信息的传输过去,还对信息处理过程的状态进行汇报。事件组xEventGroup()负责做事件状态的同步工作,就是作为一个全局变量告诉大家某个工序去到哪个阶段了,该工序没完成前,其他工序不得运作。

        Pipeline要处理的事件和状态信息不仅包括elements的,也包括设备按键这些peripherals的,就是靠xEventGroup()同步事件的状态,达到各个处理流程按序自动进行的效果。

        但是Pipeline处理的音视频信息流一定仅限于elements的,不然会报错,源代码里面有写。处理音视频信息流用的是ringbuff功能。

        Pipeline创建有2步:

        第一步是audio_pipeline_register(element),register利用了标准库queue.h创建链表,构建链表的基础个体是audio_element_item_t,其实这是个比audio_element多了些链表指针属性、状态属性,源代码里面很多时候看到item->el意思就是对audio_element进行操作。其实查看audio_pipeline这个handle的定义就可知道,其属性包含为element list和ringbuff list两项。这个element list链表的作用是方便其他函数遍历pipeline内元素,特别是查看element的state时用得多。

        Pipeline创建的第二步是link。将mp3_decoder、i2s_stream_writer等各个元素按流水线处理的顺序Link起来,注意只能link element,不能link外设。Link的操作本质是创建ringbuff的连接,即按mp3_decoder的out.rb连接ringbuff连接i2s_writer的in.rb连接ringbuff。音频解码的信息流就是ringbuff来传输。状态没被标记为linked的元素在源代码里被设定为无法遍历。

        至此,Pipeline就创建完,但离自动跑起来还差点东西。

        下一步,要register 按键这些peripherals。peripherals其实与我们的音频解码功放板的硬件设计有关,看你是使用ADC_button还是用touch方式控制音量大小、暂停快进这些功能。注意,peripherals一般是不用linked的,因为这个不产生音视频信息流,按键属于事件。

        下一步,设立event listener,这个就是Freertos的事件通讯功能,通过监听事件如何发生然后给出相应的操作指令,指令既包括系统自动调度的也包括人类玩家给的。

        最后一步,audio_pipeline_run(pipeline)。让以上的各个element任务开始run,系统就会自动监听消息,音乐开始播放器起来了。

1.3 Event

        事件既包括element任务产生的状态信息,比如信息处理到哪个阶段的内部状态信息,也包括中断、按键、延迟这些外部事件信息。这一块的源代码最让人混淆,其中有些底层代码好像是隐藏了我们看不见的。

        下一步,就是创建evt这个事件handle来处理流程所有事件。evt本身也是一个任务,以一个任务的代价来监听通道上发生的多个任务,值。

audio_event_iface_handle_t evt = audio_event_iface_init(&evt_cfg);

        下一步,就是将以上步骤创建的element和peripherals添加到evt这个handle上一起监听。

    ESP_LOGI(TAG, "[4.1] Listening event from all elements of pipeline");
    audio_pipeline_set_listener(pipeline, evt);

    ESP_LOGI(TAG, "[4.2] Listening event from peripherals");
    audio_event_iface_set_listener(esp_periph_set_get_event_iface(set), evt);

        这个evt任务既能监听来自element音频信息流的ringbuff消息,也能监听来自外设的控制指令queue message。实际这儿是一个队列集,同时监听来自多个不同类型队列。

        加上一句audio_pipeline_run(pipeline),程序就跑起来了。意思是每个element的task都开始运行,监听系统也在运行,消息队列也自动工作。

1.4 Board

        源代码里#include "board.h"这几个涉及board的头文件,关乎玩家选择哪些音频解码功放板的问题。ESP-ADF有几个主流的板子可供选择,代码已经配置好,包括ES8388这些音频板,只要选择,引脚接口都自动配置好了。我是用max98357这种简单的i2s输入的D类放大器,没什么其他控制功能,不在官方列表选项中,只能选择my_board_v1.0这种自定义类型。选择方法在下文讲。

1.5 Peripherals

        外设设备不是整个产品运作所必要的,用esp32自带的touch引脚也可实现控制,不是么?我就是这样用的。但是贴心的官方也考虑到音视频产品可能涉及的各种外设,如LED信号灯、按键控制、按键里还分ADC按键(就是MCU一个ADC输入引脚可实现感知多个不同按键按下)、SD卡等等,ESD-ADF框架将这些外设也统一好数据类型和事件接口,方便接入event_listener,用不用是你自己选择。

二、如何搭建一个用最少设备的mp3音乐播放器

        我是用ESP32+MAX98357解码功放一体放大器+废物中拆出来的小喇叭实现的,以i2s数字格式输出给功放。当然,还可以更省,就是ESP32+小喇叭就行,只是输出质量差,电源不稳定而已,靠ESP32的DAC引脚直接输出供电,质量不ok。

2.1 接线

       

        查官方手册,ESP32的i2s是慢速信号,要通过GPIO复选矩阵输出,因此可任意选择引脚。注意网上有些文章说i2s要选择GPIO25、26是误导人的。

        

        MAX98357的电源和地可以直接从ESP32的VIN和GND出来,但我试过声音会受影响,供电不稳定,还是用单独的5V电源稳妥。

        比较有意思的是MAX98357的GAIN引脚,这个起到物理增益功能,就是音量控制,接5V电源的话,音量最小,接地的话音量最大。但问题是这种音量调节太过原始了,要人手去插拔,我有个奇思妙想,能不能用ESP32的DAC输出引脚连到它的GAIN引脚,通过控制DAC电压大小来控制音量呢?下文会实验。

2.2 代码部分

        我是用官方例程play_mp3_control_example.c。在此基础上大家做些修改,就可播放音乐。

        (1)先打开sdkconfig,设置成my_board。

        (2)资源管理器界面的components文件夹下来会自动生成一个my_board_v1.0文件夹,打开里面的board_pin_config.c文件,修改下面这个函数里面的引脚号码,i2s随便的引脚都行:

esp_err_t get_i2s_pins(int port, board_i2s_pin_t *i2s_config)
{
    AUDIO_NULL_CHECK(TAG, i2s_config, return ESP_FAIL);
    if (port == 0) {
        i2s_config->mck_io_num = GPIO_NUM_0;
        i2s_config->bck_io_num = GPIO_NUM_27;
        i2s_config->ws_io_num = GPIO_NUM_26;
        i2s_config->data_out_num = GPIO_NUM_25;
        i2s_config->data_in_num = GPIO_NUM_39;

        (3)编译烧录进板子就ok。

        这儿已经能听到例程自带的3个mp3文件音乐了。只是诸如音量之类的控制还不行。

 三、进阶控制播放、暂停、音量增减等操作

3.1 添加自定义的按键控制事件        

        源代码的主程序while(1)大循环里面的get_input_voldown_id()等语句就是外设控制语句。

        我先试着加一个功能,比如播放下一首,这是源代码没有的功能。

        打开board_pin_config.c文件,在最底部添加代码:

// next mp3
int8_t get_input_next_id(void)
{
    return BUTTON_NEXT_ID;
}

       打开board_pin_config.h文件,把这个函数的声明也写出来。

        打开board_def.h文件,会看到如下:

#define BUTTON_MUTE_ID            2     /* You need to define the GPIO pins of your board */
#define BUTTON_SET_ID             3     /* You need to define the GPIO pins of your board */
#define BUTTON_MODE_ID            4     /* You need to define the GPIO pins of your board */

        其实这些数字就是对应按键的引脚号,若将BUTTON_SET_ID按键连接引脚3,按下这个按键,event_listener就会监听到这种按键事件,产生msg.data=3这个信息到消息队列里面,只要从队列里面捕捉这个3出来,就知道按键BUTTON_SET_ID发生了,然后可以编一些与这个按键有关的处理程序。

        我新增了#define BUTTON_NEXT_ID   6。表示ESP32的引脚6连接着能控制“下一首”的按键。

        至此,一个自己添加的按键控制事件就做好了。

3.2 音量控制

        挺遗憾的,我研究好久,发现MAX98357应该是不能像别的解码板那样提供一个音量控制引脚的。但可以有以下两种解决办法:(1)软件对音频数字进行处理;(2)在GAIN引脚做文章。

        方法(1)。ESP-ADF提供了一个标准函数控制音量,我找了好久终于找到alc的源文件,在ADF库/esp_codec文件夹/audio_alc.c文件。ALC是auto level control简写。实际上是在pipeline中添加一个ALC_element,位于mp3_decoder与i2s_stream_writer之间,将原本传给i2s_stream_writer的数字音频码等比放大,理论上就是乘一个数字。这个element源代码自己处理了,不用我们创建。ALC element也是有自己的cfg、init()、_open()、_close()等流程操作的。但我们不用管这些。

        用ALC功能时,关键要配置i2s_cfg.use_alc = true。系统看到=true,会自动配置ALC_element。完整的i2s_stream_writer初始化代码改成这样:

    ESP_LOGI(TAG, "[2.2] Create i2s stream to write data to codec chip");
    int player_volume=10;
    i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
    i2s_cfg.use_alc = true;
    i2s_cfg.volume = player_volume;
    i2s_cfg.type = AUDIO_STREAM_WRITER;
    i2s_stream_writer = i2s_stream_init(&i2s_cfg);

       在播放过程只需调用下面的函数就能控制音量,但当前正在播放的音乐音量改变不了,只能从下一帧开始改音量。

i2s_alc_volume_set(i2s_stream_writer, player_volume);

        方法(2),在GAIN引脚上做文章。Max98357的datasheet(下图)标注GAIN引脚可以离散化控制音量增益,若给GAIN引脚接上0.65倍VDD电压,增益就是3dB,如此类推。因此我猜想用ESP32的DAC输出引脚连接MAX98357的GAIN引脚,通过控制DAC引脚电压达到控制音量增益的效果应该是可行的,有5档可以调节。

 

        后面我验证了这种做法是可行的。用示波器看了ESP32的DAC引脚(channel 0的话是GPIO25引脚,这个不能随意分配),的确是真实的一条平稳水平线电压,不是PWM模拟,电压数值跟万用表测出来差不多。通过DAC输出不同电压到MAX98357的GAIN引脚,达到控制音量大小的效果。 添加DAC的代码如下:

#include "driver/dac_oneshot.h"
    
    ESP_LOGI(TAG, "[ 3.1 ] Initialize DAC output.");
    dac_oneshot_config_t dac_cfg = {.chan_id = DAC_CHAN_0,}; //GPIO25
    dac_oneshot_handle_t dac_handle;
    uint8_t dac_voltage=220;
    ESP_ERROR_CHECK(dac_oneshot_new_channel(&dac_cfg, &dac_handle));

        这儿dac_voltage范围是0~255,当取255时,ESP32输出DAC电压3.3V左右。 后面要更改音量时,用这个函数:

dac_oneshot_output_voltage(dac_handle, dac_voltage);

3.3 换成touch方式的外设控制

        ESP32自带touch引脚,不需要adc_button这些外设了。

        在前面Set up  event listener之前,增加这段代码:

    ESP_LOGI(TAG, "[ 3 ] Initialize peripherals");
    esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
    esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);
    periph_touch_cfg_t touch_cfg = {
        .touch_mask = TOUCH_PAD_SEL5 | TOUCH_PAD_SEL6 | TOUCH_PAD_SEL8 | TOUCH_PAD_SEL9,
        .tap_threshold_percent = 70,
    };
    esp_periph_handle_t touch_handle = periph_touch_init(&touch_cfg);
    esp_periph_start(set, touch_handle);

        这个意思是用第5、6、8、9号的touch pad(对应12、14、33、32号GPIO引脚)来控制。系统监听到5号touch被触摸了,就会返回一个msg.data=5,自己可以根据此编写相应的控制程序。

        监听5号touch引脚触摸(我设了get_input_play_id()的值是5)引发的控制程序如下:

        if ((msg.source_type == PERIPH_ID_TOUCH) && (msg.cmd == PERIPH_TOUCH_TAP))
        {
            if ((int)msg.data == get_input_play_id())
            {
                ESP_LOGI(TAG, "[ * ] [Play] touch tap event");
                audio_element_state_t el_state = audio_element_get_state(i2s_stream_writer);
                switch (el_state)
                {
                case AEL_STATE_INIT:
                    ESP_LOGI(TAG, "[ * ] Starting audio pipeline");
                    audio_pipeline_run(pipeline);
                    break;

        msg.data这时返回的就是TOUCH_PAD_SEL5(数字5)。

四、其他见解

4.1 Element实际运作起来的流程是怎样的?在各个阶段会产生什么消息出来给人捕捉?

        针对此问题,我在主程序while(1)大循环里添加了下面代码:

        /*测试段开始*/
        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *)mp3_decoder &&
            msg.cmd == AEL_MSG_CMD_REPORT_STATUS)
        {
            audio_element_status_t el_state = (int)msg.data;
            ESP_LOGI(TAG, "[ * ] [mp3_decoder] report status:%d", el_state);
            continue;
        }

        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *)i2s_stream_writer &&
            msg.cmd == AEL_MSG_CMD_REPORT_STATUS)
        {
            audio_element_status_t el_state = (int)msg.data;
            ESP_LOGI(TAG, "[ * ] [i2s_stream] report status:%d", el_state);
            continue;
        }

        /*测试段结束*/

        这测试代码目的是让 mp3_element 和i2s_stream_element每个阶段立马汇报其状态,这样就能看出从mp3_decoder到i2s_stream之间的处理过程发生了什么。

        分析一下代码。由于是element,所以无论是MP3还是i2s_stream,msg.source_type都是AUDIO_ELEMENT_TYPE_ELEMENT。msg.source就是element handle的名称。msg.cmd就比较让人难捉摸,打开audio_element.h文件,看到audio_element_msg_cmd_t类型的指令有12条,为何认为mp3_element在某阶段会发出REPORT_STATUS类型的指令到pipeline中?下文会提到几种不同的msg种类作用,现在先卖个关子。

        ESP32重启后,看串口监视器有以下输出信息:

I (775) PLAY_FLASH_MP3_CONTROL: [ 5.1 ] Start audio_pipeline
W (785) AUDIO_THREAD: Make sure selected the `CONFIG_SPIRAM_BOOT_INIT` and `CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY` by `make menuconfig`
I (875) PLAY_FLASH_MP3_CONTROL: [ * ] [mp3_decoder] report status:12
I (875) PLAY_FLASH_MP3_CONTROL: [ * ] Receive music info from mp3 decoder, sample_rates=44100, bits=16, ch=2
I (955) PLAY_FLASH_MP3_CONTROL: [ * ] [i2s_stream] report status:12
I (955) PLAY_FLASH_MP3_CONTROL: [ * ] [i2s_stream] report status:13
I (965) PLAY_FLASH_MP3_CONTROL: [ * ] [i2s_stream] report status:12
I (12635) PLAY_FLASH_MP3_CONTROL: [ * ] [mp3_decoder] report status:15
I (12675) PLAY_FLASH_MP3_CONTROL: [ * ] [i2s_stream] report status:15

        report status后面的数字12、13代表的是audio_element_status_t的枚举顺序,12是AEL_STATUS_STATE_RUNNING状态,13是AEL_STATUS_STATE_PAUSED,15是AEL_STATUS_STATE_FINISHED。

        由于while(1)循环前就运行了audio_pipeline_run(pipeline),所有element的任务都启动,因此先有mp3_decoder的RUNNING状态汇报,再有Mp3文件头信息的汇报。mp3_decoder正在运行的事件状态通过事件组的技术告知i2s_stream,接着是i2s_stream运行,然后暂停,又再开始运行。一会儿,歌的音频流播放完,mp3_decoder进入finished状态,并把finished状态汇报出来。接着i2s_stream也汇报finished。

        以上过程都是系统自动进行。可以看出来,每个element例如mp3_decoder都是一个独立的车间,该车间内部会对原材料进行read、Open、process、send_out等阶段性操作,但源代码并不是每个阶段的结果都report出来。

4.2 Elements状态、消息指令类型的定义

        第一类,错误类型信息。AEL代表audio element,IO代表信息In和Out。因此audio_element_err_t的状态用来记录element从外部队列read信息和write信息到外部队列这一过程是否成功。

typedef enum {
    AEL_IO_OK           = ESP_OK,
    AEL_IO_FAIL         = ESP_FAIL,
    AEL_IO_DONE         = -2,
    AEL_IO_ABORT        = -3,
    AEL_IO_TIMEOUT      = -4,
    AEL_PROCESS_FAIL    = -5,
} audio_element_err_t;

        第二类,记录element当前这刻的阶段信息,是element这个handle结构里的state属性,这个属性填的是audio_element_state_t里面的任意一种。不属于事件或动作信息。是用于状态对比的场合,比如按下play按键后,要根据element当前处于何种阶段而作出下一步的程序安排,若当前处于AEL_STATE_INIT阶段,play键就直接开始播放音乐;若当前处于AEL_STATE_RUNNING阶段,play键就不做任何事;若当前处于AEL_STATE_PAUSED阶段,就启动resume程序继续当前位置的播放。

/**
 * @brief Audio element state
 */
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;

        第三类,audio_element_msg_cmd_t动作指令。这儿还要细分成2部分。0~7属于普通指令,8~11属于REPORT指令。0~7指令其实我们不用管,是ESP-ADF自动的,每个element内部各个阶段完成后都会给下个阶段发指令,执行下一阶段的处理。比如消息指令AEL_MSG_CMD_STOP,是用于同步esp_err_t audio_element_on_cmd_stop()这个程序的,这个cmd_stop()程序里面的操作包括将element.state变成AEL_STATE_STOPPED状态、事件组执行xEventGroupSetBits标志位阻塞其他任务进行,对外部element汇报AEL_STATUS_STATE_STOPPED这个状态。AEL_STATUS_STATE_STOPPED是不是很容易跟AEL_STATE_STOPPED混淆?下面会讲。

/**
 * 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;

        第2部分是8~11,REPORT部分,属于msg.cmd众多指令中的一种,这部分要重点关注。实际上我们最常用的AEL_MSG_CMD_REPORT_STATUS是个信息类目,包含第四类的18种信息。AEL_MSG_CMD_REPORT_STATUS。

        第四类,element的主线任务处理完后,向其他element发出msg.data,让其他element来取件,大概这个意思。更重要的是,这方便我们人类玩家掌握程序跑到哪个阶段。audio_element_status_t的内容就是audio_element_report_status()这个函数要汇报的内容。

/**
 * 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;

        人类玩家捕捉到这个消息后,就知道该element前面阶段已处理完,看我们需要下一步需要如何操作。比如你想操作单曲循环播放,只要知道i2s_stream element汇报AEL_STATUS_STATE_FINISHED的消息,我们就知道i2s输出完毕,可以执行重播的代码。

        从下面代码就可以知道这几种消息的层次,类似界门纲目科属种:

if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *)mp3_decoder &&
            msg.cmd == AEL_MSG_CMD_REPORT_STATUS && (int)msg.data == AEL_STATUS_STATE_FINISHED)

        如何区别AEL_STATE_STOPPED、AEL_MSG_CMD_STOP、AEL_STATUS_STATE_STOPPED呢?AEL_STATE_STOPPED被赋值给element.state,标记了element当前是什么阶段,这不是一个事件或动作,好比别人家墙上挂了一块禁止停车的牌子,路人爱看就看,不看也没人强迫你看。AEL_MSG_CMD_STOP我们不用管,是Pipeline自动后台处理用的,不暴露给我们。AEL_STATUS_STATE_STOPPED我们要重点关注,这是一个动作和通知,是放到消息队列里面让别的element或玩家去取的,好比禁止停车的告知书主动发给你让你获取并签名。

4.3 添加Touch外设如何写代码        

    ESP_LOGI(TAG, "[ 3 ] Initialize peripherals");
    esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
    esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);
    periph_touch_cfg_t touch_cfg = {
        .touch_mask = TOUCH_PAD_SEL5 | TOUCH_PAD_SEL6 | TOUCH_PAD_SEL8 | TOUCH_PAD_SEL9,
        .tap_threshold_percent = 70,
    };
    esp_periph_handle_t touch_handle = periph_touch_init(&touch_cfg);
    esp_periph_start(set, touch_handle);

        esp_periph_create()这句是告诉Pipeline我们给事件的触发方式添加了外设类别。periph_touch_init()属于periph下的其中一种硬件,同级的还可以是按键外设,大家根据需要替换下面touch_handle那两句就行。

4.4 一些函数的注意事项

        audio_pipeline_run(pipeline)这个函数作用是让里面每个element都开始运作自己的task。运行前必须将Pipleline的状态设为AEL_STATE_INIT,否则Pipeline无法run起来,而pipeline里面的element状态可以是任意状态。

  • 25
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
引用: 在Ubuntu 20.04.4 LTS操作系统中,我已经安装了ESP-idf开发环境并使用了一段时间。现在我想安装esp-adf音频开发框架,但遇到了问题。可以在https://***/index.html找到相关文档。 引用: LinkGUI™ Air E32是一个开发平台,基于ESP32 WROVER芯片组,可以实现完美的图形交互体验。它将GUI系统嵌入到乐鑫开源的ESP-ADF/ESP-IDF开发环境中,提供了丰富的UI交互功能。LinkGUI™ Air E32平台由ESP32 WROVER芯片、LCD、按键、T卡插槽、喇叭插槽、麦克风和扩展接口组成。它的SDK以基于ESP-ADF的LinkGUI图形系统为核心,与ESP-ADF开发环境无缝连接,实现了WIFI/BT/音频和高级图形交互的功能。 根据您的问题,您想了解在Ubuntu操作系统上使用esp-adf和lvgl。首先,esp-adf是一个音频开发框架,用于在ESP32芯片上开发音频应用程序。它提供了丰富的音频功能和示例,可以实现音频播放、录制、语音识别等功能。 而lvgl是一个开源的图形库,用于创建嵌入式设备上的用户界面。它提供了丰富的图形元素和交互功能,可以实现按钮、滑块、进度条、列表等常见的UI元素。 在Ubuntu操作系统上,您可以按照ESP-ADF文档中提供的步骤安装和配置esp-adf开发环境。然后,您可以使用lvgl库开发具有图形界面的应用程序。您可以参考lvgl的官方文档和示例来学习如何使用lvgl库进行UI设计和交互。 总结起来,esp-adf是一个音频开发框架,lvgl是一个图形库。您可以在Ubuntu操作系统上使用esp-adf开发音频应用程序,并结合lvgl库创建图形界面。希望这能帮助到您。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Ubuntn环境下安装ESP 音频框架时出现Failed to resolve component ‘jsmn‘.问题(已解决)](https://blog.csdn.net/qq_41601311/article/details/124293303)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [基于ESP-ADF的图形系统](https://blog.csdn.net/skdev/article/details/120765588)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值