Merge branch 'feature/jpeg_dec_yuv_support' into 'master'

feat(jpeg): Support YUV in both decoder and encoder.

See merge request espressif/esp-idf!30240
This commit is contained in:
C.S.M 2024-04-18 14:27:08 +08:00
commit 684b63090b
23 changed files with 504 additions and 137 deletions

View File

@ -9,6 +9,7 @@
#include <stdint.h>
#include "esp_err.h"
#include "jpeg_types.h"
#include "hal/jpeg_types.h"
#ifdef __cplusplus
extern "C" {
@ -19,7 +20,7 @@ extern "C" {
*/
typedef struct {
jpeg_dec_output_format_t output_format; /*!< JPEG decoder output format */
jpeg_dec_rgb_element_order_t rgb_order; /*!< JPEG decoder output order */
jpeg_dec_rgb_element_order_t rgb_order; /*!< JPEG decoder output order */
jpeg_yuv_rgb_conv_std_t conv_std; /*!< JPEG decoder yuv->rgb standard */
} jpeg_decode_cfg_t;
@ -37,6 +38,7 @@ typedef struct {
typedef struct {
uint32_t width; /*!< Number of pixels in the horizontal direction */
uint32_t height; /*!< Number of pixels in the vertical direction */
jpeg_down_sampling_type_t sample_method; /*!< compressed JPEG picture sampling method */
} jpeg_decode_picture_info_t;
/**

View File

@ -20,6 +20,9 @@ typedef enum {
JPEG_DECODE_OUT_FORMAT_RGB888 = COLOR_TYPE_ID(COLOR_SPACE_RGB, COLOR_PIXEL_RGB888), /*!< output RGB888 format */
JPEG_DECODE_OUT_FORMAT_RGB565 = COLOR_TYPE_ID(COLOR_SPACE_RGB, COLOR_PIXEL_RGB565), /*!< output RGB565 format */
JPEG_DECODE_OUT_FORMAT_GRAY = COLOR_TYPE_ID(COLOR_SPACE_GRAY, COLOR_PIXEL_GRAY8), /*!< output the gray picture */
JPEG_DECODE_OUT_FORMAT_YUV444 = COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YUV444), /*!< output yuv444 format */
JPEG_DECODE_OUT_FORMAT_YUV422 = COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YUV422), /*!< output yuv422 format */
JPEG_DECODE_OUT_FORMAT_YUV420 = COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YUV420), /*!< output yuv420 format */
} jpeg_dec_output_format_t;
/**
@ -53,6 +56,7 @@ typedef enum {
JPEG_ENCODE_IN_FORMAT_RGB888 = COLOR_TYPE_ID(COLOR_SPACE_RGB, COLOR_PIXEL_RGB888), /*!< input RGB888 format */
JPEG_ENCODE_IN_FORMAT_RGB565 = COLOR_TYPE_ID(COLOR_SPACE_RGB, COLOR_PIXEL_RGB565), /*!< input RGB565 format */
JPEG_ENCODE_IN_FORMAT_GRAY = COLOR_TYPE_ID(COLOR_SPACE_GRAY, COLOR_PIXEL_GRAY8), /*!< input GRAY format */
JPEG_ENCODE_IN_FORMAT_YUV422 = COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YUV422), /*!< input YUV422 format */
} jpeg_enc_input_format_t;
/**

View File

@ -144,6 +144,7 @@ esp_err_t jpeg_decoder_get_info(const uint8_t *in_buf, uint32_t inbuf_len, jpeg_
uint16_t width = 0;
uint8_t thischar = 0;
uint8_t lastchar = 0;
uint8_t hivi = 0;
while (header_info->buffer_left) {
lastchar = thischar;
@ -155,6 +156,10 @@ esp_err_t jpeg_decoder_get_info(const uint8_t *in_buf, uint32_t inbuf_len, jpeg_
jpeg_get_bytes(header_info, 1);
height = jpeg_get_bytes(header_info, 2);
width = jpeg_get_bytes(header_info, 2);
jpeg_get_bytes(header_info, 1);
jpeg_get_bytes(header_info, 1);
hivi = jpeg_get_bytes(header_info, 1);
break;
}
// This function only used for get width and height. So only read SOF marker is enough.
@ -167,6 +172,21 @@ esp_err_t jpeg_decoder_get_info(const uint8_t *in_buf, uint32_t inbuf_len, jpeg_
picture_info->height = height;
picture_info->width = width;
switch (hivi) {
case 0x11:
picture_info->sample_method = JPEG_DOWN_SAMPLING_YUV444;
break;
case 0x21:
picture_info->sample_method = JPEG_DOWN_SAMPLING_YUV422;
break;
case 0x22:
picture_info->sample_method = JPEG_DOWN_SAMPLING_YUV420;
break;
default:
ESP_LOGE(TAG, "Sampling factor cannot be recognized");
return ESP_ERR_INVALID_STATE;
}
free(header_info);
return ESP_OK;
}
@ -195,14 +215,13 @@ esp_err_t jpeg_decoder_process(jpeg_decoder_handle_t decoder_engine, const jpeg_
decoder_engine->output_format = decode_cfg->output_format;
decoder_engine->rgb_order = decode_cfg->rgb_order;
decoder_engine->conv_std = decode_cfg->conv_std;
decoder_engine->decoded_buf = decode_outbuf;
ESP_GOTO_ON_ERROR(jpeg_parse_marker(decoder_engine, bit_stream, stream_size), err, TAG, "jpeg parse marker failed");
ESP_GOTO_ON_ERROR(jpeg_parse_header_info_to_hw(decoder_engine), err, TAG, "write header info to hw failed");
ESP_GOTO_ON_ERROR(jpeg_dec_config_dma_descriptor(decoder_engine), err, TAG, "config dma descriptor failed");
*out_size = decoder_engine->header_info->process_h * decoder_engine->header_info->process_v * decoder_engine->pixel;
*out_size = decoder_engine->header_info->process_h * decoder_engine->header_info->process_v * decoder_engine->bit_per_pixel / 8;
ESP_GOTO_ON_FALSE((*out_size <= outbuf_size), ESP_ERR_INVALID_ARG, err, TAG, "Given buffer size % " PRId32 " is smaller than actual jpeg decode output size % " PRId32 "the height and width of output picture size will be adjusted to 16 bytes aligned automatically", outbuf_size, *out_size);
dma2d_trans_config_t trans_desc = {
@ -222,8 +241,8 @@ esp_err_t jpeg_decoder_process(jpeg_decoder_handle_t decoder_engine, const jpeg_
// Blocking for JPEG decode transaction finishes.
while (1) {
jpeg_dma2d_dec_evt_t jpeg_dma2d_event;
BaseType_t ret = xQueueReceive(decoder_engine->evt_queue, &jpeg_dma2d_event, decoder_engine->timeout_tick);
ESP_GOTO_ON_FALSE(ret == pdTRUE, ESP_ERR_TIMEOUT, err, TAG, "jpeg-dma2d handle jpeg decode timeout, please check `timeout_ms` ");
BaseType_t ret_val = xQueueReceive(decoder_engine->evt_queue, &jpeg_dma2d_event, decoder_engine->timeout_tick);
ESP_GOTO_ON_FALSE(ret_val == pdTRUE, ESP_ERR_TIMEOUT, err, TAG, "jpeg-dma2d handle jpeg decode timeout, please check `timeout_ms` ");
// Dealing with JPEG event
if (jpeg_dma2d_event.jpgd_status != 0) {
@ -328,23 +347,49 @@ static esp_err_t jpeg_dec_config_dma_descriptor(jpeg_decoder_handle_t decoder_en
jpeg_dec_format_hb_t best_hb_idx = 0;
color_space_pixel_format_t picture_format;
picture_format.color_type_id = decoder_engine->output_format;
decoder_engine->pixel = color_hal_pixel_format_get_bit_depth(picture_format) / 8;
switch (decoder_engine->output_format) {
case JPEG_DECODE_OUT_FORMAT_RGB888:
best_hb_idx = JPEG_DEC_RGB888_HB;
decoder_engine->bit_per_pixel = color_hal_pixel_format_get_bit_depth(picture_format);
if (decoder_engine->no_color_conversion == false) {
switch (decoder_engine->output_format) {
case JPEG_DECODE_OUT_FORMAT_RGB888:
best_hb_idx = JPEG_DEC_RGB888_HB;
break;
case JPEG_DECODE_OUT_FORMAT_RGB565:
best_hb_idx = JPEG_DEC_RGB565_HB;
break;
case JPEG_DECODE_OUT_FORMAT_GRAY:
best_hb_idx = JPEG_DEC_GRAY_HB;
break;
case JPEG_DECODE_OUT_FORMAT_YUV444:
best_hb_idx = JPEG_DEC_YUV444_HB;
break;
default:
ESP_LOGE(TAG, "wrong, we don't support decode to such format.");
return ESP_ERR_NOT_SUPPORTED;
}
} else {
best_hb_idx = JPEG_DEC_DIRECT_OUTPUT_HB;
}
uint8_t sample_method_idx = 0;
switch (decoder_engine->sample_method) {
case JPEG_DOWN_SAMPLING_YUV444:
sample_method_idx = 0;
break;
case JPEG_DECODE_OUT_FORMAT_RGB565:
best_hb_idx = JPEG_DEC_RGB565_HB;
case JPEG_DOWN_SAMPLING_YUV422:
sample_method_idx = 1;
break;
case JPEG_DECODE_OUT_FORMAT_GRAY:
best_hb_idx = JPEG_DEC_GRAY_HB;
case JPEG_DOWN_SAMPLING_YUV420:
sample_method_idx = 2;
break;
case JPEG_DOWN_SAMPLING_GRAY:
sample_method_idx = 3;
break;
default:
ESP_LOGE(TAG, "wrong, we don't support decode to such format.");
ESP_LOGE(TAG, "wrong, we don't support such sampling mode.");
return ESP_ERR_NOT_SUPPORTED;
}
uint32_t dma_hb = dec_hb_tbl[decoder_engine->sample_method][best_hb_idx];
uint32_t dma_hb = dec_hb_tbl[sample_method_idx][best_hb_idx];
uint32_t dma_vb = decoder_engine->header_info->mcuy;
// Configure tx link descriptor
@ -375,7 +420,6 @@ static void jpeg_dec_config_dma_csc(jpeg_decoder_handle_t decoder_engine, dma2d_
dma2d_scramble_order_t post_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0;
dma2d_csc_rx_option_t rx_csc_option = DMA2D_CSC_RX_NONE;
// Config output Endians
if (decoder_engine->rgb_order == JPEG_DEC_RGB_ELEMENT_ORDER_RGB) {
if (decoder_engine->output_format == JPEG_DECODE_OUT_FORMAT_RGB565) {
@ -398,7 +442,13 @@ static void jpeg_dec_config_dma_csc(jpeg_decoder_handle_t decoder_engine, dma2d_
} else if (decoder_engine->conv_std == JPEG_YUV_RGB_CONV_STD_BT709) {
rx_csc_option = DMA2D_CSC_RX_YUV420_TO_RGB888_709;
}
} else if (decoder_engine->output_format == JPEG_DECODE_OUT_FORMAT_GRAY) {
} else if (decoder_engine->output_format == JPEG_DECODE_OUT_FORMAT_YUV444) {
if (decoder_engine->sample_method == JPEG_DOWN_SAMPLING_YUV422) {
rx_csc_option = DMA2D_CSC_RX_YUV422_TO_YUV444;
} else if (decoder_engine->sample_method == JPEG_DOWN_SAMPLING_YUV420) {
rx_csc_option = DMA2D_CSC_RX_YUV420_TO_YUV444;
}
} else {
rx_csc_option = DMA2D_CSC_RX_NONE;
}
@ -493,6 +543,27 @@ static bool jpeg_dec_transaction_on_picked(uint32_t channel_num, const dma2d_tra
return false;
}
static esp_err_t jpeg_color_space_support_check(jpeg_decoder_handle_t decoder_engine)
{
if (decoder_engine->sample_method == JPEG_DOWN_SAMPLING_YUV444) {
if (decoder_engine->output_format == JPEG_DECODE_OUT_FORMAT_YUV422 || decoder_engine->output_format == JPEG_DECODE_OUT_FORMAT_YUV420) {
ESP_LOGE(TAG, "Detected YUV444 but want to convert to YUV422/YUV420, which is not supported");
return ESP_ERR_INVALID_ARG;
}
} else if (decoder_engine->sample_method == JPEG_DOWN_SAMPLING_YUV422) {
if (decoder_engine->output_format == JPEG_DECODE_OUT_FORMAT_YUV420) {
ESP_LOGE(TAG, "Detected YUV422 but want to convert to YUV420, which is not supported");
return ESP_ERR_INVALID_ARG;
}
} else if (decoder_engine->sample_method == JPEG_DOWN_SAMPLING_YUV420) {
if (decoder_engine->output_format == JPEG_DECODE_OUT_FORMAT_YUV422) {
ESP_LOGE(TAG, "Detected YUV420 but want to convert to YUV422, which is not supported");
return ESP_ERR_INVALID_ARG;
}
}
return ESP_OK;
}
static esp_err_t jpeg_parse_header_info_to_hw(jpeg_decoder_handle_t decoder_engine)
{
ESP_RETURN_ON_FALSE(decoder_engine, ESP_ERR_INVALID_ARG, TAG, "jpeg decode handle is null");
@ -534,6 +605,12 @@ static esp_err_t jpeg_parse_header_info_to_hw(jpeg_decoder_handle_t decoder_engi
decoder_engine->sample_method = JPEG_DOWN_SAMPLING_GRAY;
}
ESP_RETURN_ON_ERROR(jpeg_color_space_support_check(decoder_engine), TAG, "jpeg decoder not support the combination of output format and down sampling format");
if ((uint32_t)decoder_engine->sample_method == (uint32_t)decoder_engine->output_format) {
decoder_engine->no_color_conversion = true;
}
// Write DHT information
dht_func[0][0](hal, header_info->huffbits[0][0], header_info->huffcode[0][0], header_info->tmp_huff);
dht_func[0][1](hal, header_info->huffbits[0][1], header_info->huffcode[0][1], header_info->tmp_huff);

View File

@ -142,6 +142,9 @@ esp_err_t jpeg_encoder_process(jpeg_encoder_handle_t encoder_engine, const jpeg_
ESP_RETURN_ON_FALSE(encode_inbuf, ESP_ERR_INVALID_ARG, TAG, "jpeg encode picture buffer is null");
ESP_RETURN_ON_FALSE(out_size, ESP_ERR_INVALID_ARG, TAG, "jpeg encode picture out_size is null");
ESP_RETURN_ON_FALSE(((uintptr_t)bit_stream % cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA)) == 0, ESP_ERR_INVALID_ARG, TAG, "jpeg encode bit stream is not aligned, please use jpeg_alloc_encoder_mem to malloc your buffer");
if (encode_cfg->src_type == JPEG_ENCODE_IN_FORMAT_YUV422) {
ESP_RETURN_ON_FALSE(encode_cfg->sub_sample == JPEG_DOWN_SAMPLING_YUV422, ESP_ERR_INVALID_ARG, TAG, "Sub sampling is not supported under this source type");
}
esp_err_t ret = ESP_OK;
@ -176,6 +179,10 @@ esp_err_t jpeg_encoder_process(jpeg_encoder_handle_t encoder_engine, const jpeg_
encoder_engine->color_space = JPEG_ENC_SRC_GRAY;
best_hb_idx = JPEG_ENC_SRC_GRAY_HB;
break;
case JPEG_ENCODE_IN_FORMAT_YUV422:
encoder_engine->color_space = JPEG_ENC_SRC_YUV422;
best_hb_idx = JPEG_ENC_SRC_YUV422_HB;
break;
default:
ESP_LOGE(TAG, "wrong, we don't support encode from such format.");
ret = ESP_ERR_NOT_SUPPORTED;
@ -198,7 +205,26 @@ esp_err_t jpeg_encoder_process(jpeg_encoder_handle_t encoder_engine, const jpeg_
ESP_GOTO_ON_ERROR(s_jpeg_set_header_info(encoder_engine), err, TAG, "set header failed");
jpeg_hal_set_quantization_coefficient(hal, encoder_engine->header_info->m_quantization_tables[0], encoder_engine->header_info->m_quantization_tables[1]);
uint32_t dma_hb = enc_hb_tbl[best_hb_idx][encoder_engine->header_info->sub_sample];
uint8_t sample_method_idx = 0;
switch (encoder_engine->header_info->sub_sample) {
case JPEG_DOWN_SAMPLING_YUV444:
sample_method_idx = 0;
break;
case JPEG_DOWN_SAMPLING_YUV422:
sample_method_idx = 1;
break;
case JPEG_DOWN_SAMPLING_YUV420:
sample_method_idx = 2;
break;
case JPEG_DOWN_SAMPLING_GRAY:
sample_method_idx = 3;
break;
default:
ESP_LOGE(TAG, "wrong, we don't support such sampling mode.");
return ESP_ERR_NOT_SUPPORTED;
}
uint32_t dma_hb = enc_hb_tbl[best_hb_idx][sample_method_idx];
uint32_t dma_vb = encoder_engine->mcuy;
ESP_GOTO_ON_FALSE((encoder_engine->header_info->header_len % cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA)) == 0, ESP_ERR_INVALID_STATE, err, TAG, "The header is not cache line aligned, please check");
@ -237,8 +263,8 @@ esp_err_t jpeg_encoder_process(jpeg_encoder_handle_t encoder_engine, const jpeg_
if (s_rcv_event.dma_evt & JPEG_DMA2D_RX_EOF) {
compressed_size = s_dma_desc_get_len(encoder_engine->rxlink);
compressed_size = JPEG_ALIGN_UP(compressed_size, cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA));
ESP_GOTO_ON_ERROR(esp_cache_msync((void*)(bit_stream + encoder_engine->header_info->header_len), compressed_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C), err, TAG, "sync memory to cache failed");
uint32_t _compressed_size = JPEG_ALIGN_UP(compressed_size, cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA));
ESP_GOTO_ON_ERROR(esp_cache_msync((void*)(bit_stream + encoder_engine->header_info->header_len), _compressed_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C), err, TAG, "sync memory to cache failed");
break;
}
}

View File

@ -39,7 +39,7 @@ const uint8_t zigzag_arr[64] = {
* decompression. It is used to decode the Huffman-coded symbols in the compressed
* data stream during the decoding process.
*/
const uint32_t dec_hb_tbl[JPEG_DOWN_SAMPLING_MAX][JPEG_DEC_BEST_HB_MAX] = {
const uint32_t dec_hb_tbl[JPEG_DOWN_SAMPLING_NUM][JPEG_DEC_BEST_HB_MAX] = {
{40, 40, 40, 32, 0},
{64, 32, 32, 64, 0},
{48, 32, 32, 48, 0},
@ -53,7 +53,7 @@ const uint32_t dec_hb_tbl[JPEG_DOWN_SAMPLING_MAX][JPEG_DEC_BEST_HB_MAX] = {
* compression. It is used to decode the Huffman-coded symbols in the compressed
* data stream during the encoding process.
*/
const uint32_t enc_hb_tbl[JPEG_ENC_BEST_HB_MAX][JPEG_DOWN_SAMPLING_MAX] = {
const uint32_t enc_hb_tbl[JPEG_ENC_BEST_HB_MAX][JPEG_DOWN_SAMPLING_NUM] = {
{40, 32, 32, 0},
{0, 64, 0, 0},
{64, 64, 48, 0},

View File

@ -56,9 +56,8 @@ struct jpeg_codec_t {
};
typedef enum {
// TODO: Support DR and YUV444 on decoder.
//JPEG_DEC_DR_HB = 0, /*!< Direct output */
//JPEG_DEC_YUV444_HB = 1, /*!< output YUV444 format */
JPEG_DEC_DIRECT_OUTPUT_HB = 0, /*!< Direct output */
JPEG_DEC_YUV444_HB = 1, /*!< output YUV444 format */
JPEG_DEC_RGB888_HB = 2, /*!< output RGB888 format */
JPEG_DEC_RGB565_HB = 3, /*!< output RGB565 format */
JPEG_DEC_GRAY_HB = 4, /*!< output the gray picture */
@ -96,9 +95,10 @@ struct jpeg_decoder_t {
jpeg_dec_header_info_t *header_info; // Pointer to current picture information
jpeg_down_sampling_type_t sample_method; // method of sampling the JPEG picture.
jpeg_dec_output_format_t output_format; // picture output format.
jpeg_dec_rgb_element_order_t rgb_order; // RGB pixel order
jpeg_dec_rgb_element_order_t rgb_order; // RGB pixel order
jpeg_yuv_rgb_conv_std_t conv_std; // YUV RGB conversion standard
uint8_t pixel; // size per pixel
bool no_color_conversion; // No color conversion, directly output based on compressed format
uint8_t bit_per_pixel; // bit size per pixel
QueueHandle_t evt_queue; // jpeg event from 2DDMA and JPEG engine
uint8_t *decoded_buf; // pointer to the rx buffer.
uint32_t total_size; // jpeg picture origin size (in bytes)
@ -127,8 +127,7 @@ typedef struct {
typedef enum {
JPEG_ENC_SRC_RGB888_HB = 0, // Input RGB888 format
// TODO: Support encoder source format for yuv422
// JPEG_ENC_SRC_YUV422_HB = 1, // Input YUV422 format
JPEG_ENC_SRC_YUV422_HB = 1, // Input YUV422 format
JPEG_ENC_SRC_RGB565_HB = 2, // Input RGB565 format
JPEG_ENC_SRC_GRAY_HB = 3, // Input GRAY format
JPEG_ENC_BEST_HB_MAX,

View File

@ -36,7 +36,7 @@ extern const uint8_t zigzag_arr[64];
* decompression. It is used to decode the Huffman-coded symbols in the compressed
* data stream during the decoding process.
*/
extern const uint32_t dec_hb_tbl[JPEG_DOWN_SAMPLING_MAX][JPEG_DEC_BEST_HB_MAX];
extern const uint32_t dec_hb_tbl[JPEG_DOWN_SAMPLING_NUM][JPEG_DEC_BEST_HB_MAX];
/**
* @brief DMA2D best hb value table for JPEG compression.
@ -45,7 +45,7 @@ extern const uint32_t dec_hb_tbl[JPEG_DOWN_SAMPLING_MAX][JPEG_DEC_BEST_HB_MAX];
* compression. It is used to decode the Huffman-coded symbols in the compressed
* data stream during the encoding process.
*/
extern const uint32_t enc_hb_tbl[JPEG_ENC_BEST_HB_MAX][JPEG_DOWN_SAMPLING_MAX];
extern const uint32_t enc_hb_tbl[JPEG_ENC_BEST_HB_MAX][JPEG_DOWN_SAMPLING_NUM];
/**
* @brief Setup the standard Huffman tables (JPEG standard sections K.3.3)

View File

@ -23,6 +23,7 @@ extern "C" {
#define DHT_TC_NUM (2) /// Table type
#define DHT_TH_NUM (2) /// Huffman table destination identifier
#define JPEG_DOWN_SAMPLING_NUM (4) // The number of down sampling methods
/**
* @brief Enum for JPEG codec working mode.
@ -44,9 +45,9 @@ typedef struct {
* @brief Enum for JPEG sampling mode.
*/
typedef enum {
JPEG_SAMPLE_MODE_YUV444 = COLOR_PIXEL_YUV444, ///< sample in YUV444
JPEG_SAMPLE_MODE_YUV422 = COLOR_PIXEL_YUV422, ///< sample in YUV422
JPEG_SAMPLE_MODE_YUV420 = COLOR_PIXEL_YUV420, ///< sample in YUV420
JPEG_SAMPLE_MODE_YUV444 = COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YUV444), ///< sample in YUV444
JPEG_SAMPLE_MODE_YUV422 = COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YUV422), ///< sample in YUV422
JPEG_SAMPLE_MODE_YUV420 = COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YUV420), ///< sample in YUV420
} jpeg_sample_mode_t;
/**
@ -64,11 +65,10 @@ typedef union {
* @brief Enumeration for jpeg decoder sample methods.
*/
typedef enum {
JPEG_DOWN_SAMPLING_YUV444 = 0, /*!< Sample by YUV444 */
JPEG_DOWN_SAMPLING_YUV422 = 1, /*!< Sample by YUV422 */
JPEG_DOWN_SAMPLING_YUV420 = 2, /*!< Sample by YUV420 */
JPEG_DOWN_SAMPLING_GRAY = 3, /*!< Sample the gray picture */
JPEG_DOWN_SAMPLING_MAX, /*!< Max value of sample enumeration */
JPEG_DOWN_SAMPLING_YUV444 = COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YUV444), /*!< Sample by YUV444 */
JPEG_DOWN_SAMPLING_YUV422 = COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YUV422), /*!< Sample by YUV422 */
JPEG_DOWN_SAMPLING_YUV420 = COLOR_TYPE_ID(COLOR_SPACE_YUV, COLOR_PIXEL_YUV420), /*!< Sample by YUV420 */
JPEG_DOWN_SAMPLING_GRAY = COLOR_TYPE_ID(COLOR_SPACE_GRAY, COLOR_PIXEL_GRAY8), /*!< Sample the gray picture */
} jpeg_down_sampling_type_t;
/**

BIN
docs/_static/diagrams/jpeg/rgb565.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/_static/diagrams/jpeg/rgb888.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/_static/diagrams/jpeg/yuv420.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/_static/diagrams/jpeg/yuv422.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/_static/diagrams/jpeg/yuv444.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -18,6 +18,7 @@ This document covers the following sections:
- `JPEG Decoder Engine <#jpeg-decoder-engine>`__ - covers behavior of JPEG decoder engine. Introduce how to use decoder engine functions to decode an image (from jpg format to raw format).
- `JPEG Encoder Engine <#jpeg-encoder-engine>`__ - covers behavior of JPEG encoder engine. Introduce how to use encoder engine functions to encode an image (from raw format to jpg format).
- `Performance Overview <#performance-overview>`__ - covers encoder and decoder performance.
- `Pixel Storage Layout for Different Color Formats <#pixel-storage-layout-for-different-color-formats>`__ - covers color space order overview required in this JPEG decoder and encoder.
- `Thread Safety <#thread-safety>`__ - lists which APIs are guaranteed to be thread safe by the driver.
- `Power Management <#power-management>`__ - describes how jpeg driver would be affected by power consumption.
- `Kconfig Options <#kconfig-options>`__ - lists the supported Kconfig options that can bring different effects to the driver.
@ -104,18 +105,28 @@ The format conversions supported by this driver are listed in the table below:
| Format of the already compressed image | Format after decompressing |
+========================================+===================================+
| | RGB565 |
| YUV444 +-----------------------------------+
| YUV444 +-----------------------------------+
| | RGB888 |
| +-----------------------------------+
| | YUV444 |
+----------------------------------------+-----------------------------------+
| | RGB565 |
| YUV422 +-----------------------------------+
| +-----------------------------------+
| | RGB888 |
| YUV422 +-----------------------------------+
| | YUV444 |
| +-----------------------------------+
| | YUV422 |
+----------------------------------------+-----------------------------------+
| | RGB565 |
| YUV420 +-----------------------------------+
| +-----------------------------------+
| | RGB888 |
| YUV420 +-----------------------------------+
| | YUV444 |
| +-----------------------------------+
| | YUV420 |
+----------------------------------------+-----------------------------------+
| GRAY | GRAY |
| GRAY | GRAY |
+----------------------------------------+-----------------------------------+
Overall, You can take following code as reference, the code is going to decode a 1080*1920 picture.
@ -167,11 +178,13 @@ The format conversions supported by this driver are listed in the table below:
+==========================+======================================+
| | YUV444 |
| +--------------------------------------+
| RGB565/RGB888 | YUV422 |
| RGB565/RGB888 | YUV422 |
| +--------------------------------------+
| | YUV420 |
+--------------------------+--------------------------------------+
| GRAY | GRAY |
| GRAY | GRAY |
+--------------------------+--------------------------------------+
| YUV422 | YUV422 |
+--------------------------+--------------------------------------+
@ -236,6 +249,12 @@ JPEG decoder performance
+--------+-------+--------------------------------------------+----------------------------------------+------------------+
| 720 | 1280 | GRAY | GRAY | 161 |
+--------+-------+--------------------------------------------+----------------------------------------+------------------+
| 480 | 800 | YUV444 | YUV444 | 129 |
+--------+-------+--------------------------------------------+----------------------------------------+------------------+
| 480 | 800 | YUV422 | YUV444/YUV422 | 190 |
+--------+-------+--------------------------------------------+----------------------------------------+------------------+
| 480 | 800 | YUV420 | YUV444/YUV420 | 253 |
+--------+-------+--------------------------------------------+----------------------------------------+------------------+
JPEG encoder performance
~~~~~~~~~~~~~~~~~~~~~~~~
@ -269,8 +288,86 @@ JPEG encoder performance
+--------+-------+-----------------------------------------+-------------------------------------------+------------------+
| 720 | 1280 | GRAY | GRAY | 163 |
+--------+-------+-----------------------------------------+-------------------------------------------+------------------+
| 480 | 800 | YUV422 | YUV422 | 146 |
+--------+-------+-----------------------------------------+-------------------------------------------+------------------+
Pixel Storage Layout for Different Color Formats
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The encoder and decoder described in this guide use the same uncompressed raw image formats (RGB, YUV). Therefore, the encoder and decoder are not discussed separately in this section. The pixel layout of the following formats applies to the input direction of the encoder and the output direction of the decoder (if supported). The specific pixel layout is shown in the following figure:
RGB888
~~~~~~
In the following picture, each small block means one bit.
.. figure:: ../../../_static/diagrams/jpeg/rgb888.png
:align: center
:alt: RGB888 pixel order
RGB888 pixel order
For RGB888, the order can be changed via :cpp:member:`jpeg_decode_cfg_t::rgb_order` sets the pixel to `RGB` order.
.. figure:: ../../../_static/diagrams/jpeg/rgb888_bigendian.png
:align: center
:alt: RGB888 pixel big endian order
RGB888 pixel big endian order
RGB565
~~~~~~
In the following picture, each small block means one bit.
.. figure:: ../../../_static/diagrams/jpeg/rgb565.png
:align: center
:alt: RGB565 pixel order
RGB565 pixel order
For RGB565, the order can be changed via :cpp:member:`jpeg_decode_cfg_t::rgb_order` sets the pixel to `RGB` order.
.. figure:: ../../../_static/diagrams/jpeg/rgb565_bigendian.png
:align: center
:alt: RGB565 pixel big endian order
RGB565 pixel big endian order
YUV444
~~~~~~
In the following picture, each small block means one byte.
.. figure:: ../../../_static/diagrams/jpeg/yuv444.png
:align: center
:alt: YUV444 pixel order
YUV444 pixel order
YUV422
~~~~~~
In the following picture, each small block means one byte.
.. figure:: ../../../_static/diagrams/jpeg/yuv422.png
:align: center
:alt: YUV422 pixel order
YUV422 pixel order
YUV420
~~~~~~
In the following picture, each small block means one byte.
.. figure:: ../../../_static/diagrams/jpeg/yuv420.png
:align: center
:alt: YUV420 pixel order
YUV420 pixel order
Thread Safety
^^^^^^^^^^^^^

View File

@ -51,7 +51,7 @@ I (13336) jpeg.example: Card unmounted
I (13336) main_task: Returned from app_main()
```
Moreover, we provided a helper script called `open_rgb.py`, which can help you easily see the outputs on your computer. For requirements component you need, you can call `pip install -r requirements.txt` under `examples/peripheral/jpeg/jpeg_decode` folder.
Also, the helper script [open_raw_picture.py](./open_raw_picture.py) simplifies the visualization of the output on your computer. For this to work, go to `examples/peripheral/jpeg/jpeg_decode` and install the requirements by running `pip install -r requirements.txt`.
## Troubleshooting

View File

@ -6,4 +6,13 @@ menu "JPEG Decode Example menu"
help
If this config item is set, format_if_mount_failed will be set to true and the card will be formatted if
the mount has failed.
config EXAMPLE_SDMMC_IO_POWER_INTERNAL_LDO
depends on SOC_SDMMC_IO_POWER_EXTERNAL
bool "SDMMC IO power supply comes from internal LDO (READ HELP!)"
default y
help
Please read the schematic first and check if the SDMMC VDD is connected to any internal LDO output.
If the SDMMC is powered by an external supplier, unselect me
endmenu

View File

@ -11,6 +11,7 @@
#include "driver/sdmmc_host.h"
#include "esp_attr.h"
#include "driver/jpeg_decode.h"
#include "sd_pwr_ctrl_by_on_chip_ldo.h"
static const char *TAG = "jpeg.example";
static sdmmc_card_t *s_card;
@ -38,6 +39,21 @@ static esp_err_t sdcard_init(void)
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
#if CONFIG_EXAMPLE_SDMMC_IO_POWER_INTERNAL_LDO
sd_pwr_ctrl_ldo_config_t ldo_config = {
.ldo_chan_id = 4, // `LDO_VO4` is used as the SDMMC IO power
};
sd_pwr_ctrl_handle_t pwr_ctrl_handle = NULL;
ret = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &pwr_ctrl_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to new an on-chip ldo power control driver");
return ret;
}
host.pwr_ctrl_handle = pwr_ctrl_handle;
#endif
// This initializes the slot without card detect (CD) and write protect (WP) signals.
// Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
@ -65,6 +81,13 @@ static void sdcard_deinit(void)
{
const char mount_point[] = MOUNT_POINT;
esp_vfs_fat_sdcard_unmount(mount_point, s_card);
#if SOC_SDMMC_IO_POWER_EXTERNAL
esp_err_t ret = sd_pwr_ctrl_del_on_chip_ldo(s_card->host.pwr_ctrl_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to delete on-chip ldo power control driver");
return;
}
#endif
}
void app_main(void)

View File

@ -0,0 +1,189 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import argparse
import cv2 as cv
import numpy as np
from numpy.typing import NDArray
def open_picture(path): # type: (str) -> list[int]
with open(path, 'rb') as f:
data = f.read()
f.close()
new_data = [int(x) for x in data]
return new_data
def picture_show_rgb888(data, h, w): # type: (list[int], int, int) -> None
data = np.array(data).reshape(h, w, 3).astype(np.uint8)
cv.imshow('data', data)
cv.waitKey()
def picture_show_rgb565(data, h, w): # type: (list[int], int, int) -> None
new_data = [0] * ((len(data) // 2) * 3)
for i in range(len(data)):
if i % 2 != 0:
new_data[3 * (i - 1) // 2 + 2] = (data[i] & 0xf8)
new_data[3 * (i - 1) // 2 + 1] |= (data[i] & 0x7) << 5
else:
new_data[3 * i // 2] = (data[i] & 0x1f) << 3
new_data[3 * i // 2 + 1] |= (data[i] & 0xe0) >> 3
new_data = np.array(new_data).reshape(h, w, 3).astype(np.uint8)
cv.imshow('data', new_data)
cv.waitKey()
def picture_show_gray(data, h, w): # type: (list[int], int, int) -> None
new_data = np.array(data).reshape(h, w, 1).astype(np.uint8)
cv.imshow('data', new_data)
cv.waitKey()
def convert_YUV_to_RGB(Y, U, V): # type: (NDArray, NDArray, NDArray) -> tuple[NDArray, NDArray, NDArray]
B = np.clip(Y + 1.7790 * (U - 128), 0, 255).astype(np.uint8)
G = np.clip(Y - 0.3455 * (U - 128) - 0.7169 * (V - 128), 0, 255).astype(np.uint8)
R = np.clip(Y + 1.4075 * (V - 128), 0, 255).astype(np.uint8)
return B, G, R
def picture_show_yuv420(data, h, w): # type: (list[int], int, int) -> None
new_u = [0] * (h * w)
new_v = [0] * (h * w)
new_y = [0] * (h * w)
for i in range(int(h * w * 1.5)):
is_even_row = ((i // (w * 1.5)) % 2 == 0)
if is_even_row:
if (i % 3 == 0):
new_u[(i // 3) * 2] = data[i]
new_u[(i // 3) * 2 + 1] = data[i]
else:
if (i % 3 == 0):
new_u[(i // 3) * 2] = new_u[int((i - (w * 1.5)) // 3) * 2]
new_u[(i // 3) * 2 + 1] = new_u[int((i - (w * 1.5)) // 3) * 2 + 1]
for i in range(int(h * w * 1.5)):
if (i // (w * 1.5)) % 2 != 0 and (i % 3 == 0):
idx = (i // 3) * 2
new_v[idx] = data[i]
new_v[idx + 1] = data[i]
for i in range(int(h * w * 1.5)):
if (i // (w * 1.5)) % 2 == 0 and (i % 3 == 0):
idx = (i // 3) * 2
new_v[idx] = new_v[int((i + (w * 1.5)) // 3) * 2]
new_v[idx + 1] = new_v[int((i + (w * 1.5)) // 3) * 2 + 1]
new_y = [data[i] for i in range(int(h * w * 1.5)) if i % 3 != 0]
Y = np.array(new_y)
U = np.array(new_u)
V = np.array(new_v)
B, G, R = convert_YUV_to_RGB(Y, U, V)
# Merge channels
new_data = np.stack((B, G, R), axis=-1)
new_data = np.array(new_data).reshape(h, w, 3).astype(np.uint8)
# Display the image
cv.imshow('data', new_data)
cv.waitKey()
def picture_show_yuv422(data, h, w): # type: (list[int], int, int) -> None
# Reshape the input data to a 2D array
data_array = np.array(data).reshape(h, w * 2)
# Separate Y, U, and V channels
Y = data_array[:, 1::2]
U = data_array[:, 0::4].repeat(2, axis=1)
V = data_array[:, 2::4].repeat(2, axis=1)
# Convert YUV to RGB
B, G, R = convert_YUV_to_RGB(Y, U, V)
# Merge channels
new_data = np.stack((B, G, R), axis=-1)
# Display the image
cv.imshow('data', new_data)
cv.waitKey()
def picture_show_yuv444(data, h, w): # type: (list[int], int, int) -> None
# Reshape the input data to a 2D array
data_array = np.array(data).reshape(h, w * 3)
# Separate Y, U, and V channels
Y = data_array[:, 2::3]
U = data_array[:, 1::3]
V = data_array[:, 0::3]
# Convert YUV to RGB
B, G, R = convert_YUV_to_RGB(Y, U, V)
# Merge channels
new_data = np.stack((B, G, R), axis=-1)
# Display the image
cv.imshow('data', new_data)
cv.waitKey()
def main(): # type: () -> None
parser = argparse.ArgumentParser(description='which mode need to show')
parser.add_argument(
'--pic_path',
type=str,
help='What is the path of your picture',
required=True)
parser.add_argument(
'--pic_type',
type=str,
help='What type you want to show',
required=True,
choices=['rgb565', 'rgb888', 'gray', 'yuv422', 'yuv420', 'yuv444'])
parser.add_argument(
'--height',
type=int,
help='the picture height',
default=480)
parser.add_argument(
'--width',
type=int,
help='the picture width',
default=640)
args = parser.parse_args()
height = args.height
width = args.width
data = open_picture(args.pic_path)
if (args.pic_type == 'rgb565'):
picture_show_rgb565(data, height, width)
elif (args.pic_type == 'rgb888'):
picture_show_rgb888(data, height, width)
elif (args.pic_type == 'gray'):
picture_show_gray(data, height, width)
elif (args.pic_type == 'yuv420'):
picture_show_yuv420(data, height, width)
elif (args.pic_type == 'yuv422'):
picture_show_yuv422(data, height, width)
elif (args.pic_type == 'yuv444'):
picture_show_yuv444(data, height, width)
else:
print('This type is not supported in this script!')
if __name__ == '__main__':
main()

View File

@ -1,91 +0,0 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import argparse
import cv2 as cv
import numpy as np
def open_picture(path): # type: (str) -> list[int]
with open(path, 'rb') as f:
data = f.read()
f.close()
new_data = [int(x) for x in data]
return new_data
def picture_show_rgb888(data, h, w): # type: (list[int], int, int) -> None
data = np.array(data).reshape(h, w, 3).astype(np.uint8)
cv.imshow('data', data)
cv.waitKey()
def picture_show_rgb565(data, h, w): # type: (list[int], int, int) -> None
new_data = [0] * ((len(data) // 2) * 3)
for i in range(len(data)):
if i % 2 != 0:
new_data[3 * (i - 1) // 2 + 2] = (data[i] & 0xf8)
new_data[3 * (i - 1) // 2 + 1] |= (data[i] & 0x7) << 5
else:
new_data[3 * i // 2] = (data[i] & 0x1f) << 3
new_data[3 * i // 2 + 1] |= (data[i] & 0xe0) >> 3
new_data = np.array(new_data).reshape(h, w, 3).astype(np.uint8)
cv.imshow('data', new_data)
cv.waitKey()
def picture_show_gray(data, h, w): # type: (list[int], int, int) -> None
new_data = np.array(data).reshape(h, w, 1).astype(np.uint8)
cv.imshow('data', new_data)
cv.waitKey()
def main(): # type: () -> None
parser = argparse.ArgumentParser(description='which mode need to show')
parser.add_argument(
'--pic_path',
type=str,
help='What is the path of your picture',
required=True)
parser.add_argument(
'--pic_type',
type=str,
help='What type you want to show',
required=True,
choices=['rgb565', 'rgb888', 'gray'])
parser.add_argument(
'--hight',
type=int,
help='the picture hight',
default=480)
parser.add_argument(
'--width',
type=int,
help='the picture width',
default=640)
args = parser.parse_args()
hight = args.hight
width = args.width
data = open_picture(args.pic_path)
if (args.pic_type == 'rgb565'):
picture_show_rgb565(data, hight, width)
elif (args.pic_type == 'rgb888'):
picture_show_rgb888(data, hight, width)
elif (args.pic_type == 'gray'):
picture_show_gray(data, hight, width)
else:
print('This type is not supported in this script!')
if __name__ == '__main__':
main()

View File

@ -6,4 +6,13 @@ menu "JPEG Encode Example menu"
help
If this config item is set, format_if_mount_failed will be set to true and the card will be formatted if
the mount has failed.
config EXAMPLE_SDMMC_IO_POWER_INTERNAL_LDO
depends on SOC_SDMMC_IO_POWER_EXTERNAL
bool "SDMMC IO power supply comes from internal LDO (READ HELP!)"
default y
help
Please read the schematic first and check if the SDMMC VDD is connected to any internal LDO output.
If the SDMMC is powered by an external supplier, unselect me
endmenu

View File

@ -10,6 +10,7 @@
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
#include "driver/jpeg_encode.h"
#include "sd_pwr_ctrl_by_on_chip_ldo.h"
static const char *TAG = "jpeg.example";
static sdmmc_card_t *s_card;
@ -35,6 +36,21 @@ static esp_err_t sdcard_init(void)
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
#if CONFIG_EXAMPLE_SDMMC_IO_POWER_INTERNAL_LDO
sd_pwr_ctrl_ldo_config_t ldo_config = {
.ldo_chan_id = 4, // `LDO_VO4` is used as the SDMMC IO power
};
sd_pwr_ctrl_handle_t pwr_ctrl_handle = NULL;
ret = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &pwr_ctrl_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to new an on-chip ldo power control driver");
return ret;
}
host.pwr_ctrl_handle = pwr_ctrl_handle;
#endif
// This initializes the slot without card detect (CD) and write protect (WP) signals.
// Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
@ -62,6 +78,13 @@ static void sdcard_deinit(void)
{
const char mount_point[] = MOUNT_POINT;
esp_vfs_fat_sdcard_unmount(mount_point, s_card);
#if SOC_SDMMC_IO_POWER_EXTERNAL
esp_err_t ret = sd_pwr_ctrl_del_on_chip_ldo(s_card->host.pwr_ctrl_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to delete on-chip ldo power control driver");
return;
}
#endif
}
void app_main(void)