I2S Audio Crackling / Pops
Posted: Wed Feb 28, 2018 11:15 pm
I'm having some trouble getting I2S audio to play how I'd like it to. When connected directly to Adafruit's MAX98357A breakout I get the occasional light pop at the start / end of the audio stream. It's not terrible and I can suppress it by ensuring audio samples fade in/out.
However, if I relay the audio through to another ESP32 I get fairly bad crackling throughout the audio playback. So the flow is: Embedded wave file > ESP32[1] I2S Output > ESP32[2] I2S Input > ESP32[2] I2S Output > MAX98357A Breakout.
The flow in bold is the problematic one.
The I2S relay setup looks like this:
The relay task is super simple:
Apologies if the code isn't super clear, I've been moving things around / playing to try and resolve the crackling.
The task loop relays audio, if any has been sent through. If it hasn't, it'll send the blank buffer which is essentially silence. The silence plays perfectly, no crackling. It's only when audio comes through that the crackling starts.
On a side note, is it possible to have the I2S driver send silence instead of the last buffer if it receives no data in time? I've yet to come across a situation where a buffer underrun doesn't result in annoying buzzes. Silence would definitely be the preferred output if there's no data.
However, if I relay the audio through to another ESP32 I get fairly bad crackling throughout the audio playback. So the flow is: Embedded wave file > ESP32[1] I2S Output > ESP32[2] I2S Input > ESP32[2] I2S Output > MAX98357A Breakout.
The flow in bold is the problematic one.
The I2S relay setup looks like this:
Code: Select all
// Input
inputPortNum = I2S_NUM_0;
inputI2sConfig.mode = (i2s_mode_t)(I2S_MODE_SLAVE | I2S_MODE_RX);
inputI2sConfig.sample_rate = 44100;
inputI2sConfig.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
inputI2sConfig.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
inputI2sConfig.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB);
inputI2sConfig.dma_buf_count = 4;
inputI2sConfig.dma_buf_len = 440;
inputI2sConfig.intr_alloc_flags = 0;
inputI2sConfig.use_apll = true;
inputPinConfig.bck_io_num = PIN_I2S_BCLK_INPUT;
inputPinConfig.ws_io_num = PIN_I2S_LRC_INPUT;
inputPinConfig.data_out_num = -1; // Not used
inputPinConfig.data_in_num = PIN_I2S_DATA_INPUT;
ESP_ERROR_CHECK(i2s_driver_install(inputPortNum, &inputI2sConfig, 0, NULL));
ESP_ERROR_CHECK(i2s_set_pin(inputPortNum, &inputPinConfig));
i2s_set_clk(inputPortNum, 44100, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_STEREO);
// Output
outputPortNum = I2S_NUM_1;
outputI2sConfig.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
outputI2sConfig.sample_rate = 44100;
outputI2sConfig.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
outputI2sConfig.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
outputI2sConfig.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB);
outputI2sConfig.dma_buf_count = 4;
outputI2sConfig.dma_buf_len = 440;
outputI2sConfig.intr_alloc_flags = 0;
outputI2sConfig.use_apll = true;
outputPinConfig.bck_io_num = PIN_I2S_BCLK_OUTPUT;
outputPinConfig.ws_io_num = PIN_I2S_LRC_OUTPUT;
outputPinConfig.data_out_num = PIN_I2S_DATA_OUTPUT;
outputPinConfig.data_in_num = -1; // Not used
ESP_ERROR_CHECK(i2s_driver_install(outputPortNum, &outputI2sConfig, 0, NULL));
ESP_ERROR_CHECK(i2s_set_pin(outputPortNum, &outputPinConfig));
i2s_set_clk(outputPortNum, 44100, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_STEREO);
ESP_ERROR_CHECK(i2s_start(inputPortNum));
ESP_ERROR_CHECK(i2s_start(outputPortNum));
Code: Select all
while (true)
{
// Read a chunk of data from input
memset(buffer, 0, sizeof(buffer));
int size = i2s_read_bytes(inputPortNum, buffer, sizeof(buffer), 5 / portTICK_PERIOD_MS); // I've tried different delays here, including no delay
if (size <= 0)
{
i2s_write_bytes(outputPortNum, buffer, sizeof(buffer), portMAX_DELAY);
continue;
}
// Write to output until we have nothing left
int written = 0;
while (written < size)
written += i2s_write_bytes(outputPortNum, &buffer[written], size - written, portMAX_DELAY);
}
The task loop relays audio, if any has been sent through. If it hasn't, it'll send the blank buffer which is essentially silence. The silence plays perfectly, no crackling. It's only when audio comes through that the crackling starts.
On a side note, is it possible to have the I2S driver send silence instead of the last buffer if it receives no data in time? I've yet to come across a situation where a buffer underrun doesn't result in annoying buzzes. Silence would definitely be the preferred output if there's no data.