348 lines
9.9 KiB
Markdown
348 lines
9.9 KiB
Markdown
# 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<void(const uint8_t*, size_t)> callback);
|
||
|
||
private:
|
||
// 新增:二进制数据队列
|
||
std::queue<std::vector<uint8_t>> 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<std::mutex> 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<std::mutex> 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 崩溃修复已完成
|
||
- ✅ 音频播放代码已实现
|
||
- ⚠️ 音频播放无声音(待调试)
|
||
- ⚠️ 需要服务器端配合验证
|