/** ****************************************************************************** * File Name : DoubleBufferedVideoController.hpp ****************************************************************************** * This file is generated by TouchGFX Generator 4.26.0. Please, do not edit! ****************************************************************************** * @attention * * Copyright (c) 2025 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. * ****************************************************************************** */ #ifndef BUFFERPOOLVIDEOCONTROLLER_HPP #define BUFFERPOOLVIDEOCONTROLLER_HPP #include #include #include #include #include "cmsis_os.h" #if defined(osCMSIS) && (osCMSIS < 0x20000) #define MUTEX_CREATE() osMutexCreate(0) #define MUTEX_LOCK(m) osMutexWait(m, osWaitForever) #define MUTEX_TYPE osMutexId #define MUTEX_UNLOCK(m) osMutexRelease(m) #define SEM_CREATE() osSemaphoreCreate(0, 1) #define SEM_POST(s) osSemaphoreRelease(s) #define SEM_TYPE osSemaphoreId #define SEM_WAIT(s) osSemaphoreWait(s, osWaitForever) #else #define MUTEX_CREATE() osMutexNew(0) #define MUTEX_LOCK(m) osMutexAcquire(m, osWaitForever) #define MUTEX_TYPE osMutexId_t #define MUTEX_UNLOCK(m) osMutexRelease(m) #define SEM_CREATE() osSemaphoreNew(1, 0, 0) #define SEM_POST(s) osSemaphoreRelease(s) #define SEM_TYPE osSemaphoreId_t #define SEM_WAIT(s) osSemaphoreAcquire(s, osWaitForever) #endif template class DoubleBufferedVideoController : public touchgfx::VideoController { public: DoubleBufferedVideoController() : VideoController(), bufferRGB(0), sizeBufferRGB(0), topBufferRGB(0), allowSkipFrames(true), semDecode(0), mutexBuffers(0) { assert((no_streams > 0) && "Video: Number of streams zero!"); // Clear decoder array memset(mjpegDecoders, 0, sizeof(mjpegDecoders)); // Initialize synchronization primitives semDecode = SEM_CREATE(); // Binary semaphore mutexBuffers = MUTEX_CREATE(); } virtual Handle registerVideoWidget(touchgfx::VideoWidget& widget) { // Running in UI thread // Find stream handle for Widget Handle handle = getFreeHandle(); streams[handle].isActive = true; // Set Widget buffer format and address widget.setVideoBufferFormat(output_format, width, height); widget.setVideoBuffer((uint8_t*)0); streams[handle].frameNumberShown = 0; // Todo, make buffer size depending on widget const uint32_t sizeOfOneDecodeBuffer = height * stride; // Allocate two buffers for this stream, if possible if (topBufferRGB + 2 * sizeOfOneDecodeBuffer > (bufferRGB + sizeBufferRGB)) { assert(0 && "registerVideoWidget: Unable to allocate two RGB buffers!"); return 0xFFFFFFFF; } streams[handle].bufferA = (uint8_t*)topBufferRGB; topBufferRGB += sizeOfOneDecodeBuffer; streams[handle].bufferB = (uint8_t*)topBufferRGB; topBufferRGB += sizeOfOneDecodeBuffer; return handle; } virtual void unregisterVideoWidget(const Handle handle) { // Running in UI thread // Reset active for this handle streams[handle].isActive = false; // If all handles are free, reset top pointer bool oneIsActive = false; for (uint32_t i = 0; i < no_streams; i++) { oneIsActive |= streams[i].isActive; } if (oneIsActive == false) { // Reset memory usage topBufferRGB = bufferRGB; } } virtual uint32_t getCurrentFrameNumber(const Handle handle) { assert(handle < no_streams); const Stream& stream = streams[handle]; return stream.frameNumberShown; } virtual void setFrameRate(const Handle handle, uint32_t ui_frames, uint32_t video_frames) { // Running in UI thread assert(handle < no_streams); Stream& stream = streams[handle]; // Reset counters stream.tickCount = 0; stream.frameCount = 0; // Save requested frame rate ratio stream.frame_rate_ticks = ui_frames; stream.frame_rate_video = video_frames; } virtual void setVideoData(const Handle handle, const uint8_t* movie, const uint32_t length) { // Running in UI thread mjpegDecoders[handle]->setVideoData(movie, length); clearState(handle); } virtual void setVideoData(const Handle handle, VideoDataReader& reader) { // Running in UI thread mjpegDecoders[handle]->setVideoData(reader); clearState(handle); } virtual void setCommand(const Handle handle, Command cmd, uint32_t param) { // Running in UI thread assert(handle < no_streams); Stream& stream = streams[handle]; switch (cmd) { case PLAY: // Cannot Play without movie if (mjpegDecoders[handle]->hasVideo()) { MUTEX_LOCK(mutexBuffers); stream.isPlaying = true; stream.cancelDecoding = false; // Reset counters stream.frameCount = 0; stream.tickCount = 0; // Seek to start of video if stopped if (stream.isStopped) { stream.seek_to_frame = 1; } stream.isStopped = false; // Kick decoder if next buffer is available stream.skip_frames = 0; if (stream.nextBuffer == 0) { stream.doDecodeNewFrame = true; SEM_POST(semDecode); } MUTEX_UNLOCK(mutexBuffers); } break; case PAUSE: stream.isPlaying = false; stream.isStopped = false; break; case SEEK: stream.seek_to_frame = param; // Reset counters stream.frameCount = 0; stream.tickCount = 0; break; case SHOW: stream.seek_to_frame = param; // Reset counters stream.frameCount = 0; stream.tickCount = 0; stream.isStopped = false; break; case STOP: stream.isPlaying = false; stream.isStopped = true; break; case SET_REPEAT: stream.repeat = (param > 0); break; } } /** * Called by VideoWidget::handleTickEvent. * Update frame counters. * Decide if widget should be invalidated. */ virtual bool updateFrame(const Handle handle, touchgfx::VideoWidget& widget) { // Running in UI thread assert(handle < no_streams); Stream& stream = streams[handle]; // Increase tickCount if playing if (stream.isPlaying) { stream.tickCount += HAL::getInstance()->getLCDRefreshCount(); } // Assume more frames are available, flag is lowered once, when changing to the last frame bool hasMoreFrames = true; // See if we have a nextBuffer and if next frame of specific frame is to be decoded if (stream.nextBuffer && (decodeForNextTick(stream) || stream.seek_to_frame > 0)) { MUTEX_LOCK(mutexBuffers); // Do nothing if seek to frame if (stream.seek_to_frame > 0) { stream.nextBuffer = 0; } if (stream.nextBuffer != 0) { // Use nextBuffer as current stream.currentBuffer = stream.nextBuffer; stream.nextBuffer = 0; // Copy frameNumber, increase count stream.frameNumberShown = stream.frameNumberNext; stream.frameCount++; hasMoreFrames = stream.hasMoreFramesAfterNext; // Update widget to show current buffer widget.setVideoBuffer(stream.currentBuffer); widget.invalidate(); if (!hasMoreFrames && !stream.repeat) { stream.isPlaying = false; } } MUTEX_UNLOCK(mutexBuffers); } // Kick decoder if playing or seeking frame and next buffer is ready if ((stream.isPlaying || stream.seek_to_frame > 0) && (stream.nextBuffer == 0)) { stream.doDecodeNewFrame = true; SEM_POST(semDecode); } return hasMoreFrames; } virtual void draw(const Handle handle, const touchgfx::Rect& invalidatedArea, const touchgfx::VideoWidget& widget) { // Running in UI thread // Nothing in this decoder } void setRGBBuffer(uint8_t* buffer, size_t sizeOfBuffer) { // Running in UI thread / main bufferRGB = buffer; topBufferRGB = bufferRGB; sizeBufferRGB = sizeOfBuffer; } void addDecoder(MJPEGDecoder& decoder, uint32_t index) { // Running in UI thread / main assert(index < no_streams); mjpegDecoders[index] = &decoder; } void endFrame() { // Running in UI thread // Nothing to do } void decoderTaskEntry() { // Running in Decoder thread!! while (1) { // Look for a stream to decode uint32_t stream_index = getStreamIndexToDecode(); if (stream_index == NO_STREAM) { // All streams decoded, wait for synchronisation signal from UI thread SEM_WAIT(semDecode); // Try from the beginning continue; } // Lock out UI by taking the mutex MUTEX_LOCK(mutexBuffers); // Now decode the stream Stream& stream = streams[stream_index]; // Select the unused buffer for decoding uint8_t* decodeBuffer = (stream.currentBuffer == stream.bufferA) ? stream.bufferB : stream.bufferA; MJPEGDecoder* const decoder = mjpegDecoders[stream_index]; // Seek or increment video frame if (stream.seek_to_frame > 0) { decoder->gotoFrame(stream.seek_to_frame); stream.seek_to_frame = 0; stream.cancelDecoding = false; } else { if (stream.skip_frames > 0) { decoder->gotoFrame(decoder->getCurrentFrameNumber() + stream.skip_frames); stream.frameCount += stream.skip_frames; stream.skip_frames = 0; } } // Unlock mutex while decoding MUTEX_UNLOCK(mutexBuffers); // Decode frame const bool hasMoreFrames = decoder->decodeNextFrame(decodeBuffer, width, height, stride); MUTEX_LOCK(mutexBuffers); // Save new frame in stream unless cancelled if (stream.cancelDecoding) { stream.cancelDecoding = false; } else { stream.nextBuffer = decodeBuffer; if (hasMoreFrames) { stream.frameNumberNext = decoder->getCurrentFrameNumber() - 1; // Points to frame after this } else { stream.frameNumberNext = 1; } stream.hasMoreFramesAfterNext = hasMoreFrames; // Lower decode flag stream.doDecodeNewFrame = false; } // Release the mutex MUTEX_UNLOCK(mutexBuffers); } } virtual void getVideoInformation(const Handle handle, touchgfx::VideoInformation* data) { assert(handle < no_streams); mjpegDecoders[handle]->getVideoInfo(data); } virtual bool getIsPlaying(const Handle handle) { assert(handle < no_streams); Stream& stream = streams[handle]; return stream.isPlaying; } virtual void setVideoFrameRateCompensation(bool allow) { allowSkipFrames = allow; } private: class Stream { public: Stream() : frameCount(0), frameNumberNext(0), frameNumberShown(0), tickCount(0), frame_rate_video(0), frame_rate_ticks(0), seek_to_frame(0), skip_frames(0), currentBuffer(0), nextBuffer(0), bufferA(0), bufferB(0), isActive(false), isPlaying(false), doDecodeNewFrame(false), cancelDecoding(false), hasMoreFramesAfterNext(false), repeat(true), isStopped(false) {} uint32_t frameCount; // Video frame count since play/speed change uint32_t frameNumberNext; // Next Video frame number (if any) uint32_t frameNumberShown; // Shown Video frame number uint32_t tickCount; // UI frames since play uint32_t frame_rate_video; // Ratio of frames wanted counter uint32_t frame_rate_ticks; // Ratio of frames wanted divider uint32_t seek_to_frame; // Requested next frame number uint32_t skip_frames; // Number of frames to skip to keep frame rate uint8_t* currentBuffer; // Current Video frame, used for drawing uint8_t* nextBuffer; // Already decode next video frame, or zero uint8_t* bufferA; // One buffer allocated for this stream uint8_t* bufferB; // Another buffer allocated for this stream bool isActive; bool isPlaying; bool doDecodeNewFrame; bool cancelDecoding; bool hasMoreFramesAfterNext; bool repeat; bool isStopped; }; enum { NO_STREAM = 0xFFFF }; MJPEGDecoder* mjpegDecoders[no_streams]; Stream streams[no_streams]; uint8_t* bufferRGB; size_t sizeBufferRGB; // Size in Bytes uint8_t* topBufferRGB; // Pointer to unused memory in buffer bool allowSkipFrames; SEM_TYPE semDecode; // Post by UI, wait by Decoder thread MUTEX_TYPE mutexBuffers; // Mutual exclusion of the video buffers and stream data /* return true, if new video frame should be decoded for the next tick (keep video decode framerate low) */ bool decodeForNextTick(Stream& stream) { // Running in UI thread // Compare tickCount/frameCount to frame_rate_ticks/frame_rate_video if ((stream.tickCount * stream.frame_rate_video) >= (stream.frame_rate_ticks * stream.frameCount)) { if (allowSkipFrames) { stream.skip_frames = (stream.tickCount * stream.frame_rate_video - stream.frame_rate_ticks * stream.frameCount) / stream.frame_rate_ticks; if (stream.skip_frames > 0) { stream.skip_frames--; } } return true; } return false; } Handle getFreeHandle() { // Running in UI thread for (uint32_t i = 0; i < no_streams; i++) { if (streams[i].isActive == false) { // Reset stream parameters streams[i] = Stream(); return static_cast(i); } } assert(0 && "Unable to find free video stream handle!"); return static_cast(0); } uint32_t getStreamIndexToDecode() { for (uint32_t i = 0; i < no_streams; i++) { Stream& stream = streams[i]; if (stream.doDecodeNewFrame) // Marked by UI for decoding { return i; } } return NO_STREAM; } void clearState(const Handle handle) { // Stop playing, and clear next buffer if any, cancel ongoing decoding setCommand(handle, STOP, 0); MUTEX_LOCK(mutexBuffers); Stream& stream = streams[handle]; if (stream.nextBuffer != 0) { // Ignore any decoded buffer stream.nextBuffer = 0; } stream.cancelDecoding = true; stream.isPlaying = false; MUTEX_UNLOCK(mutexBuffers); } }; #endif // TOUCHGFX_BUFFERPOOLVIDEOCONTROLLER_HPP