openmv/ports/stm32/stm_jpeg.c
iabdalkader cf7b84d682 ports/all: Remove old profiling macros.
Signed-off-by: iabdalkader <i.abdalkader@gmail.com>
2025-08-24 14:40:02 +02:00

853 lines
38 KiB
C

/*
* SPDX-License-Identifier: MIT
*
* Copyright (C) 2013-2024 OpenMV, LLC.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Hardware Accelerated JPEG Encoder and Decoder
*/
#include "omv_boardconfig.h"
#if (OMV_JPEG_CODEC_ENABLE == 1)
#include "imlib.h"
#include "py/mphal.h"
#include "py/runtime.h"
#include STM32_HAL_H
#include "irq.h"
#include "stm_dma.h"
#define JPEG_CODEC_TIMEOUT (1000)
#define JPEG_ALLOC_PADDING ((__SCB_DCACHE_LINE_SIZE) * 4)
#define JPEG_OUTPUT_CHUNK_SIZE (512) // The minimum output buffer size is 2x this - so 1KB.
#define JPEG_MAX_MDMA_BLOCK_SIZE (65536UL) // Maximum bytes MDMA can transfer at once.
#define JPEG_INPUT_FIFO_BYTES (32)
#define JPEG_OUTPUT_FIFO_BYTES (32)
#define JPEG_MDMA_IN (0)
#define JPEG_MDMA_OUT (1)
typedef struct jpeg_state {
volatile uint32_t in_data_len;
volatile uint32_t out_data_len_max;
volatile uint32_t out_data_len;
volatile bool input_paused;
volatile bool output_paused;
JPEG_HandleTypeDef jpeg_descr;
#if defined(OMV_MDMA_CHANNEL_JPEG_IN)
MDMA_HandleTypeDef mdma_descr[2];
#endif
} jpeg_state_t;
static jpeg_state_t JPEG_state = {};
// JIFF-APP0 header designed to be injected at the start of the JPEG byte stream.
// Contains a variable sized COM header at the end for cache alignment.
static const uint8_t JPEG_APP0[] = {
0xFF, 0xE0, // JIFF-APP0
0x00, 0x10, // 16
0x4A, 0x46, 0x49, 0x46, 0x00, // JIFF
0x01, 0x01, // V1.01
0x01, // DPI
0x00, 0x00, // Xdensity 0
0x00, 0x00, // Ydensity 0
0x00, // Xthumbnail 0
0x00, // Ythumbnail 0
0xFF, 0xFE // COM
};
void JPEG_IRQHandler() {
IRQ_ENTER(JPEG_IRQn);
HAL_JPEG_IRQHandler(&JPEG_state.jpeg_descr);
IRQ_EXIT(JPEG_IRQn);
}
#if defined(OMV_MDMA_CHANNEL_JPEG_IN)
void jpeg_mdma_irq_handler(void) {
if (MDMA->GISR0 & (1 << OMV_MDMA_CHANNEL_JPEG_IN)) {
HAL_MDMA_IRQHandler(&JPEG_state.mdma_descr[JPEG_MDMA_IN]);
}
if (MDMA->GISR0 & (1 << OMV_MDMA_CHANNEL_JPEG_OUT)) {
HAL_MDMA_IRQHandler(&JPEG_state.mdma_descr[JPEG_MDMA_OUT]);
}
}
#endif
static void jpeg_compress_get_data(JPEG_HandleTypeDef *hjpeg, uint32_t NbDecodedData) {
HAL_JPEG_Pause(hjpeg, JPEG_PAUSE_RESUME_INPUT);
JPEG_state.input_paused = true;
}
static void jpeg_compress_data_ready(JPEG_HandleTypeDef *hjpeg, uint8_t *pDataOut, uint32_t OutDataLength) {
#if defined(OMV_MDMA_CHANNEL_JPEG_IN)
if ((!(((uint32_t) pDataOut) % __SCB_DCACHE_LINE_SIZE)) && (OutDataLength == JPEG_OUTPUT_CHUNK_SIZE)) {
// Ensure any cached reads are dropped.
SCB_InvalidateDCache_by_Addr((uint32_t *) pDataOut, JPEG_OUTPUT_CHUNK_SIZE);
}
#endif
// We have received this much data.
JPEG_state.out_data_len += OutDataLength;
if ((JPEG_state.out_data_len + JPEG_OUTPUT_CHUNK_SIZE) > JPEG_state.out_data_len_max) {
// We will overflow if we receive anymore data.
HAL_JPEG_Pause(hjpeg, JPEG_PAUSE_RESUME_OUTPUT);
JPEG_state.output_paused = true;
} else {
uint8_t *new_pDataOut = pDataOut + OutDataLength;
#if defined(OMV_MDMA_CHANNEL_JPEG_IN)
// DMA will write data to the output buffer in __SCB_DCACHE_LINE_SIZE aligned chunks. At the
// end of JPEG compression the processor will manually transfer the remaining parts of the
// image in randomly aligned chunks. We only want to invalidate the cache of the output
// buffer for the initial DMA chunks. So, this code below will do that and then only
// invalidate aligned regions when the processor is moving the final parts of the image.
if ((!(((uint32_t) new_pDataOut) % __SCB_DCACHE_LINE_SIZE)) && (OutDataLength == JPEG_OUTPUT_CHUNK_SIZE)) {
SCB_InvalidateDCache_by_Addr((uint32_t *) new_pDataOut, JPEG_OUTPUT_CHUNK_SIZE);
}
#endif
// We are ok to receive more data.
HAL_JPEG_ConfigOutputBuffer(hjpeg, new_pDataOut, JPEG_OUTPUT_CHUNK_SIZE);
}
}
bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc, jpeg_subsampling_t subsampling) {
HAL_JPEG_RegisterGetDataCallback(&JPEG_state.jpeg_descr, jpeg_compress_get_data);
HAL_JPEG_RegisterDataReadyCallback(&JPEG_state.jpeg_descr, jpeg_compress_data_ready);
int mcu_size = 0;
JPEG_ConfTypeDef JPEG_Info;
JPEG_Info.ImageWidth = src->w;
JPEG_Info.ImageHeight = src->h;
JPEG_Info.ImageQuality = quality;
switch (src->pixfmt) {
case PIXFORMAT_BINARY:
case PIXFORMAT_GRAYSCALE:
mcu_size = JPEG_444_GS_MCU_SIZE;
JPEG_Info.ColorSpace = JPEG_GRAYSCALE_COLORSPACE;
JPEG_Info.ChromaSubsampling = JPEG_444_SUBSAMPLING;
break;
case PIXFORMAT_RGB565:
case PIXFORMAT_BAYER_ANY:
case PIXFORMAT_YUV_ANY:
mcu_size = JPEG_444_YCBCR_MCU_SIZE;
JPEG_Info.ColorSpace = JPEG_YCBCR_COLORSPACE;
JPEG_Info.ChromaSubsampling = JPEG_444_SUBSAMPLING;
if (subsampling == JPEG_SUBSAMPLING_AUTO) {
if (quality < 60) {
mcu_size = JPEG_422_YCBCR_MCU_SIZE;
JPEG_Info.ChromaSubsampling = JPEG_422_SUBSAMPLING;
}
} else if (subsampling == JPEG_SUBSAMPLING_422) {
mcu_size = JPEG_422_YCBCR_MCU_SIZE;
JPEG_Info.ChromaSubsampling = JPEG_422_SUBSAMPLING;
} else if (subsampling == JPEG_SUBSAMPLING_420) {
// not supported
return true;
}
break;
default:
break;
}
if (memcmp(&JPEG_state.jpeg_descr.Conf, &JPEG_Info, sizeof(JPEG_ConfTypeDef))) {
HAL_JPEG_ConfigEncoding(&JPEG_state.jpeg_descr, &JPEG_Info);
}
int mcu_w = (JPEG_Info.ChromaSubsampling == JPEG_444_SUBSAMPLING) ? JPEG_MCU_W : (JPEG_MCU_W * 2);
int src_w_mcus = (src->w + mcu_w - 1) / mcu_w;
int src_w_mcus_bytes = src_w_mcus * mcu_size;
int src_w_mcus_bytes_2 = src_w_mcus_bytes * 2;
// If dst->data == NULL then we need to fb_alloc() space for the payload which will be fb_free()'d
// by the caller. We have to alloc this memory for all cases if we return from the method.
if (!dst->data) {
uint32_t avail = fb_avail();
uint32_t space = src_w_mcus_bytes_2 + JPEG_ALLOC_PADDING;
if (avail < space) {
fb_alloc_fail();
}
dst->size = IMLIB_IMAGE_MAX_SIZE(avail - space);
dst->data = fb_alloc(dst->size, FB_ALLOC_PREFER_SIZE | FB_ALLOC_CACHE_ALIGN);
}
if (src->is_compressed) {
return true;
}
// Compute size of the APP0 header with cache alignment padding.
int app0_size = sizeof(JPEG_APP0);
int app0_unalign_size = app0_size % __SCB_DCACHE_LINE_SIZE;
int app0_padding_size = app0_unalign_size ? (__SCB_DCACHE_LINE_SIZE - app0_unalign_size) : 0;
int app0_total_size = app0_size + app0_padding_size;
if (dst->size < app0_total_size) {
return true; // overflow
}
// Adjust JPEG size and address by app0 header size.
dst->size -= app0_total_size;
uint8_t *dma_buffer = dst->data + app0_total_size;
// Destination is too small.
if (dst->size < (JPEG_OUTPUT_CHUNK_SIZE * 2)) {
return true; // overflow
}
bool jpeg_overflow = false;
JPEG_state.out_data_len_max = dst->size;
JPEG_state.out_data_len = 0;
JPEG_state.input_paused = false;
JPEG_state.output_paused = false;
uint8_t *mcu_row_buffer = fb_alloc(src_w_mcus_bytes_2, FB_ALLOC_PREFER_SPEED | FB_ALLOC_CACHE_ALIGN);
for (int y_offset = 0; y_offset < src->h; y_offset += JPEG_MCU_H) {
uint8_t *mcu_row_buffer_ptr = mcu_row_buffer + (src_w_mcus_bytes * ((y_offset / JPEG_MCU_H) % 2));
int dy = IM_MIN(JPEG_MCU_H, src->h - y_offset);
if (JPEG_Info.ChromaSubsampling == JPEG_444_SUBSAMPLING) {
for (int x_offset = 0; x_offset < src->w; x_offset += JPEG_MCU_W) {
int8_t *Y0 = (int8_t *) (mcu_row_buffer_ptr + (mcu_size * (x_offset / JPEG_MCU_W)));
int8_t *CB = Y0 + JPEG_444_GS_MCU_SIZE;
int8_t *CR = CB + JPEG_444_GS_MCU_SIZE;
int dx = IM_MIN(JPEG_MCU_W, src->w - x_offset);
// Copy 8x8 MCUs.
jpeg_get_mcu(src, x_offset, y_offset, dx, dy, Y0, CB, CR);
}
} else if (JPEG_Info.ChromaSubsampling == JPEG_422_SUBSAMPLING) {
// color only
int8_t CB[JPEG_444_GS_MCU_SIZE * 2];
int8_t CR[JPEG_444_GS_MCU_SIZE * 2];
for (int x_offset = 0; x_offset < src->w; ) {
int8_t *Y0 = (int8_t *) (mcu_row_buffer_ptr + (mcu_size * (x_offset / (JPEG_MCU_W * 2))));
int8_t *Y1 = Y0 + JPEG_444_GS_MCU_SIZE;
int8_t *CB_avg = Y1 + JPEG_444_GS_MCU_SIZE;
int8_t *CR_avg = CB_avg + JPEG_444_GS_MCU_SIZE;
for (int i = 0; i < (JPEG_444_GS_MCU_SIZE * 2);
i += JPEG_444_GS_MCU_SIZE, x_offset += JPEG_MCU_W) {
int dx = IM_MIN(JPEG_MCU_W, src->w - x_offset);
if (dx > 0) {
// Copy 8x8 MCUs.
jpeg_get_mcu(src, x_offset, y_offset, dx, dy, Y0 + i, CB + i, CR + i);
} else {
memset(Y0 + i, 0, JPEG_444_GS_MCU_SIZE);
memset(CB + i, 0, JPEG_444_GS_MCU_SIZE);
memset(CR + i, 0, JPEG_444_GS_MCU_SIZE);
}
}
// horizontal subsampling of U & V
uint32_t mask = 0x80808080;
uint32_t *CBp0 = (uint32_t *) CB;
uint32_t *CRp0 = (uint32_t *) CR;
uint32_t *CBp1 = (uint32_t *) (CB + JPEG_444_GS_MCU_SIZE);
uint32_t *CRp1 = (uint32_t *) (CR + JPEG_444_GS_MCU_SIZE);
for (int j = 0; j < JPEG_444_GS_MCU_SIZE; j += JPEG_MCU_W) {
uint32_t CBp0_3210 = *CBp0++ ^ mask;
uint32_t CBp0_avg_32_10 = __SHADD8(CBp0_3210, __UXTB16_RORn(CBp0_3210, 8)) ^ mask;
CB_avg[j] = CBp0_avg_32_10;
CB_avg[j + 1] = CBp0_avg_32_10 >> 16;
uint32_t CBp0_7654 = *CBp0++ ^ mask;
uint32_t CBp0_avg_76_54 = __SHADD8(CBp0_7654, __UXTB16_RORn(CBp0_7654, 8)) ^ mask;
CB_avg[j + 2] = CBp0_avg_76_54;
CB_avg[j + 3] = CBp0_avg_76_54 >> 16;
uint32_t CBp1_3210 = *CBp1++ ^ mask;
uint32_t CBp1_avg_32_10 = __SHADD8(CBp1_3210, __UXTB16_RORn(CBp1_3210, 8)) ^ mask;
CB_avg[j + 4] = CBp1_avg_32_10;
CB_avg[j + 5] = CBp1_avg_32_10 >> 16;
uint32_t CBp1_7654 = *CBp1++ ^ mask;
uint32_t CBp1_avg_76_54 = __SHADD8(CBp1_7654, __UXTB16_RORn(CBp1_7654, 8)) ^ mask;
CB_avg[j + 6] = CBp1_avg_76_54;
CB_avg[j + 7] = CBp1_avg_76_54 >> 16;
uint32_t CRp0_3210 = *CRp0++ ^ mask;
uint32_t CRp0_avg_32_10 = __SHADD8(CRp0_3210, __UXTB16_RORn(CRp0_3210, 8)) ^ mask;
CR_avg[j] = CRp0_avg_32_10;
CR_avg[j + 1] = CRp0_avg_32_10 >> 16;
uint32_t CRp0_7654 = *CRp0++ ^ mask;
uint32_t CRp0_avg_76_54 = __SHADD8(CRp0_7654, __UXTB16_RORn(CRp0_7654, 8)) ^ mask;
CR_avg[j + 2] = CRp0_avg_76_54;
CR_avg[j + 3] = CRp0_avg_76_54 >> 16;
uint32_t CRp1_3210 = *CRp1++ ^ mask;
uint32_t CRp1_avg_32_10 = __SHADD8(CRp1_3210, __UXTB16_RORn(CRp1_3210, 8)) ^ mask;
CR_avg[j + 4] = CRp1_avg_32_10;
CR_avg[j + 5] = CRp1_avg_32_10 >> 16;
uint32_t CRp1_7654 = *CRp1++ ^ mask;
uint32_t CRp1_avg_76_54 = __SHADD8(CRp1_7654, __UXTB16_RORn(CRp1_7654, 8)) ^ mask;
CR_avg[j + 6] = CRp1_avg_76_54;
CR_avg[j + 7] = CRp1_avg_76_54 >> 16;
}
}
}
#if defined(OMV_MDMA_CHANNEL_JPEG_IN)
// Flush the MCU row for DMA...
SCB_CleanDCache_by_Addr((uint32_t *) mcu_row_buffer_ptr, src_w_mcus_bytes);
#endif
if (!y_offset) {
#if defined(OMV_MDMA_CHANNEL_JPEG_IN)
// Invalidate the output buffer.
SCB_InvalidateDCache_by_Addr(dma_buffer, JPEG_OUTPUT_CHUNK_SIZE);
// Start the DMA process off on the first row of MCUs.
HAL_JPEG_Encode_DMA(&JPEG_state.jpeg_descr, mcu_row_buffer_ptr, src_w_mcus_bytes, dma_buffer,
JPEG_OUTPUT_CHUNK_SIZE);
#else
HAL_JPEG_Encode_IT(&JPEG_state.jpeg_descr, mcu_row_buffer_ptr, src_w_mcus_bytes, dma_buffer,
JPEG_OUTPUT_CHUNK_SIZE);
#endif
} else {
// Wait for the last row MCUs to be processed before starting the next row.
for (mp_uint_t tickstart = mp_hal_ticks_ms(); !JPEG_state.input_paused; ) {
if (JPEG_state.output_paused || ((mp_hal_ticks_ms() - tickstart) > JPEG_CODEC_TIMEOUT)) {
memset(&JPEG_state.jpeg_descr.Conf, 0, sizeof(JPEG_ConfTypeDef));
jpeg_overflow = true;
goto exit_cleanup;
}
MICROPY_EVENT_POLL_HOOK
}
// Reset the lock.
JPEG_state.input_paused = false;
// Restart the DMA process on the next row of MCUs (that were already prepared).
HAL_JPEG_ConfigInputBuffer(&JPEG_state.jpeg_descr, mcu_row_buffer_ptr, src_w_mcus_bytes);
HAL_JPEG_Resume(&JPEG_state.jpeg_descr, JPEG_PAUSE_RESUME_INPUT);
}
}
// After writing the last MCU to the JPEG core it will eventually generate an end-of-conversion
// interrupt which will finish the JPEG encoding process and clear the busy flag.
for (mp_uint_t tickstart = mp_hal_ticks_ms();
HAL_JPEG_GetState(&JPEG_state.jpeg_descr) == HAL_JPEG_STATE_BUSY_ENCODING; ) {
if (JPEG_state.output_paused || ((mp_hal_ticks_ms() - tickstart) > JPEG_CODEC_TIMEOUT)) {
memset(&JPEG_state.jpeg_descr.Conf, 0, sizeof(JPEG_ConfTypeDef));
jpeg_overflow = true;
goto exit_cleanup;
}
MICROPY_EVENT_POLL_HOOK
}
// Set output size.
dst->size = JPEG_state.out_data_len;
// STM32H7 BUG FIX! The JPEG Encoder will occasionally trigger the EOCF interrupt before writing
// a final 0x000000D9 long into the output fifo as the end of the JPEG image. When this occurs
// the output fifo will have a single 0 value in it after the encoding process finishes.
if (__HAL_JPEG_GET_FLAG(&JPEG_state.jpeg_descr, JPEG_FLAG_OFNEF) && (!JPEG_state.jpeg_descr.Instance->DOR)) {
// The encoding output process always aborts before writing JPEG_OUTPUT_CHUNK_SIZE bytes
// to the end of the dma_buffer. So, it is always safe to add one extra byte.
dma_buffer[dst->size++] = 0xD9;
}
// Update the JPEG image size by the new APP0 header and it's padding. However, we have to move
// the SOI header to the front of the image first...
dst->size += app0_total_size;
memcpy(dst->data, dma_buffer, sizeof(uint16_t)); // move SOI
memcpy(dst->data + sizeof(uint16_t), JPEG_APP0, sizeof(JPEG_APP0)); // inject APP0
// Add on a comment header with 0 padding to ensure cache alignment after the APP0 header.
*((uint16_t *) (dst->data + sizeof(uint16_t) + sizeof(JPEG_APP0))) = __REV16(app0_padding_size); // size
memset(dst->data + sizeof(uint32_t) + sizeof(JPEG_APP0), 0, app0_padding_size - sizeof(uint16_t)); // data
// Clean trailing data after 0xFFD9 at the end of the jpeg byte stream.
dst->size = jpeg_clean_trailing_bytes(dst->size, dst->data);
exit_cleanup:
// Cleanup jpeg state.
HAL_JPEG_Abort(&JPEG_state.jpeg_descr);
HAL_JPEG_UnRegisterDataReadyCallback(&JPEG_state.jpeg_descr);
HAL_JPEG_UnRegisterGetDataCallback(&JPEG_state.jpeg_descr);
fb_free(); // mcu_row_buffer (after DMA is aborted)
return jpeg_overflow;
}
static void jpeg_decompress_data_ready_abort(JPEG_HandleTypeDef *hjpeg, uint8_t *pDataOut, uint32_t OutDataLength) {
HAL_JPEG_Abort(hjpeg);
}
static void jpeg_decompress_get_data(JPEG_HandleTypeDef *hjpeg, uint32_t NbDecodedData) {
JPEG_state.jpeg_descr.pJpegInBuffPtr += NbDecodedData;
JPEG_state.in_data_len -= NbDecodedData;
HAL_JPEG_ConfigInputBuffer(&JPEG_state.jpeg_descr, JPEG_state.jpeg_descr.pJpegInBuffPtr,
IM_MIN(JPEG_state.in_data_len, JPEG_MAX_MDMA_BLOCK_SIZE));
}
static void jpeg_decompress_data_ready(JPEG_HandleTypeDef *hjpeg, uint8_t *pDataOut, uint32_t OutDataLength) {
// We have received this much data.
JPEG_state.out_data_len += OutDataLength;
int remaining = JPEG_state.out_data_len_max - JPEG_state.out_data_len;
if (!remaining) {
HAL_JPEG_Pause(hjpeg, JPEG_PAUSE_RESUME_OUTPUT);
JPEG_state.out_data_len = 0;
JPEG_state.output_paused = true;
} else {
// We are ok to receive more data.
HAL_JPEG_ConfigOutputBuffer(hjpeg, pDataOut + OutDataLength, IM_MIN(remaining, JPEG_OUTPUT_CHUNK_SIZE));
}
}
void jpeg_decompress(image_t *dst, image_t *src) {
// Verify the jpeg image is not a non-baseline jpeg image and check that is has
// valid headers up to the start-of-scan header (which cannot be trivially walked).
if (!jpeg_is_valid(src)) {
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Non-Baseline JPEGs are not supported."));
}
// Ensure src data is cache algined for MDMA and a multiple of fifo input size in bytes.
JPEG_state.in_data_len = src->size;
uint32_t diff = JPEG_state.in_data_len % JPEG_INPUT_FIFO_BYTES;
if (diff) {
JPEG_state.in_data_len += JPEG_INPUT_FIFO_BYTES - diff;
}
if (((uint32_t) src->data) % __SCB_DCACHE_LINE_SIZE) {
// Copy to cache aligned buffer.
JPEG_state.jpeg_descr.pJpegInBuffPtr = fb_alloc(JPEG_state.in_data_len, FB_ALLOC_CACHE_ALIGN);
memcpy(JPEG_state.jpeg_descr.pJpegInBuffPtr, src->data, src->size);
} else {
JPEG_state.jpeg_descr.pJpegInBuffPtr = src->data;
}
// Skip zero remaining (__SCB_DCACHE_LINE_SIZE - (src->size % __SCB_DCACHE_LINE_SIZE)) bytes
// of data as the JPEG bytestream will have already been closed via 0xFF, 0xD9.
// Set handles for header decoding.
HAL_JPEG_RegisterDataReadyCallback(&JPEG_state.jpeg_descr, jpeg_decompress_data_ready_abort);
// Decode the JPEG Header...
uint8_t temp[JPEG_OUTPUT_CHUNK_SIZE];
HAL_JPEG_Decode(&JPEG_state.jpeg_descr, JPEG_state.jpeg_descr.pJpegInBuffPtr, JPEG_state.in_data_len,
temp, JPEG_OUTPUT_CHUNK_SIZE, JPEG_CODEC_TIMEOUT);
if ((src->w != JPEG_state.jpeg_descr.Conf.ImageWidth) || (src->h != JPEG_state.jpeg_descr.Conf.ImageHeight)) {
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("JPEG Geometry does not match Image Object Geometry!"));
}
// Set handles for full decoding.
HAL_JPEG_RegisterGetDataCallback(&JPEG_state.jpeg_descr, jpeg_decompress_get_data);
HAL_JPEG_RegisterDataReadyCallback(&JPEG_state.jpeg_descr, jpeg_decompress_data_ready);
int mcu_w = JPEG_MCU_W;
int mcu_h = JPEG_MCU_H;
int mcu_size = JPEG_444_GS_MCU_SIZE;
DMA2D_HandleTypeDef DMA2D_Handle = {};
if (JPEG_state.jpeg_descr.Conf.ColorSpace == JPEG_YCBCR_COLORSPACE) {
switch (JPEG_state.jpeg_descr.Conf.ChromaSubsampling) {
case JPEG_444_SUBSAMPLING: {
mcu_w = JPEG_MCU_W;
mcu_h = JPEG_MCU_H;
mcu_size = JPEG_444_YCBCR_MCU_SIZE;
DMA2D_Handle.LayerCfg[1].ChromaSubSampling = DMA2D_NO_CSS;
break;
}
case JPEG_420_SUBSAMPLING: {
mcu_w = JPEG_MCU_W * 2;
mcu_h = JPEG_MCU_H * 2;
mcu_size = JPEG_420_YCBCR_MCU_SIZE;
DMA2D_Handle.LayerCfg[1].ChromaSubSampling = DMA2D_CSS_420;
break;
}
case JPEG_422_SUBSAMPLING: {
mcu_w = JPEG_MCU_W * 2;
mcu_h = JPEG_MCU_H;
mcu_size = JPEG_422_YCBCR_MCU_SIZE;
DMA2D_Handle.LayerCfg[1].ChromaSubSampling = DMA2D_CSS_422;
break;
}
default: {
break;
}
}
if (dst->is_color) {
DMA2D_Handle.Instance = DMA2D;
// Configure DMA2D output.
DMA2D_Handle.Init.Mode = DMA2D_M2M_PFC;
DMA2D_Handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
DMA2D_Handle.Init.OutputOffset = 0;
DMA2D_Handle.Init.AlphaInverted = DMA2D_REGULAR_ALPHA;
DMA2D_Handle.Init.RedBlueSwap = DMA2D_RB_REGULAR;
DMA2D_Handle.Init.BytesSwap = DMA2D_BYTES_REGULAR;
DMA2D_Handle.Init.LineOffsetMode = DMA2D_LOM_PIXELS;
// Configure DMA2D input.
DMA2D_Handle.LayerCfg[1].InputOffset = (((src->w + mcu_w - 1) / mcu_w) * mcu_w) - src->w;
DMA2D_Handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_YCBCR;
DMA2D_Handle.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA;
DMA2D_Handle.LayerCfg[1].InputAlpha = 0xFF;
DMA2D_Handle.LayerCfg[1].AlphaInverted = DMA2D_REGULAR_ALPHA;
DMA2D_Handle.LayerCfg[1].RedBlueSwap = DMA2D_RB_REGULAR;
// DMA2D initialization.
HAL_DMA2D_Init(&DMA2D_Handle);
HAL_DMA2D_ConfigLayer(&DMA2D_Handle, 1);
// Ensure any cached writes are dropped.
SCB_InvalidateDCache_by_Addr((uint32_t *) dst->data, image_size(dst));
}
} else if (JPEG_state.jpeg_descr.Conf.ColorSpace == JPEG_CMYK_COLORSPACE) {
if (((uint32_t) src->data) % __SCB_DCACHE_LINE_SIZE) {
fb_free(); // JPEG_state.jpeg_descr.pJpegInBuffPtr
}
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Unsupported JPEG!"));
}
uint32_t dst_w_mcus = (src->w + mcu_w - 1) / mcu_w;
uint32_t dst_w_mcus_bytes = dst_w_mcus * mcu_size;
uint32_t dst_w_mcus_bytes_2 = dst_w_mcus_bytes * 2;
JPEG_state.out_data_len_max = dst_w_mcus_bytes;
JPEG_state.out_data_len = 0;
JPEG_state.output_paused = false;
uint8_t *mcu_row_buffer = fb_alloc(dst_w_mcus_bytes_2, FB_ALLOC_PREFER_SPEED | FB_ALLOC_CACHE_ALIGN);
#if defined(OMV_MDMA_CHANNEL_JPEG_IN)
// Flush input.
SCB_CleanDCache_by_Addr((uint32_t *) JPEG_state.jpeg_descr.pJpegInBuffPtr, JPEG_state.in_data_len);
// Invalidate the MCU row for DMA.
SCB_InvalidateDCache_by_Addr((uint32_t *) mcu_row_buffer, dst_w_mcus_bytes);
// Start the DMA process on the image.
HAL_JPEG_Decode_DMA(&JPEG_state.jpeg_descr,
JPEG_state.jpeg_descr.pJpegInBuffPtr, IM_MIN(JPEG_state.in_data_len, JPEG_MAX_MDMA_BLOCK_SIZE),
mcu_row_buffer, IM_MIN(dst_w_mcus_bytes, JPEG_MAX_MDMA_BLOCK_SIZE));
#else
HAL_JPEG_Decode_IT(&JPEG_state.jpeg_descr,
JPEG_state.jpeg_descr.pJpegInBuffPtr, IM_MIN(JPEG_state.in_data_len, JPEG_MAX_MDMA_BLOCK_SIZE),
mcu_row_buffer, IM_MIN(dst_w_mcus_bytes, JPEG_MAX_MDMA_BLOCK_SIZE));
#endif
for (int y_offset = 0; y_offset < src->h; y_offset += mcu_h) {
int h = y_offset / mcu_h;
uint8_t *this_mcu_row_buffer_ptr = mcu_row_buffer + (dst_w_mcus_bytes * (h % 2));
uint8_t *next_mcu_row_buffer_ptr = mcu_row_buffer + (dst_w_mcus_bytes * ((h + 1) % 2));
int dy = IM_MIN(mcu_h, src->h - y_offset);
#if defined(OMV_MDMA_CHANNEL_JPEG_IN)
if ((y_offset + mcu_h) < src->h) {
// not last row
// Invalidate the MCU row for DMA.
SCB_InvalidateDCache_by_Addr((uint32_t *) next_mcu_row_buffer_ptr, dst_w_mcus_bytes);
}
#endif
// Wait for the MCUs to be processed.
for (mp_uint_t tick_start = mp_hal_ticks_ms(); !JPEG_state.output_paused; ) {
if ((mp_hal_ticks_ms() - tick_start) > JPEG_CODEC_TIMEOUT) {
goto exit_cleanup;
}
MICROPY_EVENT_POLL_HOOK
}
if ((y_offset + mcu_h) < src->h) {
// not last row
// Reset the lock.
JPEG_state.output_paused = false;
// Restart the DMA process on the next row of MCUs.
HAL_JPEG_ConfigOutputBuffer(&JPEG_state.jpeg_descr,
next_mcu_row_buffer_ptr, IM_MIN(dst_w_mcus_bytes, JPEG_MAX_MDMA_BLOCK_SIZE));
HAL_JPEG_Resume(&JPEG_state.jpeg_descr, JPEG_PAUSE_RESUME_OUTPUT);
}
#if defined(OMV_MDMA_CHANNEL_JPEG_IN)
// Ensure any cached reads are dropped.
SCB_InvalidateDCache_by_Addr((uint32_t *) this_mcu_row_buffer_ptr, dst_w_mcus_bytes);
#endif
if (JPEG_state.jpeg_descr.Conf.ColorSpace == JPEG_GRAYSCALE_COLORSPACE) {
for (int x_offset = 0; x_offset < src->w; x_offset += JPEG_MCU_W) {
uint8_t *Y0 = this_mcu_row_buffer_ptr + (x_offset * JPEG_MCU_H);
int dx = IM_MIN(JPEG_MCU_W, src->w - x_offset);
switch (dst->pixfmt) {
case PIXFORMAT_BINARY: {
for (int y = y_offset; y < (y_offset + dy); y++) {
uint32_t *rp = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(dst, y);
for (int x = x_offset; x < (x_offset + dx); x++) {
int p = *Y0++;
int v = p > 128;
IMAGE_PUT_BINARY_PIXEL_FAST(rp, x, v);
}
Y0 += JPEG_MCU_W - dx;
}
break;
}
case PIXFORMAT_GRAYSCALE: {
for (int y = y_offset; y < (y_offset + dy); y++) {
uint8_t *rp = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(dst, y) + x_offset;
if (dx == JPEG_MCU_W) {
*((uint32_t *) rp) = *((uint32_t *) Y0);
*(((uint32_t *) rp) + 1) = *(((uint32_t *) Y0) + 1);
} else if (dx >= 4) {
*((uint32_t *) rp) = *((uint32_t *) Y0);
if (dx >= 6) {
*(((uint16_t *) rp) + 2) = *(((uint16_t *) Y0) + 2);
if (dx & 1) {
rp[6] = Y0[6];
}
} else if (dx & 1) {
rp[4] = Y0[4];
}
} else if (dx >= 2) {
*((uint16_t *) rp) = *((uint16_t *) Y0);
if (dx & 1) {
rp[2] = Y0[2];
}
} else {
*rp = *Y0;
}
Y0 += JPEG_MCU_W;
}
break;
}
case PIXFORMAT_RGB565: {
for (int y = y_offset; y < (y_offset + dy); y++) {
uint16_t *rp = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(dst, y);
for (int x = x_offset; x < (x_offset + dx); x++) {
int p = *Y0++;
int v = COLOR_Y_TO_RGB565(p);
IMAGE_PUT_RGB565_PIXEL_FAST(rp, x, v);
}
Y0 += JPEG_MCU_W - dx;
}
break;
}
}
}
} else if (JPEG_state.jpeg_descr.Conf.ColorSpace == JPEG_YCBCR_COLORSPACE) {
switch (dst->pixfmt) {
case PIXFORMAT_BINARY: {
for (int x_offset = 0; x_offset < src->w; x_offset += mcu_w) {
for (int int_y_offset = 0; int_y_offset < mcu_h; int_y_offset += JPEG_MCU_H) {
int dy = IM_MIN(JPEG_MCU_H, src->h - int_y_offset - y_offset);
for (int int_x_offset = 0; int_x_offset < mcu_w; int_x_offset += JPEG_MCU_W) {
uint8_t *Y0 = this_mcu_row_buffer_ptr + ((x_offset / mcu_w) * mcu_size) +
(int_y_offset * mcu_w) + (int_x_offset * JPEG_MCU_H);
int dx = IM_MIN(JPEG_MCU_W, src->w - int_x_offset - x_offset);
for (int y = y_offset + int_y_offset; y < (y_offset + int_y_offset + dy); y++) {
uint32_t *rp = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(dst, y);
for (int x = x_offset + int_x_offset; x < (x_offset + int_x_offset + dx); x++) {
int p = *Y0++;
int v = p > 128;
IMAGE_PUT_BINARY_PIXEL_FAST(rp, x, v);
}
Y0 += JPEG_MCU_W - dx;
}
}
}
}
break;
}
case PIXFORMAT_GRAYSCALE: {
for (int x_offset = 0; x_offset < src->w; x_offset += mcu_w) {
for (int int_y_offset = 0; int_y_offset < mcu_h; int_y_offset += JPEG_MCU_H) {
int dy = IM_MIN(JPEG_MCU_H, src->h - int_y_offset - y_offset);
for (int int_x_offset = 0; int_x_offset < mcu_w; int_x_offset += JPEG_MCU_W) {
uint8_t *Y0 = this_mcu_row_buffer_ptr + ((x_offset / mcu_w) * mcu_size) +
(int_y_offset * mcu_w) + (int_x_offset * JPEG_MCU_H);
int dx = IM_MIN(JPEG_MCU_W, src->w - int_x_offset - x_offset);
for (int y = y_offset + int_y_offset; y < (y_offset + int_y_offset + dy); y++) {
uint8_t *rp = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(dst, y) + x_offset + int_x_offset;
if (dx == JPEG_MCU_W) {
*((uint32_t *) rp) = *((uint32_t *) Y0);
*(((uint32_t *) rp) + 1) = *(((uint32_t *) Y0) + 1);
} else if (dx >= 4) {
*((uint32_t *) rp) = *((uint32_t *) Y0);
if (dx >= 6) {
*(((uint16_t *) rp) + 2) = *(((uint16_t *) Y0) + 2);
if (dx & 1) {
rp[6] = Y0[6];
}
} else if (dx & 1) {
rp[4] = Y0[4];
}
} else if (dx >= 2) {
*((uint16_t *) rp) = *((uint16_t *) Y0);
if (dx & 1) {
rp[2] = Y0[2];
}
} else {
*rp = *Y0;
}
Y0 += JPEG_MCU_W;
}
}
}
}
break;
}
case PIXFORMAT_RGB565: {
#if !defined(OMV_MDMA_CHANNEL_JPEG_IN)
// Ensure any cached writes are written.
SCB_CleanDCache_by_Addr((uint32_t *) this_mcu_row_buffer_ptr, dst_w_mcus_bytes);
#endif
uint16_t *rp = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(dst, y_offset);
HAL_DMA2D_Start(&DMA2D_Handle, (uint32_t) this_mcu_row_buffer_ptr, (uint32_t) rp, dst->w, dy);
// Invalidate any cached reads for the previous line that was just written.
if ((y_offset - mcu_h) >= 0) {
uint16_t *previous_rp = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(dst, (y_offset - mcu_h));
SCB_InvalidateDCache_by_Addr((uint32_t *) previous_rp, dst->w * mcu_h * sizeof(uint16_t));
}
HAL_DMA2D_PollForTransfer(&DMA2D_Handle, JPEG_CODEC_TIMEOUT);
// For the last row invalidate any cached reads for the line that was just written.
if ((y_offset + mcu_h) >= src->h) {
SCB_InvalidateDCache_by_Addr((uint32_t *) rp, dst->w * mcu_h * sizeof(uint16_t));
}
break;
}
}
}
if ((y_offset + mcu_h) >= src->h) {
// last row
for (mp_uint_t tick_start = mp_hal_ticks_ms();
HAL_JPEG_GetState(&JPEG_state.jpeg_descr) == HAL_JPEG_STATE_BUSY_DECODING; ) {
if ((mp_hal_ticks_ms() - tick_start) > JPEG_CODEC_TIMEOUT) {
goto exit_cleanup;
}
MICROPY_EVENT_POLL_HOOK
}
}
}
exit_cleanup:
// Cleanup jpeg state.
HAL_JPEG_Abort(&JPEG_state.jpeg_descr);
HAL_JPEG_UnRegisterDataReadyCallback(&JPEG_state.jpeg_descr);
HAL_JPEG_UnRegisterGetDataCallback(&JPEG_state.jpeg_descr);
fb_free(); // mcu_row_buffer (after DMA is aborted)
if ((JPEG_state.jpeg_descr.Conf.ColorSpace == JPEG_YCBCR_COLORSPACE) && dst->is_color) {
HAL_DMA2D_DeInit(&DMA2D_Handle);
}
if (((uint32_t) src->data) % __SCB_DCACHE_LINE_SIZE) {
fb_free(); // JPEG_state.jpeg_descr.pJpegInBuffPtr (after DMA is aborted)
}
}
void imlib_hardware_jpeg_init() {
JPEG_state.jpeg_descr.Instance = JPEG;
HAL_JPEG_Init(&JPEG_state.jpeg_descr);
NVIC_SetPriority(JPEG_IRQn, IRQ_PRI_JPEG);
HAL_NVIC_EnableIRQ(JPEG_IRQn);
#if defined(OMV_MDMA_CHANNEL_JPEG_IN)
JPEG_state.mdma_descr[JPEG_MDMA_IN].Instance = MDMA_CHAN_TO_INSTANCE(OMV_MDMA_CHANNEL_JPEG_IN);
JPEG_state.mdma_descr[JPEG_MDMA_IN].Init.Request = MDMA_REQUEST_JPEG_INFIFO_TH;
JPEG_state.mdma_descr[JPEG_MDMA_IN].Init.TransferTriggerMode = MDMA_BUFFER_TRANSFER;
JPEG_state.mdma_descr[JPEG_MDMA_IN].Init.Priority = MDMA_PRIORITY_LOW;
JPEG_state.mdma_descr[JPEG_MDMA_IN].Init.Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE;
JPEG_state.mdma_descr[JPEG_MDMA_IN].Init.SourceInc = MDMA_SRC_INC_DOUBLEWORD;
JPEG_state.mdma_descr[JPEG_MDMA_IN].Init.DestinationInc = MDMA_DEST_INC_DISABLE;
JPEG_state.mdma_descr[JPEG_MDMA_IN].Init.SourceDataSize = MDMA_SRC_DATASIZE_DOUBLEWORD;
JPEG_state.mdma_descr[JPEG_MDMA_IN].Init.DestDataSize = MDMA_DEST_DATASIZE_WORD;
JPEG_state.mdma_descr[JPEG_MDMA_IN].Init.DataAlignment = MDMA_DATAALIGN_PACKENABLE;
JPEG_state.mdma_descr[JPEG_MDMA_IN].Init.BufferTransferLength = JPEG_INPUT_FIFO_BYTES;
JPEG_state.mdma_descr[JPEG_MDMA_IN].Init.SourceBurst = MDMA_SOURCE_BURST_4BEATS;
JPEG_state.mdma_descr[JPEG_MDMA_IN].Init.DestBurst = MDMA_DEST_BURST_8BEATS;
JPEG_state.mdma_descr[JPEG_MDMA_IN].Init.SourceBlockAddressOffset = 0;
JPEG_state.mdma_descr[JPEG_MDMA_IN].Init.DestBlockAddressOffset = 0;
HAL_MDMA_Init(&JPEG_state.mdma_descr[JPEG_MDMA_IN]);
__HAL_LINKDMA(&JPEG_state.jpeg_descr, hdmain, JPEG_state.mdma_descr[JPEG_MDMA_IN]);
JPEG_state.mdma_descr[JPEG_MDMA_OUT].Instance = MDMA_CHAN_TO_INSTANCE(OMV_MDMA_CHANNEL_JPEG_OUT);
JPEG_state.mdma_descr[JPEG_MDMA_OUT].Init.Request = MDMA_REQUEST_JPEG_OUTFIFO_TH;
JPEG_state.mdma_descr[JPEG_MDMA_OUT].Init.TransferTriggerMode = MDMA_BUFFER_TRANSFER;
JPEG_state.mdma_descr[JPEG_MDMA_OUT].Init.Priority = MDMA_PRIORITY_LOW;
JPEG_state.mdma_descr[JPEG_MDMA_OUT].Init.Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE;
JPEG_state.mdma_descr[JPEG_MDMA_OUT].Init.SourceInc = MDMA_SRC_INC_DISABLE;
JPEG_state.mdma_descr[JPEG_MDMA_OUT].Init.DestinationInc = MDMA_DEST_INC_DOUBLEWORD;
JPEG_state.mdma_descr[JPEG_MDMA_OUT].Init.SourceDataSize = MDMA_SRC_DATASIZE_WORD;
JPEG_state.mdma_descr[JPEG_MDMA_OUT].Init.DestDataSize = MDMA_DEST_DATASIZE_DOUBLEWORD;
JPEG_state.mdma_descr[JPEG_MDMA_OUT].Init.DataAlignment = MDMA_DATAALIGN_PACKENABLE;
JPEG_state.mdma_descr[JPEG_MDMA_OUT].Init.BufferTransferLength = JPEG_OUTPUT_FIFO_BYTES;
JPEG_state.mdma_descr[JPEG_MDMA_OUT].Init.SourceBurst = MDMA_SOURCE_BURST_8BEATS;
JPEG_state.mdma_descr[JPEG_MDMA_OUT].Init.DestBurst = MDMA_DEST_BURST_4BEATS;
JPEG_state.mdma_descr[JPEG_MDMA_OUT].Init.SourceBlockAddressOffset = 0;
JPEG_state.mdma_descr[JPEG_MDMA_OUT].Init.DestBlockAddressOffset = 0;
HAL_MDMA_Init(&JPEG_state.mdma_descr[JPEG_MDMA_OUT]);
__HAL_LINKDMA(&JPEG_state.jpeg_descr, hdmaout, JPEG_state.mdma_descr[JPEG_MDMA_OUT]);
#endif
}
void imlib_hardware_jpeg_deinit() {
memset(&JPEG_state.jpeg_descr.Conf, 0, sizeof(JPEG_ConfTypeDef));
HAL_JPEG_Abort(&JPEG_state.jpeg_descr);
#if defined(OMV_MDMA_CHANNEL_JPEG_IN)
HAL_MDMA_DeInit(&JPEG_state.mdma_descr[JPEG_MDMA_OUT]);
HAL_MDMA_DeInit(&JPEG_state.mdma_descr[JPEG_MDMA_IN]);
#endif
HAL_NVIC_DisableIRQ(JPEG_IRQn);
HAL_JPEG_DeInit(&JPEG_state.jpeg_descr);
}
#endif