cruelfox 发表于 2021-2-27 18:59

【ESP32-Korvo测评】(4)从SD卡播放PCM音频

<p>  ESP-Skainet 里面 examples 目录的几个例子中,有些是还有音频回放的&mdash;&mdash;直接接喇叭或者从3.5mm音频口接有源音箱可以听到声音。比如 garbage_classification 这个例子会对唤醒词以及识别到的命令词作出语音回应。<br />
  如前所述,ESP-IDF 这个软件框架包含了很多的组件,给编写程序提供了丰富的软件支持。读一读例子代码就发现,对于音频播放这件室,不需要写什么初始化代码&mdash;&mdash;系统执行到 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", &amp;host, &amp;slot_config, &amp;mount_config, &amp;card);
</code></pre>

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

<p>&nbsp;</p>

<p>  根据例子代码,在挂载 SD 卡之后就可以直接用 C 标准库的 fopen() 函数来打开 &quot;/sdcard/&quot; 这个路径下面的文件,然后就用标准库的文件读写调用访问文件即可。竟然如此简单!于是我就试着在卡上存一个 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,&amp;wbs,portMAX_DELAY);
                }
                free(pcm);
        }

        fclose(f);
    esp_vfs_fat_sdmmc_unmount();
</code></pre>

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

<p>&nbsp;</p>

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

annysky2012 发表于 2021-2-27 20:48

感谢分享

w494143467 发表于 2021-2-28 09:14

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

Jacktang 发表于 2021-3-4 21:26

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

<p>因为flash是只能一个扇区一个扇区写的</p>
页: [1]
查看完整版本: 【ESP32-Korvo测评】(4)从SD卡播放PCM音频