Rdzleo 497c1b4654 feat(rtc-only): Phase 4 - 情绪标签 → 数字人 hiyori GIF 映射 + 切换接口
按 GSD 框架 .planning/milestones/digital_human_rtc/phases/phase_04_emotion_mapping/
规划完成 Phase 4 情绪映射 + 运行时切换。

## 核心变更

### bg_gif_demo 新增运行时切换接口

```c
esp_err_t bg_gif_demo_switch_gif(const char *new_gif_path);
```

实现要点:
- 先 heap_caps_free(g_gif_data) 释放旧 PSRAM,再加载新 GIF,单峰仅一份
- 内部 static last_gif_path 去重(相同路径直接返回 ESP_OK)
- 切换后立即 lv_timer_set_period(timer, 20) 防止 lv_gif_set_src 重建为默认 10ms
- LVGL 锁保护 200ms 超时

### 22 情绪 → 3 hiyori GIF 映射表

定义在 main/dzbj/ai_chat_ui.c USE_BG_GIF_POC 包裹内:

| GIF | 情绪标签 (个数) |
|-----|----------------|
| m06 (默认/积极) | neutral, happy, laughing, funny, loving, relaxed, delicious, kissy, confident, silly, blink, curious (12) |
| m07 (思考/疲倦) | sleepy, thinking, confused, embarrassed, dizzy (5) |
| m03 (负面/严肃) | sad, crying, angry, surprised, shocked (5) |

22 个标准情绪 100% 覆盖,未映射的情绪默认 fallback 到 m06。

### ai_chat_set_emotion 改造

PoC 模式下优先切换数字人大图(不再切隐藏的 emoji 200x89):

```c
#ifdef USE_BG_GIF_POC
if (bg_gif_demo_is_running()) {
    const char *path = find_hiyori_gif(emotion);
    bg_gif_demo_switch_gif(path);
    return;
}
#endif
// 非 PoC 模式 fallback emoji 路径保留
```

## 调用链(已与现有 RTC 字幕解析对接,无需改 application.cc)

RTC 字幕 → application.cc:1419 display->SetEmotion(emo)
  → AiChatDisplay::SetEmotion (display/ai_chat_display.cc)
  → ai_chat_set_emotion (dzbj/ai_chat_ui.c)
  → bg_gif_demo_switch_gif (PoC 模式)
  → 数字人 hiyori GIF 切换

## 烧录验证(用户实测)

监控 60s 期间捕获情绪切换:
- neutral / happy → m06(已在播,去重)
- thinking → m07 切换成功 (590ms 延迟)
- confused → m07 去重跳过
- AI 回复结束 → 自动回到 neutral
- 无 abort / 重启

m06 ↔ m07 切换屏幕可视确认,m03 走相同代码路径无需重复测试。

## GSD 文档(同时提交)

- .planning/milestones/digital_human_rtc/phases/phase_04_emotion_mapping/PLAN.md
- .planning/milestones/digital_human_rtc/phases/phase_04_emotion_mapping/EMOTION_REPORT.md
2026-05-13 11:59:38 +08:00
..
2026-02-24 15:57:32 +08:00
2026-02-24 15:28:34 +08:00
2026-02-24 15:28:34 +08:00
2026-02-24 15:28:34 +08:00