11 KiB
11 KiB
Day 10 - 音频 I/O 错误修复与程序稳定性提升
日期: 2025-12-05
目标: 修复音频 I/O 错误导致的程序崩溃问题 + 验证网络通信
结果: ✅ 成功修复,程序稳定运行 36+ 秒,音频自动恢复机制生效,WebSocket 通信成功建立
📋 问题背景
初始症状
程序运行约 10 秒后,音频线程崩溃导致整个程序段错误:
[ERROR] [AudioCapture] Read error: I/O error
[ERROR] [AudioCapture] Cannot recover: I/O error
Segmentation fault (core dumped)
影响
- ❌ 程序无法稳定运行超过 10 秒
- ❌ 音频错误导致整个进程崩溃
- ❌ IMU 和摄像头线程也被终止
🔍 问题诊断
根本原因分析
经过日志和代码分析,发现两个关键问题:
1. 类型错误 - main.cpp
// ❌ 错误:使用无符号类型接收有符号返回值
size_t frames_read = mic.read(buffer, 160);
// mic.read() 返回 snd_pcm_sframes_t(有符号)
// 负数错误码被隐式转换为巨大的正数
// 导致未定义行为和内存访问错误
2. 错误处理不足 - audio_capture.cpp
// ❌ 问题:恢复失败后直接返回错误码
int err = snd_pcm_recover(m_pcm_handle, frames_read, 0);
if (err < 0) {
LOG_ERROR("[AudioCapture] Cannot recover: %s", snd_strerror(err));
return frames_read; // 返回负数,但main.cpp没有检查
}
连锁反应:
- ALSA I/O 错误发生
snd_pcm_recover()尝试恢复失败- 返回负数错误码
main.cpp中的size_t类型将负数转换为巨大正数- 后续内存访问越界
- Segmentation fault 💥
✅ 解决方案
修改 1: audio_capture.cpp - 自动设备重新初始化
文件: src/audio/audio_capture.cpp
行号: 192-216
核心改进:在 ALSA 恢复失败时,自动重新初始化设备
} else {
// 其他错误
LOG_ERROR("[AudioCapture] Read error: %s", snd_strerror(frames_read));
// 尝试自动恢复
int err = snd_pcm_recover(m_pcm_handle, frames_read, 0);
if (err < 0) {
LOG_ERROR("[AudioCapture] Cannot recover: %s, attempting to reinitialize...",
snd_strerror(err));
// 关闭设备
snd_pcm_close(m_pcm_handle);
m_pcm_handle = nullptr;
// 等待 500ms
usleep(500000);
// 重新初始化
if (!init()) {
LOG_ERROR("[AudioCapture] Failed to reinitialize device");
return -1; // 彻底失败
}
LOG_INFO("[AudioCapture] Device reinitialized successfully");
return 0; // 本次读取失败,但设备已恢复
}
return 0;
}
效果:
- ✅ I/O 错误后自动尝试恢复设备
- ✅ 减少崩溃概率
- ✅ 只在彻底无法恢复时才返回 -1
修改 2: main.cpp - 修复类型错误和添加错误检查
文件: src/main.cpp
行号: 124-133
问题 1:类型错误
// ❌ 之前:错误的类型
size_t frames_read = mic.read(buffer, 160); // 无符号类型无法检测负值
// ✅ 修正:正确的类型
snd_pcm_sframes_t frames_read = mic.read(buffer, 160); // 有符号类型
问题 2:缺少错误检查
// ✅ 添加错误检查
snd_pcm_sframes_t frames_read = mic.read(buffer, 160);
// 检查致命错误(设备重新初始化失败)
if (frames_read < 0) {
LOG_ERROR("[AUD-CAP] Fatal error, exiting thread");
break; // 安全退出线程,不会崩溃
}
if (frames_read > 0) {
ws_aud.send_binary((uint8_t*)buffer, frames_read * 2);
}
效果:
- ✅ 正确检测错误码
- ✅ 安全退出线程,不会崩溃
- ✅ 其他线程(摄像头、IMU)继续运行
🧪 验证结果
测试日志分析
[1970-01-01 01:15:59] 程序启动
# WebSocket 连接成功
[1970-01-01 01:16:00] [INFO] [WS] WebSocket connected to ws://192.168.110.188:8081/ws_audio ✅
[1970-01-01 01:16:00] [INFO] [WS] WebSocket connected to ws://192.168.110.188:8081/ws/camera ✅
[1970-01-01 01:16:00] [INFO] [CAM] WebSocket connected ✅
# 第1次 I/O 错误 - 自动恢复成功
[1970-01-01 01:16:09] [ERROR] [AudioCapture] Read error: I/O error
[1970-01-01 01:16:09] [ERROR] [AudioCapture] Cannot recover: I/O error, attempting to reinitialize...
[1970-01-01 01:16:10] [INFO] [AudioCapture] Device reinitialized successfully ✅
# 第2次 I/O 错误 - 自动恢复成功
[1970-01-01 01:16:20] [ERROR] [AudioCapture] Read error: I/O error
[1970-01-01 01:16:20] [ERROR] [AudioCapture] Cannot recover: I/O error, attempting to reinitialize...
[1970-01-01 01:16:20] [INFO] [AudioCapture] Device reinitialized successfully ✅
# 第3次 I/O 错误 - 自动恢复成功
[1970-01-01 01:16:31] [ERROR] [AudioCapture] Read error: I/O error
[1970-01-01 01:16:31] [ERROR] [AudioCapture] Cannot recover: I/O error, attempting to reinitialize...
[1970-01-01 01:16:31] [INFO] [AudioCapture] Device reinitialized successfully ✅
# 用户手动终止
[1970-01-01 01:16:35] Received signal 2, shutting down...
[1970-01-01 01:16:35] Waiting for threads to exit...
性能对比
| 指标 | 修复前 | 修复后 |
|---|---|---|
| 运行时间 | 10 秒崩溃 | 36+ 秒稳定 ✅ |
| 音频错误处理 | 崩溃 | 自动恢复(3次成功) ✅ |
| 程序状态 | Segmentation fault | 正常运行 ✅ |
| 其他线程 | 全部终止 | 持续运行 ✅ |
| WebSocket 连接 | 未测试 | 音频+摄像头均成功 ✅ |
🎉 重大突破:网络通信验证成功
WebSocket 连接状态:
- ✅ 音频 WebSocket:
ws://192.168.110.188:8081/ws_audio- 连接成功 - ✅ 摄像头 WebSocket:
ws://192.168.110.188:8081/ws/camera- 连接成功
数据传输验证:
- ✅ IMU 数据 (UDP):持续稳定传输,服务器正常接收
- ⚠️ 摄像头数据:WebSocket 连接成功,初期传输了部分 JPEG 帧,但客户端程序随后出现问题
- ⚠️ 音频数据:客户端显示采集和发送正常,但服务器端未接收到数据
意义:
- ✅ 网络通信架构已搭建完成
- ✅ WebSocket 握手和连接机制正常工作
- ✅ IMU UDP 传输链路验证成功
- ⚠️ 摄像头和音频 WebSocket 数据传输需要进一步调试
💡 技术要点
1. ALSA 错误恢复策略
三级处理机制:
// Level 1: snd_pcm_recover() - 自动恢复
int err = snd_pcm_recover(m_pcm_handle, error_code, 0);
// Level 2: 设备重新初始化 - 我们新增的
if (err < 0) {
snd_pcm_close(m_pcm_handle);
usleep(500000);
init(); // 重新打开设备
}
// Level 3: 线程安全退出 - main.cpp 中处理
if (frames_read < 0) {
break; // 退出音频线程,不影响其他模块
}
2. 类型安全
关键原则:
- ALSA API 返回有符号类型
snd_pcm_sframes_t - 必须用有符号类型接收,才能检测负数错误码
- 使用
size_t(无符号) 会导致隐式类型转换
3. 线程独立性
设计优势:
- 音频线程错误不会影响摄像头线程
- 各线程独立运行,互不干扰
- 即使音频失败,IMU 和摄像头仍然工作
📊 遗留问题
1. 音频 WebSocket 数据传输问题
现象:
- 客户端显示音频采集正常
- WebSocket 连接成功
- 客户端日志显示发送正常
- 服务器端未接收到音频数据
可能原因:
- WebSocket 二进制帧格式问题
- 数据包大小或编码问题
- 服务器端音频 WebSocket 处理逻辑问题
- 网络传输过程中数据丢失
建议排查:
- 检查服务器端音频 WebSocket 路由和处理代码
- 使用抓包工具验证客户端是否真正发送了数据
- 检查 WebSocket 帧格式是否符合服务器预期
2. 摄像头客户端程序问题
现象:
- WebSocket 连接成功
- 初期成功传输了少量 JPEG 帧(日志显示 68849 bytes, 68475 bytes 等)
- 随后客户端程序出现问题
- 日志显示:
vipp fds select timeout[2000]ms, setNum:0! - 编码器报错:
AW_MPI_VENC_GetStream failed
可能原因:
- 摄像头 Sensor 硬件连接问题(MIPI 排线)
- Sensor 供电不稳定
- VI/ISP/VENC 管道配置问题
- VBV 缓冲区溢出导致编码器卡死
影响:
- 不影响程序稳定性(不会崩溃)
- 摄像头线程持续重试获取数据
- IMU 和音频模块继续正常工作
- WebSocket 连接保持,一旦 Sensor 问题解决即可传输数据
建议排查:
- 检查 MIPI 排线连接是否牢固
- 验证摄像头供电电压是否稳定
- 检查 Device Tree 中 Sensor 配置
- 尝试降低 VBV 缓冲区大小避免溢出
- 检查 VI/ISP/VENC 管道初始化顺序
🎯 成就
修复目标达成
- ✅ 音频 I/O 错误自动恢复机制
- ✅ 程序稳定性大幅提升(10秒 → 36+秒)
- ✅ 类型安全问题修复
- ✅ 线程安全退出机制
- ✅ WebSocket 通信成功验证(音频+摄像头)
网络通信突破
- ✅ 音频 WebSocket 连接成功
- ✅ 摄像头 WebSocket 连接成功
- ✅ IMU UDP 数据传输稳定,服务器正常接收
- ✅ TCP Socket 缓冲区优化生效 (256KB)
- ✅ 摄像头初期成功传输 JPEG 帧
- ⚠️ 音频数据服务器端未接收(需进一步调试)
- ⚠️ 摄像头客户端程序问题(需排查 Sensor/编码器)
代码质量提升
- ✅ 增强错误处理健壮性
- ✅ 添加设备重新初始化逻辑
- ✅ 改进日志记录(包含恢复尝试信息)
- ✅ 防御性编程实践(检查负值返回)
- ✅ 网络通信功能验证
📝 经验总结
1. 类型安全的重要性
教训:
- 有符号/无符号类型转换可能导致严重 bug
- 检查 API 文档,使用正确的返回类型
- 编译器警告要重视(implicit conversion)
2. 错误恢复的层次化设计
最佳实践:
- 一级恢复:API 自带恢复函数(
snd_pcm_recover) - 二级恢复:设备重新初始化(本次新增)
- 三级保护:线程安全退出(避免崩溃)
3. 调试技巧
有效方法:
- 查看系统日志确认错误类型
- 分析类型转换和内存访问
- 逐步隔离问题(音频 vs 其他模块)
- 使用日志追踪恢复过程
📌 相关文件
| 文件 | 修改内容 | 行号 |
|---|---|---|
src/audio/audio_capture.cpp |
添加设备重新初始化逻辑 | 192-216 |
src/main.cpp |
修复类型错误 + 添加错误检查 | 124-133 |
状态: ✅ 音频稳定性问题已解决 + WebSocket 通信成功验证 + IMU 数据传输正常
下一步:
- 排查音频 WebSocket 数据传输问题(服务器端未接收)
- 解决摄像头客户端程序问题(Sensor 数据获取失败)
- 验证所有数据流端到端传输