Baji_Rtc_Toy/main/dzbj/bg_gif_demo.c
Rdzleo 4b7b1949d4 perf(rtc-only): Phase 6 收尾 - 卡顿优化 + PowerSaveTimer 守卫 + 开机加速
代码改动:
- AudioLoop 加 vTaskDelay(1),让出 Core 1 idle task 防 WiFi/RTC 饥饿
- BackgroundTask 优先级 2 → 5,提升 Opus 解码实时性
- LVGL 刷新 5ms → 16ms (60Hz),CPU 占用降 60%
- GIF 定时器 20ms → 33ms (3 处),PSRAM 流量减半
- AI 字幕推送 100ms 节流,避免 LVGL 锁争抢
- EnterIdleHibernate 清空 audio_decode_queue_,防 standby_sound 残留误触发首帧
- PowerSaveTimer OnEnterSleepMode 加 device_state 守卫,拦截 dialog/connecting
  期间关功放(修复欢迎语期间被静音 bug)
- 取消开机 ADC 阻塞采样,开机播报响应从 6 秒缩到 < 3 秒

新增规划:
- Phase 7 占位文档:电量保护 + PowerSaveTimer 重构 + 唤醒杂音根治 + RTC 抖动缓解

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 11:38:48 +08:00

228 lines
6.5 KiB
C
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.

/**
* bg_gif_demo.c — 背景图 + 透明 GIF 叠加(方案 C 实现)
*/
#include "bg_gif_demo.h"
#include "fatfs.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
#include "esp_lvgl_port.h"
#include "esp_spiffs.h"
#include "lvgl.h"
#include "jpeg_decoder.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static const char *TAG = "BG_GIF";
// 运行时状态
static lv_obj_t *g_bg_img = NULL;
static lv_obj_t *g_gif_obj = NULL;
static uint8_t *g_bg_data = NULL; // 背景图解码后的 RGB565 buffer (PSRAM)
static uint8_t *g_gif_data = NULL; // GIF 文件二进制 (PSRAM)
static lv_img_dsc_t g_bg_dsc;
static lv_img_dsc_t g_gif_dsc;
static bool g_running = false;
// 确保 SPIFFS 已挂载
static esp_err_t ensure_spiffs_mounted(void) {
size_t total = 0, used = 0;
if (esp_spiffs_info("storage", &total, &used) == ESP_OK) {
return ESP_OK;
}
ESP_LOGI(TAG, "SPIFFS 未挂载,自动挂载...");
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiflash",
.partition_label = "storage",
.max_files = 5,
.format_if_mount_failed = false,
};
return esp_vfs_spiffs_register(&conf);
}
// 加载 GIF 文件到 PSRAM
static esp_err_t load_gif_to_psram(const char *path) {
FILE *f = fopen(path, "rb");
if (!f) {
ESP_LOGE(TAG, "GIF 打开失败: %s", path);
return ESP_FAIL;
}
fseek(f, 0, SEEK_END);
size_t sz = ftell(f);
fseek(f, 0, SEEK_SET);
g_gif_data = heap_caps_malloc(sz, MALLOC_CAP_SPIRAM);
if (!g_gif_data) {
ESP_LOGE(TAG, "GIF PSRAM 分配失败: %d bytes", (int)sz);
fclose(f);
return ESP_ERR_NO_MEM;
}
if (fread(g_gif_data, 1, sz, f) != sz) {
heap_caps_free(g_gif_data);
g_gif_data = NULL;
fclose(f);
return ESP_FAIL;
}
fclose(f);
// 构造 GIF 的 lv_img_dsc_tCF_RAW由 LVGL gifdec 解码)
memset(&g_gif_dsc, 0, sizeof(g_gif_dsc));
g_gif_dsc.header.cf = LV_IMG_CF_RAW;
g_gif_dsc.data_size = sz;
g_gif_dsc.data = g_gif_data;
ESP_LOGI(TAG, "GIF 已加载到 PSRAM: %s (%.1f KB)", path, sz / 1024.0);
return ESP_OK;
}
// 加载并解码背景 JPG 到 RGB565 buffer
static esp_err_t load_bg_jpg(const char *path) {
esp_jpeg_image_output_t outdata;
esp_err_t ret = DecodeImg((char *)path, &g_bg_data, &outdata);
if (ret != ESP_OK || !g_bg_data) {
ESP_LOGE(TAG, "背景图解码失败: %s, err=%s", path, esp_err_to_name(ret));
return ESP_FAIL;
}
// 构造 lv_img_dsc_tTRUE_COLOR已解码 RGB565
memset(&g_bg_dsc, 0, sizeof(g_bg_dsc));
g_bg_dsc.header.cf = LV_IMG_CF_TRUE_COLOR;
g_bg_dsc.header.w = outdata.width;
g_bg_dsc.header.h = outdata.height;
g_bg_dsc.data_size = outdata.width * outdata.height * 2;
g_bg_dsc.data = g_bg_data;
ESP_LOGI(TAG, "背景图已解码: %dx%d (%.1f KB RGB565)",
outdata.width, outdata.height, g_bg_dsc.data_size / 1024.0);
return ESP_OK;
}
esp_err_t bg_gif_demo_start(const char *bg_jpg_path, const char *gif_path) {
if (g_running) {
ESP_LOGW(TAG, "已在运行,先 stop");
return ESP_ERR_INVALID_STATE;
}
if (ensure_spiffs_mounted() != ESP_OK) {
ESP_LOGE(TAG, "SPIFFS 挂载失败");
return ESP_FAIL;
}
// 1. 加载背景图(一次性,常驻)
if (load_bg_jpg(bg_jpg_path) != ESP_OK) {
return ESP_FAIL;
}
// 2. 加载 GIF
if (load_gif_to_psram(gif_path) != ESP_OK) {
free(g_bg_data);
g_bg_data = NULL;
return ESP_FAIL;
}
// 3. 在 LVGL 任务中创建图层
if (!lvgl_port_lock(200)) {
ESP_LOGE(TAG, "lvgl_port_lock 失败");
return ESP_FAIL;
}
// 底层背景图lv_img
g_bg_img = lv_img_create(lv_scr_act());
lv_img_set_src(g_bg_img, &g_bg_dsc);
lv_obj_align(g_bg_img, LV_ALIGN_CENTER, 0, 0);
// 上层:透明 GIFlv_gif自动叠加在 bg_img 之上)
g_gif_obj = lv_gif_create(lv_scr_act());
lv_gif_set_src(g_gif_obj, &g_gif_dsc);
// 240×360 GIF 在 360×360 屏幕中居中显示
lv_obj_align(g_gif_obj, LV_ALIGN_CENTER, 0, 0);
lvgl_port_unlock();
g_running = true;
ESP_LOGI(TAG, "✓ 背景 + GIF 叠加显示启动");
return ESP_OK;
}
void bg_gif_demo_stop(void) {
if (!g_running) return;
g_running = false;
if (lvgl_port_lock(200)) {
if (g_gif_obj) {
lv_obj_del(g_gif_obj);
g_gif_obj = NULL;
}
if (g_bg_img) {
lv_obj_del(g_bg_img);
g_bg_img = NULL;
}
lvgl_port_unlock();
}
if (g_bg_data) {
free(g_bg_data);
g_bg_data = NULL;
}
if (g_gif_data) {
heap_caps_free(g_gif_data);
g_gif_data = NULL;
}
ESP_LOGI(TAG, "已停止并释放资源");
}
bool bg_gif_demo_is_running(void) {
return g_running;
}
esp_err_t bg_gif_demo_switch_gif(const char *new_gif_path) {
if (!g_running) {
ESP_LOGW(TAG, "bg_gif_demo 未启动,无法切换");
return ESP_ERR_INVALID_STATE;
}
if (!new_gif_path) {
return ESP_FAIL;
}
// 去重:同路径重复调用无副作用
static char last_gif_path[64] = {0};
if (strcmp(new_gif_path, last_gif_path) == 0) {
return ESP_OK;
}
if (!lvgl_port_lock(200)) {
ESP_LOGE(TAG, "switch_gif: lvgl_port_lock 失败");
return ESP_FAIL;
}
// 1. 先释放旧 GIF PSRAM确保切换期间峰值只占用一份
if (g_gif_data) {
heap_caps_free(g_gif_data);
g_gif_data = NULL;
}
// 2. 加载新 GIF复用 load_gif_to_psram 逻辑)
if (load_gif_to_psram(new_gif_path) != ESP_OK) {
lvgl_port_unlock();
ESP_LOGE(TAG, "switch_gif: 加载失败 %s", new_gif_path);
return ESP_FAIL;
}
// 3. 更新 LVGLlv_gif_set_src 内部会重建解码器 + 重启定时器)
lv_gif_set_src(g_gif_obj, &g_gif_dsc);
// 4. set_src 内部会重建 10ms 定时器,重设为 20ms 降低 CPU 占用
// CLAUDE.md "lv_gif_set_src 会重建定时器" 经验)
lv_gif_t *gifobj = (lv_gif_t *)g_gif_obj;
if (gifobj->timer) {
lv_timer_set_period(gifobj->timer, 33); // 卡顿优化 3: 20ms→33ms 减半 PSRAM 流量
}
lvgl_port_unlock();
strncpy(last_gif_path, new_gif_path, sizeof(last_gif_path) - 1);
last_gif_path[sizeof(last_gif_path) - 1] = '\0';
ESP_LOGI(TAG, "✓ 切换 GIF: %s", new_gif_path);
return ESP_OK;
}