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
..

Sprite Sheet PoC 操作步骤

目标:把一张 GIF 转换为 RGB565 raw → 烧到 SPIFFS → AI 对话模式开机播放 不涉及 BLE/APP/云端,最快验证整条数据链路


前置准备

  • Python 3 + Pillowpip install Pillow
  • ESP-IDF v5.4.2 环境就绪

Step 1准备 GIF 文件

把你的 GIF 放到本目录:

cp <你的GIF路径> /Users/rdzleo/Desktop/Baji_Rtc_Toy/tools/sprite_poc/input.gif

Step 2转换为 RGB565 raw .bin

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 资源目录

cp sprite_test.bin /Users/rdzleo/Desktop/Baji_Rtc_Toy/spiffs_image/

编译时会自动烧到 storage 分区(看 CMakeLists.txt:21

spiffs_create_partition_image(storage spiffs_image FLASH_IN_PROJECT)

Step 4把 sprite_demo.c 加入构建

编辑 main/CMakeLists.txt,在 SRCS 列表中加入:

"dzbj/sprite_demo.c"

(如果项目用 GLOB 自动收集 dzbj/*.c 则跳过此步)


Step 5在 AI 对话模式集成 PoC 调用

最简单的方式:在 ai_chat_screen_init() 末尾添加调用。

打开 main/dzbj/ai_chat_ui.c在函数末尾return 前)添加:

#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编译烧录

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 第十三章。