MythTV  master
vsync.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2004 Doug Larrick <doug@ties.org>.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2, or (at your option)
7  * any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #include <cstdio>
20 #include <cerrno>
21 #include <cmath>
22 #include <cstdlib> // for abs(int)
23 #include <unistd.h>
24 #include <chrono> // for milliseconds
25 #include <thread> // for sleep_for
26 #include <fcntl.h>
27 
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include "compat.h"
31 
32 #ifndef _WIN32
33 #include <sys/ioctl.h>
34 #include <sys/poll.h>
35 #endif
36 
37 #include "mythcontext.h"
38 #include "mythmainwindow.h"
39 
40 #ifdef USING_VDPAU
41 #include "videoout_vdpau.h"
42 #endif
43 
44 #ifdef USING_XV
45 #include "videoout_xv.h"
46 #endif
47 
48 #ifdef __linux__
49 #include <linux/rtc.h>
50 #endif
51 
52 using namespace std;
53 #include "videooutbase.h"
54 #include "vsync.h"
55 
56 bool tryingVideoSync = false;
58 
59 #define TESTVIDEOSYNC(NAME) \
60  do { if (++s_forceskip > skip) \
61  { \
62  trial = new NAME (video_output, refresh_interval); \
63  if (trial->TryInit()) \
64  { \
65  s_forceskip = skip; \
66  tryingVideoSync = false; \
67  return trial; \
68  } \
69  delete trial; \
70  } } while (false)
71 
72 #define LOC QString("VSYNC: ")
73 
78  uint refresh_interval)
79 {
80  VideoSync *trial = nullptr;
81  tryingVideoSync = true;
82 
83  // s_forceskip allows for skipping one sync method
84  // due to crash on the previous run.
85  int skip = 0;
86  if (s_forceskip)
87  {
88  LOG(VB_PLAYBACK, LOG_INFO, LOC +
89  QString("A previous trial crashed, skipping %1").arg(s_forceskip));
90 
91  skip = s_forceskip;
92  s_forceskip = 0;
93  }
94 
95 #ifdef USING_VDPAU
96 // TESTVIDEOSYNC(VDPAUVideoSync);
97 #endif
98 #ifndef _WIN32
99  TESTVIDEOSYNC(DRMVideoSync);
100 #endif // _WIN32
101 #ifdef __linux__
102  TESTVIDEOSYNC(RTCVideoSync);
103 #endif // __linux__
104 
106 
107  tryingVideoSync=false;
108  return nullptr;
109 }
110 
115 VideoSync::VideoSync(VideoOutput *video_output, int refreshint) :
116  m_video_output(video_output), m_refresh_interval(refreshint)
117 {
118 }
119 
120 int64_t VideoSync::GetTime(void)
121 {
122  struct timeval now_tv {};
123  gettimeofday(&now_tv, nullptr);
124  return now_tv.tv_sec * 1000000LL + now_tv.tv_usec;
125 }
126 
128 {
130 }
131 
143 int VideoSync::CalcDelay(int nominal_frame_interval)
144 {
145  int64_t now = GetTime();
146 #if 0
147  LOG(VB_GENERAL, LOG_DEBUG, QString("CalcDelay: next: %1 now %2")
148  .arg(timeval_str(m_nexttrigger)) .arg(timeval_str(now)));
149 #endif
150 
151  int ret_val = m_nexttrigger - now;
152 
153 #if 0
154  LOG(VB_GENERAL, LOG_DEBUG, QString("delay %1").arg(ret_val));
155 #endif
156 
157  if (ret_val > nominal_frame_interval * 4)
158  {
159  ret_val = nominal_frame_interval * 4;
160 
161  // set nexttrigger to our new target time
162  m_nexttrigger = now + ret_val;
163  }
164 
165  if (ret_val < -nominal_frame_interval && nominal_frame_interval >= m_refresh_interval)
166  {
167  ret_val = -nominal_frame_interval;
168 
169  // set nexttrigger to our new target time
170  m_nexttrigger = now + ret_val;
171  }
172 
173  return ret_val;
174 }
175 
176 #ifndef _WIN32
177 #define DRM_VBLANK_RELATIVE 0x1;
178 
179 struct drm_wait_vblank_request {
180  int type;
181  unsigned int sequence;
182  unsigned long signal;
183 };
184 
185 struct drm_wait_vblank_reply {
186  int type;
187  unsigned int sequence;
188  long tval_sec;
189  long tval_usec;
190 };
191 
192 typedef union drm_wait_vblank {
193  struct drm_wait_vblank_request request;
194  struct drm_wait_vblank_reply reply;
195 } drm_wait_vblank_t;
196 
197 #define DRM_IOCTL_BASE 'd'
198 #define DRM_IOWR(nr,type) _IOWR(DRM_IOCTL_BASE,nr,type)
199 
200 #define DRM_IOCTL_WAIT_VBLANK DRM_IOWR(0x3a, drm_wait_vblank_t)
201 
202 static int drmWaitVBlank(int fd, drm_wait_vblank_t *vbl)
203 {
204  int ret = -1;
205 
206  do {
207  ret = ioctl(fd, DRM_IOCTL_WAIT_VBLANK, vbl);
208  vbl->request.type &= ~DRM_VBLANK_RELATIVE;
209  } while (ret && errno == EINTR);
210 
211  return ret;
212 }
213 
214 const char *DRMVideoSync::s_dri_dev = "/dev/dri/card0";
215 
216 DRMVideoSync::DRMVideoSync(VideoOutput *vo, int refresh_interval) :
217  VideoSync(vo, refresh_interval)
218 {
219  m_dri_fd = -1;
220 }
221 
222 DRMVideoSync::~DRMVideoSync()
223 {
224  if (m_dri_fd >= 0)
225  close(m_dri_fd);
226  m_dri_fd = -1;
227 }
228 
229 bool DRMVideoSync::TryInit(void)
230 {
231  drm_wait_vblank_t blank;
232 
233  m_dri_fd = open(s_dri_dev, O_RDWR);
234  if (m_dri_fd < 0)
235  {
236  LOG(VB_PLAYBACK, LOG_INFO, LOC +
237  QString("DRMVideoSync: Could not open device %1, %2")
238  .arg(s_dri_dev).arg(strerror(errno)));
239  return false; // couldn't open device
240  }
241 
242  blank.request.type = DRM_VBLANK_RELATIVE;
243  blank.request.sequence = 1;
244  if (drmWaitVBlank(m_dri_fd, &blank))
245  {
246  LOG(VB_PLAYBACK, LOG_ERR, LOC +
247  QString("DRMVideoSync: VBlank ioctl did not"
248  " work, unimplemented in this driver?"));
249  return false; // VBLANK ioctl didn't worko
250  }
251 
252  return true;
253 }
254 
255 void DRMVideoSync::Start(void)
256 {
257  // Wait for a refresh so we start out synched
258  drm_wait_vblank_t blank;
259  blank.request.type = DRM_VBLANK_RELATIVE;
260  blank.request.sequence = 1;
261  drmWaitVBlank(m_dri_fd, &blank);
263 }
264 
265 int DRMVideoSync::WaitForFrame(int nominal_frame_interval, int extra_delay)
266 {
267  // Offset for externally-provided A/V sync delay
268  m_nexttrigger += nominal_frame_interval + extra_delay;
269 
270  m_delay = CalcDelay(nominal_frame_interval);
271 #if 0
272  LOG(VB_GENERAL, LOG_DEBUG, QString("WaitForFrame at : %1").arg(m_delay));
273 #endif
274 
275  // Always sync to the next retrace execpt when we are very late.
276  if (m_delay > -(m_refresh_interval/2))
277  {
278  drm_wait_vblank_t blank;
279  blank.request.type = DRM_VBLANK_RELATIVE;
280  blank.request.sequence = 1;
281  drmWaitVBlank(m_dri_fd, &blank);
282  m_delay = CalcDelay(nominal_frame_interval);
283 #if 0
284  LOG(VB_GENERAL, LOG_DEBUG, QString("Delay at sync: %1").arg(m_delay));
285 #endif
286  }
287 
288  if (m_delay > 0)
289  {
290  // Wait for any remaining retrace intervals in one pass.
291  int n = (m_delay + m_refresh_interval - 1) / m_refresh_interval;
292 
293  drm_wait_vblank_t blank;
294  blank.request.type = DRM_VBLANK_RELATIVE;
295  blank.request.sequence = n;
296  drmWaitVBlank(m_dri_fd, &blank);
297  m_delay = CalcDelay(nominal_frame_interval);
298 #if 0
299  LOG(VB_GENERAL, LOG_DEBUG,
300  QString("Wait %1 intervals. Count %2 Delay %3")
301  .arg(n) .arg(blank.request.sequence) .arg(m_delay));
302 #endif
303  }
304 
305  return m_delay;
306 }
307 #endif /* !_WIN32 */
308 
309 #ifdef __linux__
310 #define RTCRATE 1024
311 RTCVideoSync::RTCVideoSync(VideoOutput *vo, int refresh_interval) :
312  VideoSync(vo, refresh_interval)
313 {
314  m_rtcfd = -1;
315 }
316 
317 RTCVideoSync::~RTCVideoSync()
318 {
319  if (m_rtcfd >= 0)
320  close(m_rtcfd);
321 }
322 
323 bool RTCVideoSync::TryInit(void)
324 {
325  m_rtcfd = open("/dev/rtc", O_RDONLY);
326  if (m_rtcfd < 0)
327  {
328  LOG(VB_PLAYBACK, LOG_ERR, LOC +
329  "RTCVideoSync: Could not open /dev/rtc: " + ENO);
330  return false;
331  }
332 
333  // FIXME, does it make sense to tie RTCRATE to the desired framerate?
334  if ((ioctl(m_rtcfd, RTC_IRQP_SET, RTCRATE) < 0))
335  {
336  LOG(VB_PLAYBACK, LOG_ERR, LOC +
337  "RTCVideoSync: Could not set RTC frequency: " + ENO);
338  return false;
339  }
340 
341  if (ioctl(m_rtcfd, RTC_PIE_ON, 0) < 0)
342  {
343  LOG(VB_PLAYBACK, LOG_ERR, LOC +
344  "RTCVideoSync: Could not enable periodic timer interrupts: " + ENO);
345  return false;
346  }
347 
348  return true;
349 }
350 
351 int RTCVideoSync::WaitForFrame(int nominal_frame_interval, int extra_delay)
352 {
353  m_nexttrigger += nominal_frame_interval + extra_delay;
354 
355  m_delay = CalcDelay(nominal_frame_interval);
356 
357  unsigned long rtcdata;
358  while (m_delay > 0)
359  {
360  ssize_t val = read(m_rtcfd, &rtcdata, sizeof(rtcdata));
361  m_delay = CalcDelay(nominal_frame_interval);
362 
363  if ((val < 0) && (m_delay > 0))
364  std::this_thread::sleep_for(std::chrono::microseconds(m_delay));
365  }
366  return 0;
367 }
368 #endif /* __linux__ */
369 
370 int BusyWaitVideoSync::WaitForFrame(int nominal_frame_interval, int extra_delay)
371 {
372  // Offset for externally-provided A/V sync delay
373  m_nexttrigger += nominal_frame_interval + extra_delay;
374 
375  m_delay = CalcDelay(nominal_frame_interval);
376 
377  if (m_delay > 0)
378  {
379  int cnt = 0;
380  m_cheat += 100;
381  // The usleep() is shortened by "cheat" so that this process gets
382  // the CPU early for about half the frames.
383  if (m_delay > (m_cheat - m_fudge))
384  std::this_thread::sleep_for(std::chrono::microseconds(m_delay - (m_cheat - m_fudge)));
385 
386  // If late, draw the frame ASAP. If early, hold the CPU until
387  // half as late as the previous frame (fudge).
388  m_delay = CalcDelay(nominal_frame_interval);
389  m_fudge = min(m_fudge, nominal_frame_interval);
390  while (m_delay + m_fudge > 0)
391  {
392  m_delay = CalcDelay(nominal_frame_interval);
393  cnt++;
394  }
395  m_fudge = abs(m_delay / 2);
396  if (cnt > 1)
397  m_cheat -= 200;
398  }
399  return 0;
400 }
401 
402 int USleepVideoSync::WaitForFrame(int nominal_frame_interval, int extra_delay)
403 {
404  // Offset for externally-provided A/V sync delay
405  m_nexttrigger += nominal_frame_interval + extra_delay;
406 
407  m_delay = CalcDelay(nominal_frame_interval);
408  if (m_delay > 0)
409  std::this_thread::sleep_for(std::chrono::microseconds(m_delay));
410  return 0;
411 }
VideoSync(VideoOutput *, int refreshint)
Used by BestMethod(VideoOutput*,uint) to initialize video synchronization method.
Definition: vsync.cpp:115
unsigned int uint
Definition: compat.h:140
#define TESTVIDEOSYNC(NAME)
Definition: vsync.cpp:59
int WaitForFrame(int nominal_frame_interval, int extra_delay) override
Waits for next a frame or field.
Definition: vsync.cpp:402
int m_delay
Definition: vsync.h:101
def read(device=None, features=[])
Definition: disc.py:35
int CalcDelay(int nominal_frame_interval)
Calculates the delay to the next frame.
Definition: vsync.cpp:143
This class serves as the base class for all video output methods.
Definition: videooutbase.h:46
bool tryingVideoSync
Definition: vsync.cpp:56
#define LOC
Definition: vsync.cpp:72
#define close
Definition: compat.h:16
int64_t GetTime(void)
Definition: vsync.cpp:120
static int s_forceskip
Definition: vsync.h:103
Virtual base class for all video synchronization classes.
Definition: vsync.h:45
virtual void Start(void)
Start VSync; must be called from main thread.
Definition: vsync.cpp:127
Video synchronization classes employing usleep() and busy-waits.
Definition: vsync.h:169
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:99
int m_refresh_interval
Definition: vsync.h:99
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
static VideoSync * BestMethod(VideoOutput *, uint refresh_interval)
Returns the most sophisticated video sync method available.
Definition: vsync.cpp:77
int WaitForFrame(int nominal_frame_interval, int extra_delay) override
Waits for next a frame or field.
Definition: vsync.cpp:370
int64_t m_nexttrigger
Definition: vsync.h:100