File indexing completed on 2024-05-19 04:29:18

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2022 Emmet O'Neill <emmetoneill.pdx@gmail.com>
0003    SPDX-FileCopyrightText: 2022 Eoin O'Neill <eoinoneill1991@gmail.com>
0004 
0005    SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "KisPlaybackEngineQT.h"
0009 
0010 #include "kis_debug.h"
0011 #include "kis_canvas2.h"
0012 #include "KisCanvasAnimationState.h"
0013 #include "kis_image.h"
0014 #include "kis_image_animation_interface.h"
0015 
0016 #include <QTimer>
0017 #include "animation/KisFrameDisplayProxy.h"
0018 #include "KisRollingMeanAccumulatorWrapper.h"
0019 #include "KisRollingSumAccumulatorWrapper.h"
0020 
0021 #include "KisPlaybackEngineQT.h"
0022 
0023 #include <QFileInfo>
0024 
0025 namespace {
0026 
0027 /** @brief A simple QTimer-based playback method for situations when audio is not 
0028  * used (and thus audio-video playback synchronization is not a concern).
0029  */
0030 class PlaybackDriver : public QObject
0031 {
0032     Q_OBJECT
0033 public:
0034     PlaybackDriver(QObject* parent = nullptr);
0035     ~PlaybackDriver();
0036 
0037     void setPlaybackState(PlaybackState newState);
0038 
0039     void setFramerate(int rate);
0040     void setSpeed(qreal speed);
0041     double speed();
0042     void setDropFrames(bool drop);
0043     bool dropFrames();
0044 
0045 Q_SIGNALS:
0046     void throttledShowFrame();
0047 
0048 private:
0049     void updatePlaybackLoopInterval(const int& in_fps, const qreal& in_speed);
0050 
0051 private:
0052     QTimer m_playbackLoop;
0053     double m_speed;
0054     int m_fps;
0055     bool m_dropFrames;
0056 };
0057 
0058 PlaybackDriver::PlaybackDriver(QObject *parent)
0059     : QObject(parent)
0060     , m_speed(1.0)
0061     , m_fps(24)
0062     , m_dropFrames(true)
0063 {
0064     m_playbackLoop.setTimerType(Qt::PreciseTimer);
0065     connect( &m_playbackLoop, SIGNAL(timeout()), this, SIGNAL(throttledShowFrame()) );
0066 }
0067 
0068 PlaybackDriver::~PlaybackDriver()
0069 {
0070 }
0071 
0072 void PlaybackDriver::setPlaybackState(PlaybackState newState) {
0073     switch (newState) {
0074     case PlaybackState::PLAYING:
0075         m_playbackLoop.start();
0076         break;
0077     case PlaybackState::PAUSED:
0078     case PlaybackState::STOPPED:
0079     default:
0080         m_playbackLoop.stop();
0081         break;
0082     }
0083 }
0084 
0085 void PlaybackDriver::setFramerate(int rate) {
0086     KIS_SAFE_ASSERT_RECOVER_RETURN(rate > 0);
0087     m_fps = rate;
0088     updatePlaybackLoopInterval(m_fps, m_speed);
0089 }
0090 
0091 void PlaybackDriver::setSpeed(qreal speed) {
0092     KIS_SAFE_ASSERT_RECOVER_RETURN(speed > 0.f);
0093     m_speed = speed;
0094     updatePlaybackLoopInterval(m_fps, m_speed);
0095 }
0096 
0097 double PlaybackDriver::speed()
0098 {
0099     return m_speed;
0100 }
0101 
0102 void PlaybackDriver::setDropFrames(bool drop) {
0103     m_dropFrames = drop;
0104 }
0105 
0106 bool PlaybackDriver::dropFrames() {
0107     return m_dropFrames;
0108 }
0109 
0110 void PlaybackDriver::updatePlaybackLoopInterval(const int &in_fps, const qreal &in_speed) {
0111     int loopMS = qRound( 1000.f / (qreal(in_fps) * in_speed));
0112     m_playbackLoop.setInterval(loopMS);
0113 }
0114 
0115 }
0116 
0117 // ======
0118 
0119 /** @brief Struct used to keep track of all frame time variance
0120  * and acommodate for skipped frames. Also tracks whether a frame
0121  * is still being loaded by the display proxy.
0122  *
0123  * Only allocated when playback begins.
0124  */
0125 struct FrameMeasure {
0126     static constexpr int frameStatsWindow = 50;
0127 
0128     FrameMeasure()
0129         : averageTimePerFrame(frameStatsWindow)
0130         , waitingForFrame(false)
0131         , droppedFramesStat(frameStatsWindow)
0132 
0133     {
0134         timeSinceLastFrame.start();
0135     }
0136 
0137     void reset() {
0138         timeSinceLastFrame.start();
0139         averageTimePerFrame.reset(frameStatsWindow);
0140         waitingForFrame = false;
0141         droppedFramesStat.reset(frameStatsWindow);
0142     }
0143 
0144     QElapsedTimer timeSinceLastFrame;
0145     KisRollingMeanAccumulatorWrapper averageTimePerFrame;
0146     bool waitingForFrame;
0147 
0148     KisRollingSumAccumulatorWrapper droppedFramesStat;
0149 };
0150 
0151 // ====== KisPlaybackEngineQT ======
0152 
0153 struct KisPlaybackEngineQT::Private {
0154 public:
0155     Private()
0156         : driver(new PlaybackDriver())
0157     {
0158     }
0159 
0160     ~Private() {
0161     }
0162 
0163     QScopedPointer<PlaybackDriver> driver;
0164     FrameMeasure measure;
0165 };
0166 
0167 KisPlaybackEngineQT::KisPlaybackEngineQT(QObject *parent)
0168     : KisPlaybackEngine(parent)
0169     , m_d(new Private())
0170 {
0171 }
0172 
0173 KisPlaybackEngineQT::~KisPlaybackEngineQT()
0174 {
0175 }
0176 
0177 void KisPlaybackEngineQT::seek(int frameIndex, SeekOptionFlags flags)
0178 {
0179     if (!activeCanvas())
0180         return;
0181 
0182     KIS_SAFE_ASSERT_RECOVER_RETURN(activeCanvas()->animationState());
0183     KisFrameDisplayProxy* displayProxy = activeCanvas()->animationState()->displayProxy();
0184     KIS_SAFE_ASSERT_RECOVER_RETURN(displayProxy);
0185 
0186     KIS_SAFE_ASSERT_RECOVER_RETURN(frameIndex >= 0);
0187 
0188     if (displayProxy->activeFrame() != frameIndex) {
0189         displayProxy->displayFrame(frameIndex, flags & SEEK_FINALIZE);
0190     }
0191 }
0192 
0193 void KisPlaybackEngineQT::setDropFramesMode(bool value)
0194 {
0195     KisPlaybackEngine::setDropFramesMode(value);
0196     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->driver);
0197     m_d->driver->setDropFrames(value);
0198 }
0199 
0200 boost::optional<int64_t> KisPlaybackEngineQT::activeFramesPerSecond() const
0201 {
0202     if (activeCanvas()) {
0203         return activeCanvas()->image()->animationInterface()->framerate();
0204     } else {
0205         return boost::none;
0206     }
0207 }
0208 
0209 KisPlaybackEngine::PlaybackStats KisPlaybackEngineQT::playbackStatistics() const
0210 {
0211     KisPlaybackEngine::PlaybackStats stats;
0212 
0213     if (activeCanvas()->animationState()->playbackState() == PLAYING) {
0214         const int droppedFrames = m_d->measure.droppedFramesStat.rollingSum();
0215         const int totalFrames =
0216             m_d->measure.droppedFramesStat.rollingCount() +
0217             droppedFrames;
0218 
0219         stats.droppedFramesPortion = qreal(droppedFrames) / totalFrames;
0220         stats.expectedFps = qreal(activeFramesPerSecond().get_value_or(24)) * m_d->driver->speed();
0221 
0222         const qreal avgTimePerFrame = m_d->measure.averageTimePerFrame.rollingMeanSafe();
0223         stats.realFps = !qFuzzyIsNull(avgTimePerFrame) ? 1000.0 / avgTimePerFrame : 0.0;
0224     }
0225 
0226     return stats;
0227 }
0228 
0229 void KisPlaybackEngineQT::throttledDriverCallback()
0230 {
0231     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->driver);
0232 
0233     KIS_SAFE_ASSERT_RECOVER_RETURN(activeCanvas()->animationState());
0234     KIS_SAFE_ASSERT_RECOVER_RETURN(activeCanvas()->animationState()->playbackState() == PLAYING);
0235 
0236     KisFrameDisplayProxy* displayProxy = activeCanvas()->animationState()->displayProxy();
0237     KIS_SAFE_ASSERT_RECOVER_RETURN(displayProxy);
0238 
0239     KIS_SAFE_ASSERT_RECOVER_RETURN(activeCanvas()->image());
0240     KisImageAnimationInterface *animInterface = activeCanvas()->image()->animationInterface();
0241     KIS_SAFE_ASSERT_RECOVER_RETURN(animInterface);
0242 
0243     // If we're waiting for each frame, then we delay our callback.
0244     if (m_d->measure.waitingForFrame) {
0245         // Without drop frames on, we need to factor out time that we're waiting
0246         // for a frame from our time
0247         return;
0248     }
0249 
0250     const int currentFrame = displayProxy->activeFrame();
0251     const int startFrame = animInterface->activePlaybackRange().start();
0252     const int endFrame = animInterface->activePlaybackRange().end();
0253 
0254     const int timeSinceLastFrame =  m_d->measure.timeSinceLastFrame.restart();
0255     const int timePerFrame = qRound(1000.0 / qreal(activeFramesPerSecond().get_value_or(24)) / m_d->driver->speed());
0256     m_d->measure.averageTimePerFrame(timeSinceLastFrame);
0257 
0258 
0259     // Drop frames logic...
0260     int extraFrames = 0;
0261     if (m_d->driver->dropFrames()) {
0262         const int offset = timeSinceLastFrame - timePerFrame;
0263         extraFrames = qMax(0, offset) / timePerFrame;
0264     }
0265 
0266     m_d->measure.droppedFramesStat(extraFrames);
0267 
0268     { // just advance the frame ourselves based on the displayProxy's active frame.
0269         int targetFrame = currentFrame + 1 + extraFrames;
0270 
0271         targetFrame = frameWrap(targetFrame, startFrame, endFrame);
0272 
0273         if (currentFrame != targetFrame) {
0274             // We only wait when drop frames is enabled.
0275             m_d->measure.waitingForFrame = !m_d->driver->dropFrames();
0276 
0277             bool neededRefresh = displayProxy->displayFrame(targetFrame, false);
0278 
0279             // If we didn't need to refresh, we just continue as usual.
0280             m_d->measure.waitingForFrame &= neededRefresh;
0281         }
0282     }
0283 }
0284 
0285 void KisPlaybackEngineQT::setCanvas(KoCanvasBase *p_canvas)
0286 {
0287     KisCanvas2* canvas = dynamic_cast<KisCanvas2*>(p_canvas);
0288 
0289     struct StopAndResume {
0290         StopAndResume(KisPlaybackEngineQT* p_self)
0291             : m_self(p_self) {
0292             KIS_SAFE_ASSERT_RECOVER_RETURN(m_self->m_d->driver);
0293 
0294             m_self->m_d->driver->setPlaybackState(PlaybackState::STOPPED);
0295         }
0296 
0297         ~StopAndResume() {
0298             KIS_SAFE_ASSERT_RECOVER_RETURN(m_self->m_d->driver);
0299 
0300             if (m_self->activeCanvas()) {
0301                 m_self->m_d->driver->setPlaybackState(m_self->activeCanvas()->animationState()->playbackState());
0302             }
0303         }
0304 
0305     private:
0306         KisPlaybackEngineQT* m_self;
0307     };
0308 
0309     if (activeCanvas() == canvas) {
0310         return;
0311     }
0312 
0313     if (activeCanvas()) {
0314         KisCanvasAnimationState* animationState = activeCanvas()->animationState();
0315 
0316         KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->driver);
0317 
0318         // Disconnect internal..
0319         m_d->driver.data()->disconnect(this);
0320 
0321         { // Disconnect old Image Anim Interface, prepare for new one..
0322             auto image = activeCanvas()->image();
0323             KisImageAnimationInterface* aniInterface = image ? image->animationInterface() : nullptr;
0324             if (aniInterface) {
0325                 this->disconnect(image->animationInterface());
0326                 image->animationInterface()->disconnect(this);
0327             }
0328         }
0329 
0330         { // Disconnect old display proxy, prepare for new one.
0331             KisFrameDisplayProxy* displayProxy = animationState->displayProxy();
0332 
0333             if (displayProxy) {
0334                 displayProxy->disconnect(this);
0335             }
0336         }
0337 
0338         { // Disconnect old animation state, prepare for new one..
0339             if (animationState) {
0340                 this->disconnect(animationState);
0341                 animationState->disconnect(this);
0342             }
0343         }
0344     }
0345 
0346     StopAndResume stopResume(this);
0347 
0348     KisPlaybackEngine::setCanvas(canvas);
0349 
0350     if (activeCanvas()) {
0351         KisCanvasAnimationState* animationState = activeCanvas()->animationState();
0352         KIS_ASSERT(animationState);
0353 
0354         KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->driver);
0355 
0356         { // Animation State Connections
0357             connect(animationState, &KisCanvasAnimationState::sigPlaybackStateChanged, this, [this](PlaybackState state){
0358                 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->driver);
0359 
0360                 if (state == PLAYING) {
0361                     m_d->measure.reset();
0362                 }
0363 
0364                 m_d->driver->setPlaybackState(state);
0365             });
0366 
0367             connect(animationState, &KisCanvasAnimationState::sigPlaybackSpeedChanged, this, [this](qreal value){
0368                 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->driver);
0369                 m_d->driver->setSpeed(value);
0370             });
0371             m_d->driver->setSpeed(animationState->playbackSpeed());
0372         }
0373 
0374         { // Display proxy connections
0375             KisFrameDisplayProxy* displayProxy = animationState->displayProxy();
0376             KIS_ASSERT(displayProxy);
0377             connect(displayProxy, &KisFrameDisplayProxy::sigFrameDisplayRefreshed, this, [this](){
0378                 m_d->measure.waitingForFrame = false;
0379             });
0380 
0381             connect(displayProxy, &KisFrameDisplayProxy::sigFrameRefreshSkipped, this, [this](){
0382                 m_d->measure.waitingForFrame = false;
0383             });
0384         }
0385 
0386 
0387         {   // Animation Interface Connections
0388             auto image = activeCanvas()->image();
0389             KIS_ASSERT(image);
0390             KisImageAnimationInterface* aniInterface = image->animationInterface();
0391             KIS_ASSERT(aniInterface);
0392 
0393             connect(aniInterface, &KisImageAnimationInterface::sigFramerateChanged, this, [this](){
0394                 if (!activeCanvas())
0395                     return;
0396 
0397                 KisImageWSP img = activeCanvas()->image();
0398                 KIS_SAFE_ASSERT_RECOVER_RETURN(img);
0399                 KisImageAnimationInterface* aniInterface = img->animationInterface();
0400                 KIS_SAFE_ASSERT_RECOVER_RETURN(aniInterface);
0401 
0402                 m_d->driver->setFramerate(aniInterface->framerate());
0403             });
0404 
0405             m_d->driver->setFramerate(aniInterface->framerate());
0406         }
0407 
0408         // Internal connections
0409         connect(m_d->driver.data(), SIGNAL(throttledShowFrame()), this, SLOT(throttledDriverCallback()));
0410 
0411     }
0412 }
0413 
0414 void KisPlaybackEngineQT::unsetCanvas()
0415 {
0416     setCanvas(nullptr);
0417 }
0418 
0419 #include "KisPlaybackEngineQT.moc"