【ESP32-Korvo测评】(4)从SD卡播放PCM音频
<p> ESP-Skainet 里面 examples 目录的几个例子中,有些是还有音频回放的——直接接喇叭或者从3.5mm音频口接有源音箱可以听到声音。比如 garbage_classification 这个例子会对唤醒词以及识别到的命令词作出语音回应。<br />如前所述,ESP-IDF 这个软件框架包含了很多的组件,给编写程序提供了丰富的软件支持。读一读例子代码就发现,对于音频播放这件室,不需要写什么初始化代码——系统执行到 app_main() 的时候要用的硬件已经初始化好了,只需要调用一个 <span style="color:#c0392b;"><strong>i2s_write()</strong></span> 函数来向 Codec 输出音频PCM码流就能放出声音。当然,Codec芯片和ESP32怎么连接的是需要配置的,这在工程里面需要设好(例子是为Korvo开发板定制的,默认已经配好了)。<br />
我还需要从外部获取音频数据,毕竟把音频放到程序数据里面每次都要下载Flash不方便。Korvo开发板带有 TF 卡槽,因此从 TF 卡读取音频文件来播放是最适合的。关于 TF (Micro SD) 卡的访问,想必 ESP-IDF 已经提供了支持,于是我到 IDF 的 examples 目录下去找个代码来参考。<br />
简化以后,核心的代码只需要一个函数调用 <span style="color:#c0392b;"><strong>esp_vfs_fat_sdmmc_mount()</strong></span> 就可以挂载 SD 卡:</p>
<pre>
<code class="language-cpp"> 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);
</code></pre>
<p> 这上面 slot_config 指定了 SD 卡如何与 ESP32 连接的,就是引脚的配置信息。这里用的默认的 SPI 模式,就是 Korvo 板子上的接法。根据函数的返回值判断 SD 卡是否成功识别了,如果成功,访问卡上的文件就很方便了。</p>
<p> </p>
<p> 根据例子代码,在挂载 SD 卡之后就可以直接用 C 标准库的 fopen() 函数来打开 "/sdcard/" 这个路径下面的文件,然后就用标准库的文件读写调用访问文件即可。竟然如此简单!于是我就试着在卡上存一个 PCM 文件,通过 Korvo 板子播放出来:</p>
<pre>
<code class="language-cpp"> 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();
</code></pre>
<p> 每次从文件读取了 64000 字节,对应 16-bit 单声道 32000 个采样,存放在 pcm 缓冲数组中。注意这里使用 malloc() 动态分配的内存,因为我一开始用的是局部变量,一运行堆栈就溢出了……毕竟是 FreeRTOS 给任务分配的堆栈,还是省着用吧。fread() 读文件之后就用 i2s_write() 来播放。</p>
<p> </p>
<p> 系统回放的采样率是预定好的,可以通过测量 I2S LRCK 信号的频率来确认:是 16kHz. TF 卡上存的文件要转成 16kHz 采样的,不然回放出来就变调了。<br />
由于是在一个循环中“读文件”然后“写Codec”再反复,从 ESP32 发送到 Codec 的音频码流可能发生断流的情况。这取决于 i2s_write() 函数的设计了——当它返回之后内部缓冲区是否还有未发送的数据,是否未发送的数据能坚持到下一次 i2s_write() 函数的调用“续”上码流?虽然 fread() 读 SD 卡 64000 字节时间不长,如果因为一点等待造成的音频码流中断是可以听出来的。<br />
我上面代码每次播放了 2 秒的音频,可以听出来每2秒之后有短促的间断。要解决这个 bug, 看来需要两个任务分别进行读文件和写Codec的操作了。可是我发现回放的音质并不理想,这一点间断也不是最严重的问题,暂时就不管它喽。<br />
</p>
感谢分享 <p>感谢分享!不错哈!期待后续完善咯~</p>
<p>从外部获取音频数据,比把程序数据里面然后下载到Flash操作上是比较方便</p>
<p>因为flash是只能一个扇区一个扇区写的</p>
页:
[1]