[vdr] RGB/PAL over VGA at variable frame rate

Thomas Hilber vdr at toh.cx
Tue Jul 22 18:37:05 CEST 2008


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

-------------- next part --------------
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 <sys/ioctl.h>
+#ifdef LENNY
+# include <drm/drm.h>
+#else
+# include <xf86drm.h>
+#endif
+#include <drm/radeon_drm.h>
+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,
-------------- next part --------------
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);
-------------- next part --------------
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)

-------------- next part --------------
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



More information about the vdr mailing list