ESP-ADF音频框架 -- stream

由于工作需要,需要ESP32对MP3流进行软解码,为了通过文件进行实时流模拟,研究了ESP-ADF中的流类型。相关流类型及支持的操作如下表所示。

流类型

AUDIO_STREAM_READER

(读类型)

AUDIO_STREAM_WRITER

(写类型)

算法流YN
FatFs 流YY
HTTP 流YY
I2S 流YY
 PWM 流NY
原始流YY
SPIFFS 流YY
TCP 客户端流YY
提示音流YN
嵌入式二进制文件流YN
语音合成流YN

思路一

由于MP3流本质上就是二进制数据流,所以以嵌入式二进制流作为前端第一级输入,末端输出采取FatFs流,写入文件中,具体流程如下。

[sdcard] --> 嵌入式二进制文件流 --> mp3_decoder --> fatfs_stream_writer --> [sdcard]

根据应用示例player/pipeline_embed_flash_ton所示,嵌入式二进制文件流在烧录过程中已经将MP3文件写入到flash中,ESP-ADF中没有提供具体的数据流写入API(可能ESP-IDF中有对flash进行写入的API),即没办法对数据进行动态更新,此外嵌入式二进制文件流不支持AUDIO_STREAM_WRITER,所以思路1不可行。

思路二

ESP-ADF框架提供了两种方式对流数据进行处理,分别是基于Pipeline和Element。Pipeline相对于Element,中间变量环节已经由Pipeline进行实现,更加易于管理;而Element更加容易深入了解具体的调用流程,但各个Element之间需要构建缓存空间进行数据交互及输入输出进行重定向。

基于Pipeline方式

基于Element方式

 参照示例recorder/element_wav_amr_sdcard及recorder/element_cb_sdcard_amr, 为了实现流模拟,通过(fopen,fread)读取sdcard文件数据流,并写入ringbuf1中,定义为ringbuf1的输入,将输出定义为decoder的输入。具体流程如下:

[sdcard] --> FILE --> mp3_decoder --> fatfs_stream_writer --> [sdcard]

相应的测试代码如下:

void app_main() {
    ringbuf_handle_t ringbuf01, ringbuf12;

    audio_element_handle_t mp3_decoder, fatfs_stream_writer;
    ESP_LOGI(TAG, " Create mp3 decoder to decode mp3 file");
    mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
    mp3_decoder = mp3_decoder_init(&mp3_cfg);

    ESP_LOGI(TAG, " Create fatfs stream to write data to sdcard");
    fatfs_stream_cfg_t fatfs_cfg_w = FATFS_STREAM_CFG_DEFAULT();
    fatfs_cfg_w.type = AUDIO_STREAM_WRITER;
    fatfs_stream_writer = fatfs_stream_init(&fatfs_cfg_w);

    audio_element_info_t music_info = {0};
    audio_element_getinfo(mp3_decoder, &music_info);
    audio_element_setinfo(fatfs_stream_writer, &music_info);
    audio_element_set_uri(fatfs_stream_writer, "/sdcard/out.wav");

    ringbuf01 = rb_create(RING_BUFFER_SIZE, 1);
    rb_reset(ringbuf01);
//    audio_element_set_output_ringbuf(i2s_stream_reader, ringbuf01);
    audio_element_set_input_ringbuf(mp3_decoder, ringbuf01);

    ringbuf12 = rb_create(RING_BUFFER_SIZE, 1);
    rb_reset(ringbuf12);
    audio_element_set_output_ringbuf(mp3_decoder, ringbuf12);
    audio_element_set_input_ringbuf(fatfs_stream_writer, ringbuf12);

    audio_element_set_event_callback(mp3_decoder, audio_element_event_handler, NULL);
    audio_element_set_event_callback(fatfs_stream_writer, audio_element_event_handler, NULL);

    FILE *fin = fopen("/sdcard/in.mp3", "r");

    int num = 0;
    // memset(buffer, 0, sizeof(buffer));
    char *tmp;
    tmp = (char *)malloc(RING_BUFFER_SIZE);
    int readLen = fread(tmp, sizeof(char), RING_BUFFER_SIZE, fin);
    int ret = rb_write(ringbuf01, tmp, readLen, 0);;
    free(tmp);
    tmp = NULL;
    printf("num: %d, readLen: %d\n", num++, ret);

    audio_element_run(mp3_decoder);
    audio_element_run(fatfs_stream_writer);

    audio_element_resume(mp3_decoder, 0, 0);
    audio_element_resume(fatfs_stream_writer, 0, 0);

    
    while (1)
    {
         memset(buffer, 0, sizeof(buffer));
         readLen = fread(buffer, sizeof(char), RING_BUFFER_SIZE, fin);
         write_file_ringbuf(ringbuf01, buffer, readLen);
         printf("num: %d, readLen: %d\n", num++, readLen);
         if (feof(fin))
         {
             printf("finish\n");
             break;
         }
    }
    
    ESP_LOGI(TAG, "Release all resources");
    audio_element_deinit(fatfs_stream_writer);
    audio_element_deinit(mp3_decoder);

    rb_destroy(ringbuf01);
    rb_destroy(ringbuf12);
}

但是经测试,会导致系统崩溃,还需做进一步验证。

思路三

由于需要获取输入流的控制权,所以该流类型需要支持AUDIO_STREAM_WRITER。对具体的流类型进行逐个排除。

流类型

AUDIO_STREAM_READER

(读类型)

AUDIO_STREAM_WRITER

(写类型)

备注
算法流YN
FatFs 流YY没办法控制具体的写入操作
HTTP 流YY需要联网
I2S 流YY末级输出
 PWM 流NY下一级元素无法获取数据
原始流YY
SPIFFS 流YY没办法控制具体的写入操作
TCP 客户端流YY不符合应用场景
提示音流YN不支持写入操作
嵌入式二进制文件流YN烧写的时候已固化
语音合成流YNTTS文本数据

 此外原始流提供具体的读写API

int raw_stream_write(audio_element_handle_tpipeline, char *buffer, int buf_size);
int raw_stream_read(audio_element_handle_tpipeline, char *buffer, int buf_size);
static void raw_write_task(void *para)
{
    char *uri = (char *)para;
    char *buf = audio_calloc(1, 4096);
    AUDIO_MEM_CHECK(TAG, buf, return;);

    fatfs_stream_cfg_t fatfs_cfg = FATFS_STREAM_CFG_DEFAULT();
    fatfs_cfg.task_stack = 0;
    fatfs_cfg.type = AUDIO_STREAM_READER;
    audio_element_handle_t fs = fatfs_stream_init(&fatfs_cfg);
    audio_element_set_uri(fs, uri);
    audio_element_process_init(fs);
    audio_element_run(fs);

    ESP_LOGI(TAG, "Raw writing..., URI:%s", uri);
    while (raw_task_run_flag) {
        int ret = audio_element_input(fs, buf, 4096);
        printf(".");
        if (AEL_IO_OK == ret) {
            ESP_LOGE(TAG, "Raw write done");
            audio_player_raw_feed_finish();
            break;
        }
        audio_player_raw_feed_data((uint8_t *)buf, 4096);
    }
    audio_element_process_deinit(fs);
    audio_element_deinit(fs);
    free(buf);
    ESP_LOGI(TAG, "Raw write stop");
    vTaskDelete(NULL);
}

void raw_write(const char *p)
{
    raw_task_run_flag = true;
    if (xTaskCreate(raw_write_task, "RawWriteTask", 3072, (void *)p, 5, NULL) != pdPASS) {
        ESP_LOGE(TAG, "ERROR creating raw_write_task task! Out of memory?");
    }
}

结合上述说明,具体流程如下

[sdcard] --> FILE -->raw_stream_writer--> mp3_decoder --> fatfs_stream_writer --> [sdcard]

具体实现代码如下

static void raw_write_task(void *para)
{
    char *buf = audio_calloc(1, MP3_PACK_SIZE);
    AUDIO_MEM_CHECK(TAG, buf, return;);

    ESP_LOGI(TAG, "Raw writing...\n");
    FILE *fin = fopen("/sdcard/test.mp3", "r");
    while (raw_task_run_flag) {
        int length = fread(buf, 1, MP3_PACK_SIZE, fin);
        printf("length: %d\n", length);
        if (length <= 0) {
            ESP_LOGE(TAG, "Raw write done");
            fclose(fin);
            audio_element_set_ringbuf_done(raw_stream_writer);
            audio_element_finish_state(raw_stream_writer);
            break;
        }
        raw_stream_write(raw_stream_writer, buf, length);
//        audio_player_raw_feed_data((uint8_t *)buf, 4096);
    }
    free(buf);
    ESP_LOGI(TAG, "Raw write stop");
    vTaskDelete(NULL);
}

void app_main(void)
{

    audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
    audio_pipeline_handle_t pipeline = audio_pipeline_init(&pipeline_cfg);
    mem_assert(pipeline);

    raw_stream_cfg_t raw_cfg = RAW_STREAM_CFG_DEFAULT();
    raw_cfg.type = AUDIO_STREAM_WRITER;
    raw_stream_writer = raw_stream_init(&raw_cfg);

    ESP_LOGI(TAG, " Create mp3 decoder to decode mp3 file");
    mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
    mp3_decoder = mp3_decoder_init(&mp3_cfg);

    ESP_LOGI(TAG, " Create fatfs stream to write data to sdcard");
    fatfs_stream_cfg_t fatfs_cfg_w = FATFS_STREAM_CFG_DEFAULT();
    fatfs_cfg_w.type = AUDIO_STREAM_WRITER;
    fatfs_stream_writer = fatfs_stream_init(&fatfs_cfg_w);

    ESP_LOGI(TAG, "[2.3] Register all elements to audio pipeline");
    audio_pipeline_register(pipeline, raw_stream_writer, "raw_writer");
    audio_pipeline_register(pipeline, mp3_decoder, "mp3");
    audio_pipeline_register(pipeline, fatfs_stream_writer, "fatfs_writer");

    ESP_LOGI(TAG, "[2.4] Link it together raw_stream_writer-->mp3_decoder-->fatfs_stream_writer-->[sdcard]");
    const char *link_tag[3] = {"raw_writer","mp3", "fatfs_writer"};
    audio_pipeline_link(pipeline, &link_tag[0], 3);
    

    ESP_LOGI(TAG, "[ 4 ] Set up  event listener");
    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, "[4.1] Listening event from all elements of pipeline");
    audio_pipeline_set_listener(pipeline, evt);

    raw_task_run_flag = true;
    if (xTaskCreate(raw_write_task, "RawWriteTask", 3072, NULL, 5, NULL) != pdPASS) {
        ESP_LOGE(TAG, "ERROR creating raw_write_task task! Out of memory?");
    }

    audio_element_set_uri(fatfs_stream_writer, "/sdcard/out.wav");

    audio_pipeline_run(pipeline);

    while (1) {
        audio_event_iface_msg_t msg;
        esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
        if (ret != ESP_OK) {
            continue;
        }

        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT) {
            // Set music info for a new song to be played
            if (msg.source == (void *) mp3_decoder
                && msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) {
                audio_element_info_t music_info = {0};
                audio_element_getinfo(mp3_decoder, &music_info);
                ESP_LOGI(TAG, "[ * ] Received music info from mp3 decoder, sample_rates=%d, bits=%d, ch=%d, duration=%d",
                         music_info.sample_rates, music_info.bits, music_info.channels, music_info.duration);
                audio_element_setinfo(fatfs_stream_writer, &music_info);
                
                continue;
            }

                /* Stop when the last pipeline element (fatfs_stream_writer in this case) receives stop event */
            if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *) fatfs_stream_writer
                && msg.cmd == AEL_MSG_CMD_REPORT_STATUS
                && (((int)msg.data == AEL_STATUS_STATE_STOPPED) || ((int)msg.data == AEL_STATUS_STATE_FINISHED)
                    || ((int)msg.data == AEL_STATUS_ERROR_OPEN))) {
                ESP_LOGW(TAG, "[ * ] Stop event received");
                break;
            }
        }

    }


    ESP_LOGI(TAG, "[ 6 ] Stop audio_pipeline");
    audio_pipeline_stop(pipeline);
    audio_pipeline_wait_for_stop(pipeline);
    audio_pipeline_terminate(pipeline);
    audio_pipeline_unregister(pipeline, raw_stream_writer);
    audio_pipeline_unregister(pipeline, mp3_decoder);
    audio_pipeline_unregister(pipeline, fatfs_stream_writer);

    /* Terminate the pipeline before removing the listener */
    audio_pipeline_remove_listener(pipeline);

    /* Make sure audio_pipeline_remove_listener is called before destroying event_iface */
    audio_event_iface_destroy(evt);

    /* Release all resources */
    audio_pipeline_deinit(pipeline);
    audio_element_deinit(raw_stream_writer);
    audio_element_deinit(mp3_decoder);
    audio_element_deinit(fatfs_stream_writer);
    
}

经测试,可以实现解码,输入流可控,符合具体需求。

参考链接

https://docs.espressif.com/projects/esp-adf/zh_CN/latest/api-reference/streams/index.html#

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用: 在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 ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值