MythTV  0.26-pre
vsync.cpp
Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2004 Doug Larrick <doug@ties.org>.
00003  *
00004  * This program is free software; you can redistribute it and/or modify
00005  * it under the terms of the GNU General Public License as published by
00006  * the Free Software Foundation; either version 2, or (at your option)
00007  * any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  * GNU General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software Foundation,
00016  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00017  */
00018 
00019 #include <cstdio>
00020 #include <cerrno>
00021 #include <cmath>
00022 #include <cstdlib> // for abs(int)
00023 #include <unistd.h>
00024 #include <fcntl.h>
00025 
00026 #include <sys/types.h>
00027 #include <sys/stat.h>
00028 #include "compat.h"
00029 
00030 #ifndef _WIN32
00031 #include <sys/ioctl.h>
00032 #include <sys/poll.h>
00033 #endif
00034 
00035 #include "mythcontext.h"
00036 #include "mythmainwindow.h"
00037 
00038 #ifdef USING_XV
00039 #include "videoout_xv.h"
00040 #endif
00041 
00042 #ifdef USING_VDPAU
00043 #include "videoout_vdpau.h"
00044 #endif
00045 
00046 #ifdef __linux__
00047 #include <linux/rtc.h>
00048 #endif
00049 
00050 using namespace std;
00051 #include "videooutbase.h"
00052 #include "vsync.h"
00053 
00054 bool tryingVideoSync = false;
00055 int VideoSync::m_forceskip = 0;
00056 
00057 #define TESTVIDEOSYNC(NAME) \
00058     do { if (++m_forceskip > skip) \
00059     { \
00060         trial = new NAME (video_output,     frame_interval, \
00061                           refresh_interval, halve_frame_interval); \
00062         if (trial->TryInit()) \
00063         { \
00064             m_forceskip = skip; \
00065             tryingVideoSync = false; \
00066             return trial; \
00067         } \
00068         delete trial; \
00069     } } while (false)
00070 
00071 #define LOC QString("VSYNC: ")
00072 
00076 VideoSync *VideoSync::BestMethod(VideoOutput *video_output,
00077                                  uint frame_interval, uint refresh_interval,
00078                                  bool halve_frame_interval)
00079 {
00080     VideoSync *trial = NULL;
00081     tryingVideoSync  = true;
00082 
00083     // m_forceskip allows for skipping one sync method
00084     // due to crash on the previous run.
00085     int skip = 0;
00086     if (m_forceskip)
00087     {
00088         LOG(VB_PLAYBACK, LOG_INFO, LOC +
00089             QString("A previous trial crashed, skipping %1").arg(m_forceskip));
00090 
00091         skip = m_forceskip;
00092         m_forceskip = 0;
00093     }
00094 
00095 #ifdef USING_VDPAU
00096 //    TESTVIDEOSYNC(VDPAUVideoSync);
00097 #endif
00098 #ifndef _WIN32
00099     TESTVIDEOSYNC(DRMVideoSync);
00100 #endif // _WIN32
00101 #ifdef __linux__
00102     TESTVIDEOSYNC(RTCVideoSync);
00103 #endif // __linux__
00104 
00105     TESTVIDEOSYNC(BusyWaitVideoSync);
00106 
00107     tryingVideoSync=false;
00108     return NULL;
00109 }
00110 
00115 VideoSync::VideoSync(VideoOutput *video_output,
00116                      int frameint, int refreshint,
00117                      bool halve_frame_interval) :
00118     m_video_output(video_output),   m_frame_interval(frameint),
00119     m_refresh_interval(refreshint), m_interlaced(halve_frame_interval),
00120     m_nexttrigger(0), m_delay(-1)
00121 {
00122 }
00123 
00124 int64_t VideoSync::GetTime(void)
00125 {
00126     struct timeval now_tv;
00127     gettimeofday(&now_tv, NULL);
00128     return now_tv.tv_sec * 1000000LL + now_tv.tv_usec;
00129 }
00130 
00131 void VideoSync::Start(void)
00132 {
00133     m_nexttrigger = GetTime();
00134 }
00135 
00147 int VideoSync::CalcDelay()
00148 {
00149     int64_t now = GetTime();
00150 #if 0
00151     LOG(VB_GENERAL, LOG_DEBUG, QString("CalcDelay: next: %1 now %2")
00152         .arg(timeval_str(m_nexttrigger)) .arg(timeval_str(now)));
00153 #endif
00154 
00155     int ret_val = m_nexttrigger - now;
00156 
00157 #if 0
00158     LOG(VB_GENERAL, LOG_DEBUG, QString("delay %1").arg(ret_val);
00159 #endif
00160 
00161     if (ret_val > m_frame_interval * 4)
00162     {
00163         if (m_interlaced)
00164             ret_val = (m_frame_interval / 2) * 4;
00165         else
00166             ret_val = m_frame_interval * 4;
00167 
00168         // set nexttrigger to our new target time
00169         m_nexttrigger = now + ret_val;
00170     }
00171 
00172     if (ret_val < -m_frame_interval && m_frame_interval >= m_refresh_interval)
00173     {
00174         ret_val = -m_frame_interval;
00175 
00176         // set nexttrigger to our new target time
00177         m_nexttrigger = now + ret_val;
00178     }
00179 
00180     return ret_val;
00181 }
00182 
00192 void VideoSync::KeepPhase()
00193 {
00194 #if 0
00195     LOG(VB_GENERAL, LOG_DEBUG, QString("%1").arg(m_delay));
00196 #endif
00197     if (m_delay < -(m_refresh_interval/2))
00198         m_nexttrigger += 200;
00199     else if (m_delay > -500)
00200         m_nexttrigger += -2000;
00201 }
00202 
00203 #ifndef _WIN32
00204 #define DRM_VBLANK_RELATIVE 0x1;
00205 
00206 struct drm_wait_vblank_request {
00207     int type;
00208     unsigned int sequence;
00209     unsigned long signal;
00210 };
00211 
00212 struct drm_wait_vblank_reply {
00213     int type;
00214     unsigned int sequence;
00215     long tval_sec;
00216     long tval_usec;
00217 };
00218 
00219 typedef union drm_wait_vblank {
00220     struct drm_wait_vblank_request request;
00221     struct drm_wait_vblank_reply reply;
00222 } drm_wait_vblank_t;
00223 
00224 #define DRM_IOCTL_BASE                  'd'
00225 #define DRM_IOWR(nr,type)               _IOWR(DRM_IOCTL_BASE,nr,type)
00226 
00227 #define DRM_IOCTL_WAIT_VBLANK           DRM_IOWR(0x3a, drm_wait_vblank_t)
00228 
00229 static int drmWaitVBlank(int fd, drm_wait_vblank_t *vbl)
00230 {
00231     int ret = -1;
00232 
00233     do {
00234        ret = ioctl(fd, DRM_IOCTL_WAIT_VBLANK, vbl);
00235        vbl->request.type &= ~DRM_VBLANK_RELATIVE;
00236     } while (ret && errno == EINTR);
00237 
00238     return ret;
00239 }
00240 
00241 const char *DRMVideoSync::sm_dri_dev = "/dev/dri/card0";
00242 
00243 DRMVideoSync::DRMVideoSync(VideoOutput *vo, int fr, int ri, bool intl) :
00244     VideoSync(vo, fr, ri, intl)
00245 {
00246     m_dri_fd = -1;
00247 }
00248 
00249 DRMVideoSync::~DRMVideoSync()
00250 {
00251     if (m_dri_fd >= 0)
00252         close(m_dri_fd);
00253     m_dri_fd = -1;
00254 }
00255 
00256 bool DRMVideoSync::TryInit(void)
00257 {
00258     drm_wait_vblank_t blank;
00259 
00260     m_dri_fd = open(sm_dri_dev, O_RDWR);
00261     if (m_dri_fd < 0)
00262     {
00263         LOG(VB_PLAYBACK, LOG_INFO, LOC +
00264             QString("DRMVideoSync: Could not open device %1, %2")
00265                 .arg(sm_dri_dev).arg(strerror(errno)));
00266         return false; // couldn't open device
00267     }
00268 
00269     blank.request.type = DRM_VBLANK_RELATIVE;
00270     blank.request.sequence = 1;
00271     if (drmWaitVBlank(m_dri_fd, &blank))
00272     {
00273         LOG(VB_PLAYBACK, LOG_ERR, LOC +
00274             QString("DRMVideoSync: VBlank ioctl did not"
00275                     " work, unimplemented in this driver?"));
00276         return false; // VBLANK ioctl didn't worko
00277     }
00278 
00279     return true;
00280 }
00281 
00282 void DRMVideoSync::Start(void)
00283 {
00284     // Wait for a refresh so we start out synched
00285     drm_wait_vblank_t blank;
00286     blank.request.type = DRM_VBLANK_RELATIVE;
00287     blank.request.sequence = 1;
00288     drmWaitVBlank(m_dri_fd, &blank);
00289     VideoSync::Start();
00290 }
00291 
00292 int DRMVideoSync::WaitForFrame(int sync_delay)
00293 {
00294     // Offset for externally-provided A/V sync delay
00295     m_nexttrigger += sync_delay;
00296 
00297     m_delay = CalcDelay();
00298 #if 0
00299     LOG(VB_GENERAL, LOG_DEBUG, QString("WaitForFrame at : %1").arg(m_delay));
00300 #endif
00301 
00302     // Always sync to the next retrace execpt when we are very late.
00303     if (m_delay > -(m_refresh_interval/2))
00304     {
00305         drm_wait_vblank_t blank;
00306         blank.request.type = DRM_VBLANK_RELATIVE;
00307         blank.request.sequence = 1;
00308         drmWaitVBlank(m_dri_fd, &blank);
00309         m_delay = CalcDelay();
00310 #if 0
00311         LOG(VB_GENERAL, LOG_DEBUG, QString("Delay at sync: %1").arg(m_delay));
00312 #endif
00313     }
00314 
00315     if (m_delay > 0)
00316     {
00317         // Wait for any remaining retrace intervals in one pass.
00318         int n = (m_delay + m_refresh_interval - 1) / m_refresh_interval;
00319 
00320         drm_wait_vblank_t blank;
00321         blank.request.type = DRM_VBLANK_RELATIVE;
00322         blank.request.sequence = n;
00323         drmWaitVBlank(m_dri_fd, &blank);
00324         m_delay = CalcDelay();
00325 #if 0
00326         LOG(VB_GENERAL, LOG_DEBUG, 
00327             QString("Wait %1 intervals. Count %2 Delay %3")
00328                 .arg(n) .arg(blank.request.sequence) .arg(m_delay));
00329 #endif
00330     }
00331 
00332     return m_delay;
00333 }
00334 #endif /* !_WIN32 */
00335 
00336 #ifdef __linux__
00337 #define RTCRATE 1024
00338 RTCVideoSync::RTCVideoSync(VideoOutput *vo, int fi, int ri, bool intr) :
00339     VideoSync(vo, fi, ri, intr)
00340 {
00341     m_rtcfd = -1;
00342 }
00343 
00344 RTCVideoSync::~RTCVideoSync()
00345 {
00346     if (m_rtcfd >= 0)
00347         close(m_rtcfd);
00348 }
00349 
00350 bool RTCVideoSync::TryInit(void)
00351 {
00352     m_rtcfd = open("/dev/rtc", O_RDONLY);
00353     if (m_rtcfd < 0)
00354     {
00355         LOG(VB_PLAYBACK, LOG_ERR, LOC + 
00356             "RTCVideoSync: Could not open /dev/rtc: " + ENO);
00357         return false;
00358     }
00359 
00360     // FIXME, does it make sense to tie RTCRATE to the desired framerate?
00361     if ((ioctl(m_rtcfd, RTC_IRQP_SET, RTCRATE) < 0))
00362     {
00363         LOG(VB_PLAYBACK, LOG_ERR, LOC + 
00364             "RTCVideoSync: Could not set RTC frequency: " + ENO);
00365         return false;
00366     }
00367 
00368     if (ioctl(m_rtcfd, RTC_PIE_ON, 0) < 0)
00369     {
00370         LOG(VB_PLAYBACK, LOG_ERR, LOC + 
00371             "RTCVideoSync: Could not enable periodic timer interrupts: " + ENO);
00372         return false;
00373     }
00374 
00375     return true;
00376 }
00377 
00378 int RTCVideoSync::WaitForFrame(int sync_delay)
00379 {
00380     m_nexttrigger += sync_delay;
00381 
00382     m_delay = CalcDelay();
00383 
00384     unsigned long rtcdata;
00385     while (m_delay > 0)
00386     {
00387         ssize_t val = read(m_rtcfd, &rtcdata, sizeof(rtcdata));
00388         m_delay = CalcDelay();
00389 
00390         if ((val < 0) && (m_delay > 0))
00391             usleep(m_delay);
00392     }
00393     return 0;
00394 }
00395 #endif /* __linux__ */
00396 
00397 BusyWaitVideoSync::BusyWaitVideoSync(VideoOutput *vo,
00398                                      int fr, int ri, bool intl) :
00399     VideoSync(vo, fr, ri, intl)
00400 {
00401     m_cheat = 5000;
00402     m_fudge = 0;
00403 }
00404 
00405 BusyWaitVideoSync::~BusyWaitVideoSync()
00406 {
00407 }
00408 
00409 bool BusyWaitVideoSync::TryInit(void)
00410 {
00411     return true;
00412 }
00413 
00414 int BusyWaitVideoSync::WaitForFrame(int sync_delay)
00415 {
00416     // Offset for externally-provided A/V sync delay
00417     m_nexttrigger += sync_delay;
00418 
00419     m_delay = CalcDelay();
00420 
00421     if (m_delay > 0)
00422     {
00423         int cnt = 0;
00424         m_cheat += 100;
00425         // The usleep() is shortened by "cheat" so that this process gets
00426         // the CPU early for about half the frames.
00427         if (m_delay > (m_cheat - m_fudge))
00428             usleep(m_delay - (m_cheat - m_fudge));
00429 
00430         // If late, draw the frame ASAP.  If early, hold the CPU until
00431         // half as late as the previous frame (fudge).
00432         m_delay = CalcDelay();
00433         m_fudge = min(m_fudge, m_frame_interval);
00434         while (m_delay + m_fudge > 0)
00435         {
00436             m_delay = CalcDelay();
00437             cnt++;
00438         }
00439         m_fudge = abs(m_delay / 2);
00440         if (cnt > 1)
00441             m_cheat -= 200;
00442     }
00443     return 0;
00444 }
00445 
00446 USleepVideoSync::USleepVideoSync(VideoOutput *vo,
00447                                  int fr, int ri, bool intl) :
00448     VideoSync(vo, fr, ri, intl)
00449 {
00450 }
00451 
00452 USleepVideoSync::~USleepVideoSync()
00453 {
00454 }
00455 
00456 bool USleepVideoSync::TryInit(void)
00457 {
00458     return true;
00459 }
00460 
00461 int USleepVideoSync::WaitForFrame(int sync_delay)
00462 {
00463     // Offset for externally-provided A/V sync delay
00464     m_nexttrigger += sync_delay;
00465 
00466     m_delay = CalcDelay();
00467     if (m_delay > 0)
00468         usleep(m_delay);
00469     return 0;
00470 }
00471 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends