基于ESP32-S3的16位转24位音频I2S传输失真问题排查
ESP32-S3 16位转24位音频I2S传输失真问题排查与修复
核心错误分析
你的16位转24位代码存在三个关键问题,直接导致音频失真:
- 变量初始化顺序错误:
bytes_to_write先被用于计算num_samples,但此时该变量尚未赋值,属于未定义行为,会导致采样数量计算错误。 - 16位采样处理逻辑错误:原代码将16位采样当作无符号字节拼接,未正确处理有符号采样的符号扩展,且字节序拼接方向错误。
- 目标缓冲区索引错误:循环中给
current_pos_16赋值时使用i*2作为起始索引,但24位采样每个占3字节,导致数据错位覆盖。
修正后的转换代码
uint8_t* current_pos = (uint8_t*)buf + total_sent_bytes; // 先基于原始16位音频字节数计算采样数量 size_t num_samples = bytes_to_write / 2; // 计算转换为24位后的总字节数 size_t target_bytes = num_samples * 3; uint8_t *current_pos_16 = heap_caps_malloc(target_bytes, MALLOC_CAP_SPIRAM); if (!current_pos_16) { // 内存分配失败时的处理逻辑 return; } for (int i = 0; i < num_samples; i++) { // 按小端字节序读取16位有符号采样(匹配多数音频格式) int16_t sample_16 = *(int16_t*)(current_pos + i * 2); // 左移8位扩展为24位,自动完成符号扩展 int32_t sample_24 = (int32_t)sample_16 << 8; // 按小端字节序写入24位采样到目标缓冲区 current_pos_16[i * 3] = (uint8_t)(sample_24 & 0xFF); current_pos_16[i * 3 + 1] = (uint8_t)((sample_24 >> 8) & 0xFF); current_pos_16[i * 3 + 2] = (uint8_t)((sample_24 >> 16) & 0xFF); } ESP_ERROR_CHECK(i2s_channel_write(*tx_chan, current_pos_16, target_bytes, &written_bytes, 1000)); total_sent_bytes += written_bytes; free(current_pos_16);
关键修正说明
- 变量顺序调整:先通过原始
bytes_to_write(16位音频总字节数)计算采样数,再推导24位音频的总字节数,避免未定义行为。 - 符号扩展处理:将16位采样以
int16_t类型读取,左移8位时会自动将符号位扩展到24位的高位,保证负数信号的正确转换。 - 索引与字节序修正:每个24位采样占用3字节,使用
i*3作为起始索引;按小端字节序拆分数据,若你的I2S配置为大端,可调整移位顺序(如先写入高位字节)。 - 内存安全检查:增加内存分配失败的判断,避免空指针访问导致的系统异常。
内容的提问来源于stack exchange,提问作者Emirhan Üzüm




