Merge branch 'fix/jpeg_decoder_collective_backport_v5.4' into 'release/v5.4'

fix(jpeg_decoder): JPEG Decoder collective backport to v5.4

See merge request espressif/esp-idf!37509
This commit is contained in:
morris
2025-03-31 16:53:25 +08:00
8 changed files with 125 additions and 11 deletions

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -39,6 +39,7 @@ static const char *TAG = "jpeg.decoder";
static void s_decoder_error_log_print(uint32_t status); static void s_decoder_error_log_print(uint32_t status);
static esp_err_t jpeg_dec_config_dma_descriptor(jpeg_decoder_handle_t decoder_engine); static esp_err_t jpeg_dec_config_dma_descriptor(jpeg_decoder_handle_t decoder_engine);
static esp_err_t jpeg_parse_marker(jpeg_decoder_handle_t decoder_engine, const uint8_t *in_buf, uint32_t inbuf_len); static esp_err_t jpeg_parse_marker(jpeg_decoder_handle_t decoder_engine, const uint8_t *in_buf, uint32_t inbuf_len);
static esp_err_t jpeg_check_marker(jpeg_decoder_handle_t decoder_engine);
static esp_err_t jpeg_parse_header_info_to_hw(jpeg_decoder_handle_t decoder_engine); static esp_err_t jpeg_parse_header_info_to_hw(jpeg_decoder_handle_t decoder_engine);
static bool jpeg_dec_transaction_on_picked(uint32_t channel_num, const dma2d_trans_channel_info_t *dma2d_chans, void *users_config); static bool jpeg_dec_transaction_on_picked(uint32_t channel_num, const dma2d_trans_channel_info_t *dma2d_chans, void *users_config);
@ -225,6 +226,7 @@ esp_err_t jpeg_decoder_process(jpeg_decoder_handle_t decoder_engine, const jpeg_
decoder_engine->decoded_buf = decode_outbuf; decoder_engine->decoded_buf = decode_outbuf;
ESP_GOTO_ON_ERROR(jpeg_parse_marker(decoder_engine, bit_stream, stream_size), err2, TAG, "jpeg parse marker failed"); ESP_GOTO_ON_ERROR(jpeg_parse_marker(decoder_engine, bit_stream, stream_size), err2, TAG, "jpeg parse marker failed");
ESP_GOTO_ON_ERROR(jpeg_check_marker(decoder_engine), err2, TAG, "jpeg check marker failed");
ESP_GOTO_ON_ERROR(jpeg_parse_header_info_to_hw(decoder_engine), err2, TAG, "write header info to hw failed"); ESP_GOTO_ON_ERROR(jpeg_parse_header_info_to_hw(decoder_engine), err2, TAG, "write header info to hw failed");
ESP_GOTO_ON_ERROR(jpeg_dec_config_dma_descriptor(decoder_engine), err2, TAG, "config dma descriptor failed"); ESP_GOTO_ON_ERROR(jpeg_dec_config_dma_descriptor(decoder_engine), err2, TAG, "config dma descriptor failed");
@ -631,9 +633,7 @@ static esp_err_t jpeg_parse_header_info_to_hw(jpeg_decoder_handle_t decoder_engi
dht_func[1][0](hal, header_info->huffbits[1][0], header_info->huffcode[1][0], header_info->tmp_huff); dht_func[1][0](hal, header_info->huffbits[1][0], header_info->huffcode[1][0], header_info->tmp_huff);
dht_func[1][1](hal, header_info->huffbits[1][1], header_info->huffcode[1][1], header_info->tmp_huff); dht_func[1][1](hal, header_info->huffbits[1][1], header_info->huffcode[1][1], header_info->tmp_huff);
if (header_info->dri_marker) { jpeg_ll_set_restart_interval(hal->dev, header_info->ri);
jpeg_ll_set_restart_interval(hal->dev, header_info->ri);
}
return ESP_OK; return ESP_OK;
} }
@ -716,6 +716,9 @@ static esp_err_t jpeg_parse_marker(jpeg_decoder_handle_t decoder_engine, const u
case JPEG_M_SOS: case JPEG_M_SOS:
ESP_RETURN_ON_ERROR(jpeg_parse_sos_marker(header_info), TAG, "deal sos marker failed"); ESP_RETURN_ON_ERROR(jpeg_parse_sos_marker(header_info), TAG, "deal sos marker failed");
break; break;
case JPEG_M_INV:
ESP_RETURN_ON_ERROR(jpeg_parse_inv_marker(header_info), TAG, "deal invalid marker failed");
break;
} }
if (marker == JPEG_M_SOS) { if (marker == JPEG_M_SOS) {
break; break;
@ -728,6 +731,38 @@ static esp_err_t jpeg_parse_marker(jpeg_decoder_handle_t decoder_engine, const u
return ESP_OK; return ESP_OK;
} }
static esp_err_t jpeg_default_huff_table(jpeg_dec_header_info_t *header_info)
{
// Copy default Huffman table parameters to JPEG header
// DC Coefficients
memcpy(header_info->huffbits[0][0], luminance_dc_coefficients, JPEG_HUFFMAN_BITS_LEN_TABLE_LEN);
memcpy(header_info->huffbits[0][1], chrominance_dc_coefficients, JPEG_HUFFMAN_BITS_LEN_TABLE_LEN);
// AC Coefficients
memcpy(header_info->huffbits[1][0], luminance_ac_coefficients, JPEG_HUFFMAN_BITS_LEN_TABLE_LEN);
memcpy(header_info->huffbits[1][1], chrominance_ac_coefficients, JPEG_HUFFMAN_BITS_LEN_TABLE_LEN);
// DC Values
memcpy(header_info->huffcode[0][0], luminance_dc_values, JPEG_HUFFMAN_DC_VALUE_TABLE_LEN);
memcpy(header_info->huffcode[0][1], chrominance_dc_values, JPEG_HUFFMAN_DC_VALUE_TABLE_LEN);
// AC Values
memcpy(header_info->huffcode[1][0], luminance_ac_values, JPEG_HUFFMAN_AC_VALUE_TABLE_LEN);
memcpy(header_info->huffcode[1][1], chrominance_ac_values, JPEG_HUFFMAN_AC_VALUE_TABLE_LEN);
return ESP_OK;
}
static esp_err_t jpeg_check_marker(jpeg_decoder_handle_t decoder_engine)
{
// Check if Huffman table is present in JPEG image
if (!decoder_engine->header_info->dht_marker) {
// Huffman table not present, define a default one
// This is common for USB Cameras, not to include the table into the JPEG image to save a bandwidth on a USB bus
jpeg_default_huff_table(decoder_engine->header_info);
}
return ESP_OK;
}
static void s_decoder_error_log_print(uint32_t status) static void s_decoder_error_log_print(uint32_t status)
{ {
if (status & JPEG_LL_INTR_CID_ERR) { if (status & JPEG_LL_INTR_CID_ERR) {

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -162,6 +162,8 @@ esp_err_t jpeg_parse_dht_marker(jpeg_dec_header_info_t *header_info)
num_left -= (1 + JPEG_HUFFMAN_BITS_LEN_TABLE_LEN + np); num_left -= (1 + JPEG_HUFFMAN_BITS_LEN_TABLE_LEN + np);
} }
// Record, that Huffman table present in JPEG header
header_info->dht_marker = true;
return ESP_OK; return ESP_OK;
} }
@ -173,7 +175,6 @@ esp_err_t jpeg_parse_dri_marker(jpeg_dec_header_info_t *header_info)
return ESP_ERR_INVALID_SIZE; return ESP_ERR_INVALID_SIZE;
} }
header_info->ri = jpeg_get_bytes(header_info, 2); header_info->ri = jpeg_get_bytes(header_info, 2);
header_info->dri_marker = true;
return ESP_OK; return ESP_OK;
} }
@ -185,3 +186,13 @@ esp_err_t jpeg_parse_sos_marker(jpeg_dec_header_info_t *header_info)
header_info->buffer_left += 2; header_info->buffer_left += 2;
return ESP_OK; return ESP_OK;
} }
esp_err_t jpeg_parse_inv_marker(jpeg_dec_header_info_t *header_info)
{
// Got invalid 0xFFFF, (followed by a valid marker type)
// Go one byte back, to skip the first 0xFF
header_info->buffer_offset--;
header_info->header_size--;
header_info->buffer_left++;
return ESP_OK;
}

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -86,7 +86,7 @@ typedef struct {
uint8_t huffbits[2][2][JPEG_HUFFMAN_BITS_LEN_TABLE_LEN]; // Huffman bit distribution tables [id][dcac] uint8_t huffbits[2][2][JPEG_HUFFMAN_BITS_LEN_TABLE_LEN]; // Huffman bit distribution tables [id][dcac]
uint8_t huffcode[2][2][JPEG_HUFFMAN_AC_VALUE_TABLE_LEN]; // Huffman decoded data tables [id][dcac] uint8_t huffcode[2][2][JPEG_HUFFMAN_AC_VALUE_TABLE_LEN]; // Huffman decoded data tables [id][dcac]
uint32_t tmp_huff[JPEG_HUFFMAN_AC_VALUE_TABLE_LEN]; // temp buffer to store huffman code uint32_t tmp_huff[JPEG_HUFFMAN_AC_VALUE_TABLE_LEN]; // temp buffer to store huffman code
bool dri_marker; // If we have dri marker in table bool dht_marker; // If we have Huffman table present in header
uint16_t ri; // Restart interval uint16_t ri; // Restart interval
} jpeg_dec_header_info_t; } jpeg_dec_header_info_t;

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -118,6 +118,26 @@ esp_err_t jpeg_parse_sos_marker(jpeg_dec_header_info_t *header_info);
*/ */
esp_err_t jpeg_parse_dri_marker(jpeg_dec_header_info_t *header_info); esp_err_t jpeg_parse_dri_marker(jpeg_dec_header_info_t *header_info);
/**
* @brief Parses with an invalid marker in a JPEG file.
*
* This function is called when the decoder encounters an invalid (0xFFFF) marker.
* In the baseline JPEG specification, 0xFF is always used as the "marker prefix," and the byte that follows determines
* the marker type (e.g., 0xD8 for SOI, 0xD9 for EOI, 0xDA for SOS, etc.).
* A 0xFFFF sequence, however, does not correspond to any valid, standard JPEG marker.
* In JPEG-compressed data, any single 0xFF in the entropy-coded segment is supposed to be followed by 0x00 if it is not a marker.
* Sometimes, encoders or hardware incorrectly insert repeated 0xFF bytes without the 0x00 "stuffing" byte.
* This confuses decoders that strictly follow the JPEG standard.
*
* The function handles the invalid data contained within the marker and performs
* any necessary processing or actions based on the restart interval information.
*
* @param[in] header_info Pointer to the JPEG picture information.
*
* @return ESP_OK on success, or an appropriate error code if an error occurred.
*/
esp_err_t jpeg_parse_inv_marker(jpeg_dec_header_info_t *header_info);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -11,6 +11,7 @@ project(jpeg_test)
target_add_binary_data(jpeg_test.elf "${IDF_PATH}/examples/peripherals/jpeg/jpeg_decode/resources/esp720.jpg" BINARY) target_add_binary_data(jpeg_test.elf "${IDF_PATH}/examples/peripherals/jpeg/jpeg_decode/resources/esp720.jpg" BINARY)
target_add_binary_data(jpeg_test.elf "${IDF_PATH}/examples/peripherals/jpeg/jpeg_decode/resources/esp1080.jpg" BINARY) target_add_binary_data(jpeg_test.elf "${IDF_PATH}/examples/peripherals/jpeg/jpeg_decode/resources/esp1080.jpg" BINARY)
target_add_binary_data(jpeg_test.elf "resources/no_huff.jpg" BINARY)
target_add_binary_data(jpeg_test.elf "resources/esp480.rgb" BINARY) target_add_binary_data(jpeg_test.elf "resources/esp480.rgb" BINARY)
message(STATUS "Checking jpeg registers are not read-write by half-word") message(STATUS "Checking jpeg registers are not read-write by half-word")

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Unlicense OR CC0-1.0 * SPDX-License-Identifier: Unlicense OR CC0-1.0
*/ */
@ -21,6 +21,9 @@
extern const uint8_t image_esp1080_jpg_start[] asm("_binary_esp1080_jpg_start"); extern const uint8_t image_esp1080_jpg_start[] asm("_binary_esp1080_jpg_start");
extern const uint8_t image_esp1080_jpg_end[] asm("_binary_esp1080_jpg_end"); extern const uint8_t image_esp1080_jpg_end[] asm("_binary_esp1080_jpg_end");
extern const uint8_t image_no_huff_jpg_start[] asm("_binary_no_huff_jpg_start");
extern const uint8_t image_no_huff_jpg_end[] asm("_binary_no_huff_jpg_end");
TEST_CASE("JPEG decode driver memory leaking check", "[jpeg]") TEST_CASE("JPEG decode driver memory leaking check", "[jpeg]")
{ {
jpeg_decoder_handle_t jpgd_handle; jpeg_decoder_handle_t jpgd_handle;
@ -87,3 +90,46 @@ TEST_CASE("JPEG decode performance test for 1080*1920 YUV->RGB picture", "[jpeg]
free(tx_buf_1080p); free(tx_buf_1080p);
TEST_ESP_OK(jpeg_del_decoder_engine(jpgd_handle)); TEST_ESP_OK(jpeg_del_decoder_engine(jpgd_handle));
} }
TEST_CASE("JPEG decode image without Huffman table JPEG->RGB picture", "[jpeg]")
{
jpeg_decoder_handle_t jpgd_handle;
jpeg_decode_engine_cfg_t decode_eng_cfg = {
.intr_priority = 0,
.timeout_ms = 40,
};
jpeg_decode_cfg_t decode_cfg = {
.output_format = JPEG_DECODE_OUT_FORMAT_RGB565,
};
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_no_huff = (uint8_t*)jpeg_alloc_decoder_mem(120 * 160 * 3, &rx_mem_cfg, &rx_buffer_size);
uint32_t out_size_no_huff = 0;
size_t bit_stream_length = (size_t)image_no_huff_jpg_end - (size_t)image_no_huff_jpg_start;
size_t tx_buffer_size;
uint8_t *tx_buf_no_huff = (uint8_t*)jpeg_alloc_decoder_mem(bit_stream_length, &tx_mem_cfg, &tx_buffer_size);
// Copy bit stream to psram
memcpy(tx_buf_no_huff, image_no_huff_jpg_start, bit_stream_length);
TEST_ESP_OK(jpeg_new_decoder_engine(&decode_eng_cfg, &jpgd_handle));
// Decode a picture without Huffman table only once, to ensure that the default Huffman table is defined correctly
TEST_ESP_OK(jpeg_decoder_process(jpgd_handle, &decode_cfg, tx_buf_no_huff, bit_stream_length, rx_buf_no_huff, rx_buffer_size, &out_size_no_huff));
// JPEG image resolution is 120 x 160, output format is RGB565: expected decoded size is 120 * 160 * 2
TEST_ASSERT_EQUAL(out_size_no_huff, 120 * 160 * 2);
free(rx_buf_no_huff);
free(tx_buf_no_huff);
TEST_ESP_OK(jpeg_del_decoder_engine(jpgd_handle));
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -70,6 +70,7 @@ typedef enum {
JPEG_M_JPG13 = 0xFFFD, ///< Reserved for JPEG extension JPEG_M_JPG13 = 0xFFFD, ///< Reserved for JPEG extension
JPEG_M_COM = 0xFFFE, ///< Comment JPEG_M_COM = 0xFFFE, ///< Comment
JPEG_M_TEM = 0xFF01, ///< Temporary use in arithmetic coding JPEG_M_TEM = 0xFF01, ///< Temporary use in arithmetic coding
JPEG_M_INV = 0xFFFF, ///< Invalid marker
} __attribute__((packed)) jpeg_marker_code_t; } __attribute__((packed)) jpeg_marker_code_t;