9.9 KiB
9.9 KiB
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)
-#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)
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)
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. 音频诊断增强
添加发送计数器和周期性日志:
// 每 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)
问题根因
根据日志分析:
- 服务器返回 403 Forbidden 后,客户端不断重连
- 重连期间调用
sleep(1)+continue跳过了帧采集 - VI 层持续产生帧但不消费,导致缓冲区溢出
- VBV 满后编码器完全阻塞
解决方案
// 1. 断连时不再 continue,继续采集帧
if (!connected) {
// 消费帧以清空 VBV,然后等待 1 秒重试
camera.capture_frame(...);
camera.release_frame(...);
usleep(1000000);
}
// 2. 允许丢帧防止完全阻塞
AW_MPI_VENC_ForbidDiscardingFrame(m_venc_chn, FALSE);
🧪 验证步骤
1. 编译程序
cd /path/to/avaota_app_demo
./build_main.sh
2. 上传到开发板
scp avaota_client root@192.168.110.132:/tmp/
3. 运行测试
/tmp/avaota_client
验证标准
- 摄像头持续工作超过 1 分钟
- 无
vipp fds select timeout错误 - 即使出错也能自动恢复
- 音频日志显示发送计数增长
📝 音频问题排查方向
如果客户端确认发送正常(日志显示计数增长),问题可能在:
- 服务器端
/ws_audio路由 - 检查是否正确处理 BINARY 帧 - 数据格式 - 服务器期望的格式(采样率/通道数/编码)
- 消息边界 - 320 bytes 的小包是否被正确处理
🔧 下午工作:WebSocket 崩溃修复 + 音频播放实现
问题 1: 程序崩溃 (terminate called without an active exception)
现象:当服务器返回 403 Forbidden 后程序崩溃
根因分析:
perform_handshake()失败时,m_recv_thread未启动- 但
disconnect()仍尝试join()未启动的线程 - 更严重的是,接收线程阻塞在
recv()时,disconnect()直接关闭 socket 导致未定义行为
修复方案 (ws_client.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:
// 新增:二进制数据轮询方法
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:
// 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:
// 初始化扬声器 (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:
// 接收并播放服务器返回的音频 (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 记录
可能原因:
- ⚠️ 服务器未发送 TTS 音频(最可能)
- 需要检查服务器端
/ws_audio是否正确处理音频并返回 TTS - 服务器日志是否显示 TTS 生成和发送
- 需要检查服务器端
- 二进制数据接收逻辑有误
- 音频格式不匹配(采样率/通道数/格式)
下一步调试:
// 在 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: 音频播放调试
- 添加详细日志:
- WebSocket 接收端:确认是否收到二进制帧
- 播放端:确认
poll_binary_messages是否被调用
- 检查服务器端:
- 验证服务器是否接收到客户端音频
- 验证服务器是否生成并发送 TTS 音频
- 检查返回的音频格式(16kHz, mono, S16_LE)
优先级 2: WebSocket 稳定性
- 分析服务器为什么主动断开连接
- 检查客户端是否发送了不符合协议的数据
- 考虑添加心跳机制
优先级 3: 摄像头优化
- 进一步增大 VBV 缓冲区到 4MB
- 降低 JPEG 质量或分辨率
- 调整帧率到固定值(如 10 FPS)
当前状态:
- ✅ WebSocket 崩溃修复已完成
- ✅ 音频播放代码已实现
- ⚠️ 音频播放无声音(待调试)
- ⚠️ 需要服务器端配合验证