feat(perf): accelerate image download and raster conversion

This commit is contained in:
admin
2026-02-25 16:14:04 +08:00
parent 7965818d13
commit 528f901fb5
6 changed files with 430 additions and 49 deletions

View File

@@ -5,10 +5,15 @@
#include <string.h>
#include "cJSON.h"
#include "esp_coexist.h"
#include "esp_crt_bundle.h"
#include "esp_heap_caps.h"
#include "esp_http_client.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#ifndef CONFIG_TQ_Z_IMAGE_API_ENDPOINT
#define CONFIG_TQ_Z_IMAGE_API_ENDPOINT "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation"
@@ -19,7 +24,7 @@
#endif
#ifndef CONFIG_TQ_Z_IMAGE_DEFAULT_SIZE
#define CONFIG_TQ_Z_IMAGE_DEFAULT_SIZE "1024*1536"
#define CONFIG_TQ_Z_IMAGE_DEFAULT_SIZE "512*768"
#endif
#ifndef CONFIG_TQ_Z_IMAGE_TIMEOUT_MS
@@ -38,8 +43,31 @@
#define CONFIG_TQ_Z_IMAGE_MAX_PNG_BYTES (4 * 1024 * 1024)
#endif
#ifndef CONFIG_TQ_Z_IMAGE_HTTP_READ_CHUNK_BYTES
#define CONFIG_TQ_Z_IMAGE_HTTP_READ_CHUNK_BYTES 16384
#endif
#ifndef CONFIG_TQ_Z_IMAGE_HTTP_BUFFER_BYTES
#define CONFIG_TQ_Z_IMAGE_HTTP_BUFFER_BYTES 16384
#endif
#define IMAGE_HTTP_READ_CHUNK_BYTES ((size_t)CONFIG_TQ_Z_IMAGE_HTTP_READ_CHUNK_BYTES)
#define IMAGE_HTTP_BUFFER_BYTES CONFIG_TQ_Z_IMAGE_HTTP_BUFFER_BYTES
#define IMAGE_PREWARM_TIMEOUT_MS 2500
#define IMAGE_PREWARM_STACK 5120
#if CONFIG_FREERTOS_UNICORE
#define IMAGE_WORKER_CORE_ID 0
#else
#define IMAGE_WORKER_CORE_ID 1
#endif
static const char *TAG = "image_generation";
typedef struct {
char oss_origin[160];
} image_prewarm_task_arg_t;
typedef struct {
uint8_t *data;
size_t len;
@@ -47,6 +75,40 @@ typedef struct {
size_t max_len;
} bytes_buffer_t;
static SemaphoreHandle_t s_prewarm_lock;
static bool s_prewarm_running;
static char s_last_oss_origin[160] = "https://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/";
static bool image_generation_boost_coex_for_download(void) {
#if CONFIG_ESP_COEX_SW_COEXIST_ENABLE && CONFIG_TQ_Z_IMAGE_COEX_PREFER_WIFI_DURING_DOWNLOAD
esp_err_t rc = esp_coex_preference_set(ESP_COEX_PREFER_WIFI);
if (rc != ESP_OK) {
ESP_LOGW(TAG, "failed to prefer Wi-Fi coexist mode, rc=0x%x", (unsigned)rc);
return false;
}
ESP_LOGI(TAG, "coexist prefer Wi-Fi enabled for image download");
return true;
#else
return false;
#endif
}
static void image_generation_restore_coex_after_download(bool boosted) {
#if CONFIG_ESP_COEX_SW_COEXIST_ENABLE && CONFIG_TQ_Z_IMAGE_COEX_PREFER_WIFI_DURING_DOWNLOAD
if (!boosted) {
return;
}
esp_err_t rc = esp_coex_preference_set(ESP_COEX_PREFER_BALANCE);
if (rc != ESP_OK) {
ESP_LOGW(TAG, "failed to restore coexist balance mode, rc=0x%x", (unsigned)rc);
return;
}
ESP_LOGI(TAG, "coexist preference restored to balance");
#else
(void)boosted;
#endif
}
static void image_generation_fill_err(char *err, size_t err_len, const char *msg) {
if (err != NULL && err_len > 0) {
snprintf(err, err_len, "%s", msg != NULL ? msg : "unknown error");
@@ -149,6 +211,206 @@ static const char *image_generation_api_key(void) {
return NULL;
}
static void prewarm_lock_init_if_needed(void) {
if (s_prewarm_lock != NULL) {
return;
}
s_prewarm_lock = xSemaphoreCreateMutex();
}
static bool url_extract_origin(const char *url, char *out_origin, size_t out_origin_len) {
if (url == NULL || out_origin == NULL || out_origin_len == 0) {
return false;
}
const char *scheme = strstr(url, "://");
if (scheme == NULL || scheme == url) {
return false;
}
const char *host = scheme + 3;
if (*host == '\0') {
return false;
}
const char *path = strchr(host, '/');
size_t host_len = path != NULL ? (size_t)(path - host) : strlen(host);
if (host_len == 0) {
return false;
}
int n = snprintf(out_origin,
out_origin_len,
"%.*s://%.*s/",
(int)(scheme - url),
url,
(int)host_len,
host);
return n > 0 && (size_t)n < out_origin_len;
}
static void image_generation_remember_oss_origin(const char *image_url) {
char origin[160] = {0};
if (!url_extract_origin(image_url, origin, sizeof(origin))) {
return;
}
prewarm_lock_init_if_needed();
if (s_prewarm_lock == NULL) {
return;
}
if (xSemaphoreTake(s_prewarm_lock, pdMS_TO_TICKS(100)) != pdTRUE) {
return;
}
strlcpy(s_last_oss_origin, origin, sizeof(s_last_oss_origin));
xSemaphoreGive(s_prewarm_lock);
}
static void image_generation_copy_oss_origin(char *out_origin, size_t out_origin_len) {
if (out_origin == NULL || out_origin_len == 0) {
return;
}
out_origin[0] = '\0';
prewarm_lock_init_if_needed();
if (s_prewarm_lock == NULL) {
strlcpy(out_origin, s_last_oss_origin, out_origin_len);
return;
}
if (xSemaphoreTake(s_prewarm_lock, pdMS_TO_TICKS(100)) != pdTRUE) {
strlcpy(out_origin, s_last_oss_origin, out_origin_len);
return;
}
strlcpy(out_origin, s_last_oss_origin, out_origin_len);
xSemaphoreGive(s_prewarm_lock);
}
static esp_err_t image_generation_http_prewarm_head(const char *url,
const char *auth_header,
uint32_t timeout_ms) {
if (url == NULL || url[0] == '\0') {
return ESP_ERR_INVALID_ARG;
}
esp_http_client_config_t cfg = {
.url = url,
.method = HTTP_METHOD_HEAD,
.timeout_ms = (int)timeout_ms,
.keep_alive_enable = true,
.buffer_size = 1024,
.buffer_size_tx = 1024,
.crt_bundle_attach = esp_crt_bundle_attach,
};
esp_http_client_handle_t client = esp_http_client_init(&cfg);
if (client == NULL) {
return ESP_FAIL;
}
if (auth_header != NULL && auth_header[0] != '\0') {
esp_http_client_set_header(client, "Authorization", auth_header);
}
esp_err_t rc = esp_http_client_open(client, 0);
if (rc == ESP_OK) {
(void)esp_http_client_fetch_headers(client);
esp_http_client_close(client);
}
esp_http_client_cleanup(client);
return rc;
}
static void image_generation_prewarm_task(void *arg) {
image_prewarm_task_arg_t *task_arg = (image_prewarm_task_arg_t *)arg;
char oss_origin[160] = {0};
if (task_arg != NULL) {
strlcpy(oss_origin, task_arg->oss_origin, sizeof(oss_origin));
free(task_arg);
}
int64_t start_ms = esp_timer_get_time() / 1000;
const char *api_key = image_generation_api_key();
char auth_header[192] = {0};
const char *auth = NULL;
if (api_key != NULL) {
snprintf(auth_header, sizeof(auth_header), "Bearer %s", api_key);
auth = auth_header;
}
esp_err_t gen_rc = image_generation_http_prewarm_head(CONFIG_TQ_Z_IMAGE_API_ENDPOINT,
auth,
IMAGE_PREWARM_TIMEOUT_MS);
esp_err_t oss_rc = image_generation_http_prewarm_head(oss_origin,
NULL,
IMAGE_PREWARM_TIMEOUT_MS);
int64_t done_ms = esp_timer_get_time() / 1000;
ESP_LOGI(TAG,
"https prewarm done, gen_rc=0x%x, oss_rc=0x%x, cost_ms=%lld",
(unsigned)gen_rc,
(unsigned)oss_rc,
(long long)(done_ms - start_ms));
prewarm_lock_init_if_needed();
if (s_prewarm_lock != NULL && xSemaphoreTake(s_prewarm_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
s_prewarm_running = false;
xSemaphoreGive(s_prewarm_lock);
} else {
s_prewarm_running = false;
}
vTaskDelete(NULL);
}
void image_generation_schedule_prewarm(void) {
prewarm_lock_init_if_needed();
if (s_prewarm_lock == NULL) {
return;
}
if (xSemaphoreTake(s_prewarm_lock, pdMS_TO_TICKS(100)) != pdTRUE) {
return;
}
if (s_prewarm_running) {
xSemaphoreGive(s_prewarm_lock);
return;
}
s_prewarm_running = true;
xSemaphoreGive(s_prewarm_lock);
image_prewarm_task_arg_t *arg = (image_prewarm_task_arg_t *)calloc(1, sizeof(*arg));
if (arg == NULL) {
if (xSemaphoreTake(s_prewarm_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
s_prewarm_running = false;
xSemaphoreGive(s_prewarm_lock);
} else {
s_prewarm_running = false;
}
return;
}
image_generation_copy_oss_origin(arg->oss_origin, sizeof(arg->oss_origin));
BaseType_t ok = xTaskCreatePinnedToCore(image_generation_prewarm_task,
"img_prewarm",
IMAGE_PREWARM_STACK,
arg,
4,
NULL,
IMAGE_WORKER_CORE_ID);
if (ok != pdPASS) {
free(arg);
if (xSemaphoreTake(s_prewarm_lock, pdMS_TO_TICKS(100)) == pdTRUE) {
s_prewarm_running = false;
xSemaphoreGive(s_prewarm_lock);
} else {
s_prewarm_running = false;
}
ESP_LOGW(TAG, "failed to create prewarm task");
}
}
static esp_err_t http_read_full_response(esp_http_client_handle_t client,
bytes_buffer_t *out_body,
char *err,
@@ -158,14 +420,21 @@ static esp_err_t http_read_full_response(esp_http_client_handle_t client,
return ESP_ERR_INVALID_ARG;
}
char tmp[1024];
char *tmp = (char *)malloc_prefer_psram(IMAGE_HTTP_READ_CHUNK_BYTES);
if (tmp == NULL) {
image_generation_fill_err(err, err_len, "no memory for http read buffer");
return ESP_ERR_NO_MEM;
}
while (true) {
int n = esp_http_client_read(client, tmp, sizeof(tmp));
int n = esp_http_client_read(client, tmp, IMAGE_HTTP_READ_CHUNK_BYTES);
if (n < 0) {
free(tmp);
image_generation_fill_err(err, err_len, "http read failed");
return ESP_FAIL;
}
if (n == 0) {
free(tmp);
if (esp_http_client_is_complete_data_received(client)) {
return ESP_OK;
}
@@ -175,6 +444,7 @@ static esp_err_t http_read_full_response(esp_http_client_handle_t client,
esp_err_t append_rc = bytes_buffer_append(out_body, tmp, (size_t)n);
if (append_rc != ESP_OK) {
free(tmp);
image_generation_fill_err(err, err_len, "http response too large");
return append_rc;
}
@@ -231,6 +501,16 @@ static esp_err_t http_request_collect(const esp_http_client_config_t *config,
image_generation_fill_err(err, err_len, "http response too large");
return ESP_ERR_INVALID_SIZE;
}
if (content_len > 0) {
esp_err_t reserve_rc = bytes_buffer_reserve(out_body, (size_t)content_len);
if (reserve_rc != ESP_OK) {
esp_http_client_close(client);
esp_http_client_cleanup(client);
image_generation_fill_err(err, err_len, "no memory for http response");
return reserve_rc;
}
}
if (out_status_code != NULL) {
*out_status_code = esp_http_client_get_status_code(client);
}
@@ -402,9 +682,9 @@ static esp_err_t image_generation_invoke_model(const image_generation_request_t
.url = CONFIG_TQ_Z_IMAGE_API_ENDPOINT,
.method = HTTP_METHOD_POST,
.timeout_ms = (int)req->generation_timeout_ms,
.keep_alive_enable = false,
.buffer_size = 2048,
.buffer_size_tx = 2048,
.keep_alive_enable = true,
.buffer_size = IMAGE_HTTP_BUFFER_BYTES,
.buffer_size_tx = IMAGE_HTTP_BUFFER_BYTES,
.crt_bundle_attach = esp_crt_bundle_attach,
};
@@ -491,6 +771,8 @@ static esp_err_t image_generation_invoke_model(const image_generation_request_t
return ESP_FAIL;
}
image_generation_remember_oss_origin(out_result->image_url);
ESP_LOGI(TAG,
"z-image response ready, request_id=%s, model_size=%ux%u, image_url_len=%u",
out_result->request_id[0] != '\0' ? out_result->request_id : "-",
@@ -507,13 +789,14 @@ static esp_err_t image_generation_download_png(image_generation_result_t *out_re
char *err,
size_t err_len) {
ESP_LOGI(TAG, "generated image download start, timeout_ms=%u", (unsigned)timeout_ms);
bool coex_boosted = image_generation_boost_coex_for_download();
esp_http_client_config_t config = {
.url = out_result->image_url,
.method = HTTP_METHOD_GET,
.timeout_ms = (int)timeout_ms,
.keep_alive_enable = false,
.buffer_size = 2048,
.keep_alive_enable = true,
.buffer_size = IMAGE_HTTP_BUFFER_BYTES,
.crt_bundle_attach = esp_crt_bundle_attach,
};
@@ -537,6 +820,7 @@ static esp_err_t image_generation_download_png(image_generation_result_t *out_re
(unsigned)rc,
(err != NULL && err[0] != '\0') ? err : "download failed");
bytes_buffer_free(&resp);
image_generation_restore_coex_after_download(coex_boosted);
return rc;
}
@@ -545,6 +829,7 @@ static esp_err_t image_generation_download_png(image_generation_result_t *out_re
if (rc != ESP_OK) {
bytes_buffer_free(&resp);
image_generation_fill_err(err, err_len, "image download failed");
image_generation_restore_coex_after_download(coex_boosted);
return ESP_FAIL;
}
extract_remote_error_message((const char *)resp.data, http_status, err, err_len);
@@ -553,6 +838,7 @@ static esp_err_t image_generation_download_png(image_generation_result_t *out_re
http_status,
(err != NULL && err[0] != '\0') ? err : "image download failed");
bytes_buffer_free(&resp);
image_generation_restore_coex_after_download(coex_boosted);
return ESP_FAIL;
}
@@ -563,12 +849,14 @@ static esp_err_t image_generation_download_png(image_generation_result_t *out_re
bytes_buffer_free(&resp);
image_generation_fill_err(err, err_len, "downloaded image is not png");
ESP_LOGW(TAG, "generated image download invalid png signature");
image_generation_restore_coex_after_download(coex_boosted);
return ESP_FAIL;
}
out_result->png = resp.data;
out_result->png_len = resp.len;
ESP_LOGI(TAG, "generated image download done, png_bytes=%u", (unsigned)out_result->png_len);
image_generation_restore_coex_after_download(coex_boosted);
return ESP_OK;
}
@@ -615,6 +903,8 @@ esp_err_t image_generation_generate_png(const image_generation_request_t *req,
actual.has_seed ? "set" : "auto",
(unsigned)actual.generation_timeout_ms,
(unsigned)actual.download_timeout_ms);
int64_t t0_ms = esp_timer_get_time() / 1000;
esp_err_t gen_rc = image_generation_invoke_model(&actual, out_result, err, err_len);
if (gen_rc != ESP_OK) {
ESP_LOGW(TAG,
@@ -625,7 +915,11 @@ esp_err_t image_generation_generate_png(const image_generation_request_t *req,
return gen_rc;
}
ESP_LOGI(TAG, "image generate stage done, request_id=%s", out_result->request_id[0] != '\0' ? out_result->request_id : "-");
int64_t t_gen_done_ms = esp_timer_get_time() / 1000;
ESP_LOGI(TAG,
"image generate stage done, request_id=%s, gen_cost_ms=%lld",
out_result->request_id[0] != '\0' ? out_result->request_id : "-",
(long long)(t_gen_done_ms - t0_ms));
esp_err_t dl_rc = image_generation_download_png(out_result, actual.download_timeout_ms, err, err_len);
if (dl_rc != ESP_OK) {
ESP_LOGW(TAG,
@@ -636,10 +930,15 @@ esp_err_t image_generation_generate_png(const image_generation_request_t *req,
return dl_rc;
}
int64_t t_dl_done_ms = esp_timer_get_time() / 1000;
ESP_LOGI(TAG,
"image generation pipeline done, request_id=%s, png_bytes=%u",
"image generation pipeline done, request_id=%s, png_bytes=%u, gen_ms=%lld, download_ms=%lld, total_ms=%lld",
out_result->request_id[0] != '\0' ? out_result->request_id : "-",
(unsigned)out_result->png_len);
(unsigned)out_result->png_len,
(long long)(t_gen_done_ms - t0_ms),
(long long)(t_dl_done_ms - t_gen_done_ms),
(long long)(t_dl_done_ms - t0_ms));
return ESP_OK;
}