ESP32 Converting a 16 bit audio file to 24 bit for I2S

Emirhanuzum
Posts: 3
Joined: Mon Dec 16, 2024 10:55 am

ESP32 Converting a 16 bit audio file to 24 bit for I2S

Postby Emirhanuzum » Sun May 25, 2025 7:45 pm

I am trying to convert a 16 bit audio samples to 24 bit audio samples file for replaying and mixing purposes. I am shifting it to the left by 8 bytes and then send it to the i2s_channel but the sound im getting is very distorted. This should work, because im doing the sign assignment correct. I am able to get a nice sound when i transmit a 24 bit audio sample, so my configuration should not be the problem. Why is this not working ? I am using STD_PHILIPS_SLOT_DEFAULT_CONFIG and changing the data_bit_width to 24 bit accordingly. Also the master clock multiptle is already at I2S_MCLK_MULTIPLE_384

Code: Select all

i2s_channel_disable(*tx_chan);
i2s_std_slot_config_t std_slot_config_24 = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_24BIT, I2S_SLOT_MODE_STEREO);
std_slot_config_24.slot_bit_width = I2S_SLOT_BIT_WIDTH_32BIT;
i2s_channel_reconfig_std_slot(*tx_chan, &std_slot_config_24);
i2s_channel_enable(*tx_chan);

uint8_t* current_pos = (uint8_t*)buf + total_sent_bytes;
size_t num_samples = bytes_to_write / 2;
size_t bytes_to_write = num_samples * 3; // Convert to 24-bit
uint8_t *current_pos_16 = heap_caps_malloc(bytes_to_write, MALLOC_CAP_SPIRAM);
for (int i = 0; i < num_samples; i++) {
    uint32_t sample = (uint32_t)((0x00) |
                      (current_pos[i*2] << 8) |
                      (current_pos[i*2 + 1] << 16));      
    current_pos_16[i*2] = sample & 0xFF; // Padding for 24-bit
    current_pos_16[i*2 + 1] = (sample >> 8) & 0xFF;
    current_pos_16[i*2 + 2] = (sample >> 16) & 0xFF;
}
                    
ESP_ERROR_CHECK(i2s_channel_write(*tx_chan, current_pos_16, bytes_to_write, &written_bytes, 1000));
total_sent_bytes += written_bytes;
free(current_pos_16);
This is how i transmit a 24 bit audio sample that is originally 24 bit :

Code: Select all

uint8_t* current_pos = (uint8_t*)buf + total_sent_bytes;                   
uint8_t *current_pos_24 = heap_caps_malloc(bytes_to_write, MALLOC_CAP_SPIRAM);          
                    
for (int i = 0; i < (bytes_to_write / 3); i++) {
      uint32_t sample = (uint32_t)(current_pos[i*3] | 
                                                 (current_pos[i*3 + 1] << 8) | 
                                                 (current_pos[i*3 + 2] << 16));      
      current_pos_24[i*3] = sample & 0xFF;
      current_pos_24[i*3 + 1] = (sample >> 8) & 0xFF;
      current_pos_24[i*3 + 2] = (sample >> 16) & 0xFF;
}
ESP_ERROR_CHECK(i2s_channel_write(*tx_chan, current_pos_24, bytes_to_write, &written_bytes, 1000));
total_sent_bytes += written_bytes;
free(current_pos_24);

ahsrabrifat
Posts: 201
Joined: Sat Jan 18, 2025 2:31 pm

Re: ESP32 Converting a 16 bit audio file to 24 bit for I2S

Postby ahsrabrifat » Mon May 26, 2025 8:21 am

You want to convert a signed 16-bit sample to signed 24-bit by preserving sign and shifting to left-align the sample within 24 bits.

Code: Select all

int16_t sample16 = (int16_t)((current_pos[i*2 + 1] << 8) | current_pos[i*2]);
int32_t sample24 = sample16 << 8;
Now write the sample24 LSB-first (little-endian) into 3 bytes (assuming I2S expects LSB-first):

Code: Select all


current_pos_16[i*3]     = (uint8_t)(sample24 & 0xFF);
current_pos_16[i*3 + 1] = (uint8_t)((sample24 >> 8) & 0xFF);
current_pos_16[i*3 + 2] = (uint8_t)((sample24 >> 16) & 0xFF);

You were writing to current_pos_16[i*2], i*2 + 1, i*2 + 2, which is incorrect indexing for a 3-byte sample — should be i*3, i*3+1, i*3+2.

MicroController
Posts: 2705
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: ESP32 Converting a 16 bit audio file to 24 bit for I2S

Postby MicroController » Mon May 26, 2025 9:11 am

Faster, and possibly easier: Just put a byte of 0x00 between the 16-bit values in the output, like

Code: Select all

const uint8_t* const src = ...; // source of 16-bit samples
uint8_t* const dst_24 = ...; // destination for the 24-bit values

const int16_t* const psrc_16 = (const int16_t*)src; // read int16's directly
uint8_t* pdst_24 = dst_24;

for(int i = 0; i < sample_cnt; i += 1) {
  *pdst_24 = 0x00; // set lowest 8 bits of the 24-bit value to 0
  pdst_24 += 1; // 1 byte written, advance to the next byte
  *(int16_t*)(pdst_24) = psrc_16[i]; // copy 16 bits from src to the upper 16 bits of the 24-bit dst
  pdst_24 += 2; // 2 more bytes written
}

Emirhanuzum
Posts: 3
Joined: Mon Dec 16, 2024 10:55 am

Re: ESP32 Converting a 16 bit audio file to 24 bit for I2S

Postby Emirhanuzum » Tue May 27, 2025 10:35 am

Thanks to both of you. The problem was yes the correct indexing but also the sign conversion. Because i was not assigning it to any 16 bit variable the signs were not gettin registered correctly. Now i solved the issue but there is still a bit of a distortion. The original 16 bit samples sound cleaner

MicroController
Posts: 2705
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: ESP32 Converting a 16 bit audio file to 24 bit for I2S

Postby MicroController » Wed May 28, 2025 9:53 pm

Btw, extending 16 to 24 bits using a fixed padding value (e.g. 0x00) will, in theory, change the analog output signal. For example, 16-bit 0x7fff corresponds to the maximum amplitude, while 24-bit 0x7fff00 does not. One simple way to improve that situation is to 'copy' the most significant 8 bits (excl. sign!) of the 16-bit value to the lowest 8 bits of the 24-bit value, i.e.
output[3*i+0] = (uint8_t)(sample16 >> 7);
output[3*i+1] = (uint8_t)(sample16);
output[3*i+2] = (uint8_t)(sample16 >> 8);

This 'stretches' the 16-bit range to the full 24-bit range; 0x0000 stays 0x000000 while 0x7fff becomes 0x7fffff.

Who is online

Users browsing this forum: PerplexityBot and 1 guest