4459|3

1381

帖子

2

TA的资源

五彩晶圆(初级)

楼主
 

【ESP32-Korvo测评】(4)从SD卡播放PCM音频 [复制链接]

 

  ESP-Skainet 里面 examples 目录的几个例子中,有些是还有音频回放的——直接接喇叭或者从3.5mm音频口接有源音箱可以听到声音。比如 garbage_classification 这个例子会对唤醒词以及识别到的命令词作出语音回应。
  如前所述,ESP-IDF 这个软件框架包含了很多的组件,给编写程序提供了丰富的软件支持。读一读例子代码就发现,对于音频播放这件室,不需要写什么初始化代码——系统执行到 app_main() 的时候要用的硬件已经初始化好了,只需要调用一个 i2s_write() 函数来向 Codec 输出音频PCM码流就能放出声音。当然,Codec芯片和ESP32怎么连接的是需要配置的,这在工程里面需要设好(例子是为Korvo开发板定制的,默认已经配好了)。
  我还需要从外部获取音频数据,毕竟把音频放到程序数据里面每次都要下载Flash不方便。Korvo开发板带有 TF 卡槽,因此从 TF 卡读取音频文件来播放是最适合的。关于 TF (Micro SD) 卡的访问,想必 ESP-IDF 已经提供了支持,于是我到 IDF 的 examples 目录下去找个代码来参考。
  简化以后,核心的代码只需要一个函数调用 esp_vfs_fat_sdmmc_mount() 就可以挂载 SD 卡:

    sdmmc_host_t host = SDSPI_HOST_DEFAULT();
    sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT();
    slot_config.gpio_miso = 21;
    slot_config.gpio_mosi = 18;
    slot_config.gpio_sck  = 5;
    slot_config.gpio_cs   = 23;
	slot_config.gpio_cd   = 35;

    esp_vfs_fat_sdmmc_mount_config_t mount_config = {
        .format_if_mount_failed = false,
        .max_files = 5,
        .allocation_unit_size = 16 * 1024
    };

    sdmmc_card_t* card;
    esp_err_t ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card);

  这上面 slot_config 指定了 SD 卡如何与 ESP32 连接的,就是引脚的配置信息。这里用的默认的 SPI 模式,就是 Korvo 板子上的接法。根据函数的返回值判断 SD 卡是否成功识别了,如果成功,访问卡上的文件就很方便了。

 

  根据例子代码,在挂载 SD 卡之后就可以直接用 C 标准库的 fopen() 函数来打开 "/sdcard/" 这个路径下面的文件,然后就用标准库的文件读写调用访问文件即可。竟然如此简单!于是我就试着在卡上存一个 PCM 文件,通过 Korvo 板子播放出来:

    ESP_LOGI(TAG, "Opening file");
    FILE* f = fopen("/sdcard/mazurka.pcm", "r");
    if (f == NULL) {
        ESP_LOGE(TAG, "Failed to open pcm file");
        return;
    }

	uint16_t *pcm = malloc(32000*2);
	if(!pcm)
		ESP_LOGI(TAG, "malloc() failed");
	else
	{
		while(!feof(f))
		{
			size_t wbs;
			fread(pcm, 2, 32000, f);
			i2s_write(0,pcm,64000,&wbs,portMAX_DELAY);
		}
		free(pcm);
	}

	fclose(f);
    esp_vfs_fat_sdmmc_unmount();

  每次从文件读取了 64000 字节,对应 16-bit 单声道 32000 个采样,存放在 pcm 缓冲数组中。注意这里使用 malloc() 动态分配的内存,因为我一开始用的是局部变量,一运行堆栈就溢出了……毕竟是 FreeRTOS 给任务分配的堆栈,还是省着用吧。fread() 读文件之后就用 i2s_write() 来播放。

 

  系统回放的采样率是预定好的,可以通过测量 I2S LRCK 信号的频率来确认:是 16kHz. TF 卡上存的文件要转成 16kHz 采样的,不然回放出来就变调了。
  由于是在一个循环中“读文件”然后“写Codec”再反复,从 ESP32 发送到 Codec 的音频码流可能发生断流的情况。这取决于 i2s_write() 函数的设计了——当它返回之后内部缓冲区是否还有未发送的数据,是否未发送的数据能坚持到下一次 i2s_write() 函数的调用“续”上码流?虽然 fread() 读 SD 卡 64000 字节时间不长,如果因为一点等待造成的音频码流中断是可以听出来的。
  我上面代码每次播放了 2 秒的音频,可以听出来每2秒之后有短促的间断。要解决这个 bug, 看来需要两个任务分别进行读文件和写Codec的操作了。可是我发现回放的音质并不理想,这一点间断也不是最严重的问题,暂时就不管它喽。
 

最新回复

从外部获取音频数据,比把程序数据里面然后下载到Flash操作上是比较方便 因为flash是只能一个扇区一个扇区写的   详情 回复 发表于 2021-3-4 21:26

赞赏

2

查看全部赞赏

点赞(3) 关注
 
 

回复
举报

1411

帖子

3

TA的资源

版主

沙发
 
感谢分享
 
 
 

回复

1942

帖子

2

TA的资源

版主

板凳
 

感谢分享!不错哈!期待后续完善咯~

 
 
 

回复

6828

帖子

0

TA的资源

五彩晶圆(高级)

4
 

从外部获取音频数据,比把程序数据里面然后下载到Flash操作上是比较方便

因为flash是只能一个扇区一个扇区写的

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/7 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表