# Day 14: WebSocket TTS 调试与项目清理 **日期**:2025-12-12 **目标**:解决 TTS 断续问题,清理项目冗余文件 --- ## 📅 工作摘要 ### 1. 项目清理 ✅ **删除的文件**(共 19 个): | 类型 | 文件 | |------|------| | HTTP TTS 相关 | `http_tts_stream.cpp`, `http_tts_stream.h`, `http_client.cpp`, `http_client.h` | | 测试脚本 | `build_test.sh`, `build_test_camera.sh`, `build_test_imu.sh` | | 调试脚本 | `debug_network_libs.sh`, `find_libs_v3.sh`, `setup_mic.sh`, `fix_speaker.sh` | | 测试源码 | `main_test.cpp`, `test_audio.cpp`, `test_camera.cpp`, `test_gpio.cpp`, `test_imu.cpp`, `test_network.cpp`, `test_udp_only.cpp` | | 其他 | `Makefile_test`, `build_custom.sh`, `build_phase2.sh`, `build_phase3.sh`, `test_mic.sh`, `test/` 目录 | **清理后目录结构**: ``` avaota_app_demo/ ├── build_main.sh # 主编译脚本 ├── README.md ├── MUSL_COMPILE.md └── src/ ├── Makefile # 主 Makefile ├── main.cpp # 主程序 ├── audio/ # 音频模块 ├── camera/ # 摄像头模块 ├── imu/ # IMU 模块 ├── network/ # 网络模块 (ws_client, udp_sender) └── utils/ # 日志工具 ``` ### 2. 恢复 WebSocket TTS ✅ **原因**:HTTP TTS (`/stream.wav`) 方案虽然存在,但之前 WebSocket TTS 已验证可以播放声音,只是存在断续问题。重新切换回 WebSocket TTS 进行优化。 **代码变更**: - `main.cpp`: 移除 `http_tts_player_thread()`,恢复 `audio_capture_thread()` 中的 WebSocket TTS 播放逻辑 - `Makefile`: 移除 `http_tts_stream.cpp` 和 `http_client.cpp` ### 3. 增大 TTS 预缓冲区 ✅ **问题**:Omni API 响应慢,每 1-2 秒才返回一批音频数据,导致播放缓冲区频繁欠载(underrun)。 **修改** (`main.cpp`): ```cpp // 原设置(0.5 秒预缓冲) const size_t PRE_BUFFER_FRAMES = 8000; // 0.5s const size_t MIN_PLAY_FRAMES = 3200; // 0.2s // 新设置(2 秒预缓冲) const size_t PRE_BUFFER_FRAMES = 32000; // 2s const size_t MIN_PLAY_FRAMES = 4800; // 0.3s ``` ### 4. 日志优化 ✅ 更新日志中的设备名称: - `ESP32` → `设备` (更通用) ### 5. 服务器端导航器预初始化 ✅ **问题**:客户端连接后才开始初始化导航器,导致首次连接时服务器卡顿一段时间。 **修改** (`app_main.py`):在服务器启动时预初始化所有导航组件: ```python # 在服务器启动时预初始化(避免客户端连接后延迟) print("[NAVIGATION] 预初始化导航器...") blind_path_navigator = BlindPathNavigator(yolo_seg_model, obstacle_detector) cross_street_navigator = CrossStreetNavigator(yolo_seg_model, obstacle_detector) orchestrator = NavigationMaster(blind_path_navigator, cross_street_navigator) print("[NAV MASTER] 统领状态机已预初始化") ``` **效果**:客户端连接时无需等待导航器加载,响应更快。 --- ## 📊 测试结果 ### 2 秒预缓冲测试 ``` [AUD-PLAY] Pre-buffer full (33920 frames), starting playback [WARN] [AudioPlayer] Underrun occurred, recovering... [WARN] [AudioPlayer] Underrun occurred, recovering... ...(仍有断续) ``` **结论**:2 秒预缓冲仍不足以解决问题,Omni API 发送间隔过长(~2 秒/批),播放速度远快于接收速度。 --- ## 🔴 遗留问题 ### TTS 语音断断续续 **根本原因**: - Omni API 生成音频速度 << 播放速度 - 每 1-2 秒发送一批 10KB 音频 - 即使 2 秒预缓冲,也会在长句子后半段出现 underrun **可能的解决方案**: | 方案 | 描述 | 工作量 | 效果 | |------|------|--------|------| | **继续增大预缓冲** | 3-4 秒预缓冲 | 5 分钟 | 可能有效 | | **服务器预缓冲** | 服务端收集完整 TTS 后再发送 | 2 小时 | 最佳 | | **静音填充** | 缓冲区空时插入静音,防止卡顿 | 1 小时 | 中等 | | **变速播放** | 缓冲区低时降速播放(0.95x) | 3 小时 | 高级 | **推荐下一步**: 1. 先尝试增大预缓冲到 **3-4 秒** 2. 如仍不行,考虑服务器端预缓冲方案 ### 5. 服务器端 TTS 发送优化 ✅ **发现问题**: 通过代码分析发现,`audio_stream.py` 中的 `broadcast_pcm16_realtime` 函数存在设计问题: ```python # 原来的代码:HTTP 20ms 节拍循环会阻塞整个函数 async def broadcast_pcm16_realtime(pcm16: bytes): # ... WebSocket 发送 ... # HTTP 节拍广播(阻塞!) while off < len(pcm16): # 每 20ms 发送一小块 await asyncio.sleep(next_tick - now) # 这会阻塞整个函数 ``` **问题**:虽然 WebSocket 发送在前面,但 HTTP 节拍循环会阻塞函数返回。每 1KB 数据需要约 62ms 才能完成,导致下一个 Omni 音频块被延迟处理。 **修复**:将 HTTP 节拍广播改为后台任务 ```python # 修改后:WebSocket 立即发送,HTTP 在后台执行 async def broadcast_pcm16_realtime(pcm16: bytes): # ... WebSocket 发送 ... # Day 14 优化:HTTP 广播放到后台任务 if stream_clients: asyncio.create_task(_http_pacing_broadcast(pcm16)) # 函数立即返回,不阻塞 async def _http_pacing_broadcast(pcm16: bytes): """独立后台任务处理 HTTP 节拍广播""" # 原来的 20ms 节拍循环代码 ``` **预期效果**: - WebSocket 发送后立即返回处理下一个 Omni 音频块 - TTS 传输间隔完全由 Omni API 生成速度决定 - 无额外延迟 --- ## 📝 代码变更汇总 | 文件 | 变更 | |------|------| | `main.cpp` | 移除 HTTP TTS 代码,增大预缓冲到 2 秒 | | `Makefile` | 移除 HTTP 相关源文件 | | `network/http_*` | **删除** | | 测试文件 | **删除** 19 个文件 | | **`audio_stream.py` (服务器)** | **HTTP 节拍广播改为后台任务,WebSocket 立即返回** | --- ## 🔧 技术研究:实时语音传输最佳方案 ### 协议对比 | 协议 | 适用场景 | 延迟 | |------|---------|------| | **WebRTC** | P2P 实时通话 | <100ms | | **WebSocket** | 服务器中转、AI 语音 | 100-500ms | | **HTTP Chunked** | 简单流式传输 | >500ms | ### 关键技术:Jitter Buffer 专业音频系统使用**动态抖动缓冲**: 1. 预缓冲启动(等待足够数据) 2. 动态调整缓冲区大小 3. 静音填充(缓冲区空时) --- --- ## 🎉 下午进展:TTS 流畅 + 导航卡顿问题 ### 6. TTS 播放成功 ✅ **服务器端修复生效**:重启服务器后测试,TTS 播放已流畅! **修复方法总结**: | 问题 | 原因 | 修复 | |------|------|------| | TTS 断续 | `broadcast_pcm16_realtime` 中 HTTP 20ms 节拍循环阻塞 WebSocket 发送 | 将 HTTP 广播改为后台任务 `asyncio.create_task(_http_pacing_broadcast())` | **关键代码变更** (`audio_stream.py`): ```python # 修改前:HTTP 节拍循环阻塞函数 async def broadcast_pcm16_realtime(pcm16: bytes): await ws.send_bytes(pcm16k) # WebSocket 发送 while off < len(pcm16): # HTTP 节拍循环(阻塞!) await asyncio.sleep(...) # 修改后:WebSocket 发送后立即返回 async def broadcast_pcm16_realtime(pcm16: bytes): await ws.send_bytes(pcm16k) # WebSocket 发送 if stream_clients: asyncio.create_task(_http_pacing_broadcast(pcm16)) # 后台执行 # 函数立即返回,不阻塞 ``` ### 7. 导航模式卡顿问题 🔴 (新发现) **现象**:发出"开始导航"指令后,服务器响应"盲道导航已启动",可视化界面立即卡住,FPS 从 10.0 暴跌到 0.2。 **根因分析**: 服务器端 `workflow_blindpath.py` 存在 **YOLO 检测间隔参数未使用** 的 bug: ```python # 第253-256行:定义了间隔参数 self.BLINDPATH_DETECTION_INTERVAL = 8 # 每8帧检测一次 self.last_blindpath_detection_frame = 0 # 但这个变量从未被使用! # 第424行:实际上每帧都执行 YOLO 推理 blind_path_mask, crosswalk_mask = self._detect_path_and_crosswalk(image) # 无间隔! ``` **对比障碍物检测**(正确实现): ```python # 第454行:障碍物检测正确使用了间隔 if self.frame_counter % self.OBSTACLE_DETECTION_INTERVAL == 0: detected_obstacles = self._detect_obstacles(image, blind_path_mask) ``` **推荐修复方案**: 修改 `workflow_blindpath.py` 第 424 行,添加帧间隔检查: ```python # 修改后:每 8 帧执行一次 YOLO 推理 if self.frame_counter % self.BLINDPATH_DETECTION_INTERVAL == 0: blind_path_mask, crosswalk_mask = self._detect_path_and_crosswalk(image) self.last_blindpath_mask = blind_path_mask self.last_crosswalk_mask = crosswalk_mask else: blind_path_mask = self.last_blindpath_mask crosswalk_mask = self.last_crosswalk_mask ``` **预期效果**:YOLO 推理从每帧 1 次降为每 8 帧 1 次,处理负载降低 8 倍。 --- ## 📝 Day 14 完整代码变更汇总 | 文件 | 变更 | |------|------| | `main.cpp` | 移除 HTTP TTS 代码,增大预缓冲到 2 秒 | | `Makefile` | 移除 HTTP 相关源文件 | | `network/http_*` | **删除** | | 测试文件 | **删除** 19 个文件 | | **`audio_stream.py` (服务器)** | **HTTP 节拍广播改为后台任务,WebSocket 立即返回** | --- ## 📂 待下次会话处理 ### 必须修复 1. **导航卡顿问题** - 修改 `workflow_blindpath.py` 第 424 行 - 添加 `BLINDPATH_DETECTION_INTERVAL` 帧间隔检查 - 测试验证导航模式 FPS 恢复正常 ### 已完成验证 - ✅ 摄像头 WebSocket - ✅ IMU UDP - ✅ 麦克风采集 - ✅ TTS 播放(已流畅) - ⚠️ 导航模式(待修复 YOLO 间隔问题)