feat(assets): embed LCD and audio resources in OTA app

This commit is contained in:
chenjintao
2026-10-08 16:58:33 +08:00
parent 3160a33f50
commit 7bc9a3dc76
43 changed files with 443 additions and 243 deletions
+10 -60
View File
@@ -7,15 +7,13 @@
#include "lcd_direct.h"
#include "lcd_direct_assets.h"
#include "platform_asset_bundle.h"
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "driver/gpio.h"
#include "driver/ledc.h"
@@ -55,7 +53,7 @@
#define LCD_DIRECT_PREHEAT_WAIT_TIMEOUT_MS 5000U
/*
* PNG 缓存固定6项并使用 LRU 淘汰,限制 PSRAM 峰值;边界测试恰好覆盖一次完整
* 缓存命中周期。路径上限同时约束缓存键和 LittleFS 资源路径复制。
* 缓存命中周期。名称上限同时约束缓存键和嵌入式素材索引。
*/
#define LCD_DIRECT_FRAME_CACHE_MAX 6U
#define LCD_DIRECT_BOUNDARY_TEST_CACHE_HIT_FRAMES LCD_DIRECT_FRAME_CACHE_MAX
@@ -316,55 +314,6 @@ static uint16_t lcd_direct_blend_rgb565(uint16_t dst, uint16_t src, uint8_t alph
return lcd_direct_rgb888_to_rgb565(out_r, out_g, out_b);
}
static esp_err_t lcd_direct_read_file_psram(const char *path, unsigned char **out_data, size_t *out_size)
{
/* 整个 PNG 文件优先读入 PSRAM;任何短读都视为失败并关闭句柄。 */
ESP_RETURN_ON_FALSE(path != NULL && out_data != NULL && out_size != NULL,
ESP_ERR_INVALID_ARG,
TAG,
"invalid PNG read request");
*out_data = NULL;
*out_size = 0;
errno = 0;
int fd = open(path, O_RDONLY);
if (fd < 0) {
ESP_LOGE(TAG, "open PNG asset failed path=%s errno=%d", path, errno);
return ESP_ERR_NOT_FOUND;
}
off_t file_size = lseek(fd, 0, SEEK_END);
if (file_size <= 0 || lseek(fd, 0, SEEK_SET) < 0) {
ESP_LOGE(TAG, "stat PNG asset failed path=%s errno=%d", path, errno);
close(fd);
return ESP_FAIL;
}
unsigned char *data = (unsigned char *)lcd_direct_alloc_psram((size_t)file_size);
if (data == NULL) {
ESP_LOGE(TAG, "allocate PNG file buffer failed path=%s size=%u", path, (unsigned)file_size);
close(fd);
return ESP_ERR_NO_MEM;
}
size_t offset = 0;
while (offset < (size_t)file_size) {
ssize_t bytes_read = read(fd, data + offset, (size_t)file_size - offset);
if (bytes_read <= 0) {
ESP_LOGE(TAG, "read PNG asset failed path=%s errno=%d", path, errno);
free(data);
close(fd);
return ESP_FAIL;
}
offset += (size_t)bytes_read;
}
close(fd);
*out_data = data;
*out_size = (size_t)file_size;
return ESP_OK;
}
static void lcd_direct_release_decoded_image(lcd_direct_decoded_image_t *decoded)
{
if (decoded == NULL) {
@@ -386,18 +335,19 @@ static esp_err_t lcd_direct_decode_png(const char *path,
"invalid PNG decode request");
memset(out_decoded, 0, sizeof(*out_decoded));
unsigned char *png_data = NULL;
size_t png_size = 0;
esp_err_t read_err = lcd_direct_read_file_psram(path, &png_data, &png_size);
if (read_err != ESP_OK) {
return read_err;
/* PNG 字节直接取自应用镜像的只读映射区;解码器只读取输入,不持有或释放该指针。 */
platform_asset_view_t asset = {0};
esp_err_t find_err = platform_asset_find(path, &asset);
if (find_err != ESP_OK) {
ESP_LOGE(TAG, "find PNG asset failed path=%s err=%s", path, esp_err_to_name(find_err));
return find_err;
}
unsigned char *rgba = NULL;
unsigned width = 0;
unsigned height = 0;
unsigned decode_err = lodepng_esp_decode32(&rgba, &width, &height, png_data, png_size);
free(png_data);
unsigned decode_err = lodepng_esp_decode32(&rgba, &width, &height,
asset.data, asset.size);
if (decode_err != 0U) {
ESP_LOGE(TAG, "decode PNG failed path=%s error=%u text=%s",
path,