/** ****************************************************************************** * File Name : DirectFrameBufferVideoController.hpp ****************************************************************************** * 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. * ****************************************************************************** */ #ifndef TOUCHGFX_DIRECTFRAMEBUFFERVIDEOCONTROLLER_HPP #define TOUCHGFX_DIRECTFRAMEBUFFERVIDEOCONTROLLER_HPP #include #include #include #include /** * Strategy: * Decode directly into the framebuffer in draw. * Tick will decide if we are going to a new frame. */ template class DirectFrameBufferVideoController : public touchgfx::VideoController { public: DirectFrameBufferVideoController() : VideoController(), allowSkipFrames(true) { assert((no_streams > 0) && "Video: Number of streams zero!"); // Clear arrays memset(mjpegDecoders, 0, sizeof(mjpegDecoders)); } virtual Handle registerVideoWidget(touchgfx::VideoWidget& widget) { // Find stream handle for Widget Handle handle = getFreeHandle(); streams[handle].isActive = true; //Set Widget buffer format and address widget.setVideoBufferFormat(output_format, 0, 0); widget.setVideoBuffer((uint8_t*)0); return handle; } virtual void unregisterVideoWidget(const Handle handle) { streams[handle].isActive = false; } virtual void setFrameRate(const Handle handle, uint32_t ui_frames, uint32_t video_frames) { assert(handle < no_streams); Stream& stream = streams[handle]; // Reset counters stream.frameCount = 0; stream.tickCount = 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) { assert(handle < no_streams); // Reset decoder to first frame mjpegDecoders[handle]->setVideoData(movie, length); // Lower flag to show the first frame Stream& stream = streams[handle]; stream.frameNumber = mjpegDecoders[handle]->getCurrentFrameNumber(); stream.doDecodeNextFrame = false; // Stop playing setCommand(handle, PAUSE, 0); } virtual void setVideoData(const Handle handle, touchgfx::VideoDataReader& reader) { assert(handle < no_streams); // Reset decoder to first frame mjpegDecoders[handle]->setVideoData(reader); // Lower flag to show the first frame Stream& stream = streams[handle]; stream.frameNumber = mjpegDecoders[handle]->getCurrentFrameNumber(); stream.doDecodeNextFrame = false; // Stop playing setCommand(handle, PAUSE, 0); } virtual void setCommand(const Handle handle, Command cmd, uint32_t param) { assert(handle < no_streams); Stream& stream = streams[handle]; switch (cmd) { case PLAY: // Cannot Play without movie if (mjpegDecoders[handle]->hasVideo()) { stream.isPlaying = true; stream.isShowingOneFrame = false; // Reset counters stream.frameCount = 0; stream.tickCount = 0; // If non-repeating video stopped at the end, kick to next frame if (!stream.repeat) { MJPEGDecoder* const decoder = mjpegDecoders[handle]; if (decoder->getCurrentFrameNumber() == decoder->getNumberOfFrames()) { decoder->gotoNextFrame(); } } } break; case PAUSE: stream.isPlaying = false; stream.isShowingOneFrame = 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; stream.isShowingOneFrame = true; stream.doDecodeNextFrame = true; // Reset counters stream.frameCount = 0; stream.tickCount = 0; break; case STOP: stream.isPlaying = false; stream.isShowingOneFrame = false; stream.seek_to_frame = 1; // Reset counters stream.frameCount = 0; stream.tickCount = 0; break; case SET_REPEAT: stream.repeat = (param > 0); break; } } virtual bool updateFrame(const Handle handle, touchgfx::VideoWidget& widget) { assert(handle < no_streams); Stream& stream = streams[handle]; bool hasMoreFrames = true; if (stream.isPlaying || stream.isShowingOneFrame) { // Increase tickCount stream.tickCount += HAL::getInstance()->getLCDRefreshCount(); // Lower flag stream.isShowingOneFrame = false; if (stream.doDecodeNextFrame) { MJPEGDecoder* const decoder = mjpegDecoders[handle]; // Invalidate to get widget redrawn widget.invalidate(); // Seek or increment video frame if (stream.seek_to_frame > 0) { decoder->gotoFrame(stream.seek_to_frame); hasMoreFrames = (stream.seek_to_frame < decoder->getNumberOfFrames()); stream.seek_to_frame = 0; } else { if (stream.skip_frames > 0) { decoder->gotoFrame(decoder->getCurrentFrameNumber() + stream.skip_frames); stream.frameCount += stream.skip_frames; stream.skip_frames = 0; } if (stream.repeat) { hasMoreFrames = decoder->gotoNextFrame(); } else { if (decoder->getCurrentFrameNumber() < decoder->getNumberOfFrames()) { hasMoreFrames = decoder->gotoNextFrame(); } else { stream.isPlaying = false; hasMoreFrames = false; } } } stream.frameNumber = decoder->getCurrentFrameNumber(); stream.frameCount++; } // Save decode status for next frame stream.doDecodeNextFrame = decodeForNextTick(stream); } return hasMoreFrames; } virtual void draw(const Handle handle, const touchgfx::Rect& invalidatedArea, const touchgfx::VideoWidget& widget) { assert(handle < no_streams); if (output_format != Bitmap::RGB565 && output_format != Bitmap::RGB888 && output_format != Bitmap::ARGB8888) { return; } if (mjpegDecoders[handle]->hasVideo()) { uint8_t* wbuf = (uint8_t*)touchgfx::HAL::getInstance()->lockFrameBufferForRenderingMethod(touchgfx::HAL::HARDWARE); touchgfx::Rect absolute = widget.getAbsoluteRect(); DisplayTransformation::transformDisplayToFrameBuffer(absolute); // Get frame buffer pointer to upper left of widget in framebuffer coordinates switch (output_format) { case Bitmap::RGB565: wbuf += (absolute.x + absolute.y * touchgfx::HAL::FRAME_BUFFER_WIDTH) * 2; break; case Bitmap::RGB888: wbuf += (absolute.x + absolute.y * touchgfx::HAL::FRAME_BUFFER_WIDTH) * 3; break; case Bitmap::ARGB8888: wbuf += (absolute.x + absolute.y * touchgfx::HAL::FRAME_BUFFER_WIDTH) * 4; break; default: break; } // Decode relevant part of the frame to the framebuffer mjpegDecoders[handle]->decodeFrame(invalidatedArea, wbuf, touchgfx::HAL::FRAME_BUFFER_WIDTH); // Release frame buffer touchgfx::HAL::getInstance()->unlockFrameBuffer(); } } void addDecoder(MJPEGDecoder& decoder, uint32_t index) { assert(index < no_streams); mjpegDecoders[index] = &decoder; } virtual uint32_t getCurrentFrameNumber(const Handle handle) { assert(handle < no_streams); Stream& stream = streams[handle]; return stream.frameNumber; } 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(const bool allow) { allowSkipFrames = allow; } private: class Stream { public: Stream() : frameCount(0), frameNumber(0), tickCount(0), frame_rate_video(0), frame_rate_ticks(0), seek_to_frame(0), isActive(false), isPlaying(false), isShowingOneFrame(false), repeat(true), doDecodeNextFrame(false) { } uint32_t frameCount; // Video frames decoded since play uint32_t frameNumber; // Video frame showed 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 bool isActive; bool isPlaying; bool isShowingOneFrame; bool repeat; bool doDecodeNextFrame; // High if we should go to next frame in next tick }; MJPEGDecoder* mjpegDecoders[no_streams]; Stream streams[no_streams]; bool allowSkipFrames; /** * 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/frameNumber 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() { for (uint32_t i = 0; i < no_streams; i++) { if (streams[i].isActive == false) { return static_cast(i); } } assert(0 && "Unable to find free video stream handle!"); return static_cast(0); } }; #endif // TOUCHGFX_DIRECTFRAMEBUFFERVIDEOCONTROLLER_HPP