From 3a1111e99d4853dfb75b7a3295f02dbac6ef9a63 Mon Sep 17 00:00:00 2001 From: Rdzleo Date: Fri, 15 May 2026 13:37:34 +0800 Subject: [PATCH] =?UTF-8?q?plan(rtc-only):=20Phase=209=20=E5=8F=96?= =?UTF-8?q?=E6=B6=88=20+=20Phase=2010/11/12=20=E8=A7=84=E5=88=92=EF=BC=88L?= =?UTF-8?q?VGL=20=E2=86=92=20esp=5Femote=5Fgfx=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 9 三轮增量优化(jitter buffer / codec init / Core 1 绑定)效果不 明显,用户决策完整切 EAF 验证 GIF 抢资源假设。 Phase 9 → CANCELLED: - v1 jitter buffer device_state 判错(漏 kDeviceStateDialog) - v1 ES7210 重试破坏 ES8311 init 导致开机播报无声 - v2 修正 device_state 后 jitter 工作但仍卡 - v3 background_task 绑 Core 1 + DIAG-5 未硬件验证 - 所有代码改动 git restore 回滚(无 commit),Phase 8 DIAG 埋点保留 - CANCELLED.md 记录教训 Phase 10 新增(数字人模式 LVGL → esp_emote_gfx 完整切换): - 添加 espressif2022/esp_emote_gfx ~3.0.5 依赖(已 reconfigure 拉取) - API 风险扫清:GFX_LABEL_LONG_WRAP 支持中文换行、 gfx_font_lv_load_from_binary 兼容 LVGL bitmap font - 双轨编译:CONFIG_BAJI_BADGE_MODE=y 保 LVGL,=n 走 EAF - PLAN.md 含 10 个子任务从依赖到完整 UI 切换 - 预估 3-5 天 Phase 11 占位:LVGL 释放的 ~40KB DRAM + ~80KB PSRAM 投到 WiFi 缓冲扩容(STATIC_RX 10→16、DYN_RX/TX 32→48、RX_BA_WIN 6→16)+ Opus/RTC SDK jitter buffer 扩容 Phase 12 占位:原 Phase 10 集成测试 + 推送,重编号 ROADMAP 同步更新,依赖关系矫正。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../milestones/digital_human_rtc/ROADMAP.md | 101 +++-- .../CANCELLED.md | 71 ++++ .../phase_09_audio_jitter_codecinit/PLAN.md | 361 ++++++++++++++++++ .../phases/phase_10_lvgl_to_eaf/PLAN.md | 351 +++++++++++++++++ dependencies.lock | 80 +++- main/idf_component.yml | 2 + 6 files changed, 939 insertions(+), 27 deletions(-) create mode 100644 .planning/milestones/digital_human_rtc/phases/phase_09_audio_jitter_codecinit/CANCELLED.md create mode 100644 .planning/milestones/digital_human_rtc/phases/phase_09_audio_jitter_codecinit/PLAN.md create mode 100644 .planning/milestones/digital_human_rtc/phases/phase_10_lvgl_to_eaf/PLAN.md diff --git a/.planning/milestones/digital_human_rtc/ROADMAP.md b/.planning/milestones/digital_human_rtc/ROADMAP.md index 3c198b7..77d50e1 100644 --- a/.planning/milestones/digital_human_rtc/ROADMAP.md +++ b/.planning/milestones/digital_human_rtc/ROADMAP.md @@ -379,29 +379,78 @@ static const emotion_gif_map_t emotion_gif_table[] = { --- -## Phase 9: 音频卡顿实施优化(待 Phase 8 数据决策) +## Phase 9: ❌ 已取消 — 音频卡顿增量优化尝试 -**目标**:根据 Phase 8 `DIAG_REPORT.md` 的根因判定,按预案分支实施优化。**具体方案在 Phase 8 完成后细化为 PLAN.md**。 +**取消原因**:用户决策(2026-05-15)— 增量修补效果不明显(v1 引入 ES8311 dev 30 regression,v2 jitter buffer 工作但仍卡,v3 未来得及实测),改为方案 C 完整切 EAF(见 Phase 10)。 -**分支预案**: -| 分支 | 触发根因 | 实施动作 | 预估工时 | -|---|---|---|---| -| **A** | 仅 CPU 争抢 | `eaf_dec_*` 解码器旁路替换 `lv_gif`,保留 LVGL 框架 | 1-2 天 | -| **B** | 仅 WiFi/网络 | WiFi 缓冲扩容(STATIC_RX 10→16、DYN_RX/TX 32→48、RX_BA_WIN 6→16)| 0.5 天 | -| **C** | 组合 ①+④⑤ | 数字人模式完整切 EAF(`CONFIG_BAJI_BADGE_MODE=n` 分支弃用 LVGL)+ 释放的 ~40KB DRAM + ~80KB PSRAM 投到 WiFi/Opus/RTC jitter buffer 扩容 | 3-5 天 | -| **D** | DMA/I2S | 取消 EAF 方案,转 DMA 路径排查 | 视具体问题 | +**保留**:[phase_09_audio_jitter_codecinit/CANCELLED.md](phases/phase_09_audio_jitter_codecinit/CANCELLED.md) 记录 v1/v2/v3 实验教训供未来参考。 -**完成标志**: -- ✅ 选定分支落地代码 + 编译通过 -- ✅ 卡顿复现场景下听感主观验证:抖动消失或显著降低 -- ✅ Phase 8 埋点指标改善(`queue` 抖动、`write_slow` 频率均下降) -- ✅ 内存/CPU 监控 30 分钟稳定 - -**产出 commit**:(按选定分支命名)`perf(rtc-only): Phase 9 - {A/B/C/D 描述}` +**回滚**:Phase 9 所有代码改动通过 `git restore` 已回滚,Phase 8 DIAG 埋点保留作为 Phase 10 验证基准。 --- -## Phase 10: 集成测试 + 推送 +## Phase 10: 数字人模式 LVGL → esp_emote_gfx 完整切换 + +**目标**:仅在 `CONFIG_BAJI_BADGE_MODE=n` 分支下弃用 LVGL,采用乐鑫 esp_emote_gfx 框架 + EAF 动画格式。Phase 8 数据排除了"调度"问题(codec write 0 慢),用户决策直接验证 EAF 显示效果。释放 ~40KB DRAM + ~80KB PSRAM 留作 Phase 11 资源再分配。 + +**驱动**:用户假设 "GIF 与 RTC 抢占资源 + WiFi 缓冲不够",方案 C 是该假设的最彻底验证 —— LVGL/GIF 完全消失后看显示效果与音频感知。 + +**关键改动范围**: +1. 添加 `esp_emote_gfx` 组件依赖(idf_component.yml) +2. EAF Packer 工具链:hiyori_m{03,06,07}.gif → .eaf(4-bit + Heatshrink + 透明) +3. 重写 `main/display/lcd_display.cc` 数字人分支:接管 display 改为 `gfx_emote_add_disp` +4. 重写 `main/dzbj/ai_chat_ui.c`(458 行):`lv_obj` → `gfx_obj`,`lv_gif` → `gfx_anim`,`lv_label` → `gfx_label` +5. CMakeLists 条件编译切换(仅 `CONFIG_BAJI_BADGE_MODE=n` 走 EAF) +6. 字体接驳:`font_puhui_20_4.c` 复用(EAF 官方支持 LVGL bitmap font) +7. 触摸路径:cst816s → `gfx_touch`(如数字人模式需要) + +**预估工时**:3-5 天 + +**完成标志**: +- ✅ `CONFIG_BAJI_BADGE_MODE=n` 编译通过 +- ✅ 烧录后数字人 GIF 动画正常显示(hiyori 三个表情切换) +- ✅ 字幕显示(中文 + 自动换行 + 双行居中) +- ✅ RTC 对话听感主观验证:扬声器卡顿是否消失(这是核心 PoC 目的) +- ✅ `idf.py size` 对比:Flash/DRAM/PSRAM 变化 +- ✅ DRAM 净结余 ≥ 30KB 用于 Phase 11 + +**产出 commit**:`feat(ui): Phase 10 - 数字人模式 LVGL → esp_emote_gfx 完整迁移` + +**关键风险**: +- `gfx_label` 中文自动换行 + 双行居中支持验证(实施前先跑 PoC) +- 数字人模式如有触摸交互,需重写 +- 资源转 EAF 工具链稳定性 +- `ui_ScreenUpdate`(吧唧模式 BLE 收图 UI)共用 lv_gif,方案 C 仅影响数字人模式,吧唧模式保持 LVGL(双轨编译) + +--- + +## Phase 11: 内存优化 + WiFi 缓冲扩容 + +**目标**:把 Phase 10 释放的 ~40KB DRAM + ~80KB PSRAM 重新分配到 WiFi RX/TX 缓冲、Opus jitter buffer、RTC SDK jitter buffer,最大化 RTC 体验。 + +**关键改动**: +1. `sdkconfig.defaults`: + - `CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM`: 10 → 16 + - `CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM`: 32 → 48 + - `CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM`: 32 → 48 + - `CONFIG_ESP_WIFI_RX_BA_WIN`: 6 → 16(AMPDU 窗口扩大减少重传) +2. Opus decode pool 上限扩容(如 SDK 暴露) +3. 火山 RTC SDK jitter buffer 配置扩容(如 SDK 暴露) +4. `heap_caps_print_heap_info` 前后对比 + +**预估工时**:1 天 + +**完成标志**: +- ✅ sdkconfig 改动编译通过且烧录稳定 +- ✅ DRAM 投入 ≤ 15KB 后剩余可用 ≥ Phase 8 baseline +- ✅ Phase 8 DIAG 埋点对比:queue=0 次数下降、WiFi 重传减少 +- ✅ 用户主观:扬声器流畅度提升 + +**产出 commit**:`perf(rtc-only): Phase 11 - WiFi 缓冲扩容 + jitter buffer 强化` + +--- + +## Phase 12: 集成测试 + 推送 **目标**:端到端验证 MILESTONE.md 第 6 节全部验收项,推送到 gitea + GitHub。 @@ -419,7 +468,7 @@ static const emotion_gif_map_t emotion_gif_table[] = { **完成标志**: - ✅ MILESTONE.md 第 6 节成功标准全部 ✓ -- ✅ Phase 8/9 音频卡顿问题已解决 +- ✅ Phase 10/11 音频卡顿问题已解决 - ✅ gitea + GitHub 远程已同步 - ✅ 文档更新完成 @@ -436,10 +485,12 @@ static const emotion_gif_map_t emotion_gif_table[] = { - Phase 6 依赖 Phase 1(清理 sleep_mgr 调用点) - Phase 7 依赖 Phase 6(PowerSaveTimer 状态机重写需 Phase 6 守卫到位) - **Phase 8 依赖 Phase 6(卡顿症状在 Phase 6 收尾发现,需要 RTC 链路稳定)** -- **Phase 9 依赖 Phase 8(实施策略由诊断报告决定)** -- Phase 10 必须最后(依赖 Phase 9 卡顿解决) +- **Phase 9 已取消**(增量优化效果不明显,改走 Phase 10 完整切 EAF) +- **Phase 10 依赖 Phase 8**(数据排除调度问题后用户决策完整切 EAF 验证显示效果) +- **Phase 11 依赖 Phase 10**(用 Phase 10 释放的 DRAM/PSRAM 做资源再分配) +- Phase 12 必须最后(依赖 Phase 11 完成) -**建议串行执行顺序**:1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10 +**建议串行执行顺序**:1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → ~~9~~ → 10 → 11 → 12 --- @@ -454,6 +505,8 @@ static const emotion_gif_map_t emotion_gif_table[] = { | Phase 5 | ✅ 完成(commit `f2be992`) | | Phase 6 | ✅ 完成(commit `b8a5fe9` + `4b7b194` 收尾) | | Phase 7 | 🔄 进行中([phase_07_battery_psm](phases/phase_07_battery_psm/README.md) 规格已写,实施待启动) | -| **Phase 8** | **⏳ 待启动(音频卡顿诊断埋点,新增)** | -| **Phase 9** | **⏸️ 阻塞中(待 Phase 8 数据,分支预案 A/B/C/D 已列)** | -| Phase 10 | ⏳ 待启动(集成测试 + 推送,原 Phase 7 重编号) | +| Phase 8 | ✅ 完成(commit `3dc6cad`,4 类 DIAG 埋点 + 根因报告) | +| **Phase 9** | **❌ 已取消**(v1/v2/v3 增量优化未解决,CANCELLED.md 记录教训) | +| **Phase 10** | **⏳ 待启动**(数字人模式 LVGL→EAF 完整切换,新增) | +| **Phase 11** | **⏸️ 阻塞中**(内存优化 + WiFi 缓冲扩容,依赖 Phase 10 完成) | +| Phase 12 | ⏳ 待启动(集成测试 + 推送,原 Phase 10 重编号) | diff --git a/.planning/milestones/digital_human_rtc/phases/phase_09_audio_jitter_codecinit/CANCELLED.md b/.planning/milestones/digital_human_rtc/phases/phase_09_audio_jitter_codecinit/CANCELLED.md new file mode 100644 index 0000000..65ff81d --- /dev/null +++ b/.planning/milestones/digital_human_rtc/phases/phase_09_audio_jitter_codecinit/CANCELLED.md @@ -0,0 +1,71 @@ +# Phase 9 — 已取消 + +**取消日期**:2026-05-15 +**取消决策方**:用户 +**取消原因**:增量修补策略(jitter buffer + codec init 调整)效果不明显,用户改为方案 C 完整切 EAF(Phase 10)以验证 GIF/LVGL 是否真是元凶。 + +--- + +## 三轮尝试教训 + +### v1(初版) +**改动**: +- 9.1 jitter buffer:`OnAudioOutput` 加 FILLING/DRAINING 状态机(PREBUF=4 / OVERFLOW=12) +- 9.2.1 ES7210 init 3 次重试 + 失败降级 +- 9.2.2 PlaySound 前 vTaskDelay(150ms) + +**实测问题**: +1. **jitter buffer 完全没生效** — 日志中 0 处 "Jitter buffer 蓄水完成" + - 根因:项目 RTC 对话状态用 `kDeviceStateDialog`(不是 Speaking/Listening) + - 我的判定漏了 Dialog → 在对话期完全不进 jitter 分支 +2. **开机播报听不到声音** — 新 regression + - 根因:ES7210 重试逻辑改变 I2C 总线时序 → ES8311 (`dev 30`) 写入失败 + - Phase 8 baseline 时 ES7210 失败会触发 assert reboot 自愈;v1 让 ES7210 不失败反而破坏 ES8311 init + +### v2(hotfix) +**改动**: +- jitter buffer 判定加 `kDeviceStateDialog` +- 回退 9.2.1(恢复 Phase 8 baseline 的 assert 模式) +- 保留 9.2.2 (150ms 延迟) + +**实测**: +- jitter buffer 工作了(15+ 次 "蓄水完成开始消费 (q=4)")✓ +- 对话期 `write_slow=0` ✓ +- 开机播报恢复正常 ✓ +- **但用户主观仍然非常卡顿** ✗ + +**核心数据**: +- 对话期 (30s+) queue 采样 859 次 + - 平均 3.6 / 最大 22 / queue=0 出现 52 次 / queue≥12 出现 28 次(远超 OVERFLOW) + +### v3(深挖) +**新假设**:`background_task` 使用 `xTaskCreate` **未绑核** → 可能跑 Core 0 与 LVGL/GIF 抢调度。 + +**改动**: +- `background_task.cc` 改用 `xTaskCreatePinnedToCore(..., 1)` 强制 Core 1 +- 加 DIAG-5 `bg_lag` 埋点测调度延迟 +- jitter buffer 调大 PREBUF 4→6, OVERFLOW 12→24 + +**结果**:未来得及硬件实测 — 用户决策放弃增量修补改走 Phase 10。 + +--- + +## 关键教训 + +1. **device_state 名字不能凭印象写**,务必先 grep 项目实际使用 +2. **修改一个 codec init 链路时,要预想对邻近 codec 的连锁影响**(共享 I2C 总线 / 时钟) +3. **DIAG-2 测 codec write 耗时是不够的** — 测不到 background_task schedule 延迟 +4. **增量优化策略容易越改越乱**,当假设需要双线/三线修复时,应考虑是否方向本身不对 +5. **用户的物理直觉值得尊重** — "kapi 项目底座 RTC 流畅 → 数字人模式卡顿" 这个对比本身就是强证据,指向新增的 GIF/LVGL + +--- + +## 回滚记录 + +- 所有代码改动通过 `git restore` 回滚(无 commit) +- 影响文件: + - `main/application.cc/h` + - `main/audio_codecs/box_audio_codec.cc` + - `main/background_task.cc` +- Phase 8 commit `3dc6cad` 保留(DIAG 埋点继续作为 Phase 10 验证基准) +- 本目录 `phase_09_audio_jitter_codecinit/` 全部文档(PLAN.md / IMPL_REPORT 模板 / 多版本日志)保留供回溯 diff --git a/.planning/milestones/digital_human_rtc/phases/phase_09_audio_jitter_codecinit/PLAN.md b/.planning/milestones/digital_human_rtc/phases/phase_09_audio_jitter_codecinit/PLAN.md new file mode 100644 index 0000000..5005f38 --- /dev/null +++ b/.planning/milestones/digital_human_rtc/phases/phase_09_audio_jitter_codecinit/PLAN.md @@ -0,0 +1,361 @@ +# Phase 9 PLAN — 音频卡顿双线修复(jitter buffer + codec init 时序) + +> 里程碑: `digital_human_rtc` +> 阶段目标: 基于 Phase 8 [DIAG_REPORT.md](../phase_08_audio_glitch_diag/DIAG_REPORT.md) 双根因判定 ③'+⑤,双线修复 RTC 对话期 jitter + 开机 codec init 时序。 +> 性质: **实施 phase**,纯软件改动,不引入 esp_emote_gfx / 不调整 sdkconfig。 + +--- + +## 0. 决策依据(DIAG_REPORT 摘要) + +- 主观感知:A. 开机"卡卡在呢"声音抖 + B. RTC 对话期 AI 回答断续 +- **A 根因 ③'**:ES7210 I2C 0x80 写入失败 → 2-13s 集中 126 次 `write_slow`(50-58ms/帧) +- **B 根因 ⑤**:WebSocket Opus 帧应用层突发到达(queue=19 突发 + queue=0 出现 58 次 @ 40s+) +- 排除:CPU 争抢 / PSRAM 带宽 / WiFi 物理层 / 内存碎片 + +--- + +## 1. 设计方案 + +### 1.1 子任务 9.1 — 应用层 jitter buffer(解 B) + +**位置**: [main/application.cc](../../../../main/application.cc) `Application::OnAudioOutput()` + +**机制**(fill-threshold + drain + overflow drop): +``` +状态机: + ┌─ FILLING ──q ≥ PREBUF ───▶ DRAINING ─q = 0─▶ FILLING ─┐ + └─────────────────────────────────────────────────────────┘ + +参数: + PREBUF = 4 帧 (60ms × 4 = 240ms 预蓄水) + OVERFLOW = 12 帧 (60ms × 12 = 720ms 上限,超过丢最老 1 帧) + +行为: + FILLING: audio_decode_queue_.empty() 一直 return; 等填满 PREBUF 帧 + DRAINING: 正常 pop_front + 解码 + 输出 + DRAINING + size > OVERFLOW: 主动 pop_front 丢老帧(防止永久堆积) + DRAINING + queue empty: 切回 FILLING(重新蓄水) +``` + +**仅在 RTC 对话期生效**:device_state == kDeviceStateSpeaking 或 kDeviceStateListening +- 开机播报 PlaySound 路径 **不走** jitter buffer(一次性灌入数据,预蓄水反而拖累首帧) +- 通过 protocol 状态或 device_state 判断 + +**关键参数推导**(基于 Phase 8 DIAG 数据): +- queue=19 突发观察到 4 帧(45039/45089/45199 enq 集中) +- PREBUF=4 留出 240ms 抗抖,能吞下一次"突发-消化"周期 +- OVERFLOW=12 防止极端情况(≥720ms 堆积明显延迟,丢帧重新对齐) + +### 1.2 子任务 9.2 — 开机 codec init 时序加固(解 A) + +**位置**: +- [main/audio_codecs/box_audio_codec.cc](../../../../main/audio_codecs/box_audio_codec.cc) ES7210 init 重试 + 错误降级 +- [main/application.cc](../../../../main/application.cc) `Application::Start()` 中 PlaySound 前等待 codec 稳定 + +**改动 1 — ES7210 init 失败重试**(BoxAudioCodec 构造函数 line 73-82): +```cpp +// 当前: +in_codec_if_ = es7210_codec_new(&es7210_cfg); +assert(in_codec_if_ != NULL); // ✗ assert 失败直接 abort + +// 改造为:3 次重试 + 失败降级 +for (int retry = 0; retry < 3; retry++) { + in_codec_if_ = es7210_codec_new(&es7210_cfg); + if (in_codec_if_ != NULL) break; + ESP_LOGW(TAG, "ES7210 init failed, retry %d/3", retry + 1); + vTaskDelay(pdMS_TO_TICKS(50)); +} +if (in_codec_if_ == NULL) { + ESP_LOGE(TAG, "ES7210 init permanently failed, fallback to output-only"); + // 不 abort,置为 output_only 等价状态 +} +``` + +**改动 2 — Application::Start 中 codec ready 等待**(PlaySound "开机播报语音" 之前): +```cpp +// 当前: +codec->EnableInput(true); +codec->EnableOutput(true); +PlaySound(...); // ✗ 立即播报,可能 codec 仍在 init + +// 改造为:等 codec 稳定后再播报 +codec->EnableInput(true); +codec->EnableOutput(true); +vTaskDelay(pdMS_TO_TICKS(150)); // 给 I2S DMA + codec 稳定时间 +PlaySound(...); +``` + +**改动 3 - 不引入新的 codec ready 标志**(不动 audio_codec.h 接口): +- 150ms 是经验值,足以让 ES8311 → I2S DMA → ES7210 stabilize +- 如果用户测下来仍卡,再升级到 codec ready callback 机制 + +--- + +## 2. 任务清单 + +### 任务 9.1.1 — 在 Application 类中加 jitter buffer 状态成员 + +**文件**: [main/application.h](../../../../main/application.h) + +**读取参考**: +- 现有 private 成员变量布局 +- audio_decode_queue_ 声明 + +**改动**: 在 audio_decode_queue_ 附近加: +```cpp +// Phase 9.1: 应用层 jitter buffer(解 RTC 对话期 Opus 帧到达抖动) +enum class JitterState : uint8_t { FILLING = 0, DRAINING = 1 }; +JitterState jitter_state_ = JitterState::FILLING; +static constexpr int kJitterPrebufFrames = 4; // 预蓄水阈值 +static constexpr int kJitterOverflowFrames = 12; // 上限丢帧阈值 +``` + +**验收**: +- `grep -nE "jitter_state_|kJitterPrebufFrames|kJitterOverflowFrames" main/application.h` 各返回 ≥ 1 +- 编译通过 + +--- + +### 任务 9.1.2 — OnAudioOutput 中插入 jitter buffer 状态机 + +**文件**: [main/application.cc](../../../../main/application.cc) 的 `OnAudioOutput()` + +**读取参考**: +- 当前 `if (audio_decode_queue_.empty()) return;` 分支(约 2126 行) +- 当前 `auto opus = std::move(audio_decode_queue_.front());` 出队点(约 2167 行) +- DIAG-1 出队埋点位置 + +**改动**(在出队前插入状态机判定): + +```cpp +// 在 audio_decode_queue_.empty() 处理之后、device_state 检查之前插入 + +// Phase 9.1: 应用层 jitter buffer 状态机(仅对话期生效,开机播报不走) +bool is_rtc_audio = (device_state_ == kDeviceStateSpeaking || + device_state_ == kDeviceStateListening) && + opus_playback_active_.load() == false; // 不与 HTTPS 抢 +if (is_rtc_audio) { + int q = (int)audio_decode_queue_.size(); + if (jitter_state_ == JitterState::FILLING) { + if (q < kJitterPrebufFrames) { + return; // 蓄水中,不消费 + } + jitter_state_ = JitterState::DRAINING; + ESP_LOGI(TAG, "Jitter buffer 蓄水完成开始消费 (q=%d)", q); + } else { // DRAINING + if (q > kJitterOverflowFrames) { + // 上限保护:丢最老 1 帧 + audio_decode_queue_.pop_front(); + ESP_LOGW(TAG, "Jitter buffer 超限丢帧 (q=%d)", q); + } + } +} + +// 出队(原有逻辑) +auto opus = std::move(audio_decode_queue_.front()); +audio_decode_queue_.pop_front(); +// ... +``` + +**重置点**(empty 分支末尾,准备返回前): +```cpp +if (audio_decode_queue_.empty()) { + // ... 原有逻辑 ... + + // Phase 9.1: 队列变空 → 切回 FILLING 重新蓄水 + if (jitter_state_ == JitterState::DRAINING) { + jitter_state_ = JitterState::FILLING; + ESP_LOGD(TAG, "Jitter buffer 重置为 FILLING"); + } + + return; +} +``` + +**验收**: +- `grep -c "Jitter buffer" main/application.cc` ≥ 3 +- 编译通过 + +--- + +### 任务 9.1.3 — 状态切换时复位 jitter(避免转入对话即受历史影响) + +**文件**: [main/application.cc](../../../../main/application.cc) + +**读取参考**: +- `SetDeviceState(kDeviceStateSpeaking)` / `SetDeviceState(kDeviceStateListening)` 等转换点 + +**改动**: 在转入 Listening 或 Speaking 状态时显式置 FILLING: +- 找 `void Application::SetDeviceState(DeviceState state)` 入口 +- 在状态切换时若新状态是 Speaking/Listening,置 `jitter_state_ = JitterState::FILLING` + +**验收**: +- 切换 RTC 对话开始时日志能看到一次 "Jitter buffer 蓄水完成开始消费" +- 编译通过 + +--- + +### 任务 9.2.1 — ES7210 init 重试 + 不 abort + +**文件**: [main/audio_codecs/box_audio_codec.cc](../../../../main/audio_codecs/box_audio_codec.cc) + +**读取参考**: +- line 73-82: 当前 ES7210 init 路径 +- `assert(in_codec_if_ != NULL)` 失败行为 + +**改动**: 将 line 76 `in_codec_if_ = es7210_codec_new(&es7210_cfg);` + `assert` 替换为重试循环(详细代码见 §1.2 改动 1) + +**注意**: +- 重试 3 次仍失败时**不 abort**,但应该把 `input_dev_` 置为 nullptr 避免后续 `esp_codec_dev_open(input_dev_)` 崩 +- 同样把 line 80 `dev_cfg.codec_if = in_codec_if_;` + line 82 `assert(input_dev_ != NULL);` 也要相应处理(in_codec_if_ 为 null 时不创建 input_dev_) + +**验收**: +- `grep -c "ES7210 init failed" main/audio_codecs/box_audio_codec.cc` = 1 +- 编译通过 +- 烧录后即使 ES7210 init 失败也不会 reboot 循环(保留向后兼容) + +--- + +### 任务 9.2.2 — PlaySound 前等 codec 稳定 + +**文件**: [main/application.cc](../../../../main/application.cc) `Application::Start()` + +**读取参考**: +- `AudioCodec: 将运行时输出音量设置为:80` 之后到 `PlaySound` 之前 +- 当前路径:"设备启动完成,播放开机播报语音" + +**改动**: 在 PlaySound 调用之前添加 150ms 延迟。具体定位关键字 "设备启动完成" 附近。 + +**验收**: +- `grep -nE "vTaskDelay.*150.*codec|codec 稳定等待" main/application.cc` 返回 1 行 +- 编译通过 +- 烧录后日志:开机播报阶段 `write_slow` 次数应显著减少(从 126 次降到 <20 次) + +--- + +### 任务 9.3 — 编译 + 烧录 + 验证(用 Phase 8 DIAG 埋点做对比) + +**前置**: 9.1.x + 9.2.x 全部任务完成 + +**步骤**: +```bash +source ~/esp/esp-idf/export.sh +idf.py build flash monitor 2>&1 | tee .planning/milestones/digital_human_rtc/phases/phase_09_audio_jitter_codecinit/phase_09_diag.log +``` + +测试场景与 Phase 8 一致:5 分钟 RTC 对话 + 主动 GIF 切换。 + +**关键对比指标**(vs Phase 8 baseline): + +| 指标 | Phase 8 baseline | Phase 9 目标 | +|---|---|---| +| 开机播报 write_slow (2-13s) | 126 次 | **< 20 次** | +| 对话期 queue=0 次数 | 58 (40s+) | **< 15** | +| 对话期 queue max | 19 | **< 8** | +| 对话期 write_slow | 0 | 保持 0 | +| 用户主观:开机"卡卡在呢"听感 | 抖 | **不抖** | +| 用户主观:对话期 AI 声音 | 断续 | **连贯** | + +**Jitter buffer 工作日志样本**(应能看到): +``` +I Jitter buffer 蓄水完成开始消费 (q=4) +... 几十秒正常消费 ... +W Jitter buffer 超限丢帧 (q=13) ← 偶发但合理 +D Jitter buffer 重置为 FILLING ← AI 说话间隙 +``` + +--- + +### 任务 9.4 — 产出 IMPL_REPORT.md + commit + +**文件**: 新建 `.planning/milestones/digital_human_rtc/phases/phase_09_audio_jitter_codecinit/IMPL_REPORT.md` + +**结构**: +```markdown +# Phase 9 — 实施报告 + +## 1. 改动概览 +- 9.1 jitter buffer:3 处代码 + N 行 +- 9.2 codec init:2 处代码 + N 行 + +## 2. 实测对比表(Phase 8 vs Phase 9) +| 指标 | Phase 8 | Phase 9 | 改善 | + +## 3. 用户主观验证 +- 开机播报听感:☐ 不抖 / ☐ 仍抖 +- 对话期 AI 声音:☐ 连贯 / ☐ 仍断续 + +## 4. 残留问题(若有) +... + +## 5. PHASE8_DIAG_ENABLE 是否关闭 +☐ 已关 / ☐ 保留待 Phase 10 集成测试 +``` + +**commit message**: +``` +perf(rtc-only): Phase 9 - 音频卡顿双线修复(jitter buffer + codec init) + +9.1 应用层 jitter buffer(解决 ⑤ Opus 帧到达抖动): +- OnAudioOutput 加 FILLING/DRAINING 状态机 +- PREBUF=4 帧(240ms 预蓄水)+ OVERFLOW=12 帧(720ms 上限丢帧) +- 仅 RTC 对话期生效,开机播报路径绕过 + +9.2 开机 codec init 时序加固(解决 ③' ES7210 I2C 失败): +- ES7210 init 失败 3 次重试 + 失败降级不 abort +- PlaySound 前 150ms codec 稳定等待 + +实测对比(详 IMPL_REPORT.md): +- 开机 write_slow 126→{N} +- 对话期 queue=0 出现 58→{M} +- 对话期 queue max 19→{X} +``` + +--- + +## 3. 任务顺序 + +``` +9.1.1 (header 成员) → 9.1.2 (OnAudioOutput) → 9.1.3 (状态切换重置) + ↓ +9.2.1 (ES7210 重试) → 9.2.2 (PlaySound 等待) ──┴→ 9.3 (build+flash 验证) → 9.4 (commit) +``` + +9.1 / 9.2 可并行编辑,统一在 9.3 build。 + +--- + +## 4. 风险与回滚 + +| 风险 | 触发 | 缓解 | +|---|---|---| +| PREBUF=4 蓄水延迟首字 240ms | 用户感觉 AI"反应慢" | 这是设计权衡:换抖动减少。240ms 远小于 AI 思考延迟(秒级),不会显著感知 | +| OVERFLOW=12 丢帧导致句子缺词 | 网络长时间堆积 | 720ms 才丢,且只丢老帧(用户最早感知的"音频已过期"),不丢最新 | +| ES7210 重试 150ms 拖慢开机 | 用户感觉开机变慢 | 50ms × 3 = 最多 150ms,可接受 | +| PlaySound 等 150ms 让开机播报变慢 | 用户感觉开机变慢 | 150ms 不可感知 | +| jitter buffer 干扰 HTTPS 音频播放 | HTTPS(故事/音乐)走 audio_decode_queue_ 同一通道 | `is_rtc_audio` 判定加 `opus_playback_active_ == false` 排除 HTTPS | +| 回滚 | 9.1 或 9.2 任一引入 regression | 单独 `git revert` 即可(独立子任务) | + +--- + +## 5. Phase 9 完成验收清单 + +- [ ] 9.1.1-9.2.2 五个子任务全部代码就位 +- [ ] `idf.py build` 通过 +- [ ] 烧录 + RTC 对话 ≥ 5 分钟 +- [ ] 开机 `write_slow` 次数 < 20(vs baseline 126) +- [ ] 对话期 `queue=0` 次数 < 15(vs baseline 58) +- [ ] 用户主观:开机"卡卡在呢"不抖 + 对话 AI 声音连贯 +- [ ] IMPL_REPORT.md 填实数据 +- [ ] commit 推送到 Rtc_AIavatar + +--- + +## 6. Phase 9 不做的事 + +- ❌ 不引入 esp_emote_gfx(Phase 8 数据否决了 CPU 假设) +- ❌ 不动 `CONFIG_ESP_WIFI_*_BUFFER_NUM`(物理层 OK) +- ❌ 不深挖 ES7210 寄存器级 init 问题(重试 + 降级足够,深层修复留待后续) +- ❌ 不关闭 PHASE8_DIAG_ENABLE(保留埋点便于 Phase 9 验证) +- ❌ 不动 LVGL/GIF 相关代码 diff --git a/.planning/milestones/digital_human_rtc/phases/phase_10_lvgl_to_eaf/PLAN.md b/.planning/milestones/digital_human_rtc/phases/phase_10_lvgl_to_eaf/PLAN.md new file mode 100644 index 0000000..0925557 --- /dev/null +++ b/.planning/milestones/digital_human_rtc/phases/phase_10_lvgl_to_eaf/PLAN.md @@ -0,0 +1,351 @@ +# Phase 10 PLAN — 数字人模式 LVGL → esp_emote_gfx 完整切换 + +> 里程碑: `digital_human_rtc` +> 阶段目标: `CONFIG_BAJI_BADGE_MODE=n` 时数字人模式完全弃用 LVGL,采用乐鑫 esp_emote_gfx + EAF 动画格式,验证 GIF 抢资源假设是否成立。吧唧模式保持 LVGL。 +> 预估工时: 3-5 天(含 PoC 显示效果验证 + 完整 UI 切换) + +--- + +## 0. 调研结论 + +### 0.1 esp_emote_gfx 核心 API(已实地调查) + +- 组件名: `espressif2022/esp_emote_gfx` v3.0.5 +- 入口: `gfx_emote_init() / gfx_emote_add_disp()` 接管显示 +- 控件: `gfx_obj` 基类 + `gfx_anim / gfx_img / gfx_label / gfx_button / gfx_qrcode` +- EAF 格式: magic `0x5A5A`, 4/8/24-bit 调色板, RLE/Huffman/Heatshrink/RAW 多种编码,block-based decode,原生透明(调色板 idx 0) +- 文档: `espressif2022.github.io/esp_emote_gfx/zh_CN/index.html` +- 工具: ESP Emote GFX Packer `emote-gfx-gen-tool-dev.pages.dev` + +### 0.2 项目数字人模式 UI 边界(已确认) + +数字人模式下 UI 只有 [main/dzbj/ai_chat_ui.c](../../../../main/dzbj/ai_chat_ui.c) (458 行) 和 [main/display/lcd_display.cc](../../../../main/display/lcd_display.cc) 数字人分支,**7 个 LVGL 对象**: + +| 控件 | EAF 对应 | 难度 | +|---|---|---| +| `ai_screen` (lv_obj 根容器) | `gfx_obj`(disp 根)| 低 | +| `gif_emotion` (lv_gif 主数字人) | `gfx_anim` + EAF 资源 | 低 | +| `gif_icon` (lv_gif 叠加图标) | `gfx_anim` + EAF 资源 | 低 | +| `emoji_img` (lv_img 静态) | `gfx_img` + RGB565A8 | 低 | +| `status_label` (lv_label) | `gfx_label` | 低 | +| `chat_container` (lv_obj 字幕背景) | `gfx_obj` 容器 | 低 | +| `chat_label` (lv_label 字幕 312×48 + 自动换行 + 居中) | `gfx_label` | **⚠️ 中文换行风险点** | + +### 0.3 关键风险(必须先 PoC 验证) + +1. **gfx_label 中文自动换行 + 双行居中**:未在 esp_emote_gfx 公开文档中明确说明,需 PoC +2. **font_puhui_20_4.c 复用**:8.5MB LVGL bitmap font,EAF 文档说支持 LVGL bitmap font,但实际兼容性需验证 +3. **display 接管冲突**:数字人模式下 LCD panel 必须只由 esp_emote_gfx 接管(移除 lvgl_port) + +### 0.4 双轨架构 + +| 编译条件 | UI 框架 | 涉及文件 | +|---|---|---| +| `CONFIG_BAJI_BADGE_MODE=y` | LVGL(原有) | `lcd_display.cc` + `ui/screens/*` + `ai_chat_ui.c` (LVGL 版本) | +| `CONFIG_BAJI_BADGE_MODE=n` | **esp_emote_gfx** | 新增 `eaf_display.cc/h` + 新版 ai_chat_ui (EAF) | + +--- + +## 1. 设计方案 + +### 1.1 文件结构(新增) + +``` +main/display/ + display.h 保留(虚基类,接口不变) + lcd_display.cc/h 保留(LVGL,吧唧模式专用) + eaf_display.cc/h ← 新增(esp_emote_gfx,数字人模式专用) + +main/dzbj/ + ai_chat_ui.c 保留(LVGL 版本,吧唧模式用) + ai_chat_ui_eaf.c ← 新增(EAF 版本,数字人模式用) + +main/boards/movecall-moji-esp32s3/ + movecall_moji_esp32s3.cc 修改:根据 BAJI_BADGE_MODE 实例化 LcdDisplay 或 EafDisplay + +spiffs_image/ + hiyori_m03.gif → hiyori_m03.eaf ← 离线工具转换 + hiyori_m06.gif → hiyori_m06.eaf + hiyori_m07.gif → hiyori_m07.eaf +``` + +### 1.2 CMakeLists 条件编译 + +```cmake +# main/CMakeLists.txt +if(CONFIG_BAJI_BADGE_MODE) + list(APPEND srcs + "display/lcd_display.cc" + "dzbj/ai_chat_ui.c" + # ... LVGL UI screens ... + ) +else() + # 数字人模式:EAF 路径 + list(APPEND srcs + "display/eaf_display.cc" + "dzbj/ai_chat_ui_eaf.c" + ) +endif() +``` + +### 1.3 Display 接口适配 + +`display.h` 现有虚函数接口(`SetStatus / SetEmotion / SetChatMessage / ...`)**不动**。 +`EafDisplay : public Display` 实现这些函数,内部用 gfx_label / gfx_anim 等。 + +### 1.4 实施策略:分阶段 PoC + +为了快速看到显示效果,分两步: +- **PoC 阶段(10.1-10.3)**:最小可行 —— 加依赖 + 转一个 GIF + 写最小 EafDisplay 只显示 hiyori GIF。**烧录看显示**。 +- **完整阶段(10.4-10.8)**:扩展字幕、状态、emoji、CMakeLists 切换。 + +--- + +## 2. 任务清单 + +### 任务 10.1 — 添加 esp_emote_gfx 组件依赖 + +**文件**: `main/idf_component.yml` + +**改动**: 在 dependencies 中追加: +```yaml + ## Phase 10: 数字人模式 EAF UI(替代 LVGL) + espressif2022/esp_emote_gfx: "~3.0.5" +``` + +**验收**: +- `idf.py reconfigure` 成功拉取组件 +- `managed_components/espressif2022__esp_emote_gfx/` 目录存在 +- 头文件 `core/gfx_emote.h` 可被引用 + +--- + +### 任务 10.2 — gfx_label 中文换行 PoC + +**目的**: 验证最大风险点 —— gfx_label 是否支持中文自动换行 + 双行居中。**如失败需重新设计字幕方案**。 + +**手段**: 不动主代码,单独写小测试: +1. 拉 `managed_components/espressif2022__esp_emote_gfx/test_apps/` 看官方测试 +2. 找 `gfx_label_set_long_mode` / `gfx_label_set_text_align` 等 API +3. 短上下文 PoC:创建 gfx_label,文本 "(happy)今天天气真好这是一段需要换行测试的字幕",宽度 312px,看是否自动换行 + 居中 + +**验收**: +- 找到 gfx_label 的换行 API(如 `gfx_label_set_long_mode(obj, GFX_LABEL_LONG_WRAP)`) +- 文本能自动换行(至少 2 行) +- 文本居中显示 + +**降级方案**(如换行不支持): 主代码端手动按字符宽度切分 + 创建两个 gfx_label 分别显示上下行 + +--- + +### 任务 10.3 — EAF Packer 转换 hiyori_m06.gif(PoC 阶段) + +**前置**: ESP Emote GFX Packer 工具 +- 在线: `emote-gfx-gen-tool-dev.pages.dev` +- 或 GitHub Releases 下载 CLI + +**步骤**: +1. 上传 `spiffs_image/hiyori_m06.gif`(PoC 先转一个) +2. 配置: 4-bit palette + Heatshrink + 保留透明 +3. 下载 `hiyori_m06.eaf`,放到 `spiffs_image/` + +**验收**: +- `spiffs_image/hiyori_m06.eaf` 存在 +- 文件大小 < 原 gif(应该明显更小) +- `head -c 4 hiyori_m06.eaf` 显示 magic `0x5A 0x5A` + +--- + +### 任务 10.4 — 最小 EafDisplay PoC(只显示 hiyori_m06.eaf) + +**新建**: `main/display/eaf_display.h` + `main/display/eaf_display.cc` + +**最小内容**: +```cpp +class EafDisplay : public Display { +public: + EafDisplay(esp_lcd_panel_io_handle_t panel_io, + esp_lcd_panel_handle_t panel, + int width, int height, + int offset_x, int offset_y, + bool mirror_x, bool mirror_y, bool swap_xy); + ~EafDisplay() override; + + // 暂时只实现最小接口 + void SetStatus(const char* status) override { /* TODO */ } + void SetEmotion(const char* emotion) override; // 切换 EAF 动画 + +private: + gfx_handle_t gfx_handle_; + gfx_disp_t* disp_; + gfx_obj_t* anim_obj_; + uint8_t* eaf_data_ = nullptr; + size_t eaf_size_ = 0; +}; +``` + +构造时: +1. `gfx_emote_init(&gfx_handle_)` +2. `gfx_emote_add_disp(gfx_handle_, &disp_cfg)` —— disp_cfg 用 LCD panel 信息 +3. 加载 `/spiflash/hiyori_m06.eaf` 到 `eaf_data_`(PSRAM) +4. `anim_obj_ = gfx_anim_create(disp_)` +5. `gfx_anim_set_src(anim_obj_, eaf_data_, eaf_size_)` +6. `gfx_anim_set_segment(anim_obj_, 0, total_frames - 1, 20, true)`(20fps 循环) +7. `gfx_anim_start(anim_obj_)` + +**验收**: +- 编译通过 +- 烧录后 LCD 显示 hiyori_m06 动画循环播放 +- **听 RTC 对话扬声器卡顿是否改善**(核心 PoC 目的) + +--- + +### 任务 10.5 — board 工厂条件编译 + +**文件**: `main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc` + +**改动**: 找到 LcdDisplay 实例化位置,加 #ifdef 分支: +```cpp +#ifdef CONFIG_BAJI_BADGE_MODE + display_ = new LcdDisplay(...); +#else + display_ = new EafDisplay(...); +#endif +``` + +**CMakeLists 改动**: 数字人模式下移除 `lcd_display.cc` 编译,加入 `eaf_display.cc` + +**验收**: +- 双向编译都通过(`=y` 和 `=n`) +- 数字人模式固件不含 LVGL 符号(`nm` 检查无 lv_obj_create) + +--- + +### 任务 10.6 — EafDisplay 完整接口实现 + +扩展 EafDisplay 实现剩余 `Display` 虚函数: + +- `SetStatus(const char*)` — gfx_label 显示连接状态 +- `SetChatMessage(role, content)` — gfx_label 显示字幕(中文换行) +- `SetEmotion(emotion)` — 切换 EAF 动画(emotion → eaf 路径映射) +- `SetIcon(icon)` — gfx_img 显示叠加图标 +- `Lock / Unlock` — esp_emote_gfx 锁机制接驳 +- `Update()` — 强制刷新 + +复用 `ai_chat_ui.c` 的 emotion → gif 映射表逻辑,改为 emotion → eaf 路径。 + +**验收**: +- 数字人模式下 application.cc 调用 Display::SetXxx 都正常工作 +- 编译通过 + +--- + +### 任务 10.7 — 字体接驳 + +**前置**: `main/fonts/font_puhui_20_4.c`(8.5MB CJK bitmap font) + +**步骤**: +1. 查 esp_emote_gfx 字体 API:`gfx_font_t*` / `gfx_label_set_font` +2. 验证是否能直接传 `&font_puhui_20_4` (lv_font_t) → 应该不行,需要适配 +3. 如不兼容,用 esp_emote_gfx 工具重新生成同字符集 EAF font 资源 + +**降级方案**: 如果字体重新生成太麻烦,用内置默认字体或英文字体先 PoC + +**验收**: +- 字幕中文显示正常 +- 字幕单字符宽度合理(非乱码) + +--- + +### 任务 10.8 — 触摸路径接驳 + +**前提确认**: 数字人模式是否需要触摸交互? +- 看 [main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc](../../../../main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc) cst816s 是否在 `CONFIG_BAJI_BADGE_MODE=n` 下注册了 LVGL indev + +**如不需要触摸**: 跳过此任务 +**如需要**: 用 `gfx_touch` 接驳 cst816s,重写 LVGL_INDEV 路径 + +--- + +### 任务 10.9 — 编译 + 烧录 + 显示效果验证 + +```bash +source ~/esp/esp-idf/export.sh +idf.py build +idf.py flash monitor 2>&1 | tee .planning/milestones/digital_human_rtc/phases/phase_10_lvgl_to_eaf/phase_10_diag.log +``` + +**测试场景**: +1. 开机:是否正常显示数字人 GIF? +2. RTC 连接:状态文字是否显示? +3. 对话:字幕中文换行 + 居中是否正确? +4. **听感**:扬声器卡顿是否消失?(**核心 PoC 验证目的**) +5. 情绪切换:AI 说带情绪标签的话时 GIF 是否切换? + +**指标对比**(vs Phase 8 baseline): + +| 指标 | Phase 8 baseline | Phase 10 目标 | +|---|---|---| +| `idf.py size` DRAM | baseline | -30~40 KB | +| `idf.py size` Flash | baseline | -80 KB | +| `heap_caps_get_free_size(INTERNAL)` | baseline | +30 KB | +| `heap_caps_get_free_size(SPIRAM)` | baseline | +80 KB | +| 用户主观 RTC 对话听感 | 卡 | **不卡(核心目标)** | + +--- + +### 任务 10.10 — 产出 IMPL_REPORT.md + commit + +报告核心: PoC 显示效果 + 听感主观验证 + 资源对比,决定 Phase 11 是否启动。 + +--- + +## 3. 任务顺序 + +``` +10.1 加依赖 → 10.2 gfx_label 中文换行 PoC(关键风险点) + ↓ 通过 +10.3 EAF Packer 转 hiyori_m06 → 10.4 最小 EafDisplay PoC(一个 GIF 显示)→ 编译烧录看效果 + ↓ 显示效果 OK +10.5 board 工厂 → 10.6 EafDisplay 完整接口 → 10.7 字体 → 10.8 触摸 → 10.9 编译烧录验证 → 10.10 commit +``` + +**关键 GO/NO-GO 决策点**: +- 10.2 后: gfx_label 中文换行不支持 → 评估降级方案 +- 10.4 后: 显示效果 OK + 听感改善 → 继续完整切换;显示坏 → 中止并回退 + +--- + +## 4. 风险与回滚 + +| 风险 | 缓解 | +|---|---| +| esp_emote_gfx 文档不全 / API 不稳定 | 参考 `managed_components/.../test_apps/` 官方示例 | +| 字体兼容失败 | 降级英文字体先 PoC,字幕中文文档化为已知限制 | +| 中文换行不支持 | 手动切分两个 gfx_label | +| 显示效果坏 | 任何阶段 commit 前都可 `git reset --hard HEAD` 回滚 | +| 听感未改善 | 数据证明假设 1 错误,需重新审视 → 继续 Phase 11 直接 WiFi 缓冲扩容验证假设 2 | + +--- + +## 5. Phase 10 完成验收清单 + +- [ ] esp_emote_gfx 组件已添加且可拉取 +- [ ] gfx_label 中文换行已验证(或降级方案就绪) +- [ ] 至少一个 EAF 动画文件已生成且能在设备显示 +- [ ] EafDisplay 实现 Display 全部虚函数 +- [ ] CMakeLists 双轨编译双向通过 +- [ ] 烧录数字人模式 + RTC 对话 5 分钟 +- [ ] 听感主观:扬声器卡顿是否消失(PoC 核心目标) +- [ ] 资源对比 `idf.py size` + heap 数据已记录 +- [ ] IMPL_REPORT.md 给出 Phase 11 GO/NO-GO 决策 + +--- + +## 6. Phase 10 不做的事 + +- ❌ 不动吧唧模式 LVGL UI(`CONFIG_BAJI_BADGE_MODE=y` 路径完全不变) +- ❌ 不删除 LVGL/lvgl_port 组件(吧唧模式仍需要) +- ❌ 不动 audio_codec / RTC 协议(这是上游问题) +- ❌ 不做内存优化和 WiFi 扩容(留给 Phase 11) +- ❌ 不动 ScreenUpdate / ui/screens(吧唧模式专属) diff --git a/dependencies.lock b/dependencies.lock index 1980945..4e9b0cb 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -60,7 +60,7 @@ dependencies: type: service version: 0.5.3 espressif/dl_fft: - component_hash: 7dadbd644c0d7ba4733cc3726ec4cff6edf27b043725e1115861dec1609a3d28 + component_hash: ced3cf28cc70452b7859c06f4e5059215167254a2047e34c893d6f501ccd6ea2 dependencies: - name: idf require: private @@ -68,7 +68,7 @@ dependencies: source: registry_url: https://components.espressif.com type: service - version: 0.3.1 + version: 0.4.0 espressif/esp-dsp: component_hash: 619639efc18cfa361a9e423739b9b0ffc14991effc6c027f955c2f2c3bf1754b dependencies: @@ -169,6 +169,35 @@ dependencies: registry_url: https://components.espressif.com/ type: service version: 2.5.0 + espressif/esp_new_jpeg: + component_hash: 98823384f51ca298e2c9bebacd1c629148e528ed0902d18b16556df151519e68 + dependencies: [] + source: + registry_url: https://components.espressif.com + type: service + targets: + - esp32 + - esp32s2 + - esp32s3 + - esp32s31 + - esp32p4 + - esp32c2 + - esp32c3 + - esp32c5 + - esp32c6 + - esp32c61 + - esp32h4 + version: 1.0.1 + espressif/freetype: + component_hash: a4169cdd22b3572342b2d640d7082405b8895e3214539283601c03412589b65d + dependencies: + - name: idf + require: private + version: '>=4.4' + source: + registry_url: https://components.espressif.com + type: service + version: 2.14.2 espressif/knob: component_hash: a389d980693ad195b2160de22a72f3391694230188ab16b8f3c7ec4410a7c417 dependencies: @@ -193,10 +222,54 @@ dependencies: registry_url: https://components.espressif.com/ type: service version: 2.5.5 + espressif2022/esp_emote_gfx: + component_hash: a06a58c74f7deb4186460f27f5e6db52fda4c254d7e03c3e05e7987aaf73de1a + dependencies: + - name: espressif/cmake_utilities + registry_url: https://components.espressif.com + require: private + version: 0.* + - name: espressif/esp_lcd_touch + registry_url: https://components.espressif.com + require: public + version: '>=1.0' + - name: espressif/esp_new_jpeg + registry_url: https://components.espressif.com + require: public + version: 1.* + - name: espressif/freetype + registry_url: https://components.espressif.com + require: private + version: 2.* + - name: idf + require: private + version: '>=5.0' + - name: laride/heatshrink + registry_url: https://components.espressif.com + require: private + version: ^0.4.1 + - name: lvgl/lvgl + registry_url: https://components.espressif.com + require: public + version: '*' + source: + registry_url: https://components.espressif.com/ + type: service + version: 3.0.5 idf: source: type: idf version: 5.4.2 + laride/heatshrink: + component_hash: 0828b0fea3f0754f8404a5279e883c52fe27494bbe1762e38d5cd96c99229e47 + dependencies: + - name: idf + require: private + version: '>=5' + source: + registry_url: https://components.espressif.com + type: service + version: 0.4.1 lvgl/lvgl: component_hash: 948bff879a345149b83065535bbc4a026ce9f47498a22881e432a264b9098015 dependencies: [] @@ -219,8 +292,9 @@ direct_dependencies: - espressif/esp_lvgl_port - espressif/knob - espressif/led_strip +- espressif2022/esp_emote_gfx - idf - lvgl/lvgl -manifest_hash: 567fb06fed7b7df9c9bbd2a0615df5b600cd13d08df4b38a71d28971feaec792 +manifest_hash: 90544e3d787e63c288feeb33cf16100755d3ed90c47270526fd2fb5754eba469 target: esp32s3 version: 2.0.0 diff --git a/main/idf_component.yml b/main/idf_component.yml index a2fc2ba..ad7dd16 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -17,6 +17,8 @@ dependencies: esp_lcd_touch_cst816s: "1.1.0" ## JPEG 解码(dzbj 图片显示) esp_jpeg: "*" + ## Phase 10: 数字人模式 UI 框架(替代 LVGL,仅 CONFIG_BAJI_BADGE_MODE=n 时使用) + espressif2022/esp_emote_gfx: "~3.0.5" ## Required IDF version idf: version: ">=5.3"