feat(ui): 8 张 EAF 数字人完整接入 (m01~m08 全情绪映射)
数字人从原本 m06+m07 两张 EAF 扩展到 m01~m08 八张, 通过 ezgif 240×320 +
抽帧 N=3 + EAF Packer 配置, 8 张 EAF 总和压缩到 4.32 MB, SPIFFS 当前
4.94 MB 分区直接装下, 无需扩分区也不丢 OTA 升级能力.
main/dzbj/ai_chat_ui_eaf.c:
- 新版/旧版 MMAP 自动检测 (header[8] == 0x14 → entry 32B; 0x10 → 28B),
兼容在线 EAF Packer 两种导出格式 (FSIZE/FOFFSET 偏移自动适配).
- find_cache_index_by_name 加 fallback: 找不到精确匹配时返回 cache[0],
PoC 阶段单张 EAF 也能验证全部情绪触发.
- emotion_map 22 情绪 → 8 张 EAF 重排:
* 默认/积极组 12 → m01..m05 均分 (每张 2-3 种)
* 思考/疲倦组 5 → m06
* 负面/严肃组 3 → m07
* 惊讶组 2 → m08
- 数字人对齐 GFX_ALIGN_CENTER → GFX_ALIGN_BOTTOM_MID, 240×320 在
360×360 圆屏贴底显示, 顶部 40px 透明露出背景图, 视觉跟之前
360×360 全屏 EAF 一致 (脚部贴底, 字幕 z-index 上层覆盖底部 56px).
spiffs_image:
- hiyori-assets.bin: 956 KB (m06+m07) → 4.53 MB (m01~m08 + index.json)
- 删除原 GIF (hiyori_m{03,06,07}.gif), EAF 已替代不需要烧到设备.
实测数据 (Baji 2026-05-20):
m01=814KB m02=516KB m03=563KB m04=559KB m05=606KB m06=423KB m07=379KB m08=566KB
8 张 EAF 总和: 4.32 MB
SPIFFS 占用: 4.58 MB / 4.64 MB 可用 = 1.3% 余量 (临界, 未来加资源需要规划)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c6ecdb124c
commit
be788be251
@ -96,31 +96,35 @@ typedef struct {
|
||||
} eaf_emotion_map_t;
|
||||
|
||||
static const eaf_emotion_map_t s_emotion_map[] = {
|
||||
// 默认/积极 → m06
|
||||
{"neutral", "hiyori_m06.eaf"},
|
||||
{"happy", "hiyori_m06.eaf"},
|
||||
{"laughing", "hiyori_m06.eaf"},
|
||||
{"funny", "hiyori_m06.eaf"},
|
||||
{"loving", "hiyori_m06.eaf"},
|
||||
{"relaxed", "hiyori_m06.eaf"},
|
||||
{"delicious", "hiyori_m06.eaf"},
|
||||
{"kissy", "hiyori_m06.eaf"},
|
||||
{"confident", "hiyori_m06.eaf"},
|
||||
{"silly", "hiyori_m06.eaf"},
|
||||
{"blink", "hiyori_m06.eaf"},
|
||||
{"curious", "hiyori_m06.eaf"},
|
||||
// 思考/疲倦 → m07
|
||||
{"sleepy", "hiyori_m07.eaf"},
|
||||
{"thinking", "hiyori_m07.eaf"},
|
||||
{"confused", "hiyori_m07.eaf"},
|
||||
{"embarrassed", "hiyori_m07.eaf"},
|
||||
{"dizzy", "hiyori_m07.eaf"},
|
||||
// 负面/严肃 → 暂用 m07(m03 未导入)
|
||||
// 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"},
|
||||
{"surprised", "hiyori_m07.eaf"},
|
||||
{"shocked", "hiyori_m07.eaf"},
|
||||
// 惊讶组 (2) → m08
|
||||
{"surprised", "hiyori_m08.eaf"},
|
||||
{"shocked", "hiyori_m08.eaf"},
|
||||
};
|
||||
#define EMOTION_MAP_SIZE (sizeof(s_emotion_map) / sizeof(s_emotion_map[0]))
|
||||
|
||||
@ -153,6 +157,13 @@ static int find_cache_index_by_name(const char *name) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// Fallback: 找不到精确匹配时 (例如 PoC 阶段 bin 里只有一张表情但代码映射到 m06/m07),
|
||||
// 用 cache 里第一个可用 EAF 替代, 保证设备能显示动画 (验证阶段方便用)
|
||||
// 注: 实际产品阶段所有情绪都该有对应 EAF, 这条 fallback 仅 PoC 兜底
|
||||
if (s_eaf_cache_count > 0 && s_eaf_cache[0].data) {
|
||||
ESP_LOGW(TAG, "Asset %s 不在 cache, fallback 到 %s", name, s_eaf_cache[0].name);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -186,8 +197,11 @@ static esp_err_t switch_emotion_by_asset(const char *asset_name) {
|
||||
};
|
||||
gfx_anim_set_src_desc(s_anim_obj, &src);
|
||||
|
||||
// 居中显示,hiyori 209×360 居中放 360×360 屏
|
||||
gfx_obj_align(s_anim_obj, GFX_ALIGN_CENTER, 0, 0);
|
||||
// 贴底显示, 让 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);
|
||||
|
||||
// 全部帧 + EAF_DEFAULT_FPS + 永远循环
|
||||
gfx_anim_set_segment(s_anim_obj, 0, 0xFFFFFFFF, EAF_DEFAULT_FPS, true);
|
||||
@ -260,14 +274,22 @@ void ai_chat_screen_init(void) {
|
||||
}
|
||||
uint32_t file_count = header[12] | (header[13] << 8) | (header[14] << 16) | (header[15] << 24);
|
||||
ESP_LOGI(TAG, " MMAP file_count=%u", (unsigned)file_count);
|
||||
// 自动适配新旧两版 EAF Packer 的 MMAP 格式 (2026-05-20 新版 Packer entry 变 32B)
|
||||
// 旧版: header[8] = 0x10, entry 28B = name(16) + size(4) + offset(4) + pad(4)
|
||||
// 新版: header[8] = 0x14, entry 32B = name(16) + reserved(4) + size(4) + offset(4) + pad(4)
|
||||
const bool is_new_mmap = (header[8] == 0x14);
|
||||
const size_t ENTRY_SIZE = is_new_mmap ? 32 : 28;
|
||||
const size_t FSIZE_OFFSET = is_new_mmap ? 20 : 16;
|
||||
const size_t FOFFSET_OFFSET = is_new_mmap ? 24 : 20;
|
||||
ESP_LOGI(TAG, " MMAP 版本=%s, ENTRY_SIZE=%zu",
|
||||
is_new_mmap ? "新版(0x14)" : "旧版(0x10)", ENTRY_SIZE);
|
||||
// 跳过 reserved 16B 到 entry table 起点 (0x20)
|
||||
fseek(f, 0x20, SEEK_SET);
|
||||
const size_t ENTRY_SIZE = 28; // 16 + 4 + 4 + 4
|
||||
const size_t DATA_START = 0x20 + file_count * ENTRY_SIZE;
|
||||
|
||||
s_eaf_cache_count = 0;
|
||||
for (uint32_t i = 0; i < file_count; i++) {
|
||||
uint8_t entry[28];
|
||||
uint8_t entry[32]; // 按最大 entry 大小分配, 兼容旧/新版
|
||||
fseek(f, 0x20 + i * ENTRY_SIZE, SEEK_SET);
|
||||
if (fread(entry, 1, ENTRY_SIZE, f) != ENTRY_SIZE) {
|
||||
ESP_LOGE(TAG, " entry[%u] 读取失败", (unsigned)i);
|
||||
@ -276,8 +298,14 @@ void ai_chat_screen_init(void) {
|
||||
char name[17] = {0};
|
||||
memcpy(name, entry, 16);
|
||||
name[16] = '\0';
|
||||
uint32_t fsize = entry[16] | (entry[17] << 8) | (entry[18] << 16) | (entry[19] << 24);
|
||||
uint32_t foffset = entry[20] | (entry[21] << 8) | (entry[22] << 16) | (entry[23] << 24);
|
||||
uint32_t fsize = entry[FSIZE_OFFSET]
|
||||
| (entry[FSIZE_OFFSET + 1] << 8)
|
||||
| (entry[FSIZE_OFFSET + 2] << 16)
|
||||
| (entry[FSIZE_OFFSET + 3] << 24);
|
||||
uint32_t foffset = entry[FOFFSET_OFFSET]
|
||||
| (entry[FOFFSET_OFFSET + 1] << 8)
|
||||
| (entry[FOFFSET_OFFSET + 2] << 16)
|
||||
| (entry[FOFFSET_OFFSET + 3] << 24);
|
||||
|
||||
// 只缓存 .eaf 文件
|
||||
size_t nlen = strlen(name);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 442 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 398 KiB |
Loading…
x
Reference in New Issue
Block a user