C

Qt Quick Ultralite imagedecoder Example

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial

#include "stmimagedecoder.h"
#include "stm32_mcu_specific.h"
#include "buffer_config.h"
#include <platforminterface/imagedecoder.h>
#include <platforminterface/log.h>

extern "C" {
#include "jpeg_utils.h"
}

void JPEG_InputHandler(JPEG_HandleTypeDef *hjpeg, Qul::PlatformInterface::ImageDecoder::RequestDataCallback &callback);
uint32_t JPEG_OutputHandler(JPEG_HandleTypeDef *hjpeg);
bool jpegInformation(Qul::PlatformInterface::ImageDecoder::RequestDataCallback &callback,
                     int16_t *width,
                     int16_t *height,
                     Qul::PixelFormat *actualPixelFormat,
                     Qul::PixelFormat optimalOpaquePixelFormat,
                     Qul::PixelFormat optimalAlphaPixelFormat);

StmImageDecoder::StmImageDecoder() {}

StmImageDecoder::~StmImageDecoder() {}
extern JPEG_HandleTypeDef JPEG_Handle;
JPEG_YCbCrToRGB_Convert_Function pConvert_Function = NULL;

bool StmImageDecoder::imageInformation(RequestDataCallback &callback,
                                       int16_t *width,
                                       int16_t *height,
                                       Qul::PixelFormat *actualPixelFormat,
                                       Qul::PixelFormat optimalOpaquePixelFormat,
                                       Qul::PixelFormat optimalAlphaPixelFormat)
{
    return jpegInformation(callback, width, height, actualPixelFormat, optimalOpaquePixelFormat, optimalAlphaPixelFormat);
}

uint32_t MCU_TotalNb = 0;
uint32_t MCU_BlockIndex;
__IO uint32_t Jpeg_HWDecodingEnd = 0;

uint32_t JPEG_OUT_Read_BufferIndex = 0;
uint32_t JPEG_OUT_Write_BufferIndex = 0;
__IO uint32_t Output_Is_Paused = 0;

uint32_t JPEG_IN_Read_BufferIndex = 0;
uint32_t JPEG_IN_Write_BufferIndex = 0;
__IO uint32_t Input_Is_Paused = 0;

uint8_t *FrameBufferAddress;
uint32_t fileOffset = 0;

#define DECODING_TIMEOUT 9000

static void resetDecoderContext(void)
{
    uint16_t i;

    fileOffset = 0;
    pConvert_Function = NULL;

    MCU_TotalNb = 0;
    MCU_BlockIndex = 0;
    Jpeg_HWDecodingEnd = 0;

    JPEG_OUT_Read_BufferIndex = 0;
    JPEG_OUT_Write_BufferIndex = 0;
    Output_Is_Paused = 0;

    JPEG_IN_Read_BufferIndex = 0;
    JPEG_IN_Write_BufferIndex = 0;
    Input_Is_Paused = 0;

    for (i = 0; i < NB_OUTPUT_DATA_BUFFERS; i++) {
        Jpeg_OUT_BufferTab[i].State = JPEG_BUFFER_EMPTY;
        Jpeg_OUT_BufferTab[i].DataBufferSize = 0;
    }
}

int StmImageDecoder::decodeImage(RequestDataCallback &callback,
                                 unsigned char *outbuffer,
                                 uint32_t outbufferSize,
                                 Qul::PixelFormat pixelFormat,
                                 uint32_t requiredBytesPerLine)
{
    QUL_UNUSED(outbufferSize);
    QUL_UNUSED(pixelFormat);
    QUL_UNUSED(requiredBytesPerLine);

    /*##-6- JPEG decoding with DMA (Not Blocking ) Method ################*/
    uint32_t i;
    FrameBufferAddress = outbuffer;
    resetDecoderContext();
    uint32_t jpegDecodingStart = HAL_GetTick();

    /* Read from JPG file and fill input buffers */
    for (i = 0; i < NB_INPUT_DATA_BUFFERS && fileOffset < callback.totalAvailableDataSize(); i++) {
        Jpeg_IN_BufferTab[i].DataBufferSize = callback.readData(Jpeg_IN_BufferTab[i].DataBuffer,
                                                                fileOffset,
                                                                CHUNK_SIZE_IN);
        if (Jpeg_IN_BufferTab[i].DataBufferSize % 4 != 0)
            Qul::PlatformInterface::log("size is not 4 bytes\n");
        if (uint32_t(Jpeg_IN_BufferTab[i].DataBuffer) % 4 != 0)
            Qul::PlatformInterface::log("buffer is not 4 bytes aligned\n");

        if (Jpeg_IN_BufferTab[i].DataBufferSize > 0) {
            Jpeg_IN_BufferTab[i].State = JPEG_BUFFER_FULL;
            fileOffset += Jpeg_IN_BufferTab[i].DataBufferSize;
        } else {
            Qul::PlatformInterface::log("Read fail\n");
            return -1;
        }
    }

    /* Start JPEG decoding with DMA method */
    HAL_StatusTypeDef status;

#ifdef USE_DMA_BASED_JPEG_DECODING
    //Using DMA based jpeg decoding
    status = HAL_JPEG_Decode_DMA(&JPEG_Handle,
                                 Jpeg_IN_BufferTab[0].DataBuffer,
                                 Jpeg_IN_BufferTab[0].DataBufferSize,
                                 Jpeg_OUT_BufferTab[0].DataBuffer,
                                 CHUNK_SIZE_OUT);
#else
    //Using Interrupt based jpeg decoding
    status = HAL_JPEG_Decode_IT(&JPEG_Handle,
                                Jpeg_IN_BufferTab[0].DataBuffer,
                                Jpeg_IN_BufferTab[0].DataBufferSize,
                                Jpeg_OUT_BufferTab[0].DataBuffer,
                                CHUNK_SIZE_OUT);
#endif

    if (status != HAL_OK) {
        Qul::PlatformInterface::log("HAL_JPEG_Decode_DMA failed\n");
        return 1;
    }

    /*##-7- Wait till end of JPEG decoding and perfom Input/Output Processing in BackGround  #*/
    uint32_t JpegProcessing_End = 0;
    do {
        JPEG_InputHandler(&JPEG_Handle, callback);
        JpegProcessing_End = JPEG_OutputHandler(&JPEG_Handle);
    } while (JpegProcessing_End == 0 && ((HAL_GetTick() - jpegDecodingStart) < DECODING_TIMEOUT));

    while (!Jpeg_HWDecodingEnd && ((HAL_GetTick() - jpegDecodingStart) < DECODING_TIMEOUT)) {
#if (PLATFORM_OS == baremetal)
        //Sleep until HAL_JPEG_DecodeCpltCallback
        __WFI();
#else
        //FreeRTOS based platforms can wait on semaphore to yield to other tasks.
        //Semaphore can be given up in HAL_JPEG_DecodeCpltCallback
#endif
    }

    if (!Jpeg_HWDecodingEnd && (HAL_GetTick() - jpegDecodingStart) >= DECODING_TIMEOUT) {
        Qul::PlatformInterface::log("JPEG image decoding timed out\n");
        return -1;
    }

    Jpeg_HWDecodingEnd = 0;

    return 0;
}

/**
  * @brief  JPEG Ouput Data BackGround Postprocessing .
  * @param hjpeg: JPEG handle pointer
  * @retval 1 : if JPEG processing has finished, 0 : if JPEG processing still ongoing
  */
uint32_t JPEG_OutputHandler(JPEG_HandleTypeDef *hjpeg)
{
    uint32_t ConvertedDataCount; // Looks like not being used by the STM32 implementation for decoding

    if (Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].State == JPEG_BUFFER_FULL) {
        MCU_BlockIndex += pConvert_Function(Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].DataBuffer,
                                            FrameBufferAddress,
                                            MCU_BlockIndex,
                                            Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].DataBufferSize,
                                            &ConvertedDataCount);

        Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].State = JPEG_BUFFER_EMPTY;
        Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].DataBufferSize = 0;

        JPEG_OUT_Read_BufferIndex++;
        if (JPEG_OUT_Read_BufferIndex >= NB_OUTPUT_DATA_BUFFERS) {
            JPEG_OUT_Read_BufferIndex = 0;
        }

        if (MCU_BlockIndex == MCU_TotalNb) {
            return 1;
        }
    } else if ((Output_Is_Paused == 1) && (JPEG_OUT_Write_BufferIndex == JPEG_OUT_Read_BufferIndex)
               && (Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].State == JPEG_BUFFER_EMPTY)) {
        Output_Is_Paused = 0;
        HAL_JPEG_Resume(hjpeg, JPEG_PAUSE_RESUME_OUTPUT);
    }

    return 0;
}

/**
  * @brief  JPEG Input Data BackGround processing .
  * @param hjpeg: JPEG handle pointer
  * @retval None
  */
void JPEG_InputHandler(JPEG_HandleTypeDef *hjpeg, Qul::PlatformInterface::ImageDecoder::RequestDataCallback &callback)
{
    if (Jpeg_IN_BufferTab[JPEG_IN_Write_BufferIndex].State == JPEG_BUFFER_EMPTY) {
        if (fileOffset == callback.totalAvailableDataSize())
            return; // No more data to read

        Jpeg_IN_BufferTab[JPEG_IN_Write_BufferIndex].DataBufferSize
            = callback.readData(Jpeg_IN_BufferTab[JPEG_IN_Write_BufferIndex].DataBuffer, fileOffset, CHUNK_SIZE_IN);

        if (Jpeg_IN_BufferTab[JPEG_IN_Write_BufferIndex].DataBufferSize > 0) {
            fileOffset += Jpeg_IN_BufferTab[JPEG_IN_Write_BufferIndex].DataBufferSize;
            Jpeg_IN_BufferTab[JPEG_IN_Write_BufferIndex].State = JPEG_BUFFER_FULL;
        } else {
            Qul::PlatformInterface::log("EOF reached, (or read failed)\n");
            return;
        }

        if ((Input_Is_Paused == 1) && (JPEG_IN_Write_BufferIndex == JPEG_IN_Read_BufferIndex)) {
            Input_Is_Paused = 0;
            HAL_JPEG_ConfigInputBuffer(hjpeg,
                                       Jpeg_IN_BufferTab[JPEG_IN_Read_BufferIndex].DataBuffer,
                                       Jpeg_IN_BufferTab[JPEG_IN_Read_BufferIndex].DataBufferSize);
            HAL_StatusTypeDef status = HAL_JPEG_Resume(hjpeg, JPEG_PAUSE_RESUME_INPUT);
            if (status != HAL_OK)
                Qul::PlatformInterface::log("HAL_JPEG_Resume failed\n");
        }

        JPEG_IN_Write_BufferIndex++;
        if (JPEG_IN_Write_BufferIndex >= NB_INPUT_DATA_BUFFERS) {
            JPEG_IN_Write_BufferIndex = 0;
        }
    }
}

extern "C" {
/**
* @brief  JPEG Info ready callback
* @param hjpeg: JPEG handle pointer
* @param pInfo: JPEG Info Struct pointer
* @retval None
*/
void HAL_JPEG_InfoReadyCallback(JPEG_HandleTypeDef *hjpeg, JPEG_ConfTypeDef *pInfo)
{
    QUL_UNUSED(hjpeg);

    if (JPEG_GetDecodeColorConvertFunc(pInfo, &pConvert_Function, &MCU_TotalNb) != HAL_OK) {
        return;
    }
}

/**
* @brief  JPEG Get Data callback
* @param hjpeg: JPEG handle pointer
* @param NbDecodedData: Number of decoded (consummed) bytes from input buffer
* @retval None
*/
void HAL_JPEG_GetDataCallback(JPEG_HandleTypeDef *hjpeg, uint32_t NbDecodedData)
{
    if (NbDecodedData == Jpeg_IN_BufferTab[JPEG_IN_Read_BufferIndex].DataBufferSize) {
        Jpeg_IN_BufferTab[JPEG_IN_Read_BufferIndex].State = JPEG_BUFFER_EMPTY;
        Jpeg_IN_BufferTab[JPEG_IN_Read_BufferIndex].DataBufferSize = 0;

        JPEG_IN_Read_BufferIndex++;
        if (JPEG_IN_Read_BufferIndex >= NB_INPUT_DATA_BUFFERS) {
            JPEG_IN_Read_BufferIndex = 0;
        }

        if (Jpeg_IN_BufferTab[JPEG_IN_Read_BufferIndex].State == JPEG_BUFFER_EMPTY) {
            HAL_JPEG_Pause(hjpeg, JPEG_PAUSE_RESUME_INPUT);
            Input_Is_Paused = 1;
        } else {
            HAL_JPEG_ConfigInputBuffer(hjpeg,
                                       Jpeg_IN_BufferTab[JPEG_IN_Read_BufferIndex].DataBuffer,
                                       Jpeg_IN_BufferTab[JPEG_IN_Read_BufferIndex].DataBufferSize);
        }
    } else {
        HAL_JPEG_ConfigInputBuffer(hjpeg,
                                   Jpeg_IN_BufferTab[JPEG_IN_Read_BufferIndex].DataBuffer + NbDecodedData,
                                   Jpeg_IN_BufferTab[JPEG_IN_Read_BufferIndex].DataBufferSize - NbDecodedData);
    }
}

/**
  * @brief  JPEG Data Ready callback
  * @param hjpeg: JPEG handle pointer
  * @param pDataOut: pointer to the output data buffer
  * @param OutDataLength: length of output buffer in bytes
  * @retval None
  */
void HAL_JPEG_DataReadyCallback(JPEG_HandleTypeDef *hjpeg, uint8_t *pDataOut, uint32_t OutDataLength)
{
    QUL_UNUSED(pDataOut);

    Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].State = JPEG_BUFFER_FULL;
    Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].DataBufferSize = OutDataLength;

    JPEG_OUT_Write_BufferIndex++;
    if (JPEG_OUT_Write_BufferIndex >= NB_OUTPUT_DATA_BUFFERS) {
        JPEG_OUT_Write_BufferIndex = 0;
    }

    if (Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].State != JPEG_BUFFER_EMPTY) {
        HAL_JPEG_Pause(hjpeg, JPEG_PAUSE_RESUME_OUTPUT);
        Output_Is_Paused = 1;
    }
    HAL_JPEG_ConfigOutputBuffer(hjpeg, Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].DataBuffer, CHUNK_SIZE_OUT);
}

void HAL_JPEG_ErrorCallback(JPEG_HandleTypeDef *hjpeg)
{
    uint32_t error = HAL_JPEG_GetError(hjpeg);
    Qul::PlatformInterface::log("HAL_JPEG_ErrorCallback: %x\n", error);
    if (error & HAL_JPEG_ERROR_DMA)
        Qul::PlatformInterface::log("HAL_JPEG_ERROR_DMA\n");
    if (error & HAL_JPEG_ERROR_HUFF_TABLE)
        Qul::PlatformInterface::log("HAL_JPEG_ERROR_HUFF_TABLE\n");
    if (error & HAL_JPEG_ERROR_QUANT_TABLE)
        Qul::PlatformInterface::log("HAL_JPEG_ERROR_QUANT_TABLE\n");
    if (error & HAL_JPEG_ERROR_TIMEOUT)
        Qul::PlatformInterface::log("HAL_JPEG_ERROR_TIMEOUT\n");
    if (error & HAL_DMA_ERROR_TIMEOUT)
        Qul::PlatformInterface::log("HAL_DMA_ERROR_TIMEOUT\n");
}

void HAL_JPEG_DecodeCpltCallback(JPEG_HandleTypeDef *hjpeg)
{
    QUL_UNUSED(hjpeg);
    Jpeg_HWDecodingEnd = 1;
}
}