使用外部 DAC 采样频率高于 20kSps 的 ESP32 I2S 音频播放失真
Distortion in ESP32 I2S audio playback with external DAC for sample frequency higher than 20kSps
硬件:ESP32 DevKitV1、PCM5102 分线板、SD 卡适配器。
软件:Arduino框架。
一段时间以来,我一直在努力使用 ESP32 外部的 I2S DAC 播放音频。
问题是我只能在不失真的情况下播放低采样频率,即低于 20kSps。
我一直在研究文档、https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/i2s.html 和许多其他来源,但仍然没有设法解决这个问题。
I2S配置函数:
esp_err_t I2Smixer::i2sConfig(int bclkPin, int lrckPin, int dinPin, int sample_rate)
{
// i2s configuration: Tx to ext DAC, 2's complement 16-bit PCM, mono,
const i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_CHANNEL_MONO), // only tx, external DAC
.sample_rate = sample_rate,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, // single channel
// .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL3, // highest interrupt priority that can be handeled in c
.dma_buf_count = 128, //16,
.dma_buf_len = 128, // 64
.use_apll = false,
.tx_desc_auto_clear = true};
const i2s_pin_config_t pin_config = {
.bck_io_num = bclkPin, //this is BCK pin
.ws_io_num = lrckPin, // this is LRCK pin
.data_out_num = dinPin, // this is DATA output pin
.data_in_num = I2S_PIN_NO_CHANGE // Not used
};
esp_err_t ret1 = i2s_driver_install((i2s_port_t)i2s_num, &i2s_config, 0, NULL);
esp_err_t ret2 = i2s_set_pin((i2s_port_t)i2s_num, &pin_config);
esp_err_t ret3 = i2s_set_sample_rates((i2s_port_t)i2s_num, sample_rate);
// i2s_adc_disable((i2s_port_t)i2s_num);
// esp_err_t ret3 = rtc_clk_apll_enable(1, 15, 8, 5, 6);
return ret1 + ret2 + ret3;
}
打开以 16 位单声道 PCM、44.1kHz 格式创建的波形文件:
File sample_file = SD.open("/test.wav")
在主循环中,样本被馈送到 I2S 驱动程序。
esp_err_t I2Smixer::loop()
{
esp_err_t ret1 = ESP_OK, ret2 = ESP_OK;
int32_t output = 0;
if (sample_file.available())
{
if (sample_file.size() - sample_file.position() > 2) // bytes left
{
int16_t tmp; // 16 bits signed PCM assumed
sample_file.read((uint8_t *)&tmp, 2);
output =(int32_t)tmp;
}
else
{
sample_file.close();
}
}
size_t i2s_bytes_write;
int16_t int16_t_output = (int16_t)output;
ret1 = i2s_write((i2s_port_t)i2s_num, &int16_t_output, 2, &i2s_bytes_write, portMAX_DELAY);
if (i2s_bytes_write != 2)
ret2 = ESP_FAIL;
return ret1 + ret2;
}
这适用于高达 20 kSps 的采样率。
对于 32k 或 44.1k 的采样率,会发生严重失真。我怀疑这是由 I2S DMA Tx 缓冲区引起的。
如果增加 DMA 缓冲区的数量(dma_buf_count)和缓冲区长度(dma_buf_len),那么一开始声音可以正常播放。随后,在很短的时间后,失真再次出现。我无法测量这么短的时间跨度,也许大约一秒钟,但我确实注意到它取决于 dma_buf_count 和 dma_buf_len.
接下来,我尝试将 CPU 频率提高到 240MHz,但没有任何改善。
此外,我尝试播放来自 SPIFSS 的文件,但没有任何改进。
我现在没思路了,有没有人也遇到过这个问题?
一次读取一个样本并将其推送到 I2S 驱动程序将不是驱动程序的最有效使用方式。您在每 128 字节的 DMA 缓冲区中仅使用 2 个字节。在 DMA 缓冲区为 "starved".
之前,只留下一个采样周期来推送下一个采样
以 128 字节(64 个样本)块读取文件并将整个块写入 I2S 以便有效地使用 DMA。
根据文件系统的实现,使用与文件系统的媒体、扇区大小和 DMA 缓冲相关的较大块可能会更有效一些。
硬件:ESP32 DevKitV1、PCM5102 分线板、SD 卡适配器。
软件:Arduino框架。
一段时间以来,我一直在努力使用 ESP32 外部的 I2S DAC 播放音频。 问题是我只能在不失真的情况下播放低采样频率,即低于 20kSps。 我一直在研究文档、https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/i2s.html 和许多其他来源,但仍然没有设法解决这个问题。
I2S配置函数:
esp_err_t I2Smixer::i2sConfig(int bclkPin, int lrckPin, int dinPin, int sample_rate)
{
// i2s configuration: Tx to ext DAC, 2's complement 16-bit PCM, mono,
const i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_CHANNEL_MONO), // only tx, external DAC
.sample_rate = sample_rate,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, // single channel
// .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL3, // highest interrupt priority that can be handeled in c
.dma_buf_count = 128, //16,
.dma_buf_len = 128, // 64
.use_apll = false,
.tx_desc_auto_clear = true};
const i2s_pin_config_t pin_config = {
.bck_io_num = bclkPin, //this is BCK pin
.ws_io_num = lrckPin, // this is LRCK pin
.data_out_num = dinPin, // this is DATA output pin
.data_in_num = I2S_PIN_NO_CHANGE // Not used
};
esp_err_t ret1 = i2s_driver_install((i2s_port_t)i2s_num, &i2s_config, 0, NULL);
esp_err_t ret2 = i2s_set_pin((i2s_port_t)i2s_num, &pin_config);
esp_err_t ret3 = i2s_set_sample_rates((i2s_port_t)i2s_num, sample_rate);
// i2s_adc_disable((i2s_port_t)i2s_num);
// esp_err_t ret3 = rtc_clk_apll_enable(1, 15, 8, 5, 6);
return ret1 + ret2 + ret3;
}
打开以 16 位单声道 PCM、44.1kHz 格式创建的波形文件:
File sample_file = SD.open("/test.wav")
在主循环中,样本被馈送到 I2S 驱动程序。
esp_err_t I2Smixer::loop()
{
esp_err_t ret1 = ESP_OK, ret2 = ESP_OK;
int32_t output = 0;
if (sample_file.available())
{
if (sample_file.size() - sample_file.position() > 2) // bytes left
{
int16_t tmp; // 16 bits signed PCM assumed
sample_file.read((uint8_t *)&tmp, 2);
output =(int32_t)tmp;
}
else
{
sample_file.close();
}
}
size_t i2s_bytes_write;
int16_t int16_t_output = (int16_t)output;
ret1 = i2s_write((i2s_port_t)i2s_num, &int16_t_output, 2, &i2s_bytes_write, portMAX_DELAY);
if (i2s_bytes_write != 2)
ret2 = ESP_FAIL;
return ret1 + ret2;
}
这适用于高达 20 kSps 的采样率。 对于 32k 或 44.1k 的采样率,会发生严重失真。我怀疑这是由 I2S DMA Tx 缓冲区引起的。 如果增加 DMA 缓冲区的数量(dma_buf_count)和缓冲区长度(dma_buf_len),那么一开始声音可以正常播放。随后,在很短的时间后,失真再次出现。我无法测量这么短的时间跨度,也许大约一秒钟,但我确实注意到它取决于 dma_buf_count 和 dma_buf_len.
接下来,我尝试将 CPU 频率提高到 240MHz,但没有任何改善。 此外,我尝试播放来自 SPIFSS 的文件,但没有任何改进。
我现在没思路了,有没有人也遇到过这个问题?
一次读取一个样本并将其推送到 I2S 驱动程序将不是驱动程序的最有效使用方式。您在每 128 字节的 DMA 缓冲区中仅使用 2 个字节。在 DMA 缓冲区为 "starved".
之前,只留下一个采样周期来推送下一个采样以 128 字节(64 个样本)块读取文件并将整个块写入 I2S 以便有效地使用 DMA。
根据文件系统的实现,使用与文件系统的媒体、扇区大小和 DMA 缓冲相关的较大块可能会更有效一些。