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