549 lines
16 KiB
C++
549 lines
16 KiB
C++
/*********************************************************************************/
|
|
/********** THIS FILE IS GENERATED BY TOUCHGFX DESIGNER, DO NOT MODIFY ***********/
|
|
/*********************************************************************************/
|
|
#include <jinclude.h>
|
|
#include <jpeglib.h>
|
|
#include <string.h>
|
|
#include <simulator/video/SoftwareMJPEGDecoder.hpp>
|
|
|
|
#define VIDEO_DECODE_FORMAT 16
|
|
|
|
namespace
|
|
{
|
|
struct JPEG_RGB
|
|
{
|
|
uint8_t B;
|
|
uint8_t G;
|
|
uint8_t R;
|
|
#if VIDEO_DECODE_FORMAT == 32
|
|
uint8_t X;
|
|
#endif
|
|
};
|
|
} // namespace
|
|
|
|
SoftwareMJPEGDecoder::SoftwareMJPEGDecoder(uint8_t* buffer)
|
|
: frameNumber(0), currentMovieOffset(0), indexOffset(0), firstFrameOffset(0), lastFrameEnd(0), movieLength(0), movieData(0),
|
|
reader(0), lineBuffer(buffer), aviBuffer(0), aviBufferLength(0), aviBufferStartOffset(0), lastError(AVI_NO_ERROR)
|
|
{
|
|
//clear video info
|
|
videoInfo.ms_between_frames = 0;
|
|
videoInfo.number_of_frames = 0;
|
|
videoInfo.frame_width = 0;
|
|
videoInfo.frame_height = 0;
|
|
}
|
|
|
|
int SoftwareMJPEGDecoder::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<const char*>(aviBuffer + (offset - aviBufferStartOffset));
|
|
}
|
|
else
|
|
{
|
|
src = (const char*)movieData + offset;
|
|
}
|
|
return strncmp(src, str, num);
|
|
}
|
|
|
|
inline uint32_t SoftwareMJPEGDecoder::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
|
|
{
|
|
const uint8_t* const d = movieData + offset;
|
|
return d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24);
|
|
}
|
|
}
|
|
|
|
inline uint32_t SoftwareMJPEGDecoder::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
|
|
{
|
|
const uint8_t* const d = movieData + offset;
|
|
return d[0] | (d[1] << 8);
|
|
}
|
|
}
|
|
|
|
const uint8_t* SoftwareMJPEGDecoder::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 SoftwareMJPEGDecoder::decodeNextFrame(uint8_t* buffer, uint16_t buffer_width, uint16_t buffer_height, uint32_t buffer_stride)
|
|
{
|
|
assert((frameNumber > 0) && "SoftwareMJPEGDecoder 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) // Skip empty frame
|
|
{
|
|
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 SoftwareMJPEGDecoder::gotoNextFrame()
|
|
{
|
|
assert((frameNumber > 0) && "SoftwareMJPEGDecoder 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 && chunkSize > 0)
|
|
{
|
|
// Found next frame
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//skip back to first frame
|
|
frameNumber = 1;
|
|
currentMovieOffset = firstFrameOffset; //start over
|
|
return false;
|
|
}
|
|
|
|
void SoftwareMJPEGDecoder::setVideoData(const uint8_t* movie, const uint32_t length)
|
|
{
|
|
movieData = movie;
|
|
movieLength = length;
|
|
reader = 0; //not using reader
|
|
|
|
readVideoHeader();
|
|
}
|
|
|
|
void SoftwareMJPEGDecoder::setVideoData(touchgfx::VideoDataReader& reader)
|
|
{
|
|
this->reader = &reader;
|
|
movieData = 0;
|
|
movieLength = reader.getDataLength();
|
|
|
|
readVideoHeader();
|
|
}
|
|
|
|
bool SoftwareMJPEGDecoder::hasVideo()
|
|
{
|
|
return (reader != 0) || (movieData != 0);
|
|
}
|
|
|
|
void SoftwareMJPEGDecoder::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;
|
|
}
|
|
|
|
#if VIDEO_DECODE_FORMAT == 16 || VIDEO_DECODE_FORMAT == 24 || VIDEO_DECODE_FORMAT == 32
|
|
void SoftwareMJPEGDecoder::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 && lineBuffer) //only decode if buffers are assigned.
|
|
{
|
|
/* This struct contains the JPEG decompression parameters */
|
|
struct jpeg_decompress_struct cinfo;
|
|
/* This struct represents a JPEG error handler */
|
|
struct jpeg_error_mgr jerr;
|
|
|
|
JSAMPROW lines[2] = { lineBuffer, 0 }; /* Output row buffer */
|
|
|
|
/* Step 1: allocate and initialize JPEG decompression object */
|
|
cinfo.err = jpeg_std_error(&jerr);
|
|
|
|
/* Initialize the JPEG decompression object */
|
|
jpeg_create_decompress(&cinfo);
|
|
|
|
//jpeg_stdio_src (&cinfo, file);
|
|
jpeg_mem_src(&cinfo, const_cast<uint8_t*>(mjpgdata), length);
|
|
|
|
/* Step 3: read image parameters with jpeg_read_header() */
|
|
jpeg_read_header(&cinfo, TRUE);
|
|
|
|
/* Step 4: set parameters for decompression */
|
|
cinfo.dct_method = JDCT_FLOAT;
|
|
|
|
/* Step 5: start decompressor */
|
|
jpeg_start_decompress(&cinfo);
|
|
|
|
//restrict to minimum of movie and output buffer size
|
|
const uint32_t width = MIN(bufferWidth, cinfo.image_width);
|
|
const uint32_t height = MIN(bufferHeight, cinfo.output_height);
|
|
|
|
#if VIDEO_DECODE_FORMAT == 16
|
|
uint16_t* lineptr = reinterpret_cast<uint16_t*>(outputBuffer);
|
|
#elif VIDEO_DECODE_FORMAT == 24
|
|
uint8_t* lineptr = outputBuffer;
|
|
#else
|
|
uint32_t* lineptr = reinterpret_cast<uint32_t*>(outputBuffer);
|
|
#endif
|
|
while (cinfo.output_scanline < height)
|
|
{
|
|
(void)jpeg_read_scanlines(&cinfo, lines, 1);
|
|
#if VIDEO_DECODE_FORMAT == 16
|
|
JPEG_RGB* RGB_matrix = (JPEG_RGB*)lineBuffer;
|
|
JPEG_RGB* const RGB_end = RGB_matrix + width;
|
|
while (RGB_matrix < RGB_end)
|
|
{
|
|
const uint16_t pix = ((RGB_matrix->R & 0xF8) << 8) | ((RGB_matrix->G & 0xFC) << 3) | ((RGB_matrix->B & 0xF8) >> 3);
|
|
*lineptr++ = pix;
|
|
RGB_matrix++;
|
|
}
|
|
lineptr = (uint16_t*)((uint8_t*)lineptr + bufferStride - width * 2); //move to next line
|
|
#elif VIDEO_DECODE_FORMAT == 24
|
|
memcpy(lineptr, lineBuffer, width * 3);
|
|
lineptr += bufferStride; //move to next line
|
|
#else
|
|
JPEG_RGB* RGB_matrix = (JPEG_RGB*)lineBuffer;
|
|
JPEG_RGB* const RGB_end = RGB_matrix + width;
|
|
while (RGB_matrix < RGB_end)
|
|
{
|
|
const uint32_t pix = (0xFF << 24) | (RGB_matrix->R << 16) | (RGB_matrix->G << 8) | (RGB_matrix->B);
|
|
*lineptr++ = pix;
|
|
RGB_matrix++;
|
|
}
|
|
lineptr = (uint32_t*)((uint8_t*)lineptr + bufferStride - width * 4); //move to next line
|
|
#endif
|
|
}
|
|
|
|
#ifdef SIMULATOR
|
|
cinfo.output_scanline = cinfo.output_height;
|
|
#endif
|
|
/* Step 6: Finish decompression */
|
|
jpeg_finish_decompress(&cinfo);
|
|
|
|
/* Step 7: Release JPEG decompression object */
|
|
jpeg_destroy_decompress(&cinfo);
|
|
}
|
|
}
|
|
|
|
bool SoftwareMJPEGDecoder::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);
|
|
|
|
assert(lineBuffer && "LineBuffer must be assigned prior to decoding directly to framebuffer");
|
|
|
|
/* This struct contains the JPEG decompression parameters */
|
|
struct jpeg_decompress_struct cinfo;
|
|
/* This struct represents a JPEG error handler */
|
|
struct jpeg_error_mgr jerr;
|
|
|
|
JSAMPROW lines[2] = { lineBuffer, 0 }; /* Output row buffer */
|
|
|
|
/* Step 1: allocate and initialize JPEG decompression object */
|
|
cinfo.err = jpeg_std_error(&jerr);
|
|
|
|
/* Initialize the JPEG decompression object */
|
|
jpeg_create_decompress(&cinfo);
|
|
|
|
//jpeg_stdio_src (&cinfo, file);
|
|
jpeg_mem_src(&cinfo, const_cast<uint8_t*>(mjpgdata), length);
|
|
|
|
/* Step 3: read image parameters with jpeg_read_header() */
|
|
jpeg_read_header(&cinfo, TRUE);
|
|
|
|
/* Step 4: set parameters for decompression */
|
|
cinfo.dct_method = JDCT_FLOAT;
|
|
|
|
/* Step 5: start decompressor */
|
|
jpeg_start_decompress(&cinfo);
|
|
|
|
//restrict to minimum of movie and output buffer size
|
|
const uint32_t startY = area.y;
|
|
|
|
//scan down to startY
|
|
while (cinfo.output_scanline < startY)
|
|
{
|
|
(void)jpeg_read_scanlines(&cinfo, lines, 1);
|
|
}
|
|
|
|
const uint32_t startX = area.x;
|
|
const uint32_t endX = MIN((uint32_t)area.right(), cinfo.image_width);
|
|
|
|
#if VIDEO_DECODE_FORMAT == 16
|
|
uint16_t* lineptr = reinterpret_cast<uint16_t*>(frameBuffer);
|
|
lineptr += framebuffer_width * startY;
|
|
#elif VIDEO_DECODE_FORMAT == 24
|
|
uint8_t* lineptr = frameBuffer;
|
|
lineptr += framebuffer_width * 3 * startY;
|
|
#else
|
|
uint32_t* lineptr = reinterpret_cast<uint32_t*>(frameBuffer);
|
|
lineptr += framebuffer_width * startY;
|
|
#endif
|
|
const uint32_t endY = MIN((uint32_t)area.bottom(), cinfo.output_height);
|
|
|
|
//scan relevant part
|
|
while (cinfo.output_scanline < endY)
|
|
{
|
|
(void)jpeg_read_scanlines(&cinfo, lines, 1);
|
|
#if VIDEO_DECODE_FORMAT == 16
|
|
JPEG_RGB* RGB_matrix = (JPEG_RGB*)lineBuffer;
|
|
//loop row RGB888->RGB565 for required line part
|
|
for (uint32_t counter = startX; counter < endX; counter++)
|
|
{
|
|
const uint16_t pix = ((RGB_matrix[counter].R & 0xF8) << 8) | ((RGB_matrix[counter].G & 0xFC) << 3) | ((RGB_matrix[counter].B & 0xF8) >> 3);
|
|
*(lineptr + counter) = pix;
|
|
}
|
|
lineptr += framebuffer_width; //move to next line
|
|
#elif VIDEO_DECODE_FORMAT == 24
|
|
memcpy(lineptr + startX * 3, lineBuffer + startX * 3, (endX - startX) * 3);
|
|
lineptr += framebuffer_width * 3; //move to next line
|
|
#else
|
|
memcpy(lineptr + startX, lineBuffer + startX * 4, (endX - startX) * 4);
|
|
lineptr += framebuffer_width; //move to next line
|
|
#endif
|
|
}
|
|
|
|
#ifdef SIMULATOR
|
|
cinfo.output_scanline = cinfo.output_height;
|
|
#endif
|
|
|
|
/* Step 6: Finish decompression */
|
|
jpeg_finish_decompress(&cinfo);
|
|
|
|
/* Step 7: Release JPEG decompression object */
|
|
jpeg_destroy_decompress(&cinfo);
|
|
|
|
return true;
|
|
}
|
|
#else
|
|
void SoftwareMJPEGDecoder::decodeMJPEGFrame(const uint8_t* const, const uint32_t, uint8_t*, uint16_t, uint16_t, uint32_t)
|
|
{
|
|
}
|
|
bool SoftwareMJPEGDecoder::decodeFrame(const touchgfx::Rect&, uint8_t*, uint32_t)
|
|
{
|
|
return true;
|
|
}
|
|
#endif // VIDEO_DECODE_FORMAT == 16 || VIDEO_DECODE_FORMAT == 24 || VIDEO_DECODE_FORMAT == 32
|
|
|
|
bool SoftwareMJPEGDecoder::decodeThumbnail(uint32_t frameno, uint8_t* buffer, uint16_t width, uint16_t height)
|
|
{
|
|
assert(0);
|
|
return false;
|
|
}
|
|
|
|
void SoftwareMJPEGDecoder::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 SoftwareMJPEGDecoder::getNumberOfFrames()
|
|
{
|
|
return videoInfo.number_of_frames;
|
|
}
|
|
|
|
void SoftwareMJPEGDecoder::getVideoInfo(touchgfx::VideoInformation* data)
|
|
{
|
|
*data = videoInfo;
|
|
// For unsupported decode formats, set video dimension to 0x0, to avoid drawing anything
|
|
#if VIDEO_DECODE_FORMAT == 16 || VIDEO_DECODE_FORMAT == 24 || VIDEO_DECODE_FORMAT == 32
|
|
#else
|
|
data->frame_width = 0;
|
|
data->frame_height = 0;
|
|
#endif
|
|
}
|