Tasmota/lib/libesp32_epdiy/examples/mpd_status/main/mpd_image.c
2021-04-03 17:41:35 +02:00

279 lines
7.3 KiB
C
Executable File

#include "esp_assert.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "esp_types.h"
#include "mpd/client.h"
#include "esp32/rom/tjpgd.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "mpd_image.h"
static uint8_t tjpgd_work[4096];
typedef struct {
char *image_uri;
uint8_t *decoded_image;
uint8_t *chunk_data;
uint32_t chunk_data_offset;
uint32_t chunk_data_size;
uint32_t cover_offset;
uint32_t scale;
struct mpd_connection *conn;
// use albumart exclusively
bool use_albumart;
} image_fetch_context_t;
void free_album_cover(album_cover_t *cover) {
free(cover->data);
free(cover->source_uri);
free(cover->identifier);
free(cover);
}
void handle_error(struct mpd_connection **c);
static uint32_t
feed_jpg_chunk(JDEC *jd, /* Decompressor object */
uint8_t *buff, /* Pointer to the read buffer (NULL:skip) */
uint32_t nd /* Number of bytes to read/skip from input stream */
) {
image_fetch_context_t *context = jd->device;
if (context->chunk_data == NULL) {
char offset_s[32];
snprintf(offset_s, sizeof(offset_s), "%u", context->cover_offset);
printf("use albumart: %d\n", context->use_albumart);
struct mpd_connection *c = context->conn;
if (context->use_albumart) {
if (!mpd_send_command(c, "albumart", context->image_uri, offset_s,
NULL)) {
handle_error(&c);
return -1;
}
} else {
if (!mpd_send_command(c, "readpicture", context->image_uri, offset_s,
NULL)) {
handle_error(&c);
return -1;
}
}
struct mpd_pair *pair = mpd_recv_pair_named(c, "size");
if (pair == NULL) {
if (mpd_connection_get_error(c) != MPD_ERROR_SUCCESS) {
handle_error(&c);
return -1;
}
if (!context->use_albumart) {
context->use_albumart = true;
ESP_LOGI("mpd_image", "no image with readpicture, trying albumart...\n");
if (!mpd_response_finish(c)) {
handle_error(c);
return -1;
}
int returned = feed_jpg_chunk(jd, buff, nd);
printf("returned: %d\n", returned);
return returned;
}
if (!mpd_response_finish(c)) {
handle_error(c);
return -1;
}
fprintf(stderr, "No 'size'\n");
return -1;
}
mpd_return_pair(c, pair);
pair = mpd_recv_pair_named(c, "binary");
if (pair == NULL) {
if (mpd_connection_get_error(c) != MPD_ERROR_SUCCESS) {
handle_error(c);
return -1;
}
fprintf(stderr, "No 'binary'\n");
return -1;
}
uint32_t chunk_size = strtoull(pair->value, NULL, 10);
printf("chunk size: %d\n", chunk_size);
mpd_return_pair(c, pair);
if (chunk_size == 0) {
if (!mpd_response_finish(c)) {
handle_error(c);
return -1;
}
return 0;
}
context->chunk_data = malloc(chunk_size);
if (!mpd_recv_binary(c, context->chunk_data, chunk_size)) {
handle_error(c);
return -1;
}
context->chunk_data_size = chunk_size;
context->chunk_data_offset = 0;
if (!mpd_response_finish(c)) {
handle_error(c);
return -1;
}
context->cover_offset += chunk_size;
}
// read
uint32_t n_read;
uint8_t *src = context->chunk_data;
src += context->chunk_data_offset;
if (context->chunk_data_offset + nd <= context->chunk_data_size) {
if (buff)
memcpy(buff, src, nd);
n_read = nd;
} else {
uint32_t remaining = context->chunk_data_size - context->chunk_data_offset;
if (buff)
memcpy(buff, src, remaining);
n_read = remaining;
}
context->chunk_data_offset += n_read;
if (context->chunk_data_offset >= context->chunk_data_size) {
free(context->chunk_data);
context->chunk_data = NULL;
}
return n_read;
}
/* User defined call-back function to output RGB bitmap */
static uint32_t
tjd_output(JDEC *jd, /* Decompressor object of current session */
void *bitmap, /* Bitmap data to be output */
JRECT *rect /* Rectangular region to output */
) {
image_fetch_context_t *context = jd->device;
uint32_t w = rect->right - rect->left + 1;
uint32_t h = rect->bottom - rect->top + 1;
uint32_t image_width = jd->width >> context->scale;
uint32_t even_img_width = image_width + image_width % 2;
uint8_t *bitmap_ptr = bitmap;
for (uint32_t i = 0; i < w * h; i++) {
uint8_t r = *(bitmap_ptr++);
uint8_t g = *(bitmap_ptr++);
uint8_t b = *(bitmap_ptr++);
// calculate weighted grayscale
uint32_t val = ((r * 30 + g * 59 + b * 11) / 100) >> 4;
int xx = rect->left + i % w;
if (xx < 0 || xx >= even_img_width) {
continue;
}
int yy = rect->top + i / w;
if (yy < 0 || yy >= even_img_width) {
continue;
}
uint8_t *buf_ptr =
&context->decoded_image[yy * even_img_width / 2 + xx / 2];
if (xx % 2) {
*buf_ptr = (*buf_ptr & 0x0F) | (val << 4);
} else {
*buf_ptr = (*buf_ptr & 0xF0) | val;
}
}
return 1;
}
album_cover_t *readpicture(struct mpd_connection *c, char *uri,
char *identifier) {
JDEC jd; /* Decompression object (70 bytes) */
JRESULT rc;
uint8_t scale;
image_fetch_context_t context;
context.decoded_image = NULL;
context.image_uri = uri;
context.cover_offset = 0;
context.chunk_data = NULL;
context.chunk_data_size = 0;
context.chunk_data_offset = 0;
context.conn = c;
context.use_albumart = false;
/* Prepare to decompress the file */
rc =
jd_prepare(&jd, feed_jpg_chunk, tjpgd_work, sizeof(tjpgd_work), &context);
if (rc != JDR_OK) {
ESP_LOGW("mpd_image", "jpeg prepare failed: %d\n", rc);
return NULL;
}
/* Determine scale factor */
for (scale = 0; scale < 3; scale++) {
if ((jd.width >> scale) <= 700 && (jd.height >> scale) <= 700)
break;
}
uint32_t width = jd.width >> scale;
uint32_t height = jd.height >> scale;
uint32_t even_img_width = width + width % 2;
uint8_t *buf =
heap_caps_malloc(even_img_width / 2 * height, MALLOC_CAP_SPIRAM);
if (!buf) {
ESP_LOGW("mpd_image", "cannot allocate cover info!");
return NULL;
}
context.decoded_image = buf;
context.scale = scale;
printf("orig width: %d orig height: %d\n", jd.width, jd.height);
printf("scaled width: %d scaled height: %d\n", width, height);
/* Start to decompress the JPEG file */
rc = jd_decomp(&jd, tjd_output, scale);
if (rc != JDR_OK) {
ESP_LOGW("mpd_image", "jpeg decomp failed: %d\n", rc);
return NULL;
}
album_cover_t *cover = malloc(sizeof(album_cover_t));
if (!cover) {
ESP_LOGW("mpd_image", "cannot allocate cover info!");
free(buf);
return NULL;
}
cover->data = buf;
cover->width = width;
cover->height = height;
cover->source_uri = strndup(uri, 128);
if (cover->source_uri == NULL) {
free(buf);
free(cover);
ESP_LOGW("mpd_image", "cannot allocate uri dup!");
return NULL;
}
if (identifier != NULL) {
cover->identifier = strndup(identifier, 128);
if (cover->identifier == NULL) {
free(cover->source_uri);
free(buf);
free(cover);
ESP_LOGW("mpd_image", "cannot allocate identifier dup!");
return NULL;
}
} else {
cover->identifier = NULL;
}
return cover;
}