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