# Day 11 - 摄像头稳定性优化与音频诊断 **日期**: 2025-12-09 **目标**: 解决摄像头 Sensor 数据获取超时 + 对齐 ESP32S3 协议参数 **结果**: ✅ 代码修改完成,待板端测试 --- ## 📋 问题背景 ### 1. 摄像头问题 (Day 10 遗留) ``` vipp fds select timeout[2000]ms, setNum:0! AW_MPI_VENC_GetStream failed ``` - VI 层无法从 Sensor 获取数据 - 初期成功传输几帧后失败 ### 2. 音频 WebSocket 问题 (Day 10 遗留) - WebSocket 连接成功 - 客户端显示发送正常 - **服务器端未接收到数据** --- ## ✅ 解决方案 ### 1. 摄像头优化 #### 修改 1: 增大缓冲区 (`camera.cpp`) ```diff -#define VI_BUFFER_NUM 3 // 驱动要求的最小值 -#define VBV_BUFFER_SIZE 256 // 256KB +#define VI_BUFFER_NUM 5 // 增加缓冲区避免溢出 +#define VBV_BUFFER_SIZE 1024 // 1MB VBV缓冲区 ``` **原因**:256KB VBV 对于 1280x720 JPEG 可能不足(单帧 60-80KB) #### 修改 2: 添加 `deinit()` 方法 (`camera.h`, `camera.cpp`) ```cpp void Camera::deinit() { // 停止编码器 if (m_venc_chn != MM_INVALID_CHN) { AW_MPI_VENC_StopRecvPic(m_venc_chn); AW_MPI_VENC_DestroyChn(m_venc_chn); m_venc_chn = MM_INVALID_CHN; } // ... 关闭 VI/ISP/系统 } ``` #### 修改 3: 自动恢复机制 (`main.cpp`) ```cpp int consecutive_failures = 0; const int MAX_FAILURES = 5; if (!camera.capture_frame(...)) { consecutive_failures++; if (consecutive_failures >= MAX_FAILURES) { camera.deinit(); usleep(1000000); // 1秒 camera.init(); // 重新初始化 consecutive_failures = 0; } } ``` --- ### 2. 音频诊断增强 添加发送计数器和周期性日志: ```cpp // 每 100 次发送输出诊断 if (audio_send_count % 100 == 0) { LOG_INFO("[AUD-CAP] Sent %d packets, %d bytes total", audio_send_count, audio_bytes_total); } ``` **目的**:验证客户端确实在发送数据,问题可能在服务器端 --- ## 📊 修改文件汇总 (最新) | 文件 | 修改内容 | |------|----------| | `camera.cpp` | VBV **2MB**, 允许丢帧, JPEG 质量 50 (降低帧体积) | | `main.cpp` | 音频 **16kHz** (与 ESP32 一致), 断连时继续消费帧 | --- ## 🔴 关键修复 (Day 11 Update) ### 问题根因 根据日志分析: 1. 服务器返回 **403 Forbidden** 后,客户端不断重连 2. 重连期间调用 `sleep(1)` + `continue` 跳过了帧采集 3. **VI 层持续产生帧但不消费**,导致缓冲区溢出 4. VBV 满后编码器完全阻塞 ### 解决方案 ```cpp // 1. 断连时不再 continue,继续采集帧 if (!connected) { // 消费帧以清空 VBV,然后等待 1 秒重试 camera.capture_frame(...); camera.release_frame(...); usleep(1000000); } // 2. 允许丢帧防止完全阻塞 AW_MPI_VENC_ForbidDiscardingFrame(m_venc_chn, FALSE); ``` --- ## 🧪 验证步骤 ### 1. 编译程序 ```bash cd /path/to/avaota_app_demo ./build_main.sh ``` ### 2. 上传到开发板 ```bash scp avaota_client root@192.168.110.132:/tmp/ ``` ### 3. 运行测试 ```bash /tmp/avaota_client ``` ### 验证标准 - [ ] 摄像头持续工作超过 1 分钟 - [ ] 无 `vipp fds select timeout` 错误 - [ ] 即使出错也能自动恢复 - [ ] 音频日志显示发送计数增长 --- ## 📝 音频问题排查方向 如果客户端确认发送正常(日志显示计数增长),问题可能在: 1. **服务器端 `/ws_audio` 路由** - 检查是否正确处理 BINARY 帧 2. **数据格式** - 服务器期望的格式(采样率/通道数/编码) 3. **消息边界** - 320 bytes 的小包是否被正确处理 --- ## 🔧 下午工作:WebSocket 崩溃修复 + 音频播放实现 ### 问题 1: 程序崩溃 (`terminate called without an active exception`) **现象**:当服务器返回 `403 Forbidden` 后程序崩溃 **根因分析**: 1. `perform_handshake()` 失败时,`m_recv_thread` 未启动 2. 但 `disconnect()` 仍尝试 `join()` 未启动的线程 3. 更严重的是,接收线程阻塞在 `recv()` 时,`disconnect()` 直接关闭 socket 导致未定义行为 **修复方案** ([ws_client.cpp](file:///d:/CodingProjects/Antigravity/NaviGlass/NaviGlassClient/avaota_app_demo/src/network/ws_client.cpp#L103-L129)): ```cpp void WSClient::disconnect() { // 1. 设置标志位 m_running = false; m_connected = false; // 2. 发送关闭帧 if (m_sockfd >= 0) { uint8_t close_frame[] = {0x88, 0x00}; send(m_sockfd, close_frame, sizeof(close_frame), 0); // ⭐ 关键修复:先 shutdown 中断阻塞的 recv() shutdown(m_sockfd, SHUT_RDWR); } // 3. 等待线程退出(只有实际运行的线程才 join) if (m_recv_thread.joinable()) { m_recv_thread.join(); } // 4. 关闭 socket if (m_sockfd >= 0) { close(m_sockfd); m_sockfd = -1; } } ``` --- ### 问题 2: 音频只有采集没有播放 **目标**:实现双向音频通信(采集 + 播放) **实现步骤**: #### 步骤 1: 扩展 WSClient 支持二进制数据接收 **修改** [ws_client.h](file:///d:/CodingProjects/Antigravity/NaviGlass/NaviGlassClient/avaota_app_demo/src/network/ws_client.h#L73-L77): ```cpp // 新增:二进制数据轮询方法 void poll_binary_messages(std::function callback); private: // 新增:二进制数据队列 std::queue> m_binary_queue; std::mutex m_binary_mutex; ``` **修改** [ws_client.cpp](file:///d:/CodingProjects/Antigravity/NaviGlass/NaviGlassClient/avaota_app_demo/src/network/ws_client.cpp#L227-L232): ```cpp // recv_loop 中保存二进制数据 } else if (opcode == OP_BINARY) { // 保存到队列而非丢弃 std::lock_guard lock(m_binary_mutex); m_binary_queue.push(payload); LOG_DEBUG("[WS] Received binary: %zu bytes", payload.size()); } ``` #### 步骤 2: 在主线程中集成音频播放 **修改** [main.cpp](file:///d:/CodingProjects/Antigravity/NaviGlass/NaviGlassClient/avaota_app_demo/src/main.cpp#L140-L151): ```cpp // 初始化扬声器 (hw:1,0 - I2S 接口连接 MAX98357A) AudioPlayer speaker("hw:1,0", 16000, 1); // 16kHz, mono bool speaker_enabled = false; if (speaker.init()) { LOG_INFO("[AUD-PLAY] Speaker initialized on hw:1,0"); speaker_enabled = true; } else { LOG_WARN("[AUD-PLAY] Speaker init failed, audio playback disabled"); } ``` **修改** [main.cpp](file:///d:/CodingProjects/Antigravity/NaviGlass/NaviGlassClient/avaota_app_demo/src/main.cpp#L221-L237): ```cpp // 接收并播放服务器返回的音频 (TTS) if (speaker_enabled) { ws_aud.poll_binary_messages([&](const uint8_t* data, size_t size) { // 服务器返回的是 16kHz, mono, S16_LE PCM 数据 if (size % 2 == 0) { // 确保是偶数字节(16-bit 样本) size_t frames = size / 2; snd_pcm_sframes_t written = speaker.write((const int16_t*)data, frames); if (written > 0) { LOG_DEBUG("[AUD-PLAY] Played %ld frames (%zu bytes)", written, size); } } }); } ``` --- ## 📊 测试结果分析(2025-12-09 17:30) ### ✅ 成功部分 - ✅ 扬声器初始化成功:`hw:1,0` (I2S 接口 MAX98357A) - ✅ 音频采集正常:已发送 2000+ 音频包 - ✅ IMU 正常:ICM42688 工作正常 - ✅ WebSocket 崩溃问题修复(需进一步验证) ### ❌ 当前存在的问题 #### 问题 1: **音频播放无声音** 🔴 高优先级 **现象**:日志中**没有** `[AUD-PLAY] Played X frames` 记录 **可能原因**: 1. ⚠️ **服务器未发送 TTS 音频**(最可能) - 需要检查服务器端 `/ws_audio` 是否正确处理音频并返回 TTS - 服务器日志是否显示 TTS 生成和发送 2. 二进制数据接收逻辑有误 3. 音频格式不匹配(采样率/通道数/格式) **下一步调试**: ```cpp // 在 ws_client.cpp recv_loop 中添加: } else if (opcode == OP_BINARY) { LOG_INFO("[WS] 🔔 Received binary frame: %zu bytes", payload.size()); // 调试日志 std::lock_guard lock(m_binary_mutex); m_binary_queue.push(payload); } ``` #### 问题 2: **WebSocket 频繁断开** 🟡 中优先级 ``` [WS] Server closed connection ``` - 服务器主动关闭连接(可能超时或协议错误) - 影响音频数据的持续接收 #### 问题 3: **摄像头 VBV 缓冲区满** 🟡 中优先级 ``` WARNING: BitStreamFreeBufferSize XXX is too small fail BsFull ``` - 编码器缓冲区不足 - 导致帧率波动:0.4 FPS ~ 15 FPS - 影响可视化流畅度 #### 问题 4: **可视化卡顿** 🟡 中优先级 - 可能与帧率不稳定有关 - 可能与 WebSocket 频繁重连有关 - 可能与网络带宽不足有关 --- ## 🔍 下一步行动计划 ### 优先级 1: 音频播放调试 1. **添加详细日志**: - WebSocket 接收端:确认是否收到二进制帧 - 播放端:确认 `poll_binary_messages` 是否被调用 2. **检查服务器端**: - 验证服务器是否接收到客户端音频 - 验证服务器是否生成并发送 TTS 音频 - 检查返回的音频格式(16kHz, mono, S16_LE) ### 优先级 2: WebSocket 稳定性 1. 分析服务器为什么主动断开连接 2. 检查客户端是否发送了不符合协议的数据 3. 考虑添加心跳机制 ### 优先级 3: 摄像头优化 1. 进一步增大 VBV 缓冲区到 4MB 2. 降低 JPEG 质量或分辨率 3. 调整帧率到固定值(如 10 FPS) --- **当前状态**: - ✅ WebSocket 崩溃修复已完成 - ✅ 音频播放代码已实现 - ⚠️ 音频播放无声音(待调试) - ⚠️ 需要服务器端配合验证