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

9.9 KiB
Raw Blame History

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)

问题根因

根据日志分析:

  1. 服务器返回 403 Forbidden 后,客户端不断重连
  2. 重连期间调用 sleep(1) + continue 跳过了帧采集
  3. VI 层持续产生帧但不消费,导致缓冲区溢出
  4. 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 错误
  • 即使出错也能自动恢复
  • 音频日志显示发送计数增长

📝 音频问题排查方向

如果客户端确认发送正常(日志显示计数增长),问题可能在:

  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)

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 记录

可能原因

  1. ⚠️ 服务器未发送 TTS 音频(最可能)
    • 需要检查服务器端 /ws_audio 是否正确处理音频并返回 TTS
    • 服务器日志是否显示 TTS 生成和发送
  2. 二进制数据接收逻辑有误
  3. 音频格式不匹配(采样率/通道数/格式)

下一步调试

// 在 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 崩溃修复已完成
  • 音频播放代码已实现
  • ⚠️ 音频播放无声音(待调试)
  • ⚠️ 需要服务器端配合验证