Merge branch 'bugfix/improve_jpeg_dec' into 'master'

fix(jpeg_decoder): Improve apis for better align strategy and provide buffer size parameter

See merge request espressif/esp-idf!29687
This commit is contained in:
C.S.M 2024-03-19 10:10:52 +08:00
commit 61bb752cdb
7 changed files with 97 additions and 31 deletions

View File

@ -19,7 +19,7 @@ extern "C" {
*/
typedef struct {
jpeg_dec_output_format_t output_format; /*!< JPEG decoder output format */
jpeg_dec_rgb_element_order 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;
@ -39,6 +39,13 @@ typedef struct {
uint32_t height; /*!< Number of pixels in the vertical direction */
} jpeg_decode_picture_info_t;
/**
* @brief JPEG decoder memory allocation config
*/
typedef struct {
jpeg_dec_buffer_alloc_direction_t buffer_direction; /*!< Buffer direction for jpeg decoder memory allocation */
} jpeg_decode_memory_alloc_cfg_t;
/**
* @brief Acquire a JPEG decode engine with the specified configuration.
*
@ -82,19 +89,21 @@ esp_err_t jpeg_decoder_get_info(const uint8_t *bit_stream, uint32_t stream_size,
* decoded image data is written to the `decode_outbuf` buffer, and the length of the output image data is
* returned through the `out_size` pointer.
*
* @note Please make sure that the content of `bit_stream` pointer cannot be modified until this function returns.
* @note 1.Please make sure that the content of `bit_stream` pointer cannot be modified until this function returns.
* 2.Please note that the output size of image is always the multiple of 16 depends on protocol of JPEG.
*
* @param[in] decoder_engine Handle of the JPEG decoder instance to use for processing.
* @param[in] decode_cfg Config structure of decoder.
* @param[in] bit_stream Pointer to the buffer containing the input JPEG image data.
* @param[in] stream_size Size of the input JPEG image data in bytes.
* @param[out] decode_outbuf Pointer to the buffer that will receive the decoded image data.
* @param[in] decode_outbuf Pointer to the buffer that will receive the decoded image data.
* @param[in] outbuf_size The size of `decode_outbuf`
* @param[out] out_size Pointer to a variable that will receive the length of the output image data.
* @return
* - ESP_OK: JPEG decoder process successfully.
* - ESP_ERR_INVALID_ARG: JPEG decoder process failed because of invalid argument.
*/
esp_err_t jpeg_decoder_process(jpeg_decoder_handle_t decoder_engine, const jpeg_decode_cfg_t *decode_cfg, const uint8_t *bit_stream, uint32_t stream_size, uint8_t *decode_outbuf, uint32_t *out_size);
esp_err_t jpeg_decoder_process(jpeg_decoder_handle_t decoder_engine, const jpeg_decode_cfg_t *decode_cfg, const uint8_t *bit_stream, uint32_t stream_size, uint8_t *decode_outbuf, uint32_t outbuf_size, uint32_t *out_size);
/**
* @brief Release resources used by a JPEG decoder instance.
@ -112,10 +121,12 @@ esp_err_t jpeg_del_decoder_engine(jpeg_decoder_handle_t decoder_engine);
/**
* @brief A helper function to allocate memory space for JPEG decoder.
*
* @param size The size of memory to allocate.
* @param[in] size The size of memory to allocate.
* @param[in] mem_cfg Memory configuration for memory allocation
* @param[out] allocated_size Actual allocated buffer size.
* @return Pointer to the allocated memory space, or NULL if allocation fails.
*/
void * jpeg_alloc_decoder_mem(size_t size);
void *jpeg_alloc_decoder_mem(size_t size, jpeg_decode_memory_alloc_cfg_t *mem_cfg, size_t *allocated_size);
#ifdef __cplusplus
}

View File

@ -36,7 +36,15 @@ typedef enum {
typedef enum {
JPEG_DEC_RGB_ELEMENT_ORDER_BGR = COLOR_RGB_ELEMENT_ORDER_BGR, /*!< Output the color component in small endian */
JPEG_DEC_RGB_ELEMENT_ORDER_RGB = COLOR_RGB_ELEMENT_ORDER_RGB, /*!< Output the color component in big endian */
} jpeg_dec_rgb_element_order;
} jpeg_dec_rgb_element_order_t;
/**
* @brief Enumeration for jpeg decoder alloc buffer direction.
*/
typedef enum {
JPEG_DEC_ALLOC_INPUT_BUFFER = 0, /*!< Alloc the picture input buffer, (compressed format in decoder) */
JPEG_DEC_ALLOC_OUTPUT_BUFFER = 1, /*!< Alloc the picture output buffer, (decompressed format in decoder) */
} jpeg_dec_buffer_alloc_direction_t;
/**
* @brief Type of jpeg decoder handle

View File

@ -24,6 +24,7 @@
#include "soc/dma2d_channel.h"
#include "soc/interrupts.h"
#include "esp_dma_utils.h"
#include "esp_private/esp_cache_private.h"
#if CONFIG_JPEG_ENABLE_DEBUG_LOG
// The local log level must be defined before including esp_log.h
// Set the maximum log level for this source file
@ -172,13 +173,12 @@ esp_err_t jpeg_decoder_get_info(const uint8_t *in_buf, uint32_t inbuf_len, jpeg_
return ESP_OK;
}
esp_err_t jpeg_decoder_process(jpeg_decoder_handle_t decoder_engine, const jpeg_decode_cfg_t *decode_cfg, const uint8_t *bit_stream, uint32_t stream_size, uint8_t *decode_outbuf, uint32_t *out_size)
esp_err_t jpeg_decoder_process(jpeg_decoder_handle_t decoder_engine, const jpeg_decode_cfg_t *decode_cfg, const uint8_t *bit_stream, uint32_t stream_size, uint8_t *decode_outbuf, uint32_t outbuf_size, uint32_t *out_size)
{
ESP_RETURN_ON_FALSE(decoder_engine, ESP_ERR_INVALID_ARG, TAG, "jpeg decode handle is null");
ESP_RETURN_ON_FALSE(decode_cfg, ESP_ERR_INVALID_ARG, TAG, "jpeg decode config is null");
ESP_RETURN_ON_FALSE(decode_outbuf, ESP_ERR_INVALID_ARG, TAG, "jpeg decode picture buffer 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 decode bit stream is not aligned, please use jpeg_alloc_decoder_mem to malloc your buffer");
ESP_RETURN_ON_FALSE(((uintptr_t)decode_outbuf % cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA)) == 0, ESP_ERR_INVALID_ARG, TAG, "jpeg decode decode_outbuf is not aligned, please use jpeg_alloc_decoder_mem to malloc your buffer");
ESP_RETURN_ON_FALSE(esp_dma_is_buffer_aligned(decode_outbuf, outbuf_size, ESP_DMA_BUF_LOCATION_PSRAM), ESP_ERR_INVALID_ARG, TAG, "jpeg decode decode_outbuf or out_buffer size is not aligned, please use jpeg_alloc_decoder_mem to malloc your buffer");
esp_err_t ret = ESP_OK;
@ -200,6 +200,9 @@ esp_err_t jpeg_decoder_process(jpeg_decoder_handle_t decoder_engine, const jpeg_
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;
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 hight and width of output picture size will be adjusted to 16 bytes aligned automatically", outbuf_size, *out_size);
dma2d_trans_config_t trans_desc = {
.tx_channel_num = 1,
.rx_channel_num = 1,
@ -212,10 +215,6 @@ esp_err_t jpeg_decoder_process(jpeg_decoder_handle_t decoder_engine, const jpeg_
ret = esp_cache_msync((void*)decoder_engine->header_info->buffer_offset, decoder_engine->header_info->buffer_left, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
assert(ret == ESP_OK);
// Before 2DDMA starts. invalid memory space of decoded buffer
ret = esp_cache_msync((void*)decoder_engine->decoded_buf, decoder_engine->header_info->process_h * decoder_engine->header_info->process_v * decoder_engine->pixel, ESP_CACHE_MSYNC_FLAG_DIR_M2C | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
assert(ret == ESP_OK);
ESP_GOTO_ON_ERROR(dma2d_enqueue(decoder_engine->dma2d_group_handle, &trans_desc, decoder_engine->trans_desc), err, TAG, "enqueue dma2d failed");
bool need_yield;
// Blocking for JPEG decode transaction finishes.
@ -234,12 +233,12 @@ esp_err_t jpeg_decoder_process(jpeg_decoder_handle_t decoder_engine, const jpeg_
}
if (jpeg_dma2d_event.dma_evt & JPEG_DMA2D_RX_EOF) {
ret = esp_cache_msync((void*)decoder_engine->decoded_buf, outbuf_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C);
assert(ret == ESP_OK);
break;
}
}
*out_size = decoder_engine->header_info->process_h * decoder_engine->header_info->process_v * decoder_engine->pixel;
err:
xSemaphoreGive(decoder_engine->codec_base->codec_mutex);
if (decoder_engine->codec_base->pm_lock) {
@ -280,9 +279,23 @@ esp_err_t jpeg_del_decoder_engine(jpeg_decoder_handle_t decoder_engine)
return ESP_OK;
}
void * jpeg_alloc_decoder_mem(size_t size)
void *jpeg_alloc_decoder_mem(size_t size, jpeg_decode_memory_alloc_cfg_t *mem_cfg, size_t *allocated_size)
{
return heap_caps_aligned_calloc(cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA), 1, size, JPEG_MEM_ALLOC_CAPS);
/*
Principle of buffer align.
For output buffer(for decoder is 2DDMA write to PSRAM), both address and size should be aligned according to cache invalidate.
FOr input buffer(for decoder is PSRAM write to 2DDMA), no restriction for any align (both cache writeback and requirement from 2DDMA).
*/
size_t cache_align = 0;
esp_cache_get_alignment(ESP_CACHE_MALLOC_FLAG_PSRAM, &cache_align);
if (mem_cfg->buffer_direction == JPEG_DEC_ALLOC_OUTPUT_BUFFER) {
size = ALIGN_UP(size, cache_align);
*allocated_size = size;
return heap_caps_aligned_calloc(cache_align, 1, size, MALLOC_CAP_SPIRAM);
} else {
*allocated_size = size;
return heap_caps_calloc(1, size, MALLOC_CAP_SPIRAM);
}
}
/****************************************************************

View File

@ -93,7 +93,7 @@ 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 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
QueueHandle_t evt_queue; // jpeg event from 2DDMA and JPEG engine

View File

@ -52,12 +52,22 @@ TEST_CASE("JPEG decode performance test for 1080*1920 YUV->RGB picture", "[jpeg]
.output_format = JPEG_DECODE_OUT_FORMAT_RGB565,
};
uint8_t *rx_buf_1080p = (uint8_t*)jpeg_alloc_decoder_mem(1080 * 1920 * 3);
jpeg_decode_memory_alloc_cfg_t rx_mem_cfg = {
.buffer_direction = JPEG_DEC_ALLOC_OUTPUT_BUFFER,
};
jpeg_decode_memory_alloc_cfg_t tx_mem_cfg = {
.buffer_direction = JPEG_DEC_ALLOC_INPUT_BUFFER,
};
size_t rx_buffer_size;
uint8_t *rx_buf_1080p = (uint8_t*)jpeg_alloc_decoder_mem(1080 * 1920 * 3, &rx_mem_cfg, &rx_buffer_size);
uint32_t out_size_1080p = 0;
size_t bit_stream_length = (size_t)image_esp1080_jpg_end - (size_t)image_esp1080_jpg_start;
uint8_t *tx_buf_1080p = (uint8_t*)jpeg_alloc_decoder_mem(bit_stream_length);
size_t tx_buffer_size;
uint8_t *tx_buf_1080p = (uint8_t*)jpeg_alloc_decoder_mem(bit_stream_length, &tx_mem_cfg, &tx_buffer_size);
// Copy bit stream to psram
memcpy(tx_buf_1080p, image_esp1080_jpg_start, bit_stream_length);
TEST_ESP_OK(jpeg_new_decoder_engine(&decode_eng_cfg, &jpgd_handle));
@ -67,7 +77,7 @@ TEST_CASE("JPEG decode performance test for 1080*1920 YUV->RGB picture", "[jpeg]
// Decode picture for 50 times, and get the average
uint8_t cnt = 50;
for (int i = 0; i < cnt; i++) {
TEST_ESP_OK(jpeg_decoder_process(jpgd_handle, &decode_cfg, tx_buf_1080p, bit_stream_length, rx_buf_1080p, &out_size_1080p));
TEST_ESP_OK(jpeg_decoder_process(jpgd_handle, &decode_cfg, tx_buf_1080p, bit_stream_length, rx_buf_1080p, rx_buffer_size, &out_size_1080p));
}
int64_t decode_time = ccomp_timer_stop();

View File

@ -67,8 +67,19 @@ Overall, You can take following code as reference, the code is going to decode a
.rgb_order = JPEG_DEC_RGB_ELEMENT_ORDER_BGR,
};
uint8_t *bit_stream = (uint8_t*)heap_caps_aligned_calloc(JPEG_BUFFER_MALLOC_ALIGN_VALUE, 1, bit_stream_size, MALLOC_CAP_SPIRAM);
uint8_t *out_buf = (uint8_t*)heap_caps_aligned_calloc(JPEG_BUFFER_MALLOC_ALIGN_VALUE, 1, 1920 * 1080 * 3, MALLOC_CAP_SPIRAM); // Sufficient space for output images.
size_t tx_buffer_size;
size_t rx_buffer_size;
jpeg_decode_memory_alloc_cfg_t rx_mem_cfg = {
.buffer_direction = JPEG_DEC_ALLOC_OUTPUT_BUFFER,
};
jpeg_decode_memory_alloc_cfg_t tx_mem_cfg = {
.buffer_direction = JPEG_DEC_ALLOC_INPUT_BUFFER,
};
uint8_t *bit_stream = (uint8_t*)jpeg_alloc_decoder_mem(jpeg_size, &tx_mem_cfg, &tx_buffer_size);
uint8_t *out_buf = (uint8_t*)jpeg_alloc_decoder_mem(1920 * 1088 * 3, &rx_mem_cfg, &rx_buffer_size);
jpeg_decode_picture_info_t header_info;
ESP_ERROR_CHECK(jpeg_decoder_get_info(bit_stream, bit_stream_size, &header_info));
@ -77,8 +88,9 @@ Overall, You can take following code as reference, the code is going to decode a
.. note::
Firstly, in above code, you should make sure the `bit_stream` and `out_buf` should be :c:macro:`JPEG_BUFFER_MALLOC_ALIGN_VALUE` byte aligned.
Firstly, in above code, you should make sure the `bit_stream` and `out_buf` should be aligned by certain rules. We provide a helper function :cpp:func:`jpeg_alloc_decoder_mem` to help you malloc a buffer which is aligned in both size and address.
Secondly, the content of `bit_stream` buffer should not be changed until :cpp:func:`jpeg_decoder_process` returns.
Thirdly, the width and hight of output picture would be 16 bytes aligned if original picture is formatted by YUV420 or YUV422. For example, if the input picture is 1080*1920, the output picture will be 1088*1920. That is the restriction of jpeg protocol. Please provide sufficient output buffer memory.
Thread Safety
^^^^^^^^^^^^^

View File

@ -88,6 +88,14 @@ void app_main(void)
.output_format = JPEG_DECODE_OUT_FORMAT_GRAY,
};
jpeg_decode_memory_alloc_cfg_t rx_mem_cfg = {
.buffer_direction = JPEG_DEC_ALLOC_OUTPUT_BUFFER,
};
jpeg_decode_memory_alloc_cfg_t tx_mem_cfg = {
.buffer_direction = JPEG_DEC_ALLOC_INPUT_BUFFER,
};
FILE *file_jpg_1080p = fopen(jpg_file_1080, "rb");
ESP_LOGI(TAG, "jpg_file_1080:%s", jpg_file_1080);
if (file_jpg_1080p == NULL) {
@ -98,7 +106,8 @@ void app_main(void)
fseek(file_jpg_1080p, 0, SEEK_END);
int jpeg_size_1080p = ftell(file_jpg_1080p);
fseek(file_jpg_1080p, 0, SEEK_SET);
uint8_t *tx_buf_1080p = (uint8_t*)jpeg_alloc_decoder_mem(jpeg_size_1080p);
size_t tx_buffer_size_1080p = 0;
uint8_t *tx_buf_1080p = (uint8_t*)jpeg_alloc_decoder_mem(jpeg_size_1080p, &tx_mem_cfg, &tx_buffer_size_1080p);
if (tx_buf_1080p == NULL) {
ESP_LOGE(TAG, "alloc 1080p tx buffer error");
return;
@ -115,7 +124,8 @@ void app_main(void)
fseek(file_jpg_720p, 0, SEEK_END);
int jpeg_size_720p = ftell(file_jpg_720p);
fseek(file_jpg_720p, 0, SEEK_SET);
uint8_t *tx_buf_720p = (uint8_t*)jpeg_alloc_decoder_mem(jpeg_size_720p);
size_t tx_buffer_size_720p = 0;
uint8_t *tx_buf_720p = (uint8_t*)jpeg_alloc_decoder_mem(jpeg_size_720p, &tx_mem_cfg, &tx_buffer_size_720p);
if (tx_buf_720p == NULL) {
ESP_LOGE(TAG, "alloc 720p tx buffer error");
return;
@ -123,8 +133,10 @@ void app_main(void)
fread(tx_buf_720p, 1, jpeg_size_720p, file_jpg_720p);
fclose(file_jpg_720p);
uint8_t *rx_buf_1080p = (uint8_t*)jpeg_alloc_decoder_mem(1920 * 1080 * 3);
uint8_t *rx_buf_720p = (uint8_t*)jpeg_alloc_decoder_mem(720 * 1280);
size_t rx_buffer_size_1080p = 0;
size_t rx_buffer_size_720p = 0;
uint8_t *rx_buf_1080p = (uint8_t*)jpeg_alloc_decoder_mem(1920 * 1088 * 3, &rx_mem_cfg, &rx_buffer_size_1080p);
uint8_t *rx_buf_720p = (uint8_t*)jpeg_alloc_decoder_mem(720 * 1280, &rx_mem_cfg, &rx_buffer_size_720p);
if (rx_buf_1080p == NULL) {
ESP_LOGE(TAG, "alloc 1080p rx buffer error");
return;
@ -141,8 +153,8 @@ void app_main(void)
uint32_t out_size_1080p = 0;
uint32_t out_size_720p = 0;
ESP_ERROR_CHECK(jpeg_decoder_process(jpgd_handle, &decode_cfg_rgb, tx_buf_1080p, jpeg_size_1080p, rx_buf_1080p, &out_size_1080p));
ESP_ERROR_CHECK(jpeg_decoder_process(jpgd_handle, &decode_cfg_gray, tx_buf_720p, jpeg_size_720p, rx_buf_720p, &out_size_720p));
ESP_ERROR_CHECK(jpeg_decoder_process(jpgd_handle, &decode_cfg_rgb, tx_buf_1080p, jpeg_size_1080p, rx_buf_1080p, rx_buffer_size_1080p, &out_size_1080p));
ESP_ERROR_CHECK(jpeg_decoder_process(jpgd_handle, &decode_cfg_gray, tx_buf_720p, jpeg_size_720p, rx_buf_720p, rx_buffer_size_720p, &out_size_720p));
// Write two pictures.
FILE *file_rgb_1080p = fopen(raw_file_1080, "wb");