Files
Docs/DevLogs/Day10.md
2025-12-31 16:18:28 +08:00

11 KiB
Raw Permalink Blame History

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没有检查
}

连锁反应

  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 恢复失败时,自动重新初始化设备

} 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 连接状态

  • 音频 WebSocketws://192.168.110.188:8081/ws_audio - 连接成功
  • 摄像头 WebSocketws://192.168.110.188:8081/ws/camera - 连接成功

数据传输验证

  • IMU 数据 (UDP):持续稳定传输,服务器正常接收
  • ⚠️ 摄像头数据WebSocket 连接成功,初期传输了部分 JPEG 帧,但客户端程序随后出现问题
  • ⚠️ 音频数据:客户端显示采集和发送正常,但服务器端未接收到数据

意义

  1. 网络通信架构已搭建完成
  2. WebSocket 握手和连接机制正常工作
  3. IMU UDP 传输链路验证成功
  4. ⚠️ 摄像头和音频 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 连接成功
  • 客户端日志显示发送正常
  • 服务器端未接收到音频数据

可能原因

  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. 验证所有数据流端到端传输