/** ****************************************************************************** * File Name : HardwareMJPEGDecoder.cpp ****************************************************************************** * This file is generated by TouchGFX Generator 4.26.1. Please, do not edit! ****************************************************************************** * @attention * * Copyright (c) 2026 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ #include #include extern "C" { #include #include uint32_t JPEG_Decode_DMA(JPEG_HandleTypeDef* hjpeg, uint8_t* input, uint32_t chunkSizeIn, uint8_t* output); uint32_t JPEG_OutputHandler(JPEG_HandleTypeDef* hjpeg); void HAL_JPEG_DecodeCpltCallback(JPEG_HandleTypeDef* hjpeg); void HAL_JPEG_ErrorCallback(JPEG_HandleTypeDef* hjpeg); void HAL_JPEG_DataReadyCallback(JPEG_HandleTypeDef* hjpeg, uint8_t* pDataOut, uint32_t OutDataLength); void DMA2D_CropBuffer(JPEG_Data_BufferTypeDef& job); void DMA2D_CopyBuffer(JPEG_Data_BufferTypeDef& job); void DMA2D_ExternalJobCompleted(JPEG_Data_BufferTypeDef& job); } namespace { uint8_t* FrameBufferAddress; uint32_t JPEG_InputImageIndex; uint32_t JPEG_InputImageSize_Bytes; uint32_t JPEG_InputImageAddress; volatile uint32_t Jpeg_HWDecodingEnd = 0; volatile uint32_t JPEG_output_is_paused = 0; volatile uint32_t JpegProcessing_End = 0; uint32_t MCU_TotalNb = 0; touchgfx::DMA_Interface* DMA2D_reference; volatile uint32_t JPEG_OUT_Write_BufferIndex = 0; uint32_t FrameBufferWidth; volatile uint32_t line_count = 0; } #define MCU_WIDTH_PIXELS ((uint32_t)16) #define MCU_HEIGHT_PIXELS ((uint32_t)16) #define MCU_CHROMA_420_SIZE_BYTES ((uint32_t)384) #define CHUNK_SIZE_IN ((uint32_t)(1024*52)) /* Max block size */ #define CHUNK_SIZE_OUT ((uint32_t)(MCU_CHROMA_420_SIZE_BYTES * ((800 + MCU_WIDTH_PIXELS - 1) / MCU_WIDTH_PIXELS))) LOCATION_PRAGMA_32("TouchGFX_MCU_Data_OutBuffer0") uint8_t MCU_Data_OutBuffer0[CHUNK_SIZE_OUT] LOCATION_ATTRIBUTE_32("TouchGFX_MCU_Data_OutBuffer0"); LOCATION_PRAGMA_32("TouchGFX_MCU_Data_OutBuffer1") uint8_t MCU_Data_OutBuffer1[CHUNK_SIZE_OUT] LOCATION_ATTRIBUTE_32("TouchGFX_MCU_Data_OutBuffer1"); uint8_t MCU_Cropping_Buffer[MCU_WIDTH_PIXELS * MCU_HEIGHT_PIXELS * 2]; __IO uint32_t MCU_BlockIndex = 0; SEM_TYPE semDecodingDone; extern JPEG_ConfTypeDef* JPEG_Info; extern JPEG_HandleTypeDef hjpeg; volatile uint32_t JPEG_OUT_Read_BufferIndex = 0; volatile uint32_t DMA2D_CopyBufferEnd = 0; JPEG_Data_BufferTypeDef Jpeg_OUT_BufferTab[NB_OUTPUT_DATA_BUFFERS] = { {JPEG_BUFFER_EMPTY, MCU_Data_OutBuffer0, 0, 0, NULL, false, false, 0, false}, {JPEG_BUFFER_EMPTY, MCU_Data_OutBuffer1, 0, 0, NULL, false, false, 0, false}, }; static struct JPEG_MCU_RGB_Converter { uint32_t WidthExtend; uint32_t ScaledWidth; uint32_t LastLineHeight; uint32_t MCU_pr_line; uint32_t bytes_pr_pixel; uint32_t startY; uint32_t endY; uint32_t startX; uint32_t endX; uint32_t MCUStart; uint32_t MCUEnd; uint32_t MCU_pr_job; uint32_t firstColOffset; uint32_t firstRowOffset; uint32_t lastColOffset; uint32_t lastRowOffset; } JPEG_ConvertorParams; HardwareMJPEGDecoder::HardwareMJPEGDecoder() : frameNumber(0), currentMovieOffset(0), indexOffset(0), firstFrameOffset(0), lastFrameEnd(0), movieLength(0), movieData(0), reader(0), aviBuffer(0), aviBufferLength(0), aviBufferStartOffset(0), lastError(AVI_NO_ERROR) { /* Clear video info */ videoInfo.frame_height = 0; videoInfo.frame_width = 0; videoInfo.ms_between_frames = 0; videoInfo.number_of_frames = 0; /* Create decoding semaphore */ semDecodingDone = SEM_CREATE(); } int HardwareMJPEGDecoder::compare(const uint32_t offset, const char* str, uint32_t num) { const char* src; if (reader != 0) { /* Assuming data is in buffer! */ src = reinterpret_cast(aviBuffer + (offset - aviBufferStartOffset)); } else { src = (const char*)movieData + offset; } return strncmp(src, str, num); } inline uint32_t HardwareMJPEGDecoder::getU32(const uint32_t offset) { if (reader != 0) { /* Assuming data is in buffer! */ const uint32_t index = offset - aviBufferStartOffset; return aviBuffer[index + 0] | (aviBuffer[index + 1] << 8) | (aviBuffer[index + 2] << 16) | (aviBuffer[index + 3] << 24); } else { volatile const uint8_t* const d = movieData + offset; uint32_t val = 0U; val |= d[0]; val |= d[1] << 8; val |= d[2] << 16; val |= d[3] << 24; return val; } } inline uint32_t HardwareMJPEGDecoder::getU16(const uint32_t offset) { if (reader != 0) { /* Assuming data is in buffer! */ const uint32_t index = offset - aviBufferStartOffset; return aviBuffer[index + 0] | (aviBuffer[index + 1] << 8); } else { volatile const uint8_t* const d = movieData + offset; uint32_t val = 0U; val |= d[0]; val |= d[1] << 8; return val; } } const uint8_t* HardwareMJPEGDecoder::readData(uint32_t offset, uint32_t length) { if (reader != 0) { if (length > aviBufferLength) { lastError = AVI_ERROR_FILE_BUFFER_TO_SMALL; assert(!"Buffer to small"); } reader->seek(offset); if (!reader->readData(aviBuffer, length)) { lastError = AVI_ERROR_EOF_REACHED; } aviBufferStartOffset = offset; return aviBuffer; } return movieData + offset; } bool HardwareMJPEGDecoder::decodeNextFrame(uint8_t* buffer, uint16_t buffer_width, uint16_t buffer_height, uint32_t buffer_stride) { assert((frameNumber > 0) && "HardwareMJPEGDecoder decoding without frame data!"); /* find next frame and decode it */ readData(currentMovieOffset, 8); uint32_t streamNo = getU16(currentMovieOffset); uint32_t chunkType = getU16(currentMovieOffset + 2); uint32_t chunkSize = getU32(currentMovieOffset + 4); const uint16_t STREAM0 = 0x3030; const uint16_t TYPEDC = 0x6364; bool isCurrentFrameLast; /* play frame if we have it all */ if (currentMovieOffset + 8 + chunkSize < movieLength) { if (streamNo == STREAM0 && chunkType == TYPEDC && chunkSize > 0) { currentMovieOffset += 8; /* decode frame */ const uint8_t* chunk = readData(currentMovieOffset, chunkSize); decodeMJPEGFrame(chunk, chunkSize, buffer, buffer_width, buffer_height, buffer_stride); frameNumber++; } isCurrentFrameLast = false; /* Advance to next frame */ currentMovieOffset += chunkSize; if (chunkSize == 0) /* Empty frame - Skip */ { currentMovieOffset += 8; } currentMovieOffset = (currentMovieOffset + 1) & 0xFFFFFFFE; /* pad to next word */ if (currentMovieOffset == lastFrameEnd) { frameNumber = 1; currentMovieOffset = firstFrameOffset; /* start over */ isCurrentFrameLast = true; } } else { frameNumber = 1; currentMovieOffset = firstFrameOffset; /* start over */ isCurrentFrameLast = true; } return !isCurrentFrameLast; } bool HardwareMJPEGDecoder::gotoNextFrame() { assert((frameNumber > 0) && "HardwareMJPEGDecoder decoding without frame data!"); readData(currentMovieOffset, 8); uint32_t chunkSize = getU32(currentMovieOffset + 4); /* increment until next video frame */ while (currentMovieOffset + 8 + chunkSize < movieLength) { /* increment one frame */ currentMovieOffset += chunkSize + 8; currentMovieOffset = (currentMovieOffset + 1) & 0xFFFFFFFE; /* pad to next word */ frameNumber++; /* next chunk */ readData(currentMovieOffset, 8); /* check it is a video frame */ uint32_t streamNo = getU16(currentMovieOffset); uint32_t chunkType = getU16(currentMovieOffset + 2); chunkSize = getU32(currentMovieOffset + 4); const uint16_t STREAM0 = 0x3030; const uint16_t TYPEDC = 0x6364; if (streamNo == STREAM0 && chunkType == TYPEDC) { /* Found next frame */ return true; } } /* skip back to first frame */ frameNumber = 1; currentMovieOffset = firstFrameOffset; /* start over */ return false; } void HardwareMJPEGDecoder::setVideoData(const uint8_t* movie, const uint32_t length) { movieData = movie; movieLength = length; reader = 0; /* not using reader */ readVideoHeader(); } void HardwareMJPEGDecoder::setVideoData(touchgfx::VideoDataReader& reader) { this->reader = &reader; movieData = 0; movieLength = reader.getDataLength(); readVideoHeader(); } bool HardwareMJPEGDecoder::hasVideo() { return (reader != 0) || (movieData != 0); } void HardwareMJPEGDecoder::readVideoHeader() { /* Start from the start */ currentMovieOffset = 0; lastError = AVI_NO_ERROR; /* Make header available in buffer */ readData(0, 72); /* Decode the movie header to find first frame */ /* Must be RIFF file */ if (compare(currentMovieOffset, "RIFF", 4)) { lastError = AVI_ERROR_NOT_RIFF; assert(!"RIFF header not found"); } /* skip fourcc and length */ currentMovieOffset += 8; if (compare(currentMovieOffset, "AVI ", 4)) { lastError = AVI_ERROR_AVI_HEADER_NOT_FOUND; assert(!"AVI header not found"); } currentMovieOffset += 4; if (compare(currentMovieOffset, "LIST", 4)) { lastError = AVI_ERROR_AVI_LIST_NOT_FOUND; assert(!"AVI LIST not found"); } /* save AVI List info */ const uint32_t aviListSize = getU32(currentMovieOffset + 4); const uint32_t aviListOffset = currentMovieOffset; assert(aviListSize); /* look into header to find frame rate */ bool foundFrame = true; uint32_t offset = currentMovieOffset + 8; if (compare(offset, "hdrl", 4)) { lastError = AVI_ERROR_AVI_HDRL_NOT_FOUND; foundFrame = false; } offset += 4; if (compare(offset, "avih", 4)) { lastError = AVI_ERROR_AVI_AVIH_NOT_FOUND; foundFrame = false; } if (foundFrame) { offset += 8; /* skip fourcc and cb in AVIMAINHEADER */ videoInfo.ms_between_frames = getU32(offset) / 1000; videoInfo.number_of_frames = getU32(offset + 16); videoInfo.frame_width = getU32(offset + 32); videoInfo.frame_height = getU32(offset + 36); } /* skip rest of AVI header, start from end of AVI List */ /* look for list with 'movi' header */ uint32_t listOffset = aviListOffset + aviListSize + 8; readData(listOffset, 12); while (compare(listOffset + 8, "movi", 4) && (lastError == AVI_NO_ERROR) && listOffset < movieLength) { const uint32_t listSize = getU32(listOffset + 4) + 8; listOffset += listSize; readData(listOffset, 12); } if (lastError != AVI_NO_ERROR) { lastError = AVI_ERROR_MOVI_NOT_FOUND; return; } /* save first frame and end of last frame */ currentMovieOffset = listOffset + 8 + 4; /* skip LIST and 'movi' */ lastFrameEnd = listOffset + 8 + getU32(listOffset + 4); /* find idx */ const uint32_t listSize = getU32(listOffset + 4) + 8; listOffset += listSize; readData(listOffset, 4); if (!compare(listOffset, "idx1", 4)) { indexOffset = listOffset; } else { lastError = AVI_ERROR_IDX1_NOT_FOUND; return; } /* start on first frame */ frameNumber = 1; /* next frame number is 1 */ firstFrameOffset = currentMovieOffset; } void HardwareMJPEGDecoder::decodeMJPEGFrame(const uint8_t* const mjpgdata, const uint32_t length, uint8_t* outputBuffer, uint16_t bufferWidth, uint16_t bufferHeight, uint32_t bufferStride) { if (length == 0) { return; } if (outputBuffer) /* only decode if buffers are assigned. */ { /* Update JPEG conversion parameters */ JPEG_ConvertorParams.bytes_pr_pixel = 2; JPEG_ConvertorParams.WidthExtend = videoInfo.frame_width; if ((JPEG_ConvertorParams.WidthExtend % 16) != 0) { JPEG_ConvertorParams.WidthExtend += 16 - (JPEG_ConvertorParams.WidthExtend % 16); } JPEG_ConvertorParams.ScaledWidth = 800 * JPEG_ConvertorParams.bytes_pr_pixel; JPEG_ConvertorParams.MCU_pr_line = JPEG_ConvertorParams.WidthExtend / MCU_WIDTH_PIXELS; JPEG_ConvertorParams.LastLineHeight = (videoInfo.frame_height % MCU_HEIGHT_PIXELS) == 0 ? 0 : MCU_HEIGHT_PIXELS - (videoInfo.frame_height % MCU_HEIGHT_PIXELS); FrameBufferWidth = bufferStride / 2; FrameBufferAddress = outputBuffer; JPEG_Decode_DMA(&hjpeg, const_cast(mjpgdata), length, outputBuffer); DMA2D_reference = dma; do { JpegProcessing_End = JPEG_OutputHandler(&hjpeg); /* If nothing to do, allow other tasks */ if (JpegProcessing_End == 2) { SEM_WAIT(semDecodingDone); } } while (JpegProcessing_End != 1); /* reset flag */ Jpeg_HWDecodingEnd = 0; DMA2D_CopyBufferEnd = 0; } } bool HardwareMJPEGDecoder::decodeFrame(const touchgfx::Rect& area, uint8_t* frameBuffer, uint32_t framebuffer_width) { /* Assuming that chunk is available and streamNo and chunkType is correct. */ /* Check by gotoNextFrame */ readData(currentMovieOffset, 8); const uint32_t length = getU32(currentMovieOffset + 4); /* Ensure whole frame is read */ const uint8_t* mjpgdata = readData(currentMovieOffset + 8, length); /* Update JPEG conversion parameters */ JPEG_ConvertorParams.bytes_pr_pixel = 2; JPEG_ConvertorParams.WidthExtend = videoInfo.frame_width; if ((JPEG_ConvertorParams.WidthExtend % 16) != 0) { JPEG_ConvertorParams.WidthExtend += 16 - (JPEG_ConvertorParams.WidthExtend % 16); } JPEG_ConvertorParams.ScaledWidth = 800 * JPEG_ConvertorParams.bytes_pr_pixel; JPEG_ConvertorParams.MCU_pr_line = JPEG_ConvertorParams.WidthExtend / MCU_WIDTH_PIXELS; JPEG_ConvertorParams.startY = area.y; JPEG_ConvertorParams.endY = MIN((uint32_t)area.bottom(), videoInfo.frame_height); JPEG_ConvertorParams.startX = area.x; JPEG_ConvertorParams.endX = MIN((uint32_t)area.right(), videoInfo.frame_width); JPEG_ConvertorParams.MCUStart = JPEG_ConvertorParams.startX / MCU_WIDTH_PIXELS; JPEG_ConvertorParams.MCUEnd = (JPEG_ConvertorParams.endX + MCU_WIDTH_PIXELS - 1) / MCU_WIDTH_PIXELS; // Ceil division JPEG_ConvertorParams.MCU_pr_job = JPEG_ConvertorParams.MCUEnd - JPEG_ConvertorParams.MCUStart; JPEG_ConvertorParams.firstColOffset = JPEG_ConvertorParams.startX % MCU_WIDTH_PIXELS; JPEG_ConvertorParams.firstRowOffset = JPEG_ConvertorParams.startY % MCU_HEIGHT_PIXELS; JPEG_ConvertorParams.lastColOffset = (JPEG_ConvertorParams.endX % MCU_WIDTH_PIXELS) == 0 ? 0 : MCU_WIDTH_PIXELS - (JPEG_ConvertorParams.endX % MCU_WIDTH_PIXELS); JPEG_ConvertorParams.lastRowOffset = (JPEG_ConvertorParams.endY % MCU_HEIGHT_PIXELS) == 0 ? 0 : MCU_HEIGHT_PIXELS - (JPEG_ConvertorParams.endY % MCU_HEIGHT_PIXELS); FrameBufferWidth = framebuffer_width; FrameBufferAddress = frameBuffer; JPEG_Decode_DMA(&hjpeg, const_cast(mjpgdata), length, frameBuffer); DMA2D_reference = dma; do { JpegProcessing_End = JPEG_OutputHandler(&hjpeg); /* If nothing to do, wait */ if (JpegProcessing_End == 2) { SEM_WAIT(semDecodingDone); } } while (JpegProcessing_End != 1); /* reset flag */ Jpeg_HWDecodingEnd = 0; DMA2D_CopyBufferEnd = 0; return true; } bool HardwareMJPEGDecoder::decodeThumbnail(uint32_t frameno, uint8_t* buffer, uint16_t width, uint16_t height) { assert(0); return false; } void HardwareMJPEGDecoder::gotoFrame(uint32_t frameNumber) { if (frameNumber == 0) { frameNumber = 1; } if (frameNumber > getNumberOfFrames()) { frameNumber = getNumberOfFrames(); } uint32_t offset = indexOffset + 8 + (frameNumber - 1) * 16; readData(offset, 16); currentMovieOffset = getU32(offset + 8) + firstFrameOffset - 4; this->frameNumber = frameNumber; } uint32_t HardwareMJPEGDecoder::getNumberOfFrames() { return videoInfo.number_of_frames; } void HardwareMJPEGDecoder::setRepeatVideo(bool repeat) { } void HardwareMJPEGDecoder::getVideoInfo(touchgfx::VideoInformation* data) { *data = videoInfo; } /* C HELPER FUNCTIONS */ extern "C" { /** * @brief Decode_DMA * @param hjpeg: JPEG handle pointer * @param JPEGImageBufferAddress : jpg image buffer Address. * @param JPEGImageSize_Bytes : jpg image size in bytes. * @param DestAddress : ARGB8888 destination Frame Buffer Address. * @retval None */ uint32_t JPEG_Decode_DMA(JPEG_HandleTypeDef* hjpeg, uint8_t* input, uint32_t chunkSizeIn /* length */, uint8_t* output) { JPEG_output_is_paused = 0; JPEG_OUT_Read_BufferIndex = 0; JPEG_OUT_Write_BufferIndex = 0; JPEG_InputImageIndex = 0; JPEG_InputImageAddress = (uint32_t)input; JPEG_InputImageSize_Bytes = chunkSizeIn; MCU_BlockIndex = 0; line_count = 0; /* Init buffers */ for (uint32_t i = 0; i < NB_OUTPUT_DATA_BUFFERS; ++i) { Jpeg_OUT_BufferTab[i].State = JPEG_BUFFER_EMPTY; Jpeg_OUT_BufferTab[i].DataBufferSize = 0; Jpeg_OUT_BufferTab[i].MCU_index = 0; Jpeg_OUT_BufferTab[i].OutputBuffer = NULL; Jpeg_OUT_BufferTab[i].MCU_drawn = 0; Jpeg_OUT_BufferTab[i].DoCropping = false; Jpeg_OUT_BufferTab[i].FirstJob = false; Jpeg_OUT_BufferTab[i].LastJob = false; } Jpeg_OUT_BufferTab[0].FirstJob = true; if (JPEG_ConvertorParams.firstRowOffset != 0) { Jpeg_OUT_BufferTab[0].DoCropping = true; } /* Do not return from this function until done with decoding all chunks. */ HAL_JPEG_Decode_DMA(hjpeg, input, CHUNK_SIZE_IN, Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].DataBuffer, MCU_CHROMA_420_SIZE_BYTES * JPEG_ConvertorParams.MCU_pr_line); return 0; } /** * @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) { uint32_t hMCU, vMCU; if (pInfo->ChromaSubsampling == JPEG_420_SUBSAMPLING) { if ((pInfo->ImageWidth % 16) != 0) { pInfo->ImageWidth += (16 - (pInfo->ImageWidth % 16)); } if ((pInfo->ImageHeight % 16) != 0) { pInfo->ImageHeight += (16 - (pInfo->ImageHeight % 16)); } hMCU = (pInfo->ImageWidth / MCU_WIDTH_PIXELS); vMCU = (pInfo->ImageHeight / MCU_HEIGHT_PIXELS); MCU_TotalNb = (hMCU * vMCU); } else { if (pInfo->ChromaSubsampling == JPEG_422_SUBSAMPLING) { if ((pInfo->ImageWidth % 16) != 0) { pInfo->ImageWidth += (16 - (pInfo->ImageWidth % 16)); } if ((pInfo->ImageHeight % 8) != 0) { pInfo->ImageHeight += (8 - (pInfo->ImageHeight % 8)); } } if (pInfo->ChromaSubsampling == JPEG_444_SUBSAMPLING) { if ((pInfo->ImageWidth % 8) != 0) { pInfo->ImageWidth += (8 - (pInfo->ImageWidth % 8)); } if ((pInfo->ImageHeight % 8) != 0) { pInfo->ImageHeight += (8 - (pInfo->ImageHeight % 8)); } } } } /** * @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) { /* Input buffer has been consumed by the peripheral and to ask for a new data chunk if the operation (encoding/decoding) has not been complete yet. */ JPEG_InputImageIndex += NbDecodedData; if (JPEG_InputImageIndex < JPEG_InputImageSize_Bytes) { JPEG_InputImageAddress = JPEG_InputImageAddress + NbDecodedData; uint32_t inDataLength = JPEG_InputImageSize_Bytes - JPEG_InputImageIndex; HAL_JPEG_ConfigInputBuffer(hjpeg, (uint8_t*)JPEG_InputImageAddress, inDataLength >= CHUNK_SIZE_IN ? CHUNK_SIZE_IN : inDataLength); } } /** * @brief JPEG Data Ready callback. Data has been converted from JPEG to YCbCr. * @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) { line_count += MCU_HEIGHT_PIXELS; Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].OutputBuffer = FrameBufferAddress; /* Increment framebuffer */ FrameBufferAddress += FrameBufferWidth * MCU_HEIGHT_PIXELS * JPEG_ConvertorParams.bytes_pr_pixel; /* Decode until we reach area to draw */ if (line_count <= JPEG_ConvertorParams.startY) { HAL_JPEG_ConfigOutputBuffer(hjpeg, Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].DataBuffer, MCU_CHROMA_420_SIZE_BYTES * JPEG_ConvertorParams.MCU_pr_line); return; } Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].State = JPEG_BUFFER_FULL; Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].DataBufferSize = OutDataLength; Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].MCU_drawn = 0; /* Left column requires cropping */ if (JPEG_ConvertorParams.firstColOffset != 0) { Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].DoCropping = true; } if (line_count < JPEG_ConvertorParams.endY) { Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].LastJob = false; JPEG_OUT_Write_BufferIndex++; if (JPEG_OUT_Write_BufferIndex >= NB_OUTPUT_DATA_BUFFERS) { JPEG_OUT_Write_BufferIndex = 0; } /* if the other buffer is full, then ui thread might be converting it */ if (Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].State != JPEG_BUFFER_EMPTY) { HAL_JPEG_Pause(hjpeg, JPEG_PAUSE_RESUME_OUTPUT); JPEG_output_is_paused = 1; } HAL_JPEG_ConfigOutputBuffer(hjpeg, Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].DataBuffer, MCU_CHROMA_420_SIZE_BYTES * JPEG_ConvertorParams.MCU_pr_line); } /* Stop decoding when we exit area to draw */ if (line_count >= JPEG_ConvertorParams.endY) { Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].LastJob = true; Jpeg_HWDecodingEnd = 1; HAL_JPEG_Pause(hjpeg, JPEG_PAUSE_RESUME_OUTPUT); } /* Signal Hardware Decoding to wake up */ if (!DMA2D_reference->isDMARunning()) { SEM_POST(semDecodingDone); } } /** * @brief JPEG Error callback * @param hjpeg: JPEG handle pointer * @retval None */ void HAL_JPEG_ErrorCallback(JPEG_HandleTypeDef* hjpeg) { __disable_irq(); while (1) { } } /** * @brief JPEG Decode complete callback * @param hjpeg: JPEG handle pointer * @retval None */ void HAL_JPEG_DecodeCpltCallback(JPEG_HandleTypeDef* hjpeg) { Jpeg_HWDecodingEnd = 1; } } /** * @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) { /* Decode frame complete */ if (Jpeg_HWDecodingEnd && DMA2D_CopyBufferEnd) { /* Abort any ongoing operations */ if (HAL_JPEG_GetState(hjpeg) == HAL_JPEG_STATE_BUSY_DECODING) { HAL_JPEG_Abort(hjpeg); } return 1; } /* Try to start DMA2D video transfer if next buffer if full */ if (!DMA2D_reference->isDMARunning() && (Jpeg_OUT_BufferTab[JPEG_OUT_Read_BufferIndex].State == JPEG_BUFFER_FULL) && (DMA2D_CopyBufferEnd == 0)) { DMA2D_reference->start(); } /* Start JPEG IP if paused and next buffer is empty */ if ((JPEG_output_is_paused == 1) && (Jpeg_OUT_BufferTab[JPEG_OUT_Write_BufferIndex].State == JPEG_BUFFER_EMPTY) && (Jpeg_HWDecodingEnd == 0)) { JPEG_output_is_paused = 0; HAL_JPEG_Resume(hjpeg, JPEG_PAUSE_RESUME_OUTPUT); } return 2; } /** * @brief Configures external DMA2D job to copy YCbCr data to RGB buffer(s) * @param job: External job reference * @retval None */ void DMA2D_CopyBuffer(JPEG_Data_BufferTypeDef& job) { const uint32_t width = JPEG_ConvertorParams.MCU_pr_job * MCU_WIDTH_PIXELS - job.MCU_drawn * MCU_WIDTH_PIXELS - JPEG_ConvertorParams.lastColOffset; const uint32_t scaledWidth = (width % MCU_WIDTH_PIXELS) == 0 ? 0 : MCU_WIDTH_PIXELS - (width % MCU_WIDTH_PIXELS); const uint32_t srcOffset = (JPEG_ConvertorParams.MCUStart + job.MCU_drawn) * MCU_CHROMA_420_SIZE_BYTES; const uint32_t dstOffset = JPEG_ConvertorParams.MCUStart * MCU_WIDTH_PIXELS * JPEG_ConvertorParams.bytes_pr_pixel + job.MCU_drawn * MCU_WIDTH_PIXELS * JPEG_ConvertorParams.bytes_pr_pixel; /* Mark job as fully drawn */ job.MCU_drawn = JPEG_ConvertorParams.MCU_pr_job; /* DMA2D OPFCCR register configuration */ WRITE_REG(DMA2D->OPFCCR, DMA2D_OUTPUT_RGB565); /* Configure DMA2D data size */ if (job.LastJob) /* Last line of frame */ { WRITE_REG(DMA2D->NLR, (MCU_HEIGHT_PIXELS - JPEG_ConvertorParams.lastRowOffset) | (width << DMA2D_NLR_PL_Pos)); } else { WRITE_REG(DMA2D->NLR, MCU_HEIGHT_PIXELS | (width << DMA2D_NLR_PL_Pos)); } /* Configure DMA2D destination address */ WRITE_REG(DMA2D->OMAR, reinterpret_cast(job.OutputBuffer + dstOffset)); /* DMA2D OOR register configuration */ WRITE_REG(DMA2D->OOR, FrameBufferWidth - width); /* DMA2D FGOR register configuration */ WRITE_REG(DMA2D->FGOR, scaledWidth); /* DMA2D FGPFCCR register configuration */ WRITE_REG(DMA2D->FGPFCCR, DMA2D_INPUT_YCBCR | (DMA2D_CSS_420 << DMA2D_FGPFCCR_CSS_Pos) | (DMA2D_REPLACE_ALPHA << DMA2D_FGPFCCR_AM_Pos) | (0xFFU << DMA2D_FGPFCCR_ALPHA_Pos)); /* Configure DMA2D source address */ WRITE_REG(DMA2D->FGMAR, reinterpret_cast(job.DataBuffer + srcOffset)); /* Configure DMA2D contol register */ WRITE_REG(DMA2D->CR, DMA2D_M2M_PFC | DMA2D_IT_TC | DMA2D_CR_START | DMA2D_IT_CE | DMA2D_IT_TE); } /** * @brief Configures external DMA2D job to copy and crop YCbCr data to an RGB cropping buffer * @param job: External job reference * @retval None */ void DMA2D_CropBuffer(JPEG_Data_BufferTypeDef& job) { const uint32_t colLeftOffset = job.MCU_drawn == 0 ? JPEG_ConvertorParams.firstColOffset : 0; const uint32_t colRightOffset = job.MCU_drawn == JPEG_ConvertorParams.MCU_pr_job - 1 ? JPEG_ConvertorParams.lastColOffset : 0; const uint32_t rowTopOffset = job.FirstJob ? JPEG_ConvertorParams.firstRowOffset : 0; const uint32_t rowBottomOffset = job.LastJob ? JPEG_ConvertorParams.lastRowOffset : 0; const uint32_t srcOffset = (JPEG_ConvertorParams.MCUStart + job.MCU_drawn) * MCU_CHROMA_420_SIZE_BYTES; const uint32_t dstOffset = JPEG_ConvertorParams.MCUStart * MCU_WIDTH_PIXELS * JPEG_ConvertorParams.bytes_pr_pixel + job.MCU_drawn * MCU_WIDTH_PIXELS * JPEG_ConvertorParams.bytes_pr_pixel + rowTopOffset * JPEG_ConvertorParams.bytes_pr_pixel * FrameBufferWidth + colLeftOffset * JPEG_ConvertorParams.bytes_pr_pixel; const uint32_t cropSrcOffset = colLeftOffset * JPEG_ConvertorParams.bytes_pr_pixel + rowTopOffset * JPEG_ConvertorParams.bytes_pr_pixel * MCU_HEIGHT_PIXELS; /* Update job and assert if more cropping is needed */ job.MCU_drawn++; if ((JPEG_ConvertorParams.firstRowOffset == 0) || !job.FirstJob) { job.DoCropping = false; } /* Configure BlitOp */ touchgfx::BlitOp blitOp; blitOp.operation = touchgfx::BLIT_OP_COPY; blitOp.pSrc = reinterpret_cast(MCU_Cropping_Buffer + cropSrcOffset); blitOp.nSteps = MCU_WIDTH_PIXELS - colLeftOffset - colRightOffset; blitOp.nLoops = MCU_HEIGHT_PIXELS - rowTopOffset - rowBottomOffset; blitOp.srcLoopStride = MCU_WIDTH_PIXELS; blitOp.dstLoopStride = FrameBufferWidth; blitOp.pDst = reinterpret_cast(job.OutputBuffer + dstOffset); blitOp.srcFormat = touchgfx::Bitmap::RGB565; blitOp.dstFormat = touchgfx::Bitmap::RGB565; DMA2D_reference->addToQueue(blitOp); /* DMA2D OPFCCR register configuration */ WRITE_REG(DMA2D->OPFCCR, DMA2D_OUTPUT_RGB565); /* Configure DMA2D data size */ WRITE_REG(DMA2D->NLR, MCU_HEIGHT_PIXELS | (MCU_WIDTH_PIXELS << DMA2D_NLR_PL_Pos)); /* Configure DMA2D destination address */ WRITE_REG(DMA2D->OMAR, reinterpret_cast(MCU_Cropping_Buffer)); /* DMA2D OOR register configuration */ WRITE_REG(DMA2D->OOR, 0); /* DMA2D FGOR register configuration */ WRITE_REG(DMA2D->FGOR, 0); /* DMA2D FGPFCCR register configuration */ WRITE_REG(DMA2D->FGPFCCR, DMA2D_INPUT_YCBCR | (DMA2D_CSS_420 << DMA2D_FGPFCCR_CSS_Pos) | (DMA2D_REPLACE_ALPHA << DMA2D_FGPFCCR_AM_Pos) | (0xFFU << DMA2D_FGPFCCR_ALPHA_Pos)); /* Configure DMA2D source address */ WRITE_REG(DMA2D->FGMAR, reinterpret_cast(job.DataBuffer + srcOffset)); /* Configure DMA2D contol register */ WRITE_REG(DMA2D->CR, DMA2D_M2M_PFC | DMA2D_IT_TC | DMA2D_CR_START | DMA2D_IT_CE | DMA2D_IT_TE); } /** * @brief External DMA2D job complete callback * @param job: External job reference * @retval None */ void DMA2D_ExternalJobCompleted(JPEG_Data_BufferTypeDef& job) { /* Mark job done if all MCUs are drawn */ if (job.MCU_drawn == JPEG_ConvertorParams.MCU_pr_job) { job.State = JPEG_BUFFER_EMPTY; job.DataBufferSize = 0; job.DoCropping = false; job.FirstJob = false; JPEG_OUT_Read_BufferIndex++; if (JPEG_OUT_Read_BufferIndex >= NB_OUTPUT_DATA_BUFFERS) { JPEG_OUT_Read_BufferIndex = 0; } /* Check if last line */ if (job.LastJob) { DMA2D_CopyBufferEnd = 1; } /* Signal decoder thread to wake up and continue decoding */ SEM_POST(semDecodingDone); } }