/* * Copyright (C) 2005-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #include "VideoReferenceClock.h" #include "ServiceBroker.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "utils/MathUtils.h" #include "utils/TimeUtils.h" #include "utils/log.h" #include "windowing/GraphicContext.h" #include "windowing/VideoSync.h" #include "windowing/WinSystem.h" #include CVideoReferenceClock::CVideoReferenceClock() : CThread("RefClock") { m_SystemFrequency = CurrentHostFrequency(); m_ClockSpeed = 1.0; m_TotalMissedVblanks = 0; m_UseVblank = false; m_CurrTime = 0; m_LastIntTime = 0; m_CurrTimeFract = 0.0; m_RefreshRate = 0.0; m_MissedVblanks = 0; m_VblankTime = 0; m_vsyncStopEvent.Reset(); Start(); } CVideoReferenceClock::~CVideoReferenceClock() { m_bStop = true; m_vsyncStopEvent.Set(); StopThread(); } void CVideoReferenceClock::Start() { if(CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_USEDISPLAYASCLOCK) && !IsRunning()) Create(); } void CVideoReferenceClock::CBUpdateClock(int NrVBlanks, uint64_t time, void *clock) { { CVideoReferenceClock *refClock = static_cast(clock); std::unique_lock lock(refClock->m_CritSection); refClock->m_VblankTime = time; refClock->UpdateClock(NrVBlanks, true); } } void CVideoReferenceClock::Process() { bool SetupSuccess = false; int64_t Now; while(!m_bStop) { m_pVideoSync = CServiceBroker::GetWinSystem()->GetVideoSync(this); if (m_pVideoSync) { SetupSuccess = m_pVideoSync->Setup(CBUpdateClock); UpdateRefreshrate(); } std::unique_lock SingleLock(m_CritSection); Now = CurrentHostCounter(); m_CurrTime = Now; m_LastIntTime = m_CurrTime; m_CurrTimeFract = 0.0; m_ClockSpeed = 1.0; m_TotalMissedVblanks = 0; m_MissedVblanks = 0; if (SetupSuccess) { m_UseVblank = true; //tell other threads we're using vblank as clock m_VblankTime = Now; //initialize the timestamp of the last vblank SingleLock.unlock(); // we might got signalled while we did not wait if (!m_vsyncStopEvent.Signaled()) { //run the clock m_pVideoSync->Run(m_vsyncStopEvent); m_vsyncStopEvent.Reset(); } } else { SingleLock.unlock(); CLog::Log(LOGDEBUG, "CVideoReferenceClock: Setup failed, falling back to CurrentHostCounter()"); } SingleLock.lock(); m_UseVblank = false; //we're back to using the systemclock SingleLock.unlock(); //clean up the vblank clock if (m_pVideoSync) { m_pVideoSync->Cleanup(); m_pVideoSync.reset(); } if (!SetupSuccess) break; } } //this is called from the vblank run function and from CVideoReferenceClock::Wait in case of a late update void CVideoReferenceClock::UpdateClock(int NrVBlanks, bool CheckMissed) { if (CheckMissed) //set to true from the vblank run function, set to false from Wait and GetTime { if (NrVBlanks < m_MissedVblanks) //if this is true the vblank detection in the run function is wrong CLog::Log( LOGDEBUG, "CVideoReferenceClock: detected {} vblanks, missed {}, refreshrate might have changed", NrVBlanks, m_MissedVblanks); NrVBlanks -= m_MissedVblanks; //subtract the vblanks we missed m_MissedVblanks = 0; } else { m_MissedVblanks += NrVBlanks; //tell the vblank clock how many vblanks it missed m_TotalMissedVblanks += NrVBlanks; //for the codec information screen m_VblankTime += m_SystemFrequency * static_cast(NrVBlanks) / MathUtils::round_int(m_RefreshRate); //set the vblank time forward } if (NrVBlanks > 0) //update the clock with the adjusted frequency if we have any vblanks { double increment = UpdateInterval() * NrVBlanks; double integer = floor(increment); m_CurrTime += static_cast(integer + 0.5); //make sure it gets correctly converted to int //accumulate what we lost due to rounding in m_CurrTimeFract, then add the integer part of that to m_CurrTime m_CurrTimeFract += increment - integer; integer = floor(m_CurrTimeFract); m_CurrTime += static_cast(integer + 0.5); m_CurrTimeFract -= integer; } } double CVideoReferenceClock::UpdateInterval() const { return m_ClockSpeed / m_RefreshRate * static_cast(m_SystemFrequency); } //called from dvdclock to get the time int64_t CVideoReferenceClock::GetTime(bool interpolated /* = true*/) { std::unique_lock SingleLock(m_CritSection); //when using vblank, get the time from that, otherwise use the systemclock if (m_UseVblank) { int64_t NextVblank; int64_t Now; Now = CurrentHostCounter(); //get current system time NextVblank = TimeOfNextVblank(); //get time when the next vblank should happen while(Now >= NextVblank) //keep looping until the next vblank is in the future { UpdateClock(1, false); //update clock when next vblank should have happened already NextVblank = TimeOfNextVblank(); //get time when the next vblank should happen } if (interpolated) { //interpolate from the last time the clock was updated double elapsed = static_cast(Now - m_VblankTime) * m_ClockSpeed; //don't interpolate more than 2 vblank periods elapsed = std::min(elapsed, UpdateInterval() * 2.0); //make sure the clock doesn't go backwards int64_t intTime = m_CurrTime + static_cast(elapsed); if (intTime > m_LastIntTime) m_LastIntTime = intTime; return m_LastIntTime; } else { return m_CurrTime; } } else { return CurrentHostCounter(); } } void CVideoReferenceClock::SetSpeed(double Speed) { std::unique_lock SingleLock(m_CritSection); //VideoPlayer can change the speed to fit the rereshrate if (m_UseVblank) { if (Speed != m_ClockSpeed) { m_ClockSpeed = Speed; CLog::Log(LOGDEBUG, "CVideoReferenceClock: Clock speed {:0.2f} %", m_ClockSpeed * 100.0); } } } double CVideoReferenceClock::GetSpeed() { std::unique_lock SingleLock(m_CritSection); //VideoPlayer needs to know the speed for the resampler if (m_UseVblank) return m_ClockSpeed; else return 1.0; } void CVideoReferenceClock::UpdateRefreshrate() { std::unique_lock SingleLock(m_CritSection); m_RefreshRate = static_cast(m_pVideoSync->GetFps()); m_ClockSpeed = 1.0; CLog::Log(LOGDEBUG, "CVideoReferenceClock: Detected refreshrate: {:.3f} hertz", m_RefreshRate); } //VideoPlayer needs to know the refreshrate for matching the fps of the video playing to it double CVideoReferenceClock::GetRefreshRate(double* interval /*= NULL*/) { std::unique_lock SingleLock(m_CritSection); if (m_UseVblank) { if (interval) *interval = m_ClockSpeed / m_RefreshRate; return m_RefreshRate; } else return -1; } #define MAXVBLANKDELAY 13LL //guess when the next vblank should happen, //based on the refreshrate and when the previous one happened //increase that by 30% to allow for errors int64_t CVideoReferenceClock::TimeOfNextVblank() const { return m_VblankTime + (m_SystemFrequency / MathUtils::round_int(m_RefreshRate) * MAXVBLANKDELAY / 10LL); } //for the codec information screen bool CVideoReferenceClock::GetClockInfo(int& MissedVblanks, double& ClockSpeed, double& RefreshRate) const { std::unique_lock SingleLock(m_CritSection); if (m_UseVblank) { MissedVblanks = m_TotalMissedVblanks; ClockSpeed = m_ClockSpeed; RefreshRate = m_RefreshRate; return true; } return false; }