375 lines
11 KiB
Markdown
375 lines
11 KiB
Markdown
# 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`
|
||
|
||
```cpp
|
||
// ❌ 错误:使用无符号类型接收有符号返回值
|
||
size_t frames_read = mic.read(buffer, 160);
|
||
|
||
// mic.read() 返回 snd_pcm_sframes_t(有符号)
|
||
// 负数错误码被隐式转换为巨大的正数
|
||
// 导致未定义行为和内存访问错误
|
||
```
|
||
|
||
#### 2. **错误处理不足** - `audio_capture.cpp`
|
||
|
||
```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没有检查
|
||
}
|
||
```
|
||
|
||
**连锁反应**:
|
||
1. ALSA I/O 错误发生
|
||
2. `snd_pcm_recover()` 尝试恢复失败
|
||
3. 返回负数错误码
|
||
4. `main.cpp` 中的 `size_t` 类型将负数转换为巨大正数
|
||
5. 后续内存访问越界
|
||
6. **Segmentation fault** 💥
|
||
|
||
---
|
||
|
||
## ✅ 解决方案
|
||
|
||
### 修改 1: `audio_capture.cpp` - 自动设备重新初始化
|
||
|
||
**文件**: `src/audio/audio_capture.cpp`
|
||
**行号**: 192-216
|
||
|
||
**核心改进**:在 ALSA 恢复失败时,自动重新初始化设备
|
||
|
||
```cpp
|
||
} 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:类型错误
|
||
|
||
```cpp
|
||
// ❌ 之前:错误的类型
|
||
size_t frames_read = mic.read(buffer, 160); // 无符号类型无法检测负值
|
||
|
||
// ✅ 修正:正确的类型
|
||
snd_pcm_sframes_t frames_read = mic.read(buffer, 160); // 有符号类型
|
||
```
|
||
|
||
#### 问题 2:缺少错误检查
|
||
|
||
```cpp
|
||
// ✅ 添加错误检查
|
||
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 帧,但客户端程序随后出现问题
|
||
- ⚠️ **音频数据**:客户端显示采集和发送正常,但服务器端未接收到数据
|
||
|
||
**意义**:
|
||
1. ✅ 网络通信架构已搭建完成
|
||
2. ✅ WebSocket 握手和连接机制正常工作
|
||
3. ✅ IMU UDP 传输链路验证成功
|
||
4. ⚠️ 摄像头和音频 WebSocket 数据传输需要进一步调试
|
||
|
||
---
|
||
|
||
## 💡 技术要点
|
||
|
||
### 1. ALSA 错误恢复策略
|
||
|
||
**三级处理机制**:
|
||
|
||
```cpp
|
||
// 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 连接成功
|
||
- 客户端日志显示发送正常
|
||
- **服务器端未接收到音频数据**
|
||
|
||
**可能原因**:
|
||
1. WebSocket 二进制帧格式问题
|
||
2. 数据包大小或编码问题
|
||
3. 服务器端音频 WebSocket 处理逻辑问题
|
||
4. 网络传输过程中数据丢失
|
||
|
||
**建议排查**:
|
||
- 检查服务器端音频 WebSocket 路由和处理代码
|
||
- 使用抓包工具验证客户端是否真正发送了数据
|
||
- 检查 WebSocket 帧格式是否符合服务器预期
|
||
|
||
---
|
||
|
||
### 2. 摄像头客户端程序问题
|
||
|
||
**现象**:
|
||
- WebSocket 连接成功
|
||
- 初期成功传输了少量 JPEG 帧(日志显示 68849 bytes, 68475 bytes 等)
|
||
- 随后客户端程序出现问题
|
||
- 日志显示:`vipp fds select timeout[2000]ms, setNum:0!`
|
||
- 编码器报错:`AW_MPI_VENC_GetStream failed`
|
||
|
||
**可能原因**:
|
||
1. 摄像头 Sensor 硬件连接问题(MIPI 排线)
|
||
2. Sensor 供电不稳定
|
||
3. VI/ISP/VENC 管道配置问题
|
||
4. 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. 错误恢复的层次化设计
|
||
|
||
**最佳实践**:
|
||
1. **一级恢复**:API 自带恢复函数(`snd_pcm_recover`)
|
||
2. **二级恢复**:设备重新初始化(本次新增)
|
||
3. **三级保护**:线程安全退出(避免崩溃)
|
||
|
||
### 3. 调试技巧
|
||
|
||
**有效方法**:
|
||
- 查看系统日志确认错误类型
|
||
- 分析类型转换和内存访问
|
||
- 逐步隔离问题(音频 vs 其他模块)
|
||
- 使用日志追踪恢复过程
|
||
|
||
---
|
||
|
||
## 📌 相关文件
|
||
|
||
| 文件 | 修改内容 | 行号 |
|
||
|------|----------|------|
|
||
| `src/audio/audio_capture.cpp` | 添加设备重新初始化逻辑 | 192-216 |
|
||
| `src/main.cpp` | 修复类型错误 + 添加错误检查 | 124-133 |
|
||
|
||
---
|
||
|
||
**状态**: ✅ **音频稳定性问题已解决 + WebSocket 通信成功验证 + IMU 数据传输正常**
|
||
**下一步**:
|
||
1. 排查音频 WebSocket 数据传输问题(服务器端未接收)
|
||
2. 解决摄像头客户端程序问题(Sensor 数据获取失败)
|
||
3. 验证所有数据流端到端传输
|