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"