plan(rtc-only): Phase 9 取消 + Phase 10/11/12 规划(LVGL → esp_emote_gfx)

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) <noreply@anthropic.com>
This commit is contained in:
Rdzleo 2026-05-15 13:37:34 +08:00
parent 3dc6cadf49
commit 3a1111e99d
6 changed files with 939 additions and 27 deletions

View File

@ -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 regressionv2 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 → .eaf4-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 → 16AMPDU 窗口扩大减少重传)
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 6PowerSaveTimer 状态机重写需 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 重编号) |

View File

@ -0,0 +1,71 @@
# Phase 9 — 已取消
**取消日期**2026-05-15
**取消决策方**:用户
**取消原因**增量修补策略jitter buffer + codec init 调整)效果不明显,用户改为方案 C 完整切 EAFPhase 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
### v2hotfix
**改动**
- 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 模板 / 多版本日志)保留供回溯

View File

@ -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 buffer3 处代码 + N 行
- 9.2 codec init2 处代码 + 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` 次数 < 20vs baseline 126
- [ ] 对话期 `queue=0` 次数 < 15vs baseline 58
- [ ] 用户主观:开机"卡卡在呢"不抖 + 对话 AI 声音连贯
- [ ] IMPL_REPORT.md 填实数据
- [ ] commit 推送到 Rtc_AIavatar
---
## 6. Phase 9 不做的事
- ❌ 不引入 esp_emote_gfxPhase 8 数据否决了 CPU 假设)
- ❌ 不动 `CONFIG_ESP_WIFI_*_BUFFER_NUM`(物理层 OK
- ❌ 不深挖 ES7210 寄存器级 init 问题(重试 + 降级足够,深层修复留待后续)
- ❌ 不关闭 PHASE8_DIAG_ENABLE保留埋点便于 Phase 9 验证)
- ❌ 不动 LVGL/GIF 相关代码

View File

@ -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 fontEAF 文档说支持 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.gifPoC 阶段)
**前置**: 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吧唧模式专属

View File

@ -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

View File

@ -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"