diff --git a/libs/ui/KisSyncedAudioPlayback.h b/libs/ui/KisSyncedAudioPlayback.h --- a/libs/ui/KisSyncedAudioPlayback.h +++ b/libs/ui/KisSyncedAudioPlayback.h @@ -15,6 +15,7 @@ void syncWithVideo(qint64 position); bool isPlaying() const; + qint64 position() const; void setVolume(qreal value); diff --git a/libs/ui/KisSyncedAudioPlayback.cpp b/libs/ui/KisSyncedAudioPlayback.cpp --- a/libs/ui/KisSyncedAudioPlayback.cpp +++ b/libs/ui/KisSyncedAudioPlayback.cpp @@ -99,6 +99,11 @@ return m_d->player.state() == QMediaPlayer::PlayingState; } +qint64 KisSyncedAudioPlayback::position() const +{ + return m_d->player.position(); +} + void KisSyncedAudioPlayback::setVolume(qreal value) { m_d->player.setVolume(qRound(100.0 * value)); diff --git a/libs/ui/canvas/kis_animation_player.h b/libs/ui/canvas/kis_animation_player.h --- a/libs/ui/canvas/kis_animation_player.h +++ b/libs/ui/canvas/kis_animation_player.h @@ -77,7 +77,7 @@ private: void connectCancelSignals(); void disconnectCancelSignals(); - void uploadFrame(int time); + void uploadFrame(int time, bool forceSyncAudio); private: struct Private; diff --git a/libs/ui/canvas/kis_animation_player.cpp b/libs/ui/canvas/kis_animation_player.cpp --- a/libs/ui/canvas/kis_animation_player.cpp +++ b/libs/ui/canvas/kis_animation_player.cpp @@ -115,16 +115,31 @@ int incFrame(int frame, int inc) { frame += inc; if (frame > lastFrame) { - frame = firstFrame + frame - lastFrame - 1; + const int framesFromFirst = frame - firstFrame; + const int rangeLength = lastFrame - firstFrame + 1; + frame = firstFrame + framesFromFirst % rangeLength; } return frame; } - qint64 frameToMSec(int value, int fps) { - return qreal(value) / fps * 1000.0; + qint64 framesToMSec(qreal value, int fps) const { + return qRound(value / fps * 1000.0); + } + float msecToFrames(qint64 value, int fps) const { + return qRound(qreal(value) * fps / 1000.0); } - int msecToFrame(qint64 value, int fps) { - return qreal(value) * fps / 1000.0; + int framesToWalltime(qreal frame, int fps) const { + return qRound(framesToMSec(frame, fps) / playbackSpeed); + } + qreal walltimeToFrames(qint64 time, int fps) const { + return msecToFrames(time, fps) * playbackSpeed; + } + + qreal playbackTimeInFrames(int fps) const { + const qint64 cycleLength = lastFrame - firstFrame + 1; + const qreal framesPlayed = walltimeToFrames(playbackTime.elapsed(), fps); + const qreal framesSinceFirst = std::fmod(initialFrame + framesPlayed - firstFrame, cycleLength); + return firstFrame + framesSinceFirst; } }; @@ -138,6 +153,7 @@ m_d->playbackSpeed = 1.0; m_d->timer = new QTimer(this); + m_d->timer->setTimerType(Qt::PreciseTimer); connect(m_d->timer, SIGNAL(timeout()), this, SLOT(slotUpdate())); m_d->timer->setSingleShot(true); @@ -289,6 +305,10 @@ if (m_d->syncedAudio) { m_d->syncedAudio->setSoundOffsetTolerance(m_d->audioOffsetTolerance); } + + if (m_d->playing) { + slotUpdatePlaybackTimer(); + } } void KisAnimationPlayer::slotUpdatePlaybackTimer() @@ -307,11 +327,16 @@ m_d->expectedFrame = qBound(m_d->firstFrame, m_d->expectedFrame, m_d->lastFrame); - m_d->expectedInterval = qreal(1000) / fps / m_d->playbackSpeed; + m_d->expectedInterval = m_d->framesToWalltime(1, fps); m_d->lastTimerInterval = m_d->expectedInterval; if (m_d->syncedAudio) { m_d->syncedAudio->setSpeed(m_d->playbackSpeed); + + const qint64 expectedAudioTime = m_d->framesToMSec(m_d->expectedFrame, fps); + if (qAbs(m_d->syncedAudio->position() - expectedAudioTime) > m_d->framesToMSec(1.5, fps)) { + m_d->syncedAudio->syncWithVideo(expectedAudioTime); + } } m_d->timer->start(m_d->expectedInterval); @@ -327,8 +352,9 @@ void KisAnimationPlayer::play() { - { - const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface(); + + const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface(); + { const KisTimeRange &range = animation->playbackRange(); if (!range.isValid()) return; @@ -373,15 +399,15 @@ m_d->playing = true; + m_d->expectedFrame = animation->currentUITime(); slotUpdatePlaybackTimer(); - m_d->expectedFrame = m_d->firstFrame; m_d->lastPaintedFrame = -1; connectCancelSignals(); if (m_d->syncedAudio) { KisImageAnimationInterface *animationInterface = m_d->canvas->image()->animationInterface(); - m_d->syncedAudio->play(m_d->frameToMSec(m_d->firstFrame, animationInterface->framerate())); + m_d->syncedAudio->play(m_d->framesToMSec(m_d->expectedFrame, animationInterface->framerate())); } emit sigPlaybackStarted(); @@ -433,22 +459,24 @@ void KisAnimationPlayer::displayFrame(int time) { - uploadFrame(time); + uploadFrame(time, true); } void KisAnimationPlayer::slotUpdate() { - uploadFrame(-1); + uploadFrame(-1, false); } -void KisAnimationPlayer::uploadFrame(int frame) +void KisAnimationPlayer::uploadFrame(int frame, bool forceSyncAudio) { KisImageAnimationInterface *animationInterface = m_d->canvas->image()->animationInterface(); + const int fps = animationInterface->framerate(); + const bool syncToAudio = !forceSyncAudio && m_d->dropFramesMode && m_d->syncedAudio && m_d->syncedAudio->isPlaying(); if (frame < 0) { - const int currentTime = m_d->playbackTime.elapsed(); - const int framesDiff = currentTime - m_d->nextFrameExpectedTime; - const qreal framesDiffNorm = qreal(framesDiff) / m_d->expectedInterval; + const qreal currentTimeInFrames = syncToAudio ? + m_d->msecToFrames(m_d->syncedAudio->position(), fps) : + m_d->playbackTimeInFrames(fps); // qDebug() << ppVar(framesDiff) // << ppVar(m_d->expectedFrame) @@ -456,24 +484,37 @@ // << ppVar(m_d->lastTimerInterval); if (m_d->dropFramesMode) { - const int numDroppedFrames = qMax(0, qRound(framesDiffNorm)); - frame = m_d->incFrame(m_d->expectedFrame, numDroppedFrames); + const int currentFrame = qFloor(currentTimeInFrames); + const int framesToDrop = qMax(0, currentFrame - m_d->expectedFrame); + frame = m_d->incFrame(m_d->expectedFrame, framesToDrop); } else { frame = m_d->expectedFrame; } - m_d->nextFrameExpectedTime = currentTime + m_d->expectedInterval; - - m_d->lastTimerInterval = qMax(0.0, m_d->lastTimerInterval - 0.5 * framesDiff); - m_d->expectedFrame = m_d->incFrame(frame, 1); + m_d->expectedFrame = m_d->incFrame(frame, 1); + + if (!m_d->dropFramesMode) { + const qint64 currentTime = m_d->playbackTime.elapsed(); + const qint64 framesDiff = currentTime - m_d->nextFrameExpectedTime; + + m_d->nextFrameExpectedTime = currentTime + m_d->expectedInterval; + m_d->lastTimerInterval = qMax(0.0, m_d->lastTimerInterval - 0.5 * framesDiff); + } else if (m_d->expectedFrame >= frame) { + const int timeToNextFrame = m_d->framesToWalltime(m_d->expectedFrame - currentTimeInFrames, fps); + m_d->lastTimerInterval = qMax(0, timeToNextFrame); + } else { + // Animation restarting + forceSyncAudio = true; + m_d->lastTimerInterval = m_d->expectedInterval; + } m_d->timer->start(m_d->lastTimerInterval); m_d->playbackStatisticsCompressor.start(); } - if (m_d->syncedAudio) { - const int msecTime = m_d->frameToMSec(frame, animationInterface->framerate()); + if (m_d->syncedAudio && (!syncToAudio || forceSyncAudio)) { + const int msecTime = m_d->framesToMSec(frame, fps); if (isPlaying()) { slotSyncScrubbingAudio(msecTime); } else {