@SEGA Project Sekai
978 字
5 分钟
FFmpeg+SDL 音频播放调试问题总结
FFmpeg+SDL 音频播放调试问题总结
一、内存访问段错误(SIGSEGV)
1.1 现象描述
程序执行 SDL_QueueAudio
或 memcpy
时崩溃,GDB指向 __memmove_avx_unaligned_erms
(AVX指令内存拷贝函数),错误信号 SIGSEGV
(非法内存访问)。
1.2 子问题1:野指针(引用已释放的FFmpeg帧内存)
1.2.1 错误代码
// AudioDecoder::decode_frame(错误版)bool AudioDecoder::decode_frame(AudioFrame& out_frame) { AVFrame* resampled_frame = get_resampled_frame(); out_frame.data[0] = resampled_frame->data[0]; out_frame.nb_samples = resampled_frame->nb_samples; av_frame_unref(resampled_frame); return true;}
// play函数(触发段错误)void play(...) { AudioFrame frame; SDL_AudioDeviceID dev_id = init_sdl(); while (!decoder.is_eof()) { decoder.decode_frame(frame); SDL_QueueAudio(dev_id, frame.data[0], frame.nb_samples*2*2); }}
1.2.2 根本原因
FFmpeg的 av_frame_unref
会释放帧的 data
缓冲区,代码仅保存地址引用未深拷贝,导致后续访问野指针。
1.2.3 修复代码
// AudioDecoder::decode_frame(正确版)bool AudioDecoder::decode_frame(AudioFrame& out_frame) { AVFrame* resampled_frame = get_resampled_frame(); size_t total_size = resampled_frame->nb_samples * 2 * 2; out_frame.data[0] = (uint8_t*)malloc(total_size); memcpy(out_frame.data[0], resampled_frame->data[0], total_size); out_frame.nb_samples = resampled_frame->nb_samples; av_frame_unref(resampled_frame); return true;}
// play函数补充释放void play(...) { SDL_QueueAudio(...); free(frame.data[0]);}
1.3 子问题2:内存未对齐(AVX指令集要求不满足)
1.3.1 错误代码
// 错误:用普通new分配缓冲区(无法保证16字节对齐)out_frame.data[0] = new uint8_t[total_size];memcpy(out_frame.data[0], resampled_frame->data[0], total_size);
1.3.2 根本原因
AVX指令集要求内存地址16字节对齐,普通 new
/malloc
可能返回非对齐地址(如 0x7fffe001da41
),触发段错误。
1.3.3 修复代码
// 用posix_memalign分配对齐内存uint8_t* buf = nullptr;int align = 16;posix_memalign((void**)&buf, align, total_size);out_frame.data[0] = buf;memcpy(out_frame.data[0], resampled_frame->data[0], total_size);free(buf);
二、终端输出持续滚动
2.1 现象描述
实时刷新区(解码信息、进度条)未在原位置更新,而是不断新增行,导致终端输出滚动,干扰调试。
2.2 错误代码
// 错误:每次更新用endl新增行,未覆盖旧内容void update_progress(...) { std::cout << "1. 解码信息:帧=" << decode_count << endl; std::cout << "2. 进度:" << progress << "%" << endl;}
2.3 根本原因
endl
会触发换行+缓冲区刷新,每次更新新增2行;未用ANSI转义序列控制光标位置,导致无法覆盖旧内容。
2.4 修复代码
void update_progress(int decode_count, float progress) { std::cout << "\033[2A"; // 上移2行(回到刷新区开头) std::cout << "\033[K"; // 清除当前行 std::cout << "1. 解码信息:帧=" << decode_count; std::cout << "\n\033[K"; // 换行+清除下一行 std::cout << "2. 进度:" << progress << "%"; std::cout << "\033[2B"; // 下移2行(回到原位置) fflush(stdout);}
三、音频播放卡顿
3.1 现象描述
音频播放有断续感,SDL队列频繁为空,GDB查看CPU占用高(终端操作占比超50%)。
3.2 错误代码
// 错误:每帧都执行终端刷新,占用大量CPUwhile (!decoder.is_eof()) { decoder.decode_frame(frame); SDL_QueueAudio(...); update_progress(...); // 每帧调用终端刷新(ANSI序列+输出)}
3.3 根本原因
终端操作(ANSI序列解析、文本渲染、缓冲区同步)耗时远高于音频帧处理(约0.4~1ms/帧),导致SDL队列数据供应不及时,出现”断流”。
3.4 修复代码
// 优化:每10帧刷新一次终端,减少90%操作int frame_counter = 0;const int refresh_interval = 10;
while (!decoder.is_eof()) { decoder.decode_frame(frame); SDL_QueueAudio(...); frame_counter++;
if (frame_counter % refresh_interval == 0) { update_progress(...); }}
四、环境适配差异(CLion 卡顿 vs WSL2 流畅)
4.1 现象描述
CLion中运行卡顿,WSL2(Ubuntu终端+GDB)中流畅,CPU占用差异显著(CLion中终端操作占比70%,WSL2中占比10%)。
4.2 根本原因
- CLion终端中间层开销:CLion需捕获
stdout
→转换编码(UTF-8→UTF-16)→UI渲染,比WSL2原生终端慢10~100倍; - 资源优先级:CLion(IDE)占用大量CPU,音频程序优先级被压低;WSL2中程序优先级更高,资源抢占少;
- ANSI序列处理效率:CLion解析ANSI序列需映射到UI控件,WSL2内核直接处理,耗时可忽略。
4.3 解决方案
- CLion中使用WSL2终端:
配置路径:Run/Debug Configurations
→Terminal
→Use custom terminal
→ 填入C:\Windows\System32\wsl.exe -d Ubuntu
; - 彻底禁用终端输出:
注释所有cout
/cerr
,仅保留核心播放逻辑; - 提高程序优先级:
Windows任务管理器→详细信息→右键程序→设置优先级为”高”。
FFmpeg+SDL 音频播放调试问题总结
https://mizuki.mysqil.com/posts/20251010/play/ 部分信息可能已经过时