ESP-ADF音频框架 -- stream

2023-11-01 12:20
文章标签 音频 框架 stream esp adf

本文主要是介绍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 playedif (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#

这篇关于ESP-ADF音频框架 -- stream的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/322949

相关文章

2.1/5.1和7.1声道系统有什么区别? 音频声道的专业知识科普

《2.1/5.1和7.1声道系统有什么区别?音频声道的专业知识科普》当设置环绕声系统时,会遇到2.1、5.1、7.1、7.1.2、9.1等数字,当一遍又一遍地看到它们时,可能想知道它们是什... 想要把智能电视自带的音响升级成专业级的家庭影院系统吗?那么你将面临一个重要的选择——使用 2.1、5.1 还是

java Stream操作转换方法

《javaStream操作转换方法》文章总结了Java8中流(Stream)API的多种常用方法,包括创建流、过滤、遍历、分组、排序、去重、查找、匹配、转换、归约、打印日志、最大最小值、统计、连接、... 目录流创建1、list 转 map2、filter()过滤3、foreach遍历4、groupingB

MyBatis框架实现一个简单的数据查询操作

《MyBatis框架实现一个简单的数据查询操作》本文介绍了MyBatis框架下进行数据查询操作的详细步骤,括创建实体类、编写SQL标签、配置Mapper、开启驼峰命名映射以及执行SQL语句等,感兴趣的... 基于在前面几章我们已经学习了对MyBATis进行环境配置,并利用SqlSessionFactory核

cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个?

跨平台系列 cross-plateform 跨平台应用程序-01-概览 cross-plateform 跨平台应用程序-02-有哪些主流技术栈? cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个? cross-plateform 跨平台应用程序-04-React Native 介绍 cross-plateform 跨平台应用程序-05-Flutte

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF

数据治理框架-ISO数据治理标准

引言 "数据治理"并不是一个新的概念,国内外有很多组织专注于数据治理理论和实践的研究。目前国际上,主要的数据治理框架有ISO数据治理标准、GDI数据治理框架、DAMA数据治理管理框架等。 ISO数据治理标准 改标准阐述了数据治理的标准、基本原则和数据治理模型,是一套完整的数据治理方法论。 ISO/IEC 38505标准的数据治理方法论的核心内容如下: 数据治理的目标:促进组织高效、合理地

ZooKeeper 中的 Curator 框架解析

Apache ZooKeeper 是一个为分布式应用提供一致性服务的软件。它提供了诸如配置管理、分布式同步、组服务等功能。在使用 ZooKeeper 时,Curator 是一个非常流行的客户端库,它简化了 ZooKeeper 的使用,提供了高级的抽象和丰富的工具。本文将详细介绍 Curator 框架,包括它的设计哲学、核心组件以及如何使用 Curator 来简化 ZooKeeper 的操作。 1

【Kubernetes】K8s 的安全框架和用户认证

K8s 的安全框架和用户认证 1.Kubernetes 的安全框架1.1 认证:Authentication1.2 鉴权:Authorization1.3 准入控制:Admission Control 2.Kubernetes 的用户认证2.1 Kubernetes 的用户认证方式2.2 配置 Kubernetes 集群使用密码认证 Kubernetes 作为一个分布式的虚拟

Spring Framework系统框架

序号表示的是学习顺序 IoC(控制反转)/DI(依赖注入): ioc:思想上是控制反转,spring提供了一个容器,称为IOC容器,用它来充当IOC思想中的外部。 我的理解就是spring把这些对象集中管理,放在容器中,这个容器就叫Ioc这些对象统称为Bean 用对象的时候不用new,直接外部提供(bean) 当外部的对象有关系的时候,IOC给它俩绑好(DI) DI和IO

Sentinel 高可用流量管理框架

Sentinel 是面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。 Sentinel 具有以下特性: 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应