feat(eaf+aec): 切换 Elf 14 表情资源 + 软件 AEC 彻底关闭恢复语音打断
1. EAF 数字人资源从 hiyori(8 表情)切换为 Elf 精灵(14 表情): - 新增 spiffs_image/Elf-expr.bin(5.82 MB,14 个英文情绪状态 EAF) - 删除 spiffs_image/hiyori-assets.bin(旧 8 表情) - ai_chat_ui_eaf.c:EAF_ASSETS_PATH 改为 Elf-expr.bin,cache 槽 8→16 - 22 种 RTC 情绪标签重新映射到 14 个 Elf 表情(语义就近) - 默认/fallback 表情 hiyori_m06 → Standby_Norm(常态待机) 2. 数字人显示调整: - 禁用 JPG 背景图(纯黑背景,移除 DecodeImg/s_bg_* 逻辑) - 数字人对齐 GFX_ALIGN_BOTTOM_MID → GFX_ALIGN_CENTER(上下左右居中) - 待命状态显示 Standby_Norm(idle → neutral → Standby_Norm 已映射) 3. 软件 AEC 彻底关闭(解决"说话中无法打断"): - ENABLE_SOFTWARE_AEC=0,拦在 InitAec() 入口 - aec_handle_ 永不创建 → ref ring buffer 不分配 - 省内部 SRAM ~35-50 KB + PSRAM ~10-15 KB - mic 原始 PCM 上行交火山 RTC 服务端 AEC 处理 - 实测:语音打断恢复正常(之前软件 AEC 过度压制用户语音导致无法打断) - 改回 1 可恢复设备端软件 AEC 4. 新增文档 docs/Rtc_AIavatar/OTA与EAF存储空间权衡分析.md: - 分析 OTA 双分区成本(5.5MB)vs 14 表情 EAF(5.82MB) - 16MB Flash 上两者不可兼得(差 2MB),推荐升级 32MB(N32R8) 5. sdkconfig 同步 ESP-IDF 自动重生成的 SOC/camera/phy 配置差异 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
d4362311cc
commit
baa86d203c
1801
05-最新日志.txt
1801
05-最新日志.txt
File diff suppressed because it is too large
Load Diff
187
docs/Rtc_AIavatar/OTA与EAF存储空间权衡分析.md
Normal file
187
docs/Rtc_AIavatar/OTA与EAF存储空间权衡分析.md
Normal file
@ -0,0 +1,187 @@
|
||||
# OTA 与 EAF 存储空间权衡分析
|
||||
|
||||
> **分支**:`adaptation_eaf_rtc_badge_dual_mode`
|
||||
> **分析日期**:2026-06-12
|
||||
> **硬件**:ESP32-S3-N16R8(16 MB Flash / 8 MB PSRAM / 320 KB Internal SRAM)
|
||||
> **背景**:当前为支持 14 表情 EAF 动画(`Elf-expr.bin` 5.82 MB),牺牲了 OTA 双分区。本文分析"同时保留 OTA + EAF"所需空间,以及 Flash 升级方案。
|
||||
> **结论先行**:16 MB Flash 上"双 OTA + 完整 14 表情 EAF"差约 2 MB 硬缺口,推荐升级 32 MB Flash(N32R8)。
|
||||
|
||||
---
|
||||
|
||||
## 一、之前 OTA 占用了多少空间
|
||||
|
||||
### 移除 OTA 前的分区表(commit `6b166f4~1`)
|
||||
|
||||
```
|
||||
# Name, Type, SubType, Offset, Size
|
||||
nvs, data, nvs, 0x9000, 0x4000 # 16 KB
|
||||
otadata, data, ota, 0xd000, 0x2000 # 8 KB ← OTA 状态记录
|
||||
phy_init, data, phy, 0xf000, 0x1000 # 4 KB
|
||||
ota_0, app, ota_0, 0x10000, 0x580000 # 5.50 MB App 运行槽 A
|
||||
ota_1, app, ota_1, 0x590000, 0x580000 # 5.50 MB App 备份槽 B(OTA 专用)
|
||||
storage, data, spiffs, 0xB10000, 0x4F0000 # 4.94 MB 资源
|
||||
```
|
||||
|
||||
### OTA 机制的本质成本
|
||||
|
||||
A/B 双槽 OTA 需要**两个等大的 app 分区**:
|
||||
- 运行时跑 `ota_0`
|
||||
- 升级时把新固件写入 `ota_1`
|
||||
- 校验通过后下次从 `ota_1` 启动
|
||||
- 失败可回滚到 `ota_0`(这就是 OTA 可靠性的来源)
|
||||
|
||||
**OTA 真正"额外"吃掉的空间 = `ota_1` 整块 5.5 MB + `otadata` 8 KB ≈ 5.5 MB**
|
||||
|
||||
---
|
||||
|
||||
## 二、当前状态(牺牲 OTA 后,commit `6b166f4` 起)
|
||||
|
||||
### 当前分区表
|
||||
|
||||
```
|
||||
# Name, Type, SubType, Offset, Size
|
||||
nvs, data, nvs, 0x9000, 0x4000 # 16 KB
|
||||
phy_init, data, phy, 0xf000, 0x1000 # 4 KB
|
||||
factory, app, factory, 0x10000, 0x700000 # 7.00 MB 单 app(无备份槽)
|
||||
storage, data, spiffs, 0x710000, 0x8F0000 # 8.94 MB 资源
|
||||
```
|
||||
|
||||
### 当前占用
|
||||
|
||||
| 分区 | 大小 | 已用 | 余量 | 利用率 |
|
||||
|---|---|---|---|---|
|
||||
| factory (单 app) | 7.00 MB | **5.72 MB** | 1.28 MB | 82% |
|
||||
| storage (SPIFFS) | 8.94 MB | **5.90 MB** | 3.04 MB | 66% |
|
||||
|
||||
**SPIFFS 内容**(5.90 MB):
|
||||
|
||||
| 文件 | 大小 |
|
||||
|---|---|
|
||||
| `Elf-expr.bin`(14 表情 EAF)| 5.82 MB |
|
||||
| `Background_360x360.jpg` | 20 KB |
|
||||
| `02.jpg` / `default.jpg` | 58 KB |
|
||||
|
||||
### 移除 OTA 换来的 5.5 MB 去向
|
||||
|
||||
| 去向 | 变化 |
|
||||
|---|---|
|
||||
| App 分区 | 5.5 MB → 7 MB(+1.5 MB,因加入 EAF 代码 + 字体后固件变大)|
|
||||
| SPIFFS | 4.94 MB → 8.94 MB(+4.0 MB,装下 14 表情 Elf-expr.bin)|
|
||||
|
||||
---
|
||||
|
||||
## 三、同时保留 OTA + EAF 所需空间
|
||||
|
||||
### 核心矛盾
|
||||
|
||||
OTA 要双 app 槽,EAF 要大 SPIFFS,两个都是大胃口资源。
|
||||
|
||||
### 在当前 16 MB Flash 上算账
|
||||
|
||||
| 需求 | 空间 |
|
||||
|---|---|
|
||||
| 双 OTA app 槽(每槽 ≥ 6 MB,留固件增长余量)| 6 × 2 = **12 MB** |
|
||||
| `Elf-expr.bin` EAF(5.82 MB + 余量)| **6 MB** |
|
||||
| nvs + otadata + phy_init | 0.03 MB |
|
||||
| **合计需求** | **≈ 18 MB** |
|
||||
| **当前 Flash** | **16 MB** |
|
||||
| **缺口** | **❌ 差约 2 MB(且无任何增长余量)** |
|
||||
|
||||
**结论:当前 16 MB Flash 装不下"双 OTA + 完整 14 表情 EAF"**,至少差 2 MB,实际更多(没留固件/资源增长空间)。
|
||||
|
||||
---
|
||||
|
||||
## 四、16 MB 上的妥协方案(不升级 Flash)
|
||||
|
||||
如果坚持不换 Flash,只能砍功能:
|
||||
|
||||
| 方案 | 做法 | 代价 |
|
||||
|---|---|---|
|
||||
| A. 砍字体 + 减表情 | 字体裁到 ~500 KB(省 0.9 MB)→ 固件降到 ~4.8 MB → OTA 槽 2×5 MB=10 MB;EAF 减到 8 表情 ~3.5 MB | 表情少一半 + 字库变小 |
|
||||
| B. 单 OTA 槽(非标准)| factory + 1 个 ota 槽 | 升级失败无回滚,有变砖风险 |
|
||||
| C. EAF 改 LRU 按需加载 | EAF 仍在 SPIFFS(不省 Flash),只省 PSRAM | **不解决 Flash 问题** |
|
||||
|
||||
**这些妥协都不理想** —— 要么砍功能,要么牺牲 OTA 可靠性。
|
||||
|
||||
---
|
||||
|
||||
## 五、推荐方案:升级 32 MB Flash(N16R8 → N32R8)
|
||||
|
||||
### 模组对比
|
||||
|
||||
| 项 | N16R8(当前)| N32R8(升级后)|
|
||||
|---|---|---|
|
||||
| Flash | 16 MB | **32 MB** |
|
||||
| PSRAM | 8 MB | 8 MB(不变)|
|
||||
| 模组改动 | — | 换贴片,PCB 不动 |
|
||||
| 软件改动 | — | 只改 `partitions.csv` + `CONFIG_ESPTOOLPY_FLASHSIZE` |
|
||||
| 模组单价 | — | +$1~2 |
|
||||
|
||||
### 32 MB 上的舒适分区方案
|
||||
|
||||
| 分区 | 大小 | 说明 |
|
||||
|---|---|---|
|
||||
| nvs + otadata + phy | ~0.03 MB | — |
|
||||
| ota_0 | 7 MB | App 槽 A(固件 5.72 MB + 余量)|
|
||||
| ota_1 | 7 MB | App 槽 B(OTA 备份)|
|
||||
| storage (SPIFFS) | **~18 MB** | EAF 5.82 MB + 未来可扩到 30+ 表情 |
|
||||
| **合计** | **32 MB** | ✅ OTA + EAF 双全 + 大量余量 |
|
||||
|
||||
### 收益
|
||||
|
||||
- ✅ 完整保留 A/B 双槽 OTA(可靠回滚)
|
||||
- ✅ 完整 14 表情 EAF,且 SPIFFS 还剩 ~12 MB 给未来扩展
|
||||
- ✅ 软件几乎零改动(只改分区表 + flash size)
|
||||
- ✅ 一次升级,长期不再为存储发愁
|
||||
|
||||
---
|
||||
|
||||
## 六、决策建议
|
||||
|
||||
| 目标 | 推荐方案 |
|
||||
|---|---|
|
||||
| **要 OTA + 完整 EAF + 长期省心** | 🟢 **升级 32 MB Flash(N32R8)** ← 最优 |
|
||||
| 暂时不换硬件、能接受砍功能 | 🟡 16 MB + 砍字体 + 减到 8 表情 + OTA |
|
||||
| 当前阶段先验证功能、OTA 不急 | 🟠 保持现状(无 OTA),量产前再上 32 MB |
|
||||
|
||||
**一句话**:在 16 MB 上"鱼(OTA)与熊掌(14 表情 EAF)不可兼得",差 2 MB 是硬缺口。**要两个都要,升级到 32 MB Flash 是最干净的解**,软件几乎不用改,单价只 +$1~2。
|
||||
|
||||
---
|
||||
|
||||
## 七、Flash 升级实施要点(升级 32 MB 后)
|
||||
|
||||
升级模组后软件侧需改 2 处:
|
||||
|
||||
1. **`sdkconfig` / `sdkconfig.defaults`**:
|
||||
```
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE="32MB"
|
||||
```
|
||||
|
||||
2. **`partitions.csv`**(恢复双 OTA + 大 SPIFFS):
|
||||
```
|
||||
# Name, Type, SubType, Offset, Size
|
||||
nvs, data, nvs, 0x9000, 0x4000
|
||||
otadata, data, ota, 0xd000, 0x2000
|
||||
phy_init, data, phy, 0xf000, 0x1000
|
||||
ota_0, app, ota_0, 0x10000, 0x700000 # 7 MB
|
||||
ota_1, app, ota_1, 0x710000, 0x700000 # 7 MB
|
||||
storage, data, spiffs, 0xE10000, 0x11F0000 # ~18 MB
|
||||
```
|
||||
|
||||
3. **代码侧**:恢复 `ota.cc` 编译 + `app_update` 依赖(当前 dual-mode 分支已 stub 化为禁用,需回退 commit `6b166f4` 的 OTA 移除部分)。
|
||||
|
||||
> 注:以上分区为参考值,实施时用 `idf.py partition-table` 校验总和不超 32 MB。
|
||||
|
||||
---
|
||||
|
||||
## 附录:关键数据速查
|
||||
|
||||
| 项 | 数值 |
|
||||
|---|---|
|
||||
| 当前固件 `kapi.bin` | 5.72 MB |
|
||||
| 当前 EAF `Elf-expr.bin` | 5.82 MB(14 表情)|
|
||||
| OTA 单备份槽成本 | ~5.5 MB(一个等大 app 分区)|
|
||||
| 16 MB 上 OTA+EAF 缺口 | ~2 MB(硬缺口)|
|
||||
| 推荐 Flash 升级 | 16 MB → 32 MB(N32R8)|
|
||||
| 升级后 SPIFFS 余量 | ~12 MB(扩展空间)|
|
||||
@ -60,6 +60,16 @@ extern "C" void ai_chat_resume_animation(void);
|
||||
#define PHASE8_DIAG_ENABLE 0
|
||||
#endif
|
||||
|
||||
// ============================================================
|
||||
// 软件 AEC 总开关:改 0 暂时关闭 (aec_handle_ 永不创建 → 所有 aec_active 判断为 false)
|
||||
// → 数据流回退 baseline 无回声消除,用于测试语音打断灵敏度;改回 1 恢复
|
||||
// 彻底关闭:拦在 InitAec() 入口,AEC 实例 + ref ring buffer 都不分配,
|
||||
// 省内部 SRAM (~35-50 KB) + PSRAM (~10-15 KB)
|
||||
// ============================================================
|
||||
#ifndef ENABLE_SOFTWARE_AEC
|
||||
#define ENABLE_SOFTWARE_AEC 0
|
||||
#endif
|
||||
|
||||
#if PHASE8_DIAG_ENABLE
|
||||
#include <esp_heap_caps.h>
|
||||
static int64_t g_diag_queue_last_us = 0; // queue 深度日志节流(50ms)
|
||||
@ -146,6 +156,14 @@ Application::~Application() {
|
||||
// aec_process(mic, delayed_ref, clean) 输出消除回声的 PCM 上行 RTC
|
||||
// 特点: 不启后台任务, 应用主导调度, 不抢 RTC; codec 保持 baseline 1ch 16-bit
|
||||
void Application::InitAec() {
|
||||
#if !ENABLE_SOFTWARE_AEC
|
||||
static bool warned = false;
|
||||
if (!warned) {
|
||||
warned = true;
|
||||
ESP_LOGW(TAG, "⚠️ 软件 AEC 已关闭 (ENABLE_SOFTWARE_AEC=0),走 baseline 无回声消除,mic 原始上行交服务端处理");
|
||||
}
|
||||
return; // aec_handle_ 永不创建 → ref ring buffer 不分配 → 所有 aec_active 判断为 false
|
||||
#endif
|
||||
if (aec_handle_ != nullptr) {
|
||||
return;
|
||||
}
|
||||
@ -255,9 +273,9 @@ void Application::GetDelayedRef(int16_t *ref_out, int samples) {
|
||||
// 累积不足 chunk_size 或 ref 未就绪时 → passthrough (不改 mic)
|
||||
void Application::ApplyAEC(std::vector<int16_t>& mic_inout) {
|
||||
if (aec_handle_ == nullptr) {
|
||||
InitAec(); // lazy init
|
||||
InitAec(); // lazy init(AEC 关闭时 InitAec 不创建 handle)
|
||||
if (aec_handle_ == nullptr || aec_chunk_size_ <= 0) {
|
||||
return; // 初始化失败, passthrough
|
||||
return; // handle 未创建(AEC 关闭)或初始化失败 → passthrough,mic 原始上行
|
||||
}
|
||||
}
|
||||
int n = (int)mic_inout.size();
|
||||
|
||||
@ -52,14 +52,11 @@ typedef struct {
|
||||
char name[40]; // 文件名
|
||||
} eaf_cache_entry_t;
|
||||
|
||||
static eaf_cache_entry_t s_eaf_cache[8]; // 预留 8 个表情槽位
|
||||
static eaf_cache_entry_t s_eaf_cache[16]; // 预留 16 个表情槽位(Elf 资源含 14 个 EAF,方案 A 全预加载)
|
||||
static int s_eaf_cache_count = 0;
|
||||
|
||||
// Phase 10 step 1: 背景图
|
||||
#define EAF_BG_JPG_PATH "/spiflash/Background_360x360.jpg"
|
||||
static uint8_t *s_bg_rgb565_data = NULL; // 解码后的 RGB565 buffer(永不释放)
|
||||
static gfx_image_dsc_t s_bg_image_dsc;
|
||||
static gfx_obj_t *s_bg_img_obj = NULL;
|
||||
// Phase 10 step 1: 背景图(已禁用 — 用户要求纯黑背景,数字人居中显示)
|
||||
// 保留 JPG 背景代码已移除,disp 背景色黑色 0x000000 通过 chroma key 透明露出
|
||||
|
||||
// Phase 10 step 2: 字幕
|
||||
static gfx_obj_t *s_chat_label = NULL;
|
||||
@ -71,8 +68,8 @@ static const char *TAG = "AI_CHAT_EAF";
|
||||
// ==========================================================
|
||||
// 配置常量
|
||||
// ==========================================================
|
||||
#define EAF_ASSETS_PATH "/spiflash/hiyori-assets.bin"
|
||||
#define EAF_MAX_FILES 3 // index.json + 2 个 EAF (m06 + m07)
|
||||
#define EAF_ASSETS_PATH "/spiflash/Elf-expr.bin"
|
||||
#define EAF_MAX_FILES 16 // index.json + 14 个 Elf EAF 表情(方案 A 全预加载)
|
||||
#define EAF_DEFAULT_FPS 14 // 与工具配置一致
|
||||
#define LCD_W 360
|
||||
#define LCD_H 360
|
||||
@ -96,37 +93,40 @@ typedef struct {
|
||||
} eaf_emotion_map_t;
|
||||
|
||||
static const eaf_emotion_map_t s_emotion_map[] = {
|
||||
// 22 种情绪 → 8 张 EAF (m01~m08, 2026-05-20 全 8 张接入)
|
||||
// 注: 具体动作内容需要用户在 EAF Packer 中确认后调整, 当前是按情绪类别 +
|
||||
// 编号顺序的初版分配, 烧录后看效果再精准调整
|
||||
// 默认/积极组 (12 → m01..m05 均分, 每张约 2-3 种情绪)
|
||||
{"neutral", "hiyori_m01.eaf"},
|
||||
{"happy", "hiyori_m01.eaf"},
|
||||
{"blink", "hiyori_m01.eaf"},
|
||||
{"laughing", "hiyori_m02.eaf"},
|
||||
{"funny", "hiyori_m02.eaf"},
|
||||
{"curious", "hiyori_m02.eaf"},
|
||||
{"loving", "hiyori_m03.eaf"},
|
||||
{"relaxed", "hiyori_m03.eaf"},
|
||||
{"delicious", "hiyori_m04.eaf"},
|
||||
{"kissy", "hiyori_m04.eaf"},
|
||||
{"confident", "hiyori_m05.eaf"},
|
||||
{"silly", "hiyori_m05.eaf"},
|
||||
// 思考/疲倦组 (5) → m06
|
||||
{"sleepy", "hiyori_m06.eaf"},
|
||||
{"thinking", "hiyori_m06.eaf"},
|
||||
{"confused", "hiyori_m06.eaf"},
|
||||
{"embarrassed", "hiyori_m06.eaf"},
|
||||
{"dizzy", "hiyori_m06.eaf"},
|
||||
// 负面/严肃组 (3) → m07
|
||||
{"sad", "hiyori_m07.eaf"},
|
||||
{"crying", "hiyori_m07.eaf"},
|
||||
{"angry", "hiyori_m07.eaf"},
|
||||
// 惊讶组 (2) → m08
|
||||
{"surprised", "hiyori_m08.eaf"},
|
||||
{"shocked", "hiyori_m08.eaf"},
|
||||
// 22 种 RTC 情绪标签 → Elf 精灵 14 个 EAF 表情
|
||||
// Elf 资源命名是英文情绪状态(非 m01~m08 编号),按语义就近映射
|
||||
// 烧录后看效果再精准调整某条映射即可
|
||||
//
|
||||
// 积极/中性组
|
||||
{"neutral", "Standby_Norm.eaf"}, // 中性 → 常态待机
|
||||
{"relaxed", "Standby_Norm.eaf"}, // 放松 → 常态待机
|
||||
{"happy", "Happy.eaf"}, // 开心
|
||||
{"laughing", "Happy.eaf"}, // 大笑 → 开心
|
||||
{"funny", "Happy.eaf"}, // 搞笑 → 开心
|
||||
{"loving", "Shy.eaf"}, // 喜爱 → 害羞
|
||||
{"kissy", "Shy.eaf"}, // 亲亲 → 害羞
|
||||
{"delicious", "Happy.eaf"}, // 美味 → 开心
|
||||
{"confident", "Flighty.eaf"}, // 自信 → 浮躁
|
||||
{"silly", "Flighty.eaf"}, // 呆萌 → 浮躁
|
||||
{"blink", "Standby_Rand.eaf"}, // 眨眼 → 随机待机
|
||||
{"curious", "Listen.eaf"}, // 好奇 → 聆听
|
||||
// 思考/疲倦组
|
||||
{"sleepy", "Doze.eaf"}, // 困倦 → 打瞌睡
|
||||
{"dizzy", "Doze.eaf"}, // 眩晕 → 打瞌睡
|
||||
{"thinking", "Doubt.eaf"}, // 思考 → 疑惑
|
||||
{"confused", "Doubt.eaf"}, // 困惑 → 疑惑
|
||||
{"embarrassed", "Wronged.eaf"}, // 尴尬 → 委屈
|
||||
// 负面/严肃组
|
||||
{"sad", "Wronged.eaf"}, // 伤心 → 委屈
|
||||
{"crying", "Wronged.eaf"}, // 哭泣 → 委屈
|
||||
{"angry", "Angry.eaf"}, // 生气
|
||||
// 惊讶组
|
||||
{"surprised", "Awaken.eaf"}, // 惊讶 → 惊醒
|
||||
{"shocked", "Awaken.eaf"}, // 震惊 → 惊醒
|
||||
};
|
||||
#define EMOTION_MAP_SIZE (sizeof(s_emotion_map) / sizeof(s_emotion_map[0]))
|
||||
// 备注: Speak.eaf(说话) / Listen.eaf(聆听) / Sleep.eaf(睡觉) / Turn_Around.eaf(转身)
|
||||
// 可由对话状态机触发(非情绪标签),后续按需接入
|
||||
|
||||
// ==========================================================
|
||||
// LCD flush 回调 (gfx → esp_lcd_panel_draw_bitmap)
|
||||
@ -197,11 +197,8 @@ static esp_err_t switch_emotion_by_asset(const char *asset_name) {
|
||||
};
|
||||
gfx_anim_set_src_desc(s_anim_obj, &src);
|
||||
|
||||
// 贴底显示, 让 240×320 数字人脚部紧贴屏底
|
||||
// 底部 40px (= 360-320) 给数字人脚部 (跟之前 360×360 EAF 视觉一致)
|
||||
// 顶部 40px 留透明空白 (chroma key 透明, 露出背景图)
|
||||
// 字幕 56 高度在数字人上层 (z-index), 显示时覆盖最下方约 56px (含脚部)
|
||||
gfx_obj_align(s_anim_obj, GFX_ALIGN_BOTTOM_MID, 0, 0);
|
||||
// 上下左右居中显示(无论 240×240 还是 360×360 资源都居中到屏幕中央)
|
||||
gfx_obj_align(s_anim_obj, GFX_ALIGN_CENTER, 0, 0);
|
||||
|
||||
// 全部帧 + EAF_DEFAULT_FPS + 永远循环
|
||||
gfx_anim_set_segment(s_anim_obj, 0, 0xFFFFFFFF, EAF_DEFAULT_FPS, true);
|
||||
@ -260,7 +257,7 @@ void ai_chat_screen_init(void) {
|
||||
// [0x20+] file entry table,每 entry = 28B (16B name + 4B size + 4B offset + 4B pad)
|
||||
// [data] table 后是数据段。每个文件: 2B 0x5A 0x5A magic prefix + size 字节数据。
|
||||
// entry.offset 是相对数据段起点的偏移(指向文件的 magic prefix 起点)
|
||||
ESP_LOGI(TAG, "解析 hiyori-assets.bin:");
|
||||
ESP_LOGI(TAG, "解析 EAF 资源包: %s", EAF_ASSETS_PATH);
|
||||
FILE *f = fopen(EAF_ASSETS_PATH, "rb");
|
||||
if (!f) {
|
||||
ESP_LOGE(TAG, "打开 %s 失败", EAF_ASSETS_PATH);
|
||||
@ -401,43 +398,11 @@ void ai_chat_screen_init(void) {
|
||||
// 4. 设置背景色 = BG_COLOR (0x000000 黑色,与 LVGL 版一致)
|
||||
gfx_disp_set_bg_color(s_disp, GFX_COLOR_HEX(0x000000));
|
||||
|
||||
// 4.5 加载 360x360 静态背景图(JPG → esp_jpeg 解码 → RGB565 → gfx_img)
|
||||
// 必须先于 gfx_anim_create,让背景在最底层(数字人在上层)
|
||||
{
|
||||
esp_jpeg_image_output_t bg_outdata;
|
||||
esp_err_t bg_ret = DecodeImg((char *)EAF_BG_JPG_PATH, &s_bg_rgb565_data, &bg_outdata);
|
||||
if (bg_ret != ESP_OK || !s_bg_rgb565_data) {
|
||||
ESP_LOGW(TAG, "背景图加载失败: %s(继续无背景)", EAF_BG_JPG_PATH);
|
||||
} else {
|
||||
s_bg_image_dsc.header.magic = C_ARRAY_HEADER_MAGIC;
|
||||
s_bg_image_dsc.header.cf = GFX_COLOR_FORMAT_RGB565;
|
||||
s_bg_image_dsc.header.w = bg_outdata.width;
|
||||
s_bg_image_dsc.header.h = bg_outdata.height;
|
||||
s_bg_image_dsc.header.stride = bg_outdata.width * 2;
|
||||
s_bg_image_dsc.data_size = bg_outdata.width * bg_outdata.height * 2;
|
||||
s_bg_image_dsc.data = s_bg_rgb565_data;
|
||||
// 4.5 背景图已禁用(用户要求纯黑背景,不显示 JPG 背景图)
|
||||
// 数字人透明区域通过 chroma key 露出 disp 背景色(黑色 0x000000)
|
||||
// s_bg_img_obj 保持 NULL,DecodeImg / EAF_BG_JPG_PATH 不再调用
|
||||
|
||||
gfx_emote_lock(s_emote_handle);
|
||||
s_bg_img_obj = gfx_img_create(s_disp);
|
||||
if (s_bg_img_obj) {
|
||||
gfx_img_src_t bg_src = {
|
||||
.type = GFX_IMG_SRC_TYPE_IMAGE_DSC,
|
||||
.data = &s_bg_image_dsc,
|
||||
};
|
||||
gfx_img_set_src_desc(s_bg_img_obj, &bg_src);
|
||||
gfx_obj_align(s_bg_img_obj, GFX_ALIGN_CENTER, 0, 0);
|
||||
ESP_LOGI(TAG, "背景图已加载: %dx%d (%u KB RGB565 @ %p)",
|
||||
bg_outdata.width, bg_outdata.height,
|
||||
(unsigned)(bg_outdata.width * bg_outdata.height * 2 / 1024),
|
||||
s_bg_rgb565_data);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "gfx_img_create 失败");
|
||||
}
|
||||
gfx_emote_unlock(s_emote_handle);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 创建动画对象 + 加载默认表情 m06(在背景之后创建 → 显示在背景上层)
|
||||
// 5. 创建动画对象 + 加载默认表情(无背景图,直接显示在黑色背景上)
|
||||
s_anim_obj = gfx_anim_create(s_disp);
|
||||
if (!s_anim_obj) {
|
||||
ESP_LOGE(TAG, "gfx_anim_create 失败");
|
||||
@ -446,8 +411,8 @@ void ai_chat_screen_init(void) {
|
||||
|
||||
s_initialized = true;
|
||||
|
||||
// 默认表情 = neutral → m06
|
||||
switch_emotion_by_asset("hiyori_m06.eaf");
|
||||
// 默认表情 = neutral → Standby_Norm(常态待机)
|
||||
switch_emotion_by_asset("Standby_Norm.eaf");
|
||||
|
||||
// 6. 字幕 label(在 anim 之后创建,确保 z-index 在最上层)
|
||||
gfx_emote_lock(s_emote_handle);
|
||||
@ -494,7 +459,7 @@ void ai_chat_set_emotion(const char* emotion) {
|
||||
if (!emotion || !s_initialized) return;
|
||||
|
||||
// 查映射表
|
||||
const char *asset_name = "hiyori_m06.eaf"; // 默认 fallback
|
||||
const char *asset_name = "Standby_Norm.eaf"; // 默认 fallback(常态待机)
|
||||
for (size_t i = 0; i < EMOTION_MAP_SIZE; i++) {
|
||||
if (strcmp(emotion, s_emotion_map[i].emotion) == 0) {
|
||||
asset_name = s_emotion_map[i].asset_name;
|
||||
|
||||
21
sdkconfig
21
sdkconfig
@ -14,6 +14,7 @@ CONFIG_SOC_GDMA_SUPPORTED=y
|
||||
CONFIG_SOC_AHB_GDMA_SUPPORTED=y
|
||||
CONFIG_SOC_GPTIMER_SUPPORTED=y
|
||||
CONFIG_SOC_LCDCAM_SUPPORTED=y
|
||||
CONFIG_SOC_LCDCAM_CAM_SUPPORTED=y
|
||||
CONFIG_SOC_LCDCAM_I80_LCD_SUPPORTED=y
|
||||
CONFIG_SOC_LCDCAM_RGB_LCD_SUPPORTED=y
|
||||
CONFIG_SOC_MCPWM_SUPPORTED=y
|
||||
@ -101,7 +102,7 @@ CONFIG_SOC_CPU_HAS_FPU=y
|
||||
CONFIG_SOC_HP_CPU_HAS_MULTIPLE_CORES=y
|
||||
CONFIG_SOC_CPU_BREAKPOINTS_NUM=2
|
||||
CONFIG_SOC_CPU_WATCHPOINTS_NUM=2
|
||||
CONFIG_SOC_CPU_WATCHPOINT_MAX_REGION_SIZE=64
|
||||
CONFIG_SOC_CPU_WATCHPOINT_MAX_REGION_SIZE=0x40
|
||||
CONFIG_SOC_SIMD_PREFERRED_DATA_ALIGNMENT=16
|
||||
CONFIG_SOC_DS_SIGNATURE_MAX_BIT_LEN=4096
|
||||
CONFIG_SOC_DS_KEY_PARAM_MD_IV_LENGTH=16
|
||||
@ -208,7 +209,7 @@ CONFIG_SOC_RTCIO_INPUT_OUTPUT_SUPPORTED=y
|
||||
CONFIG_SOC_RTCIO_HOLD_SUPPORTED=y
|
||||
CONFIG_SOC_RTCIO_WAKE_SUPPORTED=y
|
||||
CONFIG_SOC_LP_IO_CLOCK_IS_INDEPENDENT=y
|
||||
CONFIG_SOC_SDM_GROUPS=y
|
||||
CONFIG_SOC_SDM_GROUPS=1
|
||||
CONFIG_SOC_SDM_CHANNELS_PER_GROUP=8
|
||||
CONFIG_SOC_SDM_CLK_SUPPORT_APB=y
|
||||
CONFIG_SOC_SPI_PERIPH_NUM=3
|
||||
@ -369,6 +370,9 @@ CONFIG_SOC_BLE_DEVICE_PRIVACY_SUPPORTED=y
|
||||
CONFIG_SOC_BLUFI_SUPPORTED=y
|
||||
CONFIG_SOC_ULP_HAS_ADC=y
|
||||
CONFIG_SOC_PHY_COMBO_MODULE=y
|
||||
CONFIG_SOC_LCDCAM_CAM_SUPPORT_RGB_YUV_CONV=y
|
||||
CONFIG_SOC_LCDCAM_CAM_PERIPH_NUM=1
|
||||
CONFIG_SOC_LCDCAM_CAM_DATA_WIDTH_MAX=16
|
||||
CONFIG_IDF_CMAKE=y
|
||||
CONFIG_IDF_TOOLCHAIN="gcc"
|
||||
CONFIG_IDF_TOOLCHAIN_GCC=y
|
||||
@ -1039,6 +1043,7 @@ CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
|
||||
# CONFIG_BT_BLE_42_DTM_TEST_EN is not set
|
||||
CONFIG_BT_BLE_42_ADV_EN=y
|
||||
CONFIG_BT_BLE_42_SCAN_EN=y
|
||||
CONFIG_BT_BLE_VENDOR_HCI_EN=y
|
||||
# CONFIG_BT_BLE_HIGH_DUTY_ADV_INTERVAL is not set
|
||||
# CONFIG_BT_ABORT_WHEN_ALLOCATION_FAILS is not set
|
||||
# end of Bluedroid Options
|
||||
@ -1260,6 +1265,7 @@ CONFIG_ESP_TLS_USE_DS_PERIPHERAL=y
|
||||
# CONFIG_ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL is not set
|
||||
# CONFIG_ESP_TLS_PSK_VERIFICATION is not set
|
||||
# CONFIG_ESP_TLS_INSECURE is not set
|
||||
CONFIG_ESP_TLS_DYN_BUF_STRATEGY_SUPPORTED=y
|
||||
# end of ESP-TLS
|
||||
|
||||
#
|
||||
@ -1287,6 +1293,12 @@ CONFIG_ESP_ERR_TO_NAME_LOOKUP=y
|
||||
CONFIG_ESP_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
|
||||
# end of Common ESP-related
|
||||
|
||||
#
|
||||
# ESP-Driver:Camera Controller Configurations
|
||||
#
|
||||
# CONFIG_CAM_CTLR_DVP_CAM_ISR_CACHE_SAFE is not set
|
||||
# end of ESP-Driver:Camera Controller Configurations
|
||||
|
||||
#
|
||||
# ESP-Driver:GPIO Configurations
|
||||
#
|
||||
@ -1604,8 +1616,11 @@ CONFIG_ESP_PHY_RF_CAL_PARTIAL=y
|
||||
# CONFIG_ESP_PHY_RF_CAL_NONE is not set
|
||||
# CONFIG_ESP_PHY_RF_CAL_FULL is not set
|
||||
CONFIG_ESP_PHY_CALIBRATION_MODE=0
|
||||
CONFIG_ESP_PHY_PLL_TRACK_PERIOD_MS=1000
|
||||
# CONFIG_ESP_PHY_PLL_TRACK_DEBUG is not set
|
||||
# CONFIG_ESP_PHY_RECORD_USED_TIME is not set
|
||||
CONFIG_ESP_PHY_IRAM_OPT=y
|
||||
# CONFIG_ESP_PHY_DEBUG is not set
|
||||
# end of PHY
|
||||
|
||||
#
|
||||
@ -2276,6 +2291,7 @@ CONFIG_MBEDTLS_DYNAMIC_BUFFER=y
|
||||
# CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK is not set
|
||||
# CONFIG_MBEDTLS_SSL_CONTEXT_SERIALIZATION is not set
|
||||
# CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE is not set
|
||||
# CONFIG_MBEDTLS_SSL_KEYING_MATERIAL_EXPORT is not set
|
||||
CONFIG_MBEDTLS_PKCS7_C=y
|
||||
# end of mbedTLS v3.x related
|
||||
|
||||
@ -3349,6 +3365,7 @@ CONFIG_BT_NIMBLE_COEX_PHY_CODED_TX_RX_TLIM_DIS=y
|
||||
CONFIG_SW_COEXIST_ENABLE=y
|
||||
CONFIG_ESP32_WIFI_SW_COEXIST_ENABLE=y
|
||||
CONFIG_ESP_WIFI_SW_COEXIST_ENABLE=y
|
||||
# CONFIG_CAM_CTLR_DVP_CAM_ISR_IRAM_SAFE is not set
|
||||
# CONFIG_MCPWM_ISR_IN_IRAM is not set
|
||||
# CONFIG_EVENT_LOOP_PROFILING is not set
|
||||
CONFIG_POST_EVENTS_FROM_ISR=y
|
||||
|
||||
BIN
spiffs_image/Elf-expr.bin
Normal file
BIN
spiffs_image/Elf-expr.bin
Normal file
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user