Skip to content

Commit a7e445c

Browse files
committed
Image Player Hot Cacher
1 parent 3a9467f commit a7e445c

File tree

5 files changed

+296
-106
lines changed

5 files changed

+296
-106
lines changed

wled00/FX.cpp

Lines changed: 27 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "driver/ppa.h"
1515
#include "driver/jpeg_decode.h"
1616
#include "esp_h264_dec_sw.h"
17+
#include "ImageCacheManager.h"
1718
#endif
1819
#ifdef WLEDMM_FASTPATH
1920
#undef SEGMENT
@@ -9129,93 +9130,10 @@ int get_sequence_folder(const std::string& base_path, std::string& selected_path
91299130
return 0;
91309131
}
91319132

9132-
#if defined(ARDUINO_ARCH_ESP32P4)
9133-
extern "C" {
9134-
int p4_mul16x16(uint8_t* outpacket, uint8_t* brightness, uint16_t num_loops, uint8_t* pixelbuffer);
9135-
}
9136-
#endif
9137-
91389133
float quantize16(float value) {
91399134
return roundf(value * 16.0f) / 16.0f;
91409135
}
91419136

9142-
#include <string>
9143-
#include <vector>
9144-
#include <dirent.h>
9145-
#include <sys/stat.h>
9146-
#include <cstdio>
9147-
#include <cstdlib>
9148-
#include <cstring>
9149-
9150-
// Structure to hold image data
9151-
struct ImageData {
9152-
uint8_t* buffer;
9153-
size_t size;
9154-
};
9155-
9156-
// Static cache and folder tracking
9157-
static std::vector<ImageData> image_cache;
9158-
static std::string cached_folder;
9159-
9160-
// Preload all JPEG files from a folder into PSRAM
9161-
bool preload_images(const std::string& folder_path) {
9162-
9163-
if (folder_path == cached_folder) return true; // Already loaded
9164-
9165-
long unsigned timer = micros();
9166-
9167-
// Clear previous cache
9168-
for (auto& img : image_cache) {
9169-
free(img.buffer);
9170-
}
9171-
image_cache.clear();
9172-
9173-
DIR* dir = opendir(folder_path.c_str());
9174-
if (!dir) return false;
9175-
9176-
struct dirent* entry;
9177-
struct stat st;
9178-
std::vector<std::string> jpeg_files;
9179-
9180-
while ((entry = readdir(dir)) != nullptr) {
9181-
std::string name = entry->d_name;
9182-
std::string full_path = folder_path + "/" + name;
9183-
9184-
if ((name.find(".jpg") != std::string::npos || name.find(".jpeg") != std::string::npos) &&
9185-
stat(full_path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
9186-
jpeg_files.push_back(full_path);
9187-
}
9188-
}
9189-
closedir(dir);
9190-
9191-
for (const auto& path : jpeg_files) {
9192-
FILE* file = fopen(path.c_str(), "rb");
9193-
if (!file) continue;
9194-
9195-
fseek(file, 0, SEEK_END);
9196-
size_t size = ftell(file);
9197-
rewind(file);
9198-
9199-
uint8_t* buffer = (uint8_t*)ps_malloc(size); // PSRAM allocation
9200-
if (!buffer) {
9201-
fclose(file);
9202-
continue;
9203-
}
9204-
9205-
fread(buffer, 1, size, file);
9206-
fclose(file);
9207-
9208-
image_cache.push_back({ buffer, size });
9209-
}
9210-
9211-
cached_folder = folder_path;
9212-
USER_PRINTF("Caching took %0.2f seconds.\n", float((micros()-timer)/1000000));
9213-
return !image_cache.empty();
9214-
}
9215-
9216-
#include <sstream>
9217-
#include <iomanip>
9218-
92199137
uint16_t IRAM_ATTR mode_PPA_TESTBED() {
92209138
#ifdef SOC_PPA_SUPPORTED // always for PPA effects
92219139

@@ -9260,7 +9178,7 @@ uint16_t IRAM_ATTR mode_PPA_TESTBED() {
92609178
return 1;
92619179
}
92629180

9263-
static uint16_t frame = 1;
9181+
static uint16_t frame = 0;
92649182
std::string folder_path;
92659183
static std::string last_folder_path;
92669184

@@ -9270,34 +9188,44 @@ uint16_t IRAM_ATTR mode_PPA_TESTBED() {
92709188
return 1;
92719189
}
92729190

9273-
// Only preload if folder has changed
9274-
if (folder_path != last_folder_path) {
9275-
if (!preload_images(folder_path)) {
9276-
USER_PRINTLN("Failed to preload images");
9277-
delay(500);
9278-
return 1;
9279-
}
9280-
last_folder_path = folder_path;
9191+
if (frame >= ImageCacheManager::getInstance().getFolderSize(folder_path)) {
9192+
frame = 0;
92819193
}
92829194

9283-
if (frame - 1 >= image_cache.size()) {
9284-
frame = 1;
9285-
}
9195+
ImageData* img = ImageCacheManager::getInstance().getImage(folder_path, frame);
9196+
9197+
uint8_t* file_jpeg = NULL;
9198+
size_t file_jpeg_size = 0;
92869199

9287-
uint8_t* file_jpeg = image_cache[frame - 1].buffer;
9288-
size_t file_jpeg_size = image_cache[frame - 1].size;
9200+
if (img) {
9201+
// Got the image, display it...
9202+
// display_jpeg(img->buffer, img->size);
9203+
// ESP_LOGI("APP", "Displaying %s, image %d", current_folder.c_str(), folder_path);
9204+
9205+
file_jpeg = img->buffer;
9206+
file_jpeg_size = img->size;
92899207

9208+
// Move to the next image, wrapping around if necessary
9209+
frame++;
9210+
if (frame >= ImageCacheManager::getInstance().getFolderSize(folder_path)) {
9211+
frame = 0;
9212+
}
9213+
} else {
9214+
// Could not get image (it doesn't exist or loading stopped due to PSRAM limit)
9215+
USER_PRINTF("Could not get image %d from %s. Waiting...", frame, folder_path.c_str());
9216+
}
9217+
92909218
if (!file_jpeg || file_jpeg_size == 0) {
92919219
USER_PRINTF("Cached image data missing for frame %d\n", frame);
9292-
frame = 1;
9220+
frame = 0;
92939221
delay(500);
92949222
return 1;
92959223
}
92969224

92979225
if (file_jpeg == NULL) {
92989226
DEBUG_PRINTLN("NULL at JPEG pointer!");
92999227
delay(500);
9300-
return 1;
9228+
return 0;
93019229
}
93029230

93039231
frame++;

wled00/ImageCacheManager.cpp

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#include "ImageCacheManager.h"
2+
#include "esp_psram.h"
3+
#include "esp_log.h"
4+
#include <dirent.h>
5+
#include <sys/stat.h>
6+
#include <algorithm>
7+
#include "Arduino.h"
8+
9+
static const char* TAG = "ImageCache";
10+
11+
// Singleton instance definition
12+
ImageCacheManager& ImageCacheManager::getInstance() {
13+
static ImageCacheManager instance;
14+
return instance;
15+
}
16+
17+
// Constructor: Initialize resources
18+
ImageCacheManager::ImageCacheManager() :
19+
preload_task_handle(NULL),
20+
psram_limit(0),
21+
psram_used(0)
22+
{
23+
cache_mutex = xSemaphoreCreateMutex();
24+
size_t total_psram = esp_psram_get_size();
25+
psram_limit = static_cast<size_t>(total_psram * 0.8);
26+
ESP_LOGI(TAG, "Total PSRAM: %u bytes, Cache Limit (80%%): %u bytes", total_psram, psram_limit);
27+
}
28+
29+
// Destructor: Clean up resources
30+
ImageCacheManager::~ImageCacheManager() {
31+
clearCache();
32+
vSemaphoreDelete(cache_mutex);
33+
}
34+
35+
// Start the non-blocking preload task
36+
void ImageCacheManager::startPreload(const std::string& root_path) {
37+
if (preload_task_handle != NULL) {
38+
ESP_LOGW(TAG, "Preload task is already running.");
39+
return;
40+
}
41+
clearCache();
42+
preload_root_path = root_path.c_str(); // Convert std::string to psram_string
43+
xTaskCreate(_preloadTask, "preload_task", 4096, this, IMAGECACHE_BG_PRIORITY, &preload_task_handle);
44+
}
45+
46+
// The main 'get' function with on-demand loading
47+
ImageData* ImageCacheManager::getImage(const std::string& folder_path, size_t index) {
48+
psram_string ps_folder_path = folder_path.c_str();
49+
xSemaphoreTake(cache_mutex, portMAX_DELAY);
50+
auto it = image_cache.find(ps_folder_path);
51+
52+
if (it != image_cache.end() && index < it->second.size()) {
53+
ImageData* data = &it->second[index];
54+
xSemaphoreGive(cache_mutex);
55+
return data;
56+
}
57+
xSemaphoreGive(cache_mutex);
58+
59+
ESP_LOGI(TAG, "On-demand loading folder: %s", folder_path.c_str());
60+
if (_loadFolderSync(ps_folder_path)) {
61+
xSemaphoreTake(cache_mutex, portMAX_DELAY);
62+
it = image_cache.find(ps_folder_path);
63+
if (it != image_cache.end() && index < it->second.size()) {
64+
ImageData* data = &it->second[index];
65+
xSemaphoreGive(cache_mutex);
66+
return data;
67+
}
68+
xSemaphoreGive(cache_mutex);
69+
}
70+
return nullptr;
71+
}
72+
73+
size_t ImageCacheManager::getFolderSize(const std::string& folder_path) {
74+
size_t size = 0;
75+
psram_string ps_folder_path = folder_path.c_str();
76+
xSemaphoreTake(cache_mutex, portMAX_DELAY);
77+
auto it = image_cache.find(ps_folder_path);
78+
if (it != image_cache.end()) {
79+
size = it->second.size();
80+
}
81+
xSemaphoreGive(cache_mutex);
82+
return size;
83+
}
84+
85+
// The background task implementation
86+
void ImageCacheManager::_preloadTask(void* params) {
87+
ImageCacheManager* manager = static_cast<ImageCacheManager*>(params);
88+
DIR* dir = opendir(manager->preload_root_path.c_str());
89+
if (!dir) {
90+
ESP_LOGE(TAG, "Failed to open root directory: %s", manager->preload_root_path.c_str());
91+
manager->preload_task_handle = NULL;
92+
vTaskDelete(NULL);
93+
return;
94+
}
95+
96+
struct dirent* entry;
97+
while ((entry = readdir(dir)) != nullptr) {
98+
psram_string name = entry->d_name;
99+
if (entry->d_type == DT_DIR && name.rfind("sequence", 0) == 0) {
100+
psram_string full_path = manager->preload_root_path + "/" + name;
101+
ESP_LOGI(TAG, "Preloading folder: %s", full_path.c_str());
102+
manager->_loadFolderSync(full_path);
103+
}
104+
}
105+
closedir(dir);
106+
107+
ESP_LOGI(TAG, "Background preloading finished.");
108+
manager->preload_task_handle = NULL;
109+
vTaskDelete(NULL);
110+
}
111+
112+
// The core synchronous loading logic, now using PSRAM for the temporary filename list
113+
bool ImageCacheManager::_loadFolderSync(const psram_string& folder_path) {
114+
xSemaphoreTake(cache_mutex, portMAX_DELAY);
115+
if (image_cache.count(folder_path)) {
116+
xSemaphoreGive(cache_mutex);
117+
return true;
118+
}
119+
xSemaphoreGive(cache_mutex);
120+
121+
DIR* dir = opendir(folder_path.c_str());
122+
if (!dir) return false;
123+
124+
// This temporary vector and its strings are now allocated in PSRAM
125+
using psram_string_vector = std::vector<psram_string, PSRAM_Allocator<psram_string>>;
126+
psram_string_vector jpeg_files;
127+
128+
struct dirent* entry;
129+
while ((entry = readdir(dir)) != nullptr) {
130+
psram_string name = entry->d_name;
131+
if (name.find(".jpg") != psram_string::npos || name.find(".jpeg") != psram_string::npos) {
132+
jpeg_files.push_back(folder_path + "/" + name);
133+
}
134+
}
135+
closedir(dir);
136+
137+
std::sort(jpeg_files.begin(), jpeg_files.end());
138+
139+
xSemaphoreTake(cache_mutex, portMAX_DELAY);
140+
image_cache[folder_path].reserve(jpeg_files.size());
141+
xSemaphoreGive(cache_mutex);
142+
143+
bool loaded_any = false;
144+
for (const auto& path : jpeg_files) {
145+
struct stat st;
146+
stat(path.c_str(), &st);
147+
size_t size = st.st_size;
148+
149+
if (psram_used + size > psram_limit) {
150+
ESP_LOGW(TAG, "PSRAM limit reached. Stopping load at %s", path.c_str());
151+
break;
152+
}
153+
154+
FILE* file = fopen(path.c_str(), "rb");
155+
if (!file) continue;
156+
157+
uint8_t* buffer = (uint8_t*)heap_caps_malloc(size, MALLOC_CAP_SPIRAM);
158+
if (!buffer) {
159+
ESP_LOGE(TAG, "Failed to allocate %u bytes in PSRAM for %s", size, path.c_str());
160+
fclose(file);
161+
continue;
162+
}
163+
164+
fread(buffer, 1, size, file);
165+
fclose(file);
166+
167+
xSemaphoreTake(cache_mutex, portMAX_DELAY);
168+
image_cache[folder_path].push_back({buffer, size});
169+
psram_used += size;
170+
xSemaphoreGive(cache_mutex);
171+
loaded_any = true;
172+
}
173+
return loaded_any;
174+
}
175+
176+
void ImageCacheManager::clearCache() {
177+
xSemaphoreTake(cache_mutex, portMAX_DELAY);
178+
for (auto& pair : image_cache) {
179+
for (auto& img : pair.second) {
180+
free(img.buffer);
181+
}
182+
}
183+
image_cache.clear();
184+
psram_used = 0;
185+
xSemaphoreGive(cache_mutex);
186+
ESP_LOGI(TAG, "Image cache cleared.");
187+
}

0 commit comments

Comments
 (0)