Rdzleo eb96130fc9 feat(Rtc_AIavatar): 数字人透明 GIF 显示方案 PoC 完成(背景图+透明GIF叠加)
源代码变更:
- main/dzbj/bg_gif_demo.c/h: 方案 C 最终实现 - JPG 背景图(lv_img) + 透明 GIF(lv_gif) 叠加
- main/dzbj/dual_gif_demo.c/h: 方案 B 中间产物 - 双 GIF 循环切换
- main/dzbj/sprite_demo.c/h: 方案 A 已弃用 - DMA 直写 GRAM 与 LVGL 争抢 LCD IO 失败
- main/dzbj/ai_chat_ui.c: 集成 USE_BG_GIF_POC 开关,加载背景图+透明 GIF
- main/dzbj/lcd.c: panel_handle 移除 static,便于其他模块访问
- main/CMakeLists.txt: 新增 3 个 dzbj 模块编译

资源新增:
- spiffs_image/Background_360x360.jpg: 设备背景图(20KB)
- spiffs_image/hiyori_m05.gif: Cubism Editor 直接导出的透明 GIF(2.3MB)
- docs/Rtc_AIavatar/: Live2D 模型(Hiyori/Haru) + 32 段 Haru GIF + 方案文档第18章 PoC 实战记录
- tools/sprite_poc/: Python GIF→RGB565 转换脚本

踩坑要点(详见 docs/Rtc_AIavatar 第18章):
- PIL Image.quantize() 会破坏 RGBA 透明度,必须改用 gifsicle
- PIL 保存动画 GIF 仅第1帧有透明,后续帧不透明 - LVGL gifdec 按帧读取
- Cubism Editor 直接导出 GIF 才能逐帧保留透明信息(FREE 版限制部分模型)
- gifsicle --lossy 会严重锯齿化,去掉只保留 --colors 256 + -O3 即可
- 裁剪居中需用全帧 bbox 不能只看第1帧(Live2D 角色每帧位置有偏移)
- LVGL 默认不支持 PNG,背景图用 JPG + esp_jpeg 解码到 RGB565 buffer
- 透明 GIF 显示黑色背景: gifdec.c canvas 初始化 alpha 须改为 0x00
2026-05-12 17:14:49 +08:00

175 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Sprite Sheet PoC 操作步骤
> 目标:把一张 GIF 转换为 RGB565 raw → 烧到 SPIFFS → AI 对话模式开机播放
> 不涉及 BLE/APP/云端,最快验证整条数据链路
---
## 前置准备
- Python 3 + Pillow`pip install Pillow`
- ESP-IDF v5.4.2 环境就绪
---
## Step 1准备 GIF 文件
把你的 GIF 放到本目录:
```bash
cp <你的GIF路径> /Users/rdzleo/Desktop/Baji_Rtc_Toy/tools/sprite_poc/input.gif
```
---
## Step 2转换为 RGB565 raw .bin
```bash
cd /Users/rdzleo/Desktop/Baji_Rtc_Toy/tools/sprite_poc
# 默认 200x200
python3 gif_to_rgb565.py input.gif sprite_test.bin
# 或自定义尺寸(与 AI 对话默认 emotion 一致)
python3 gif_to_rgb565.py input.gif sprite_test.bin 200 89
```
**输出示例**
```
原始 GIF: (240, 240), 帧数估计 6
帧 0: duration=100ms
帧 1: duration=100ms
...
转换信息:
目标尺寸: 200x200
帧数: 6
平均 FPS: 10
单帧大小: 80.0 KB
帧数据总大小: 480.0 KB
✓ 输出: sprite_test.bin
总大小: 480.1 KB (491600 bytes)
```
**注意**
- 单帧不能超过 LCD 分辨率360×360 = 253KB
- 总大小不要超过 SPIFFS 剩余空间(约 3MB - 已用图片)
---
## Step 3放入 SPIFFS 资源目录
```bash
cp sprite_test.bin /Users/rdzleo/Desktop/Baji_Rtc_Toy/spiffs_image/
```
编译时会自动烧到 storage 分区(看 `CMakeLists.txt:21`
```cmake
spiffs_create_partition_image(storage spiffs_image FLASH_IN_PROJECT)
```
---
## Step 4把 sprite_demo.c 加入构建
编辑 `main/CMakeLists.txt`,在 SRCS 列表中加入:
```cmake
"dzbj/sprite_demo.c"
```
(如果项目用 GLOB 自动收集 dzbj/*.c 则跳过此步)
---
## Step 5在 AI 对话模式集成 PoC 调用
最简单的方式:在 `ai_chat_screen_init()` 末尾添加调用。
打开 `main/dzbj/ai_chat_ui.c`在函数末尾return 前)添加:
```c
#include "sprite_demo.h"
void ai_chat_screen_init(void) {
// ... 原有 LVGL UI 创建代码 ...
// === PoC替换默认 GIF 显示为 sprite sheet ===
// 隐藏原有 GIF 对象
if (gif_emotion) {
lv_obj_add_flag(gif_emotion, LV_OBJ_FLAG_HIDDEN);
}
// 启动 sprite demo异步加载完自动播放
sprite_demo_start("/spiflash/sprite_test.bin");
}
```
> 注意sprite_demo 直接调用 esp_lcd_panel_draw_bitmap 写 LCD GRAM
> 会覆盖 LVGL 渲染的内容。所以要先把原 LVGL GIF 对象隐藏。
---
## Step 6编译烧录
```bash
cd /Users/rdzleo/Desktop/Baji_Rtc_Toy
source ~/esp/esp-idf/v5.4.2/esp-idf/export.sh
# 完整编译(含 SPIFFS 镜像生成)
idf.py build
# 烧录(包含 storage 分区,确保新 .bin 被写入)
idf.py -p /dev/tty.usbmodem834401 flash monitor
```
---
## Step 7观察效果
**串口日志应该看到**
```
I (3500) SPRITE_DEMO: Sprite Pack: 200x200, 总帧数=6, 大小=491520 B
I (3501) SPRITE_DEMO: Entry: "test", 6 帧 @ 10 FPS
I (3520) SPRITE_DEMO: 已加载 480.0 KB 到 PSRAM
I (3521) SPRITE_DEMO: ✓ 开始播放 @ 10 FPS (interval=100000 us)
```
**LCD 屏幕**:进入 AI 对话模式后,原 GIF 表情位置显示你的 sprite 动画循环播放。
---
## 验证清单
进入 AI 对话模式后,请重点观察:
- [ ] **画面正确**sprite 颜色是否正常(如果偏色 → 字节序问题,调整 Python 的 `'>H'` 大端为 `'<H'` 小端)
- [ ] **动画流畅**:帧间过渡是否平滑(如果卡顿 → 检查 esp_timer 是否被阻塞)
- [ ] **CPU 占用**:用 `esp_get_free_heap_size` 或 idle hook 测量。预期 sprite 显示侧 <5% CPUvs GIF 方案 ~30%
- [ ] **音频共存** RTC 对话同时跑音频是否流畅这是关键验证点
- [ ] **PSRAM 占用**日志中应该看到 ~500KB 分配esp_get_free_heap_size 应有 7MB+ 剩余
---
## 故障排查
| 现象 | 可能原因 | 解决 |
|------|---------|------|
| `magic 不匹配` | .bin 没烧到 SPIFFS | 重新烧录确认 `spiffs_create_partition_image` 生效 |
| `PSRAM 分配失败` | 帧太大 | 缩小尺寸到 200×200 或更小 |
| 画面偏色 | RGB565 字节序错 | Python 脚本改为 `'<H'` 小端 |
| 画面错位 | 字节序混合 | 检查 LCD 是否需要 swap_color_bytes |
| 闪烁 | LVGL 同时在刷该区域 | 永久隐藏原 GIF 对象 |
| 音频卡顿 | sprite 任务优先级太高 | esp_timer 优先级降低或检查 PSRAM 争抢 |
---
## 下一步
PoC 通过后
1. **扩展到多情绪**Python 脚本支持多目录输入生成多情绪 sprite pack
2. **接入 RTC 信令** `application.cc` OnBotMessage 中根据 emotion 切换帧索引
3. **方案 D 升级** SPIFFS 改为专用 Flash 分区 + mmap释放 PSRAM
4. **BLE 接收** APP 通过 BLE 下发新 sprite pack替换烧录方式
详见 `docs/数字人表情渲染方案_云端预渲染+BLE+OTA.md` 第十三章