4 Commits

Author SHA1 Message Date
70f0cdd07a feat(rtc): 偶发连接失败完整修复 (A+B+C 三件套)
实测根因 (DIAG 埋点确认): 火山 RTC SDK 启动时一次性申请大量 lwIP socket fd,
默认 CONFIG_LWIP_MAX_SOCKETS=10 不够 SDK 分配, 触发 SocketConnection-Lite.c:191
bind local ip failed → ICE 协商失败 → wait connect bits=0x0 超时.

实测对比:
  修复前: 冷启动 RTC join 30+ 秒超时 × 3 次失败
  修复后: 冷启动 RTC join 1.6 秒成功, 软退出 + 唤醒重连 2.3 秒成功 

修复内容:

[A] sdkconfig: CONFIG_LWIP_MAX_SOCKETS=10 → 20
    根治 lwIP socket fd 不足. 16 是临界值, 20 留 25% 余量应对 burst 场景
    (HTTP 重试 / DNS 查询 / NTP 同步并发). 代价: +6 fd × ~200B = 1.2 KB RAM (忽略).

[B] application.h/cc + volc_rtc_protocol.h/cc: 失败 3 次后销毁 + 重建 engine
    新增 VolcRtcProtocol::ForceRebuildEngine() public 方法.
    OpenAudioChannel 连续失败 3 次时调用 (application.cc:566-573):
      - 销毁 rtc_handle_ + reset SDK 内部状态污染
      - 等待 2 秒让 lwIP 释放残留 socket fd (TIME_WAIT)
      - 触发 Phase 6 重建路径 (rtc_handle_=nullptr → Start())
    应对 A 修复后仍可能出现的 SDK 内部状态错乱 (e.g. ICE Agent 异常).
    本次实测未触发 (A 已解决主要问题), 但保留作为兜底防御.

[C] volc_rtc_protocol.cc: DIAG_RTC_BIND_ENABLE 一键开关诊断埋点
    在 join_room 前/后 + ForceRebuildEngine 前/后打印:
      - lwIP socket fd 使用量 (sockets=N/MAX)
      - heap free + psram free
      - WiFi rssi
      - 失败时的 errno + strerror
    验证完成后改 0 关闭, 编译器消除 #if 块, 零运行时开销.

文件改动:
  sdkconfig                              | LWIP_MAX_SOCKETS 10→20
  main/application.h                     | +audio_channel_retry_count_
  main/application.cc                    | +重试计数 + static_cast → ForceRebuildEngine 调用
  main/protocols/volc_rtc_protocol.h     | +ForceRebuildEngine() 声明
  main/protocols/volc_rtc_protocol.cc    | +DIAG 埋点 + diag_count_used_sockets() + ForceRebuildEngine()

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 10:23:21 +08:00
22b7a70d7d fix: 同步 Kapi 软 RTC 退出五连修到数字人项目(待命音 + 欢迎语杂音)
从 Kapi commit b1577d8 / a3a476f 完整移植 5 个修复,覆盖三类问题:
1. 开机/唤醒后按 BOOT 进 RTC 房间,欢迎语前 1-3 秒杂音
2. 软 RTC 退出(41s 无对话触发 Dialog watchdog)后待命音"卡卡正在待命"无声/杂音/被截
3. 软退出后按 BOOT 唤醒,欢迎语前杂音

【修复 1】OnAudioChannelOpened EnableOutput(true) 后立刻灌 200ms silence
  - 防止 I2S DMA 启动后到 RTC 真实 PCM 到达 1-3s 空窗的杂音

【修复 2】LeaveRoom 加 notify_closed 参数(默认 true 不变老路径)
  - hibernate 路径传 false 跳过 on_audio_channel_closed_ 回调
  - 避免回调链 player_pipeline_close → EnableOutput(false) 误关 codec
    导致待命音无声

【修复 3】LeaveRoom 不再 volc_rtc_destroy, 保留 rtc_handle_
  - 唤醒时 OpenAudioChannel 直接 volc_rtc_start 复用 handle, 不死循环
  - 服务端 AI 任务无需 destroy 也会按 180s 兜底机制清理

【修复 4 - 最隐蔽】LeaveRoom 末尾重置 downlink_is_pcm_ = false
  - 火山 RTC 下行是 PCM, DataCallback 设 downlink_is_pcm_=true
  - 不重置 → PlaySound 的 Opus 包被 OnAudioOutput 当成 raw PCM 字节流
    直接写 codec → 杂音而非待命音
  - 唤醒重连后 DataCallback 收下一包会自动重置, 不影响欢迎语

【修复 5】OnAudioInput 入口加 hibernating_ guard
  - hibernate 期间禁用输入侧, 防止访问关闭的 codec → std::bad_alloc abort
  - 不冻结 OnAudioOutput, 让待命音队列正常被消费

【EnterIdleHibernate 重写】套用 Kapi 新顺序:
  Step 0: hibernating_=true + 50ms (让 OnAudioInput guard 生效)
  Step 1: LeaveRoom(false) (codec output 保留)
  Step 2: background_task->WaitForCompletion
  Step 3: 清空 audio_decode_queue_
  Step 4: EnableInput(false) + close recorder_pipeline
  Step 5: 强制 esp_pm 禁用 Light Sleep
  Step 5.5: EnableOutput(false→true) + 200ms silence (清 LeaveRoom 副作用)
  Step 6: SetDeviceState(idle) → PlaySound 待命音
  Step 7: WaitForAudioPlayback (队列消费完毕)
  Step 7.5: background_task->WaitForCompletion + vTaskDelay(1000)
            (DMA + ES8311 FIFO + 功放尾音衰减, 防尾音截断)
  Step 8: player_pipeline_close
  Step 9: NVS idle_cycles_++
  Step 10: 显示字幕"已自动退出RTC对话..."(数字人特有, 保留)

【WakeFromHibernate】调整 hibernating_=false 顺序
  - 先放下 hibernating_, 让 ToggleChatState 期间 OnAudioInput guard 通过
  - 否则 ToggleChatState 期间音频上行迟迟不开

编译: kapi.bin 0x41c000 (4.21MB), 分区 25% 空闲。
实测三项全通: 欢迎语干净 + 待命音清晰完整 + 唤醒欢迎语干净。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 10:11:36 +08:00
b8a5fe958f feat(rtc-only): Phase 6 - RTC 空闲软休眠(B+C 双源 + 真退房 + 字幕提示 + 内存兜底)
按 GSD 框架 .planning/milestones/digital_human_rtc/phases/phase_06_idle_hibernate/
规划完成 Phase 6 软退出 RTC 机制。替代旧的"40s 硬重启退出"方案。

## 核心变更

### 1. 倒计时刷新(B+C 双源方案)

| 方案 | 监听源 | 实施位置 | 状态 |
|------|--------|---------|------|
| A 扬声器流 | I2S/PCM 输出 | application.cc audio output 3 处 | **宏关闭**(PHASE6_ENABLE_AUDIO_FALLBACK) |
| **B 字幕监听** | RTC subtitle 消息 | application.cc:1300 subtitle 分支 | **启用** |
| **C 智能体状态** | RTC conv_status 消息 | application.cc:1260 conv_status 分支 | **启用** |

复用现有 DIALOG_IDLE_COUNTDOWN_SECONDS=40 不新增常量。

### 2. 真退出 RTC 房间(释放 License)

- 新增 Protocol 基类虚函数 LeaveRoom(默认回退到 CloseAudioChannel)
- VolcRtcProtocol::LeaveRoom 覆写:volc_rtc_stop + volc_rtc_destroy
  - 火山官方文档明确:真退房必须 leaveRoom + destroyRTCEngine
  - CloseAudioChannel 只 stop 不够(真人仍在房间继续计费)
- 服务端 AI 任务在 180s 内自动清理(火山平台机制)

### 3. EnterIdleHibernate / WakeFromHibernate

EnterIdleHibernate 流程(严格顺序):
1. protocol_->LeaveRoom()                  # 真退房
2. codec->EnableInput/Output(false)        # 重置 codec 状态机
3. recorder_pipeline_close()
4. hibernating_.store(true)                # 关键:先设标志阻止 PowerSaveTimer
5. esp_pm_configure(light_sleep=false)     # 双保险禁用 Light Sleep
6. SetDeviceState(kDeviceStateIdle)
7. idle_cycles_++ + NVS 持久化
8. 字幕"已自动退出RTC对话,按BOOT键重新连接RTC"(5 次重试间隔 200ms)

WakeFromHibernate 流程:
1. 检查 idle_cycles_ >= 50 → 硬重启清理碎片(兜底)
2. 清空字幕
3. ToggleChatState → OpenAudioChannel → 自动重建 rtc_handle_
4. RTC 重新加入房间(实测 2-3s 完成)

### 4. CanEnterSleepMode 加 hibernating 检查

防止 hibernate 期间 PowerSaveTimer 触发 esp_pm_configure(light_sleep=true)
导致 I2C 总线进入低功耗 → 唤醒后 ES7210/ES8311 通信失败 abort。

### 5. Dialog Watchdog 触发动作改造

旧:esp_restart() 整机重启(黑屏 15-25s + WiFi 重连)
新:Schedule(EnterIdleHibernate) 软退房(不熄屏 + 字幕提示)

### 6. BOOT 唤醒走 WakeFromHibernate 路径

iot_button 回调中检测 IsHibernating(),派发到独立 task 执行
WakeFromHibernate(避免阻塞 esp_timer 任务,CLAUDE.md 经验)。

### 7. OpenAudioChannel 适配重建

LeaveRoom 销毁 rtc_handle_ 后,OpenAudioChannel 头部检测 NULL
触发 Start() 异步重建,轮询 5s 等待就绪。NVS 缓存 device_secret
所以重建通常 100ms 完成。

## 实测验证(用户协作)

| 阶段 | 时间 |
|------|------|
| 40s 触发软休眠 |  |
| LeaveRoom 真退房 |  "✓ 已真退出 RTC 房间(leaveRoom + destroyRTCEngine)" |
| 屏幕保持 + 字幕显示 |  "已自动退出RTC对话,按BOOT键重新连接RTC" |
| BOOT 按键唤醒 |  |
| RTC 实例重建 |  100ms |
| RTC 重新加入房间 |  2-3s |
| 连续 2 次软休眠+唤醒 |  无 abort/I2C 失败 |
| 时间对比 | 旧硬重启 15-25s → 软休眠 3-5s(省 80%) |

## 6 个关键踩坑修复(详见 HIBERNATE_REPORT.md)

1. codec 状态机未重置 → 唤醒后 I2C abort
2. PowerSaveTimer Light Sleep 干扰 I2C 总线
3. hibernating_ 设置时序错误
4. dynamic_cast 在 -fno-rtti 下编译失败 → 改基类虚函数
5. LeaveRoom 后 OpenAudioChannel 直接失败 → 加重建逻辑
6. 字幕 LVGL 锁竞争 → 推迟到最后 + 5 次重试

## 文档产出(同时提交)

- .planning/.../phase_06_idle_hibernate/PLAN.md(含实施变更记录 V1-V6)
- .planning/.../phase_06_idle_hibernate/HIBERNATE_REPORT.md(验证报告)
- .planning/.../ROADMAP.md(Phase 1-5  + Phase 6 进行中状态更新)
- docs/Rtc_AIavatar/数字人表情渲染方案_云端预渲染+BLE+OTA.md
  新增第 19 章 RTC 空闲倒计时方案选型与软退出(9 小节)
- docs/Rtc_AIavatar/RTC软退出方案_移植参考.md
  完整移植参考(10 章 + 3 附录,可移植到其他火山 RTC 项目)
- docs/Rtc_AIavatar/音频卡顿_全局资源分析.md
  全局资源分析 + 13 项优化建议(不改代码)
2026-05-13 17:28:36 +08:00
03496efecb 1、初始化代码,待适配中.... 2026-02-24 15:28:34 +08:00