From patchwork Tue Jul 22 16:37:05 2008 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: thomas X-Patchwork-Id: 12664 Received: from mail-in-14.arcor-online.net ([151.189.21.54]) by www.linuxtv.org with esmtp (Exim 4.63) (envelope-from ) id 1KLKrV-00079g-A5 for vdr@linuxtv.org; Tue, 22 Jul 2008 18:37:25 +0200 Received: from mail-in-05-z2.arcor-online.net (mail-in-05-z2.arcor-online.net [151.189.8.17]) by mail-in-14.arcor-online.net (Postfix) with ESMTP id 35A21187957 for ; Tue, 22 Jul 2008 18:37:11 +0200 (CEST) Received: from mail-in-17.arcor-online.net (mail-in-17.arcor-online.net [151.189.21.57]) by mail-in-05-z2.arcor-online.net (Postfix) with ESMTP id 127992DACCF for ; Tue, 22 Jul 2008 18:37:11 +0200 (CEST) Received: from chiricahua.toh.cx (dslb-084-056-011-011.pools.arcor-ip.net [84.56.11.11]) by mail-in-17.arcor-online.net (Postfix) with ESMTP id C37202BD15E for ; Tue, 22 Jul 2008 18:37:10 +0200 (CEST) Received: from roja.toh.cx ([192.168.144.196]) by chiricahua.toh.cx with esmtp (Exim 4.63) (envelope-from ) id 1KLKrO-0004qj-Cl for vdr@linuxtv.org; Tue, 22 Jul 2008 18:37:10 +0200 Received: from toh by roja.toh.cx with local (Exim 4.63) (envelope-from ) id 1KLKrJ-0004dM-2M for vdr@linuxtv.org; Tue, 22 Jul 2008 18:37:05 +0200 Date: Tue, 22 Jul 2008 18:37:05 +0200 From: Thomas Hilber To: VDR Mailing List Message-ID: <20080722163705.GA17041@roja.toh.cx> MIME-Version: 1.0 Content-Disposition: inline User-Agent: Mutt/1.5.13 (2006-08-11) X-Virus-Scanned: ClamAV 0.93.3/7779/Tue Jul 22 11:22:01 2008 on mail-in-17.arcor-online.net X-Virus-Status: Clean X-LSpam-Score: -2.6 (--) X-LSpam-Report: No, score=-2.6 required=5.0 tests=BAYES_00=-2.599 autolearn=ham Subject: [vdr] RGB/PAL over VGA at variable frame rate X-BeenThere: vdr@linuxtv.org X-Mailman-Version: 2.1.9 Precedence: list Reply-To: VDR Mailing List List-Id: VDR Mailing List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 22 Jul 2008 16:37:25 -0000 Status: O X-Status: X-Keywords: X-UID: 17329 Hi list, the last few days I made some interesting experiences with VGA cards I now want to share with you. goal ---- develop a budget card based VDR with PAL/RGB output and FF like output quality problem ------- as we all know current VGA graphics output quality suffers from certain limitations. Graphics cards known so far operate at a fixed frame rate not properly synchronized with the stream. Thus fields or even frames do often not appear the right time at the ouput. Some are doubled others are lost. Finally leading to more or less jerky playback. To a certain degree you can workaround this by software deinterlacing. At the cost of worse picture quality when playing interlaced material. Also CPU load is considerably increased by that. It appeared to be a privilege of so called full featured cards (expensive cards running proprietary firmware) to output true RGB PAL at variable framerate. Thus always providing full stream synchronicity. I've always been bothered by that and finally started to develop a few patches with the goal in mind to overcome these VGA graphics limitations. solution -------- graphics cards basically are not designed for variable frame rates. Once you have setup their timing you are not provided any means like registers to synchronize the frame rate with external timers. But that's exactly what's needed for signal output to stay in sync with the frame rate provided by xine-lib or other software decoders. To extend/reduce the overall time between vertical retrace I first dynamically added/removed a few scanlines to the modeline but with bad results. By doing so the picture was visibly jumping on the TV set. After some further experimenting I finally found a solution to fine adjust the frame rate of my elderly Radeon type card. This time without any bad side effects on the screen. Just trimming the length of a few scanlines during vertical retrace period does the trick. Then I tried to implement the new functionality by applying only minimum changes to my current VDR development system. Radeon DRM driver is perfectly suited for that. I just had to add a few lines of code there. I finally ended up in a small patch against Radeon DRM driver and a even smaller one against xine-lib. The last one also could take place directly in the Xserver. Please see attachments for code samples. When xine-lib calls PutImage() it checks whether to increase/decrease Xservers frame rate. This way after a short adaption phase xine-lib can place it's PutImage() calls right in the middle between 2 adjacent vertical blanking intervals. This provides maximum immunity against jitter. And even better: no more frames/fields are lost due to stream and graphics card frequency drift. Because we now cease from any deinterlacing we enjoy discontinuation of all its disadvantages: If driving a device with native interlaced input (e.g. a traditional TV Set or modern TFT with good RGB support) we have no deinterlacing artifacts anymore. Since softdecoders now are relieved of any CPU intensive deinterlacing we now can build cheap budget card based VDRs with slow CPUs. Please find attached 2 small patches showing you the basic idea and a description of my test environment. The project is far from complete but even at this early stage of development shows promising results. It should give you some rough ideas how to recycle your old hardware to a smoothly running budget VDR with high quality RGB video output. some suggestions what to do next: - detection of initial field parity - faster initial frame rate synchronisation after starting replay - remove some hard coded constants (special dependencies on my system's timing) Some more information about the project is also available here http://www.vdr-portal.de/board/thread.php?threadid=78480 Currently it's all based on Radeons but I'll try to also port it to other type of VGA cards. There will be some updates in the near future. stay tuned. -Thomas diff -ru drivers/char/drm.org/radeon_drm.h drivers/char/drm/radeon_drm.h --- drivers/char/drm.org/radeon_drm.h 2008-01-24 23:58:37.000000000 +0100 +++ drivers/char/drm/radeon_drm.h 2008-07-20 17:51:08.000000000 +0200 @@ -442,6 +442,8 @@ * KW: actually it's illegal to change any of this (backwards compatibility). */ +#define SYNC_FIELDS + /* Radeon specific ioctls * The device specific ioctl range is 0x40 to 0x79. */ @@ -473,6 +475,9 @@ #define DRM_RADEON_SETPARAM 0x19 #define DRM_RADEON_SURF_ALLOC 0x1a #define DRM_RADEON_SURF_FREE 0x1b +#ifdef SYNC_FIELDS +#define DRM_RADEON_VSYNC 0x1c +#endif #define DRM_IOCTL_RADEON_CP_INIT DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_CP_INIT, drm_radeon_init_t) #define DRM_IOCTL_RADEON_CP_START DRM_IO( DRM_COMMAND_BASE + DRM_RADEON_CP_START) @@ -501,6 +506,9 @@ #define DRM_IOCTL_RADEON_SETPARAM DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_SETPARAM, drm_radeon_setparam_t) #define DRM_IOCTL_RADEON_SURF_ALLOC DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_SURF_ALLOC, drm_radeon_surface_alloc_t) #define DRM_IOCTL_RADEON_SURF_FREE DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_SURF_FREE, drm_radeon_surface_free_t) +#ifdef SYNC_FIELDS +#define DRM_IOCTL_RADEON_VSYNC DRM_IOWR(DRM_COMMAND_BASE + DRM_RADEON_VSYNC, drm_radeon_vsync_t) +#endif typedef struct drm_radeon_init { enum { @@ -722,6 +730,19 @@ unsigned int address; } drm_radeon_surface_free_t; +#ifdef SYNC_FIELDS +typedef struct drm_radeon_vsync { + struct timeval vbl_now; /* time when this ioctl() has been called */ + struct timeval vbl_since; /* time since last vertical blank */ + unsigned vbl_received; /* continuously counting blanking intervals */ + unsigned vbl_trim; /* graphics card frame rate adjust */ +} drm_radeon_vsync_t; + +#define VBL_IGNORE 0x80000000 +#define VBL_SLOWER 0x40000000 + +#endif + #define DRM_RADEON_VBLANK_CRTC1 1 #define DRM_RADEON_VBLANK_CRTC2 2 diff -ru drivers/char/drm.org/radeon_drv.h drivers/char/drm/radeon_drv.h --- drivers/char/drm.org/radeon_drv.h 2008-01-24 23:58:37.000000000 +0100 +++ drivers/char/drm/radeon_drv.h 2008-07-20 16:07:50.000000000 +0200 @@ -294,6 +294,13 @@ /* starting from here on, data is preserved accross an open */ uint32_t flags; /* see radeon_chip_flags */ unsigned long fb_aper_offset; + +#ifdef SYNC_FIELDS + /* sync fields circuitry */ + struct timeval vbl_last; /* remember last vblank */ + u32 trim; +#endif + } drm_radeon_private_t; typedef struct drm_radeon_buf_priv { @@ -419,6 +426,9 @@ #define RADEON_CLOCK_CNTL_INDEX 0x0008 #define RADEON_CONFIG_APER_SIZE 0x0108 #define RADEON_CONFIG_MEMSIZE 0x00f8 +#ifdef SYNC_FIELDS +#define RADEON_CRTC_H_TOTAL_DISP 0x0200 +#endif #define RADEON_CRTC_OFFSET 0x0224 #define RADEON_CRTC_OFFSET_CNTL 0x0228 # define RADEON_CRTC_TILE_EN (1 << 15) diff -ru drivers/char/drm.org/radeon_irq.c drivers/char/drm/radeon_irq.c --- drivers/char/drm.org/radeon_irq.c 2008-01-24 23:58:37.000000000 +0100 +++ drivers/char/drm/radeon_irq.c 2008-07-20 20:08:06.000000000 +0200 @@ -102,6 +102,25 @@ (vblank_crtc & DRM_RADEON_VBLANK_CRTC2))) atomic_inc(&dev->vbl_received); +#ifdef SYNC_FIELDS + do_gettimeofday(&dev_priv->vbl_last); + + /* + * if requested we tamper with length of few + * horizontal lines here. + * + * don't try this at home:-) + */ + if (dev_priv->trim) { + int val = RADEON_READ(RADEON_CRTC_H_TOTAL_DISP); + int ooc = dev_priv->trim & 0xff; + + udelay(dev_priv->trim >> 16 & 0xff); + RADEON_WRITE(RADEON_CRTC_H_TOTAL_DISP, val + (dev_priv->trim & VBL_SLOWER ? ooc : -ooc)); + udelay(dev_priv->trim >> 8 & 0xff); + RADEON_WRITE(RADEON_CRTC_H_TOTAL_DISP, val); + } +#endif DRM_WAKEUP(&dev->vbl_queue); drm_vbl_send_signals(dev); } diff -ru drivers/char/drm.org/radeon_state.c drivers/char/drm/radeon_state.c --- drivers/char/drm.org/radeon_state.c 2008-01-24 23:58:37.000000000 +0100 +++ drivers/char/drm/radeon_state.c 2008-07-20 21:20:24.000000000 +0200 @@ -2100,6 +2100,32 @@ return 0; } +#ifdef SYNC_FIELDS +static int radeon_vsync(struct drm_device *dev, void *data, struct drm_file *file_priv) +{ + drm_radeon_private_t *dev_priv = dev->dev_private; + drm_radeon_vsync_t *vsyncp = (drm_radeon_vsync_t *)data; + + if (!(vsyncp->vbl_trim & VBL_IGNORE)) { + if (dev_priv->trim != vsyncp->vbl_trim) { +// printk(KERN_DEBUG "[drm] changed radeon drift trim from %x -> %x\n", dev_priv->trim, vsyncp->vbl_trim); + dev_priv->trim = vsyncp->vbl_trim; + } + } + do_gettimeofday(&vsyncp->vbl_now); + if (vsyncp->vbl_now.tv_usec < dev_priv->vbl_last.tv_usec) { + vsyncp->vbl_since.tv_sec = vsyncp->vbl_now.tv_sec - dev_priv->vbl_last.tv_sec - 1; + vsyncp->vbl_since.tv_usec = vsyncp->vbl_now.tv_usec - dev_priv->vbl_last.tv_usec + 1000000; + } else { + vsyncp->vbl_since.tv_sec = vsyncp->vbl_now.tv_sec - dev_priv->vbl_last.tv_sec; + vsyncp->vbl_since.tv_usec = vsyncp->vbl_now.tv_usec - dev_priv->vbl_last.tv_usec; + } + vsyncp->vbl_received = atomic_read(&dev->vbl_received); + vsyncp->vbl_trim = dev_priv->trim; + return 0; +} +#endif + static int radeon_cp_clear(struct drm_device *dev, void *data, struct drm_file *file_priv) { drm_radeon_private_t *dev_priv = dev->dev_private; @@ -3184,7 +3210,10 @@ DRM_IOCTL_DEF(DRM_RADEON_IRQ_WAIT, radeon_irq_wait, DRM_AUTH), DRM_IOCTL_DEF(DRM_RADEON_SETPARAM, radeon_cp_setparam, DRM_AUTH), DRM_IOCTL_DEF(DRM_RADEON_SURF_ALLOC, radeon_surface_alloc, DRM_AUTH), - DRM_IOCTL_DEF(DRM_RADEON_SURF_FREE, radeon_surface_free, DRM_AUTH) + DRM_IOCTL_DEF(DRM_RADEON_SURF_FREE, radeon_surface_free, DRM_AUTH), +#ifdef SYNC_FIELDS + DRM_IOCTL_DEF(DRM_RADEON_VSYNC, radeon_vsync, DRM_AUTH), +#endif }; int radeon_max_ioctl = DRM_ARRAY_SIZE(radeon_ioctls); sample hardware configuration: processor: Pentium III (Coppermine) 800MHz memory: 512MB SAT-budget card: TT-S1401 graphic: AGP Radeon 9200 SE (RV280) VGA-to-SCART RGB adapter like this: http://www.sput.nl/hardware/tv-x.html my current software configuration: - debian lenny - kernel 2.6.24-1-686 - xine-lib 1.1.8 - xineliboutput Version 1.0.0rc2 - yes, it could be a newer one:-) - xserver-xorg-video-radeon 1:6.9.0-1 important params in xineliboutput setup.conf: xineliboutput.Decoder.PesBuffers = 500 xineliboutput.DisplayAspect = automatic xineliboutput.Frontend = sxfe xineliboutput.Fullscreen = 0 xineliboutput.Modeline = xineliboutput.Video.AutoCrop = 0 xineliboutput.Video.Deinterlace = none xineliboutput.Video.Driver = xv xineliboutput.Video.FieldOrder = 0 xineliboutput.Video.Overscan = 0 xineliboutput.Video.Port = 0.0 xineliboutput.Video.Scale = 1 xineliboutput.X11.WindowHeight = 575 xineliboutput.X11.WindowWidth = 720 interlaced PAL modeline for xserver: Modeline "720x576i" 13.875 720 744 808 888 576 580 585 625 -HSync -Vsync interlace Option "ForceMinDotClock" "12MHz" (see other attachment for full xorg.conf description) Section "ServerLayout" Identifier "X.org Configured" Screen 0 "Screen0" 0 0 InputDevice "Mouse0" "CorePointer" InputDevice "Keyboard0" "CoreKeyboard" EndSection Section "Files" RgbPath "/etc/X11/rgb" ModulePath "/usr/lib/xorg/modules" FontPath "/usr/share/fonts/X11/misc" FontPath "/usr/share/fonts/X11/cyrillic" FontPath "/usr/share/fonts/X11/100dpi/:unscaled" FontPath "/usr/share/fonts/X11/75dpi/:unscaled" FontPath "/usr/share/fonts/X11/Type1" FontPath "/usr/share/fonts/X11/100dpi" FontPath "/usr/share/fonts/X11/75dpi" FontPath "/var/lib/defoma/x-ttcidfont-conf.d/dirs/TrueType" EndSection Section "Module" Load "GLcore" Load "record" Load "extmod" Load "dri" Load "xtrap" Load "glx" Load "dbe" EndSection Section "InputDevice" Identifier "Keyboard0" Driver "kbd" EndSection Section "InputDevice" Identifier "Mouse0" Driver "mouse" Option "Protocol" "auto" Option "Device" "/dev/input/mice" Option "ZAxisMapping" "4 5 6 7" EndSection Section "Monitor" Identifier "Monitor0" VendorName "Monitor Vendor" ModelName "Monitor Model" Modeline "720x576i" 13.875 720 744 808 888 576 580 585 625 -HSync -Vsync interlace EndSection Section "Device" Identifier "Card0" Driver "radeon" VendorName "ATI Technologies Inc" BoardName "Radeon 9100 IGP" Option "ForceMinDotClock" "12MHz" EndSection Section "Screen" Identifier "Screen0" Device "Card0" Monitor "Monitor0" DefaultDepth 24 SubSection "Display" Viewport 0 0 Depth 24 Modes "720x576i" EndSubSection EndSection diff -ru xine-lib.org/src/video_out/Makefile.am xine-lib/src/video_out/Makefile.am --- xine-lib.org/src/video_out/Makefile.am 2007-08-29 21:56:36.000000000 +0200 +++ xine-lib/src/video_out/Makefile.am 2008-07-11 16:29:26.000000000 +0200 @@ -116,7 +116,7 @@ xineplug_vo_out_xshm_la_CFLAGS = $(VISIBILITY_FLAG) $(X_CFLAGS) $(MLIB_CFLAGS) -fno-strict-aliasing xineplug_vo_out_xv_la_SOURCES = $(X11OSD) deinterlace.c video_out_xv.c -xineplug_vo_out_xv_la_LIBADD = $(XV_LIBS) $(X_LIBS) $(XINE_LIB) $(PTHREAD_LIBS) $(LTLIBINTL) +xineplug_vo_out_xv_la_LIBADD = $(XV_LIBS) $(X_LIBS) $(XINE_LIB) $(PTHREAD_LIBS) $(LTLIBINTL) -ldrm xineplug_vo_out_xv_la_CFLAGS = $(VISIBILITY_FLAG) $(X_CFLAGS) $(XV_CFLAGS) -fno-strict-aliasing xineplug_vo_out_xvmc_la_SOURCES = deinterlace.c video_out_xvmc.c diff -ru xine-lib.org/src/video_out/video_out_xv.c xine-lib/src/video_out/video_out_xv.c --- xine-lib.org/src/video_out/video_out_xv.c 2008-02-07 18:03:12.000000000 +0100 +++ xine-lib/src/video_out/video_out_xv.c 2008-07-20 21:12:08.000000000 +0200 @@ -73,6 +73,20 @@ #include "vo_scale.h" #include "x11osd.h" +#define SYNC_FIELDS + +#ifdef SYNC_FIELDS +#define LENNY +#include +#ifdef LENNY +# include +#else +# include +#endif +#include +extern int drmOpen(); +#endif + #define LOCK_DISPLAY(this) {if(this->lock_display) this->lock_display(this->user_data); \ else XLockDisplay(this->display);} #define UNLOCK_DISPLAY(this) {if(this->unlock_display) this->unlock_display(this->user_data); \ @@ -827,6 +841,103 @@ LOCK_DISPLAY(this); start_time = timeOfDay(); if (this->use_shm) { + +#ifdef SYNC_FIELDS + static int fd; + static drm_radeon_vsync_t vsync; + + if (!fd) { + drm_radeon_setparam_t vbl_activate; + + if ((fd = drmOpen("radeon", 0)) < 0) { + printf("drmOpen: %s\n", strerror(errno)); + } + vbl_activate.param = RADEON_SETPARAM_VBLANK_CRTC; + vbl_activate.value = DRM_RADEON_VBLANK_CRTC1; + if (ioctl(fd, DRM_IOCTL_RADEON_SETPARAM, &vbl_activate)) { + printf("DRM_IOCTL_RADEON_SETPARAM: %s\n", strerror(errno)); + } + } + if (ioctl(fd, DRM_IOCTL_RADEON_VSYNC, &vsync)) { + printf("DRM_IOCTL_RADEON_VSYNC: %s\n", strerror(errno)); + } + +/* + * here we continuously monitor and correct placement of xine-lib's + * xv_display_frame() call in relation to vertical blanking intervals (VBI) + * of graphics card. + * + * to achieve maximum immunity against jitter we always center the call + * to xv_display_frame() within the middle of 2 consecutive VBIs. + * + * there are no special hysteresis requirements: + * we even can choose another vbl_trim value each adjacent call + * + * theory of operation: + * + * - a vbl_trim value of 0 yields in a slower graphics card frame rate + * + * - thus increasing the time between 2 VBIs + * + * - as a result from xv_display_frame()-call point of view the + * time distance to the last VBI decreases + * + * - dependend on value of vsync.vbl_since.tv_usec (the elapsed + * time since last VBI) we decide whether to increase + * or to decrease graphics cards framerate. + * + * - illustration of how VBI of graphics card wanders into + * the center of xines xv_display_frame() calls, if graphics card framerate + * is a little slower than xine's call to xv_display_frame() + * + * xine reference clock (calls to xv_display_frame()): + * ______ ________ ________ ________ + * |________| |________| |________| |_______ + * ^ ^ + * | | + * graphics card clock (VBLANK edges): | + * _______| _________ _________| ________ + * |_________| |_________| |_________| |__ + * | | + * out of center centered + * | | + * | <--- edge drifts into the middle ---> | + * | of xine reference clock phase | + * + * some annotations: + * + * RGB PAL runs at 50Hz resulting in cycle duration of 20000us + * so we ideally would use a SYNC_POINT value of 10000 here. + * but we also have to consider some latency until Xserver finally + * executes putimage() + * + * FIX_ME! + * we currently abuse sync mechanism to avoid tearing when + * textured XV adaptor is selected. so we at the moment place SYNC_POINT + * close to the end of active display phase. + * + * VALUES HERE ARE QUICKLY HACKED AND ARE VALID FOR MY SYSTEM. + * YOU MUST ADAPT THEM TO YOUR NEEDS UNTIL AUTOMATIC + * TIMING TRIM ALGORITHM WILL BE IMPLEMENTED. + */ + +// tinajas sync collection +//#define SYNC_POINT 16500 +//#define VBL_TRIM_UPPER 0x00c84618 /* -351 */ +//#define VBL_TRIM_LOWER 0 /* +341 */ + +// senitas sync collection +#define SYNC_POINT 15000 +#define VBL_TRIM_UPPER 0x00c84605 /* -234 */ +#define VBL_TRIM_LOWER 0x40c8460b /* +228 */ + + if (vsync.vbl_since.tv_usec < SYNC_POINT) { + vsync.vbl_trim = VBL_TRIM_UPPER; + } else { + vsync.vbl_trim = VBL_TRIM_LOWER; + } +#endif + XvShmPutImage(this->display, this->xv_port, this->drawable, this->gc, this->cur_frame->image, this->sc.displayed_xoffset, this->sc.displayed_yoffset,