diff --git a/main/dzbj/ai_chat_ui_eaf.c b/main/dzbj/ai_chat_ui_eaf.c index 5600d24..9ce5fb4 100644 --- a/main/dzbj/ai_chat_ui_eaf.c +++ b/main/dzbj/ai_chat_ui_eaf.c @@ -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); diff --git a/spiffs_image/hiyori-assets.bin b/spiffs_image/hiyori-assets.bin index 5ce20b0..517d07a 100644 Binary files a/spiffs_image/hiyori-assets.bin and b/spiffs_image/hiyori-assets.bin differ diff --git a/spiffs_image/hiyori_m03.gif b/spiffs_image/hiyori_m03.gif deleted file mode 100644 index 7e52e90..0000000 Binary files a/spiffs_image/hiyori_m03.gif and /dev/null differ diff --git a/spiffs_image/hiyori_m06.gif b/spiffs_image/hiyori_m06.gif deleted file mode 100644 index e540595..0000000 Binary files a/spiffs_image/hiyori_m06.gif and /dev/null differ diff --git a/spiffs_image/hiyori_m07.gif b/spiffs_image/hiyori_m07.gif deleted file mode 100644 index 163d206..0000000 Binary files a/spiffs_image/hiyori_m07.gif and /dev/null differ