STM32通过I2S录制WAV音频卡顿失真问题排查求助
STM32F446RE I2S立体声录音卡顿失真问题排查与解决
问题现象
使用STM32F446RE通过I2S协议从两个INMP441 MEMS麦克风采集立体声音频,基于FreeRTOS+HAL库录制到micro SD卡的WAV文件时,出现卡顿失真(类似达斯·维达音效),Audacity中可见真实音频数据间插入持续噪声。已尝试增大消息队列、更换帧大小与采样率、调整通道数、单麦克风录制等操作,均未解决。
现有固件架构与代码
RTOS任务架构
- UART中断接收启停命令任务
- WAV写入状态机任务
- I2S音频数据处理任务
WriteWavFileTask代码
switch(audio_state) { case STATE_START_RECORDING: sprintf(filename, "%saud_%03d.wav", SDPath, count++); do { res = f_open(&file_ptr, filename, FA_CREATE_ALWAYS|FA_WRITE); } while(res != FR_OK); res = fwrite_wav_header(&file_ptr, I2S_SAMPLE_FREQUENCY, I2S_FRAME, 2); HAL_I2S_Receive_DMA(&hi2s2, aud_buf, READ_SIZE); audio_state = STATE_RECORDING; break; case STATE_RECORDING: osDelay(50); break; case STATE_STOP: HAL_I2S_DMAStop(&hi2s2); while(osMessageQueueGetCount(AudioQueueHandle)) osDelay(1000); filesize = f_size(&file_ptr); data_len = filesize - 44; total_len = filesize - 8; f_lseek(&file_ptr, 4); f_write(&file_ptr, (uint8_t*)&total_len, 4, bw); f_lseek(&file_ptr, 40); f_write(&file_ptr, (uint8_t*)&data_len, 4, bw); f_close(&file_ptr); audio_state = STATE_IDLE; break; case STATE_IDLE: osThreadSuspend(WAVHandle); audio_state = STATE_START_RECORDING; break; default: osDelay(50); break; }
代码宏定义
#define I2S_DATA_WORD_LENGTH (24) // industry-standard 24-bit I2S #define I2S_FRAME (32) // bits per sample #define READ_SIZE (128) // samples to read from I2S #define WRITE_SIZE (READ_SIZE*I2S_FRAME/16) // half words to write #define WRITE_SIZE_BYTES (WRITE_SIZE*2) // bytes to write #define I2S_SAMPLE_FREQUENCY (16000) // sample frequency
I2S数据处理任务代码
void convert_endianness(uint32_t *array, uint16_t Size) { for (int i = 0; i < Size; i++) { array[i] = __REV(array[i]); } } void HAL_I2S_RxCpltCallback(I2S_HandleTypeDef *hi2s) { convert_endianness((uint32_t *)aud_buf, READ_SIZE); osMessageQueuePut(AudioQueueHandle, aud_buf, 0L, 0); HAL_I2S_Receive_DMA(hi2s, aud_buf, READ_SIZE); } void pvrWriteAudioTask(void *argument) { /* USER CODE BEGIN pvrWriteAudioTask */ static UINT *bw; static uint16_t aud_ptr[WRITE_SIZE]; /* Infinite loop */ for(;;) { osMessageQueueGet(AudioQueueHandle, aud_ptr, 0L, osWaitForever); res = f_write(&file_ptr, aud_ptr, WRITE_SIZE_BYTES, bw); } /* USER CODE END pvrWriteAudioTask */ }
硬件配置
已提供I2S与时钟配置截图。
问题根源与解决方法
问题根源
音频数据缓冲方式不合理:ISR中执行字节序转换+消息队列推送操作开销过大,导致DMA接收中断响应不及时;同时单缓冲模式下,DMA传输完成后重新启动的间隙会丢失数据,最终表现为卡顿与噪声插入。
优化方案
- 降低ISR开销:将字节序转换操作从ISR移至后台数据处理任务,减少中断响应时间
- 实现DMA循环双缓冲:配置I2S DMA为循环双缓冲模式,避免DMA重启间隙的数据丢失,确保音频数据连续采集
优化后代码及DMA配置截图已附上。
内容的提问来源于stack exchange,提问作者Wonky
相关产品推荐
相关产品推荐

