1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "DVDClock.h"
10 
11 #include "VideoReferenceClock.h"
12 #include "cores/VideoPlayer/Interface/TimingConstants.h"
13 #include "threads/SingleLock.h"
14 #include "utils/MathUtils.h"
15 #include "utils/TimeUtils.h"
16 #include "utils/log.h"
17 
18 #include <inttypes.h>
19 #include <math.h>
20 
CDVDClock()21 CDVDClock::CDVDClock()
22 {
23   CSingleLock lock(m_systemsection);
24 
25   m_pauseClock = 0;
26   m_bReset = true;
27   m_paused = false;
28   m_speedAfterPause = DVD_PLAYSPEED_PAUSE;
29   m_iDisc = 0;
30   m_maxspeedadjust = 5.0;
31   m_systemAdjust = 0;
32   m_speedAdjust = 0;
33   m_startClock = 0;
34   m_vSyncAdjust = 0;
35   m_frameTime = DVD_TIME_BASE / 60.0;
36 
37   m_videoRefClock.reset(new CVideoReferenceClock());
38   m_lastSystemTime = m_videoRefClock->GetTime();
39   m_systemOffset = m_videoRefClock->GetTime();
40   m_systemFrequency = CurrentHostFrequency();
41   m_systemUsed = m_systemFrequency;
42 }
43 
44 CDVDClock::~CDVDClock() = default;
45 
46 // Returns the current absolute clock in units of DVD_TIME_BASE (usually microseconds).
GetAbsoluteClock(bool interpolated)47 double CDVDClock::GetAbsoluteClock(bool interpolated /*= true*/)
48 {
49   CSingleLock lock(m_systemsection);
50 
51   int64_t current;
52   current = m_videoRefClock->GetTime(interpolated);
53 
54   return SystemToAbsolute(current);
55 }
56 
GetClock(bool interpolated)57 double CDVDClock::GetClock(bool interpolated /*= true*/)
58 {
59   CSingleLock lock(m_critSection);
60 
61   int64_t current = m_videoRefClock->GetTime(interpolated);
62   m_systemAdjust += m_speedAdjust * (current - m_lastSystemTime);
63   m_lastSystemTime = current;
64 
65   return SystemToPlaying(current);
66 }
67 
GetClock(double & absolute,bool interpolated)68 double CDVDClock::GetClock(double& absolute, bool interpolated /*= true*/)
69 {
70   int64_t current = m_videoRefClock->GetTime(interpolated);
71 
72   CSingleLock lock(m_systemsection);
73   absolute = SystemToAbsolute(current);
74 
75   m_systemAdjust += m_speedAdjust * (current - m_lastSystemTime);
76   m_lastSystemTime = current;
77 
78   return SystemToPlaying(current);
79 }
80 
SetVsyncAdjust(double adjustment)81 void CDVDClock::SetVsyncAdjust(double adjustment)
82 {
83   CSingleLock lock(m_critSection);
84   m_vSyncAdjust = adjustment;
85 }
86 
GetVsyncAdjust()87 double CDVDClock::GetVsyncAdjust()
88 {
89   CSingleLock lock(m_critSection);
90   return m_vSyncAdjust;
91 }
92 
Pause(bool pause)93 void CDVDClock::Pause(bool pause)
94 {
95   CSingleLock lock(m_critSection);
96 
97   if (pause && !m_paused)
98   {
99     if (!m_pauseClock)
100       m_speedAfterPause = m_systemFrequency * DVD_PLAYSPEED_NORMAL / m_systemUsed;
101     else
102       m_speedAfterPause = DVD_PLAYSPEED_PAUSE;
103 
104     SetSpeed(DVD_PLAYSPEED_PAUSE);
105     m_paused = true;
106   }
107   else if (!pause && m_paused)
108   {
109     m_paused = false;
110     SetSpeed(m_speedAfterPause);
111   }
112 }
113 
Advance(double time)114 void CDVDClock::Advance(double time)
115 {
116   CSingleLock lock(m_critSection);
117 
118   if (m_pauseClock)
119   {
120     m_pauseClock += time / DVD_TIME_BASE * m_systemFrequency;
121   }
122 }
123 
SetSpeed(int iSpeed)124 void CDVDClock::SetSpeed(int iSpeed)
125 {
126   // this will sometimes be a little bit of due to rounding errors, ie clock might jump a bit when changing speed
127   CSingleLock lock(m_critSection);
128 
129   if (m_paused)
130   {
131     m_speedAfterPause = iSpeed;
132     return;
133   }
134 
135   if (iSpeed == DVD_PLAYSPEED_PAUSE)
136   {
137     if (!m_pauseClock)
138       m_pauseClock = m_videoRefClock->GetTime();
139     return;
140   }
141 
142   int64_t current;
143   int64_t newfreq = m_systemFrequency * DVD_PLAYSPEED_NORMAL / iSpeed;
144 
145   current = m_videoRefClock->GetTime();
146   if (m_pauseClock)
147   {
148     m_startClock += current - m_pauseClock;
149     m_pauseClock = 0;
150   }
151 
152   m_startClock = current - (int64_t)((double)(current - m_startClock) * newfreq / m_systemUsed);
153   m_systemUsed = newfreq;
154 }
155 
SetSpeedAdjust(double adjust)156 void CDVDClock::SetSpeedAdjust(double adjust)
157 {
158   CLog::Log(LOGDEBUG, "CDVDClock::SetSpeedAdjust - adjusted:%f", adjust);
159 
160   CSingleLock lock(m_critSection);
161   m_speedAdjust = adjust;
162 }
163 
GetSpeedAdjust()164 double CDVDClock::GetSpeedAdjust()
165 {
166   CSingleLock lock(m_critSection);
167   return m_speedAdjust;
168 }
169 
ErrorAdjust(double error,const char * log)170 double CDVDClock::ErrorAdjust(double error, const char* log)
171 {
172   CSingleLock lock(m_critSection);
173 
174   double clock, absolute, adjustment;
175   clock = GetClock(absolute);
176 
177   // skip minor updates while speed adjust is active
178   // -> adjusting buffer levels
179   if (m_speedAdjust != 0 && error < DVD_MSEC_TO_TIME(100))
180   {
181     return 0;
182   }
183 
184   adjustment = error;
185 
186   if (m_vSyncAdjust != 0)
187   {
188     // Audio ahead is more noticeable then audio behind video.
189     // Correct if aufio is more than 20ms ahead or more then
190     // 27ms behind. In a worst case scenario we switch from
191     // 20ms ahead to 21ms behind (for fps of 23.976)
192     if (error > 0.02 * DVD_TIME_BASE)
193       adjustment = m_frameTime;
194     else if (error < -0.027 * DVD_TIME_BASE)
195       adjustment = -m_frameTime;
196     else
197       adjustment = 0;
198   }
199 
200   if (adjustment == 0)
201     return 0;
202 
203   Discontinuity(clock+adjustment, absolute);
204 
205   CLog::Log(LOGDEBUG, "CDVDClock::ErrorAdjust - %s - error:%f, adjusted:%f",
206                       log, error, adjustment);
207   return adjustment;
208 }
209 
Discontinuity(double clock,double absolute)210 void CDVDClock::Discontinuity(double clock, double absolute)
211 {
212   CSingleLock lock(m_critSection);
213   m_startClock = AbsoluteToSystem(absolute);
214   if(m_pauseClock)
215     m_pauseClock = m_startClock;
216   m_iDisc = clock;
217   m_bReset = false;
218   m_systemAdjust = 0;
219   m_speedAdjust = 0;
220 }
221 
SetMaxSpeedAdjust(double speed)222 void CDVDClock::SetMaxSpeedAdjust(double speed)
223 {
224   CSingleLock lock(m_speedsection);
225 
226   m_maxspeedadjust = speed;
227 }
228 
229 //returns the refreshrate if the videoreferenceclock is running, -1 otherwise
UpdateFramerate(double fps,double * interval)230 int CDVDClock::UpdateFramerate(double fps, double* interval /*= NULL*/)
231 {
232   //sent with fps of 0 means we are not playing video
233   if(fps == 0.0)
234     return -1;
235 
236   m_frameTime = 1/fps * DVD_TIME_BASE;
237 
238   //check if the videoreferenceclock is running, will return -1 if not
239   double rate = m_videoRefClock->GetRefreshRate(interval);
240 
241   if (rate <= 0)
242     return -1;
243 
244   CSingleLock lock(m_speedsection);
245 
246   double weight = (rate * 2) / fps;
247 
248   //set the speed of the videoreferenceclock based on fps, refreshrate and maximum speed adjust set by user
249   if (m_maxspeedadjust > 0.05)
250   {
251     if (weight / MathUtils::round_int(weight) < 1.0 + m_maxspeedadjust / 100.0 &&
252       weight / MathUtils::round_int(weight) > 1.0 - m_maxspeedadjust / 100.0)
253       weight = MathUtils::round_int(weight);
254   }
255   double speed = (rate * 2.0 ) / (fps * weight);
256   lock.Leave();
257 
258   m_videoRefClock->SetSpeed(speed);
259 
260   return rate;
261 }
262 
GetClockInfo(int & MissedVblanks,double & ClockSpeed,double & RefreshRate) const263 bool CDVDClock::GetClockInfo(int& MissedVblanks, double& ClockSpeed, double& RefreshRate) const
264 {
265   return m_videoRefClock->GetClockInfo(MissedVblanks, ClockSpeed, RefreshRate);
266 }
267 
SystemToAbsolute(int64_t system)268 double CDVDClock::SystemToAbsolute(int64_t system)
269 {
270   return DVD_TIME_BASE * (double)(system - m_systemOffset) / m_systemFrequency;
271 }
272 
AbsoluteToSystem(double absolute)273 int64_t CDVDClock::AbsoluteToSystem(double absolute)
274 {
275   return (int64_t)(absolute / DVD_TIME_BASE * m_systemFrequency) + m_systemOffset;
276 }
277 
SystemToPlaying(int64_t system)278 double CDVDClock::SystemToPlaying(int64_t system)
279 {
280   int64_t current;
281 
282   if (m_bReset)
283   {
284     m_startClock = system;
285     m_systemUsed = m_systemFrequency;
286     if(m_pauseClock)
287       m_pauseClock = m_startClock;
288     m_iDisc = 0;
289     m_systemAdjust = 0;
290     m_speedAdjust = 0;
291     m_vSyncAdjust = 0;
292     m_bReset = false;
293   }
294 
295   if (m_pauseClock)
296     current = m_pauseClock;
297   else
298     current = system;
299 
300   return DVD_TIME_BASE * (double)(current - m_startClock + m_systemAdjust) / m_systemUsed + m_iDisc;
301 }
302 
GetClockSpeed()303 double CDVDClock::GetClockSpeed()
304 {
305   CSingleLock lock(m_critSection);
306 
307   double speed = (double)m_systemFrequency / m_systemUsed;
308   return m_videoRefClock->GetSpeed() * speed + m_speedAdjust;
309 }
310