Separate JPEG decoder and add JPEG EOF detection

This commit is contained in:
me-no-dev
2018-11-19 11:20:04 +01:00
parent 0d2547a501
commit 89fcfdecb3
4 changed files with 260 additions and 142 deletions

View File

@ -0,0 +1,118 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "esp_jpg_decode.h"
#include "rom/tjpgd.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h"
#define TAG ""
#else
#include "esp_log.h"
static const char* TAG = "esp_jpg_decode";
#endif
typedef struct {
jpg_scale_t scale;
jpg_reader_cb reader;
jpg_writer_cb writer;
void * arg;
size_t len;
size_t index;
} esp_jpg_decoder_t;
static const char * jd_errors[] = {
"Succeeded",
"Interrupted by output function",
"Device error or wrong termination of input stream",
"Insufficient memory pool for the image",
"Insufficient stream input buffer",
"Parameter error",
"Data format error",
"Right format but not supported",
"Not supported JPEG standard"
};
static uint32_t _jpg_write(JDEC *decoder, void *bitmap, JRECT *rect)
{
uint16_t x = rect->left;
uint16_t y = rect->top;
uint16_t w = rect->right + 1 - x;
uint16_t h = rect->bottom + 1 - y;
uint8_t *data = (uint8_t *)bitmap;
esp_jpg_decoder_t * jpeg = (esp_jpg_decoder_t *)decoder->device;
if (jpeg->writer) {
return jpeg->writer(jpeg->arg, x, y, w, h, data);
}
return 0;
}
static uint32_t _jpg_read(JDEC *decoder, uint8_t *buf, uint32_t len)
{
esp_jpg_decoder_t * jpeg = (esp_jpg_decoder_t *)decoder->device;
if (jpeg->len && len > (jpeg->len - jpeg->index)) {
len = jpeg->len - jpeg->index;
}
if (len) {
len = jpeg->reader(jpeg->arg, jpeg->index, buf, len);
if (!len) {
ESP_LOGE(TAG, "Read Fail at %u/%u", jpeg->index, jpeg->len);
}
jpeg->index += len;
}
return len;
}
esp_err_t esp_jpg_decode(size_t len, jpg_scale_t scale, jpg_reader_cb reader, jpg_writer_cb writer, void * arg)
{
static uint8_t work[3100];
JDEC decoder;
esp_jpg_decoder_t jpeg;
jpeg.len = len;
jpeg.reader = reader;
jpeg.writer = writer;
jpeg.arg = arg;
jpeg.scale = scale;
jpeg.index = 0;
JRESULT jres = jd_prepare(&decoder, _jpg_read, work, 3100, &jpeg);
if(jres != JDR_OK){
ESP_LOGE(TAG, "JPG Header Parse Failed! %s", jd_errors[jres]);
return ESP_FAIL;
}
uint16_t output_width = decoder.width / (1 << (uint8_t)(jpeg.scale));
uint16_t output_height = decoder.height / (1 << (uint8_t)(jpeg.scale));
//output start
writer(arg, 0, 0, output_width, output_height, NULL);
//output write
jres = jd_decomp(&decoder, _jpg_write, (uint8_t)jpeg.scale);
//output end
writer(arg, output_width, output_height, output_width, output_height, NULL);
if (jres != JDR_OK) {
ESP_LOGE(TAG, "JPG Decompression Failed! %s", jd_errors[jres]);
return ESP_FAIL;
}
//check if all data has been consumed.
if (len && jpeg.index < len) {
_jpg_read(&decoder, NULL, len - jpeg.index);
}
return ESP_OK;
}

View File

@ -0,0 +1,43 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _ESP_JPG_DECODE_H_
#define _ESP_JPG_DECODE_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
typedef enum {
JPG_SCALE_NONE,
JPG_SCALE_2X,
JPG_SCALE_4X,
JPG_SCALE_8X,
JPG_SCALE_MAX = JPG_SCALE_8X
} jpg_scale_t;
typedef size_t (* jpg_reader_cb)(void * arg, size_t index, uint8_t *buf, size_t len);
typedef bool (* jpg_writer_cb)(void * arg, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *data);
esp_err_t esp_jpg_decode(size_t len, jpg_scale_t scale, jpg_reader_cb reader, jpg_writer_cb writer, void * arg);
#ifdef __cplusplus
}
#endif
#endif /* _ESP_JPG_DECODE_H_ */

View File

@ -14,12 +14,12 @@
#include <stddef.h> #include <stddef.h>
#include <string.h> #include <string.h>
#include "img_converters.h" #include "img_converters.h"
#include "rom/tjpgd.h"
#include "esp_spiram.h" #include "esp_spiram.h"
#include "soc/efuse_reg.h" #include "soc/efuse_reg.h"
#include "esp_heap_caps.h" #include "esp_heap_caps.h"
#include "yuv.h" #include "yuv.h"
#include "sdkconfig.h" #include "sdkconfig.h"
#include "esp_jpg_decode.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h" #include "esp32-hal-log.h"
@ -31,14 +31,6 @@ static const char* TAG = "to_bmp";
static const int BMP_HEADER_LEN = 54; static const int BMP_HEADER_LEN = 54;
typedef enum {
JPEG_DIV_NONE,
JPEG_DIV_2,
JPEG_DIV_4,
JPEG_DIV_8,
JPEG_DIV_MAX
} jpeg_div_t;
typedef struct { typedef struct {
uint32_t filesize; uint32_t filesize;
uint32_t reserved; uint32_t reserved;
@ -57,45 +49,49 @@ typedef struct {
} bmp_header_t; } bmp_header_t;
typedef struct { typedef struct {
jpeg_div_t scale; uint16_t width;
const void * src; uint16_t height;
size_t len; const uint8_t *input;
size_t index; uint8_t *output;
uint16_t width; } rgb_jpg_decoder;
uint16_t height;
uint8_t * dst;
size_t dstlen;
} jpg_frame_decoder_t;
const char * jpgd_errors[] = {
"Succeeded",
"Interrupted by output function",
"Device error or wrong termination of input stream",
"Insufficient memory pool for the image",
"Insufficient stream input buffer",
"Parameter error",
"Data format error",
"Right format but not supported",
"Not supported JPEG standard"
};
static void *_malloc(size_t size) static void *_malloc(size_t size)
{ {
return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
} }
static uint32_t jpg_write_bmp(JDEC *decoder, void *bitmap, JRECT *rect) //output buffer and image width
static bool _rgb_write(void * arg, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *data)
{ {
jpg_frame_decoder_t * jpeg = (jpg_frame_decoder_t *)decoder->device; rgb_jpg_decoder * jpeg = (rgb_jpg_decoder *)arg;
if(!data){
if(x == 0 && y == 0){
//write start
jpeg->width = w;
jpeg->height = h;
//if output is null, this is BMP
if(!jpeg->output){
jpeg->output = (uint8_t *)_malloc((w*h*3)+BMP_HEADER_LEN);
if(!jpeg->output){
return false;
}
}
} else {
//write end
}
return true;
}
size_t jw = jpeg->width*3; size_t jw = jpeg->width*3;
size_t t = rect->top * jw; size_t t = y * jw;
size_t b = (rect->bottom * jw) + 1; size_t b = t + (h * jw);
size_t l = rect->left * 3; size_t l = x * 3;
size_t w = (rect->right + 1 - rect->left) * 3; uint8_t *out = jpeg->output+BMP_HEADER_LEN;
uint8_t *data = (uint8_t *)bitmap;
uint8_t *out = jpeg->dst;
uint8_t *o = out; uint8_t *o = out;
size_t iy, ix; size_t iy, ix;
w = w * 3;
for(iy=t; iy<b; iy+=jw) { for(iy=t; iy<b; iy+=jw) {
o = out+iy+l; o = out+iy+l;
for(ix=0; ix<w; ix+= 3) { for(ix=0; ix<w; ix+= 3) {
@ -105,48 +101,69 @@ static uint32_t jpg_write_bmp(JDEC *decoder, void *bitmap, JRECT *rect)
} }
data+=w; data+=w;
} }
return 1; return true;
} }
static uint32_t jpg_read_frame(JDEC *decoder, uint8_t *buf, uint32_t len) //input buffer
static uint32_t _jpg_read(void * arg, size_t index, uint8_t *buf, size_t len)
{ {
jpg_frame_decoder_t * jpeg = (jpg_frame_decoder_t *)decoder->device; rgb_jpg_decoder * jpeg = (rgb_jpg_decoder *)arg;
if(buf) { if(buf) {
memcpy(buf, (const uint8_t *)jpeg->src + jpeg->index, len); memcpy(buf, jpeg->input + index, len);
} }
jpeg->index += len;
return len; return len;
} }
static uint8_t jpg_work_buffer[3100]; static bool jpg2rgb888(const uint8_t *src, size_t src_len, uint8_t * out, jpg_scale_t scale)
bool jpg2rgb888(const uint8_t *src, size_t src_len, uint8_t * out, jpeg_div_t scale)
{ {
JDEC decoder; rgb_jpg_decoder jpeg;
jpg_frame_decoder_t jpeg;
jpeg.src = src;
jpeg.len = src_len;
jpeg.index = 0;
jpeg.width = 0; jpeg.width = 0;
jpeg.height = 0; jpeg.height = 0;
jpeg.dst = out; jpeg.input = src;
jpeg.dstlen = 0; jpeg.output = out;
jpeg.scale = scale;
JRESULT jres = jd_prepare(&decoder, jpg_read_frame, jpg_work_buffer, 3100, &jpeg); if(esp_jpg_decode(src_len, scale, _jpg_read, _rgb_write, (void*)&jpeg) != ESP_OK){
if(jres != JDR_OK) {
ESP_LOGE(TAG, "jd_prepare failed! %s", jpgd_errors[jres]);
return false; return false;
} }
jpeg.width = decoder.width / (1 << (uint8_t)(jpeg.scale)); return true;
jpeg.height = decoder.height / (1 << (uint8_t)(jpeg.scale)); }
jpeg.dstlen = jpeg.width*jpeg.height*3;
jres = jd_decomp(&decoder, jpg_write_bmp, (uint8_t)jpeg.scale); bool jpg2bmp(const uint8_t *src, size_t src_len, uint8_t ** out, size_t * out_len)
if(jres != JDR_OK) { {
ESP_LOGE(TAG, "jd_decomp failed! %s", jpgd_errors[jres]);
rgb_jpg_decoder jpeg;
jpeg.width = 0;
jpeg.height = 0;
jpeg.input = src;
jpeg.output = NULL;
if(esp_jpg_decode(src_len, JPG_SCALE_NONE, _jpg_read, _rgb_write, (void*)&jpeg) != ESP_OK){
return false; return false;
} }
size_t output_size = jpeg.width*jpeg.height*3;
jpeg.output[0] = 'B';
jpeg.output[1] = 'M';
bmp_header_t * bitmap = (bmp_header_t*)&jpeg.output[2];
bitmap->reserved = 0;
bitmap->filesize = output_size+BMP_HEADER_LEN;
bitmap->fileoffset_to_pixelarray = BMP_HEADER_LEN;
bitmap->dibheadersize = 40;
bitmap->width = jpeg.width;
bitmap->height = -jpeg.height;//set negative for top to bottom
bitmap->planes = 1;
bitmap->bitsperpixel = 24;
bitmap->compression = 0;
bitmap->imagesize = output_size;
bitmap->ypixelpermeter = 0x0B13 ; //2835 , 72 DPI
bitmap->xpixelpermeter = 0x0B13 ; //2835 , 72 DPI
bitmap->numcolorspallette = 0;
bitmap->mostimpcolor = 0;
*out = jpeg.output;
*out_len = output_size+BMP_HEADER_LEN;
return true; return true;
} }
@ -154,7 +171,7 @@ bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint
{ {
int pix_count = 0; int pix_count = 0;
if(format == PIXFORMAT_JPEG) { if(format == PIXFORMAT_JPEG) {
return jpg2rgb888(src_buf, src_len, rgb_buf, JPEG_DIV_NONE); return jpg2rgb888(src_buf, src_len, rgb_buf, JPG_SCALE_NONE);
} else if(format == PIXFORMAT_RGB888) { } else if(format == PIXFORMAT_RGB888) {
memcpy(rgb_buf, src_buf, src_len); memcpy(rgb_buf, src_buf, src_len);
} else if(format == PIXFORMAT_RGB565) { } else if(format == PIXFORMAT_RGB565) {
@ -203,73 +220,6 @@ bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint
return true; return true;
} }
bool jpg2bmp(const uint8_t *src, size_t src_len, uint8_t ** out, size_t * out_len)
{
JDEC decoder;
uint8_t * out_buf = NULL;
jpg_frame_decoder_t jpeg;
jpeg.src = src;
jpeg.len = src_len;
jpeg.index = 0;
jpeg.width = 0;
jpeg.height = 0;
jpeg.dst = NULL;
jpeg.dstlen = 0;
jpeg.scale = JPEG_DIV_NONE;
*out = jpeg.dst;
*out_len = jpeg.dstlen;
JRESULT jres = jd_prepare(&decoder, jpg_read_frame, jpg_work_buffer, 3100, &jpeg);
if(jres != JDR_OK) {
ESP_LOGE(TAG, "jd_prepare failed! %s", jpgd_errors[jres]);
return false;
}
jpeg.width = decoder.width / (1 << (uint8_t)(jpeg.scale));
jpeg.height = decoder.height / (1 << (uint8_t)(jpeg.scale));
size_t output_size = jpeg.width*jpeg.height*3;
//setup output buffer
jpeg.dstlen = output_size+BMP_HEADER_LEN;
out_buf = (uint8_t *)_malloc(jpeg.dstlen);
if(!out_buf) {
ESP_LOGE(TAG, "_malloc failed! %u", jpeg.dstlen);
return false;
}
jpeg.dst = out_buf+BMP_HEADER_LEN;
out_buf[0] = 'B';
out_buf[1] = 'M';
bmp_header_t * bitmap = (bmp_header_t*)&out_buf[2];
bitmap->reserved = 0;
bitmap->filesize = jpeg.dstlen;
bitmap->fileoffset_to_pixelarray = BMP_HEADER_LEN;
bitmap->dibheadersize = 40;
bitmap->width = jpeg.width;
bitmap->height = -jpeg.height;//set negative for top to bottom
bitmap->planes = 1;
bitmap->bitsperpixel = 24;
bitmap->compression = 0;
bitmap->imagesize = output_size;
bitmap->ypixelpermeter = 0x0B13 ; //2835 , 72 DPI
bitmap->xpixelpermeter = 0x0B13 ; //2835 , 72 DPI
bitmap->numcolorspallette = 0;
bitmap->mostimpcolor = 0;
jres = jd_decomp(&decoder, jpg_write_bmp, (uint8_t)jpeg.scale);
if(jres != JDR_OK) {
ESP_LOGE(TAG, "jd_decomp failed! %s", jpgd_errors[jres]);
free(out_buf);
return false;
}
*out = out_buf;
*out_len = jpeg.dstlen;
return true;
}
bool fmt2bmp(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t ** out, size_t * out_len) bool fmt2bmp(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t ** out, size_t * out_len)
{ {
if(format == PIXFORMAT_JPEG) { if(format == PIXFORMAT_JPEG) {

View File

@ -614,6 +614,24 @@ static void IRAM_ATTR dma_filter_buffer(size_t buf_idx)
if (buf_idx == SIZE_MAX) { if (buf_idx == SIZE_MAX) {
if(!s_state->fb->ref) { if(!s_state->fb->ref) {
s_state->fb->len = s_state->dma_filtered_count * buf_len; s_state->fb->len = s_state->dma_filtered_count * buf_len;
if(s_state->fb->format == PIXFORMAT_JPEG){
//find end of file 0xFF 0xD9
uint8_t * dptr = &s_state->fb->buf[s_state->fb->len - 1];
while(dptr > s_state->fb->buf){
if(dptr[0] == 0xFF && dptr[1] == 0xD9 && dptr[2] == 0x00 && dptr[3] == 0x00){
dptr += 2;
s_state->fb->len = dptr - s_state->fb->buf;
if((s_state->fb->len & 0x1FF) == 0){
s_state->fb->len += 1;
}
if((s_state->fb->len % 100) == 0){
s_state->fb->len += 1;
}
break;
}
dptr--;
}
}
} }
if(s_state->fb->len) { if(s_state->fb->len) {
if(s_state->config.fb_count == 1) { if(s_state->config.fb_count == 1) {
@ -647,17 +665,6 @@ static void IRAM_ATTR dma_filter_buffer(size_t buf_idx)
s_state->fb->height = resolution[s_state->sensor.framesize][1]; s_state->fb->height = resolution[s_state->sensor.framesize][1];
s_state->fb->format = s_state->sensor.pixformat; s_state->fb->format = s_state->sensor.pixformat;
} }
if(s_state->config.pixel_format == PIXFORMAT_JPEG) {
if(!s_state->dma_filtered_count) { //first buffer
uint32_t sig = *((uint32_t *)s_state->fb->buf) & 0xFFFFFF;
if(sig != 0xffd8ff) {
//ets_printf("*");
//maybe skip frame?
return;
}
}
}
s_state->dma_filtered_count++; s_state->dma_filtered_count++;
} }