Rdzleo f2be9922b6 feat(rtc-only): Phase 5 - RTC 字幕显示恢复(透明背景 + 2 行黑字 + 锁优化)
按 GSD 框架 .planning/milestones/digital_human_rtc/phases/phase_05_subtitle_restore/
规划完成 Phase 5 字幕显示恢复。

## 核心变更(main/dzbj/ai_chat_ui.c)

### 1. chat_container 重构

- 新增 static chat_container 变量(lv_obj 父容器)
- 尺寸 320×56(= 2 行字 + padding 4px*2)
- 位置 LV_ALIGN_BOTTOM_MID 距底 10px
- 完全透明背景(LV_OPA_TRANSP),无灰底
- 初始 HIDDEN,有内容时显示

### 2. chat_label 改造

- 黑色文本(用户反馈白字在浅色背景上不清晰)
- 尺寸 312×48 限制最多 2 行
- LV_LABEL_LONG_WRAP → LV_LABEL_LONG_DOT,超出 2 行自动 ... 截断
- font_puhui_20_4 中文字体不变

### 3. ai_chat_set_chat_message() 实现

原为空函数(PoC 期间 return),本 Phase 完整实现:
- 锁外去重:static last_content[256],相同内容直接返回
- lvgl_port_lock 200ms → 500ms(GIF 解码繁忙时给予更长等待)
- 内容空时隐藏容器,非空显示
- 成功更新后缓存 last_content

### 4. z-index 修复

bg_gif_demo_start() 后立即 lv_obj_move_foreground(chat_container)
否则 bg_img/gif_obj 后于 chat_container 创建会遮挡字幕

## 实测验证(用户协作)

60s 对话期间:
-  AI 字幕完整推送 3 次(含 54 字符长字幕)
-  LVGL 锁超时 14 次 → 0 次(锁外去重生效)
-  表情切换 + 字幕同步工作
-  长字幕自动 2 行截断
-  无 abort/重启

## 调用链(已对接 application.cc 现有逻辑,无需改协议层)

RTC 字幕 → display->SetChatMessage(role, msg)
  → AiChatDisplay::SetChatMessage
  → ai_chat_set_chat_message() ← 本 Phase 实现

## GSD 文档

- PLAN.md
- SUBTITLE_REPORT.md(含锁优化对比 + 布局规划 + 用户决策记录)
2026-05-13 13:35:41 +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