[PATCH] Support for H.264 encoded framebuffer updates



This patch implements support in gtk-vnc for framebuffer updates
encoded as H.264 frames. Decoding is performed using VA API.

This patch is meant to be used with corresponding server-side
support, as provided by a patch for qemu ui/vnc module:
see http://lists.nongnu.org/archive/html/qemu-devel/2013-01/msg01388.html

This feature is enabled through the --with-libva configure option.

With this H.264-based encoding, if multiple framebuffer update messages
are generated for a single server framebuffer modification, the H.264
frame data is sent only with the first update message. Subsequent update
framebuffer messages will contain only the coordinates and size of the
additional updated regions.

Whenever possible, the implementation attempts to bypass updating the
local framebuffer and instead lets the GPU directly put the decoded
picture onto the screen, which provides a significant advantage in terms
of resource usage and latency. In case the Gtk widget does not support
an underlying native window, the decoded picture is retrieved from
the GPU and used to update the local framebuffer, like other VNC
encodings do.

Signed-off-by: David Verbeiren <david verbeiren intel com>
---
 configure.ac            |   17 ++
 examples/gvncviewer.c   |    1 +
 src/Makefile.am         |    4 +-
 src/libgvnc_sym.version |    2 +
 src/vncconnection.c     |   50 +++
 src/vncconnection.h     |    3 +
 src/vncdisplay.c        |  126 +++++---
 src/vncencoding_h264.c  |  768 +++++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 923 insertions(+), 48 deletions(-)
 create mode 100644 src/vncencoding_h264.c

diff --git a/configure.ac b/configure.ac
index 22402e1..4a4d5a2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -204,6 +204,23 @@ AC_SUBST(CAIRO_CFLAGS)
 AC_SUBST(CAIRO_LIBS)
 
 
+AC_ARG_WITH(libva,
+[  --with-libva            enable libva support (VA H.264 encoding)],
+[case "${withval}" in
+   yes|no) ;;
+   *)      AC_MSG_ERROR([bad value ${withval} for libva option]) ;;
+ esac],[withval=no])
+
+WITH_LIBVA=0
+if test "${withval}" = "yes"; then
+  PKG_CHECK_MODULES(VA, [libva x11 libva-x11],
+           [WITH_LIBVA=1], [WITH_LIBVA=0])
+fi
+AC_DEFINE_UNQUOTED([WITH_LIBVA],[$WITH_LIBVA], [Whether to use libva])
+AC_SUBST(VA_CFLAGS)
+AC_SUBST(VA_LIBS)
+
+
 AC_ARG_WITH(libview,
 [  --with-libview          enable libview support in gvncviewer],
 [case "${withval}" in
diff --git a/examples/gvncviewer.c b/examples/gvncviewer.c
index 1cececf..6f7b6df 100644
--- a/examples/gvncviewer.c
+++ b/examples/gvncviewer.c
@@ -27,6 +27,7 @@
 #endif
 #include <gtk/gtk.h>
 #include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
 #include <stdlib.h>
 #include <string.h>
 #include <glib.h>
diff --git a/src/Makefile.am b/src/Makefile.am
index 102a2e9..b35653a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -37,7 +37,8 @@ libgvnc_1_0_la_LIBADD = \
 			$(GTHREAD_LIBS) \
 			$(GDK_PIXBUF_LIBS) \
 			$(GNUTLS_LIBS) \
-			$(SASL_LIBS)
+			$(SASL_LIBS) \
+			$(VA_LIBS)
 libgvnc_1_0_la_CFLAGS = \
 			$(GOBJECT_CFLAGS) \
 			$(GIO_CFLAGS) \
@@ -45,6 +46,7 @@ libgvnc_1_0_la_CFLAGS = \
 			$(GDK_PIXBUF_CFLAGS) \
 			$(GNUTLS_CFLAGS) \
 			$(SASL_CFLAGS) \
+			$(VA_CFLAGS) \
 			$(WARN_CFLAGS) \
 			-DSYSCONFDIR=\""$(sysconfdir)"\" \
 			-DPACKAGE_LOCALE_DIR=\""$(datadir)/locale"\" \
diff --git a/src/libgvnc_sym.version b/src/libgvnc_sym.version
index 9adec25..fa9faaf 100644
--- a/src/libgvnc_sym.version
+++ b/src/libgvnc_sym.version
@@ -99,6 +99,8 @@
 	vnc_pixel_format_free;
 	vnc_pixel_format_get_type;
 
+	vnc_connection_set_native_window;
+
     local:
 	*;
 };
diff --git a/src/vncconnection.c b/src/vncconnection.c
index 4b25a96..58116e2 100644
--- a/src/vncconnection.c
+++ b/src/vncconnection.c
@@ -140,6 +140,9 @@ struct g_condition_wait_source
 #define VNC_CONNECTION_GET_PRIVATE(obj)                                 \
     (G_TYPE_INSTANCE_GET_PRIVATE((obj), VNC_TYPE_CONNECTION, VncConnectionPrivate))
 
+#ifdef WITH_LIBVA
+typedef struct h264_t h264_t;
+#endif
 
 struct _VncConnectionPrivate
 {
@@ -235,6 +238,11 @@ struct _VncConnectionPrivate
     VncAudio *audio;
     VncAudioSample *audio_sample;
     guint audio_timer;
+
+#if WITH_LIBVA
+    struct h264_t *h264;
+#endif
+    gulong native_window;
 };
 
 G_DEFINE_TYPE(VncConnection, vnc_connection, G_TYPE_OBJECT);
@@ -400,6 +408,7 @@ static gboolean g_condition_wait(g_condition_wait_func func, gpointer data)
 enum {
     PROP_0,
     PROP_FRAMEBUFFER,
+    PROP_NATIVE_WINDOW,
 };
 
 
@@ -415,6 +424,9 @@ static void vnc_connection_get_property(GObject *object,
     case PROP_FRAMEBUFFER:
         g_value_set_object(value, priv->fb);
         break;
+    case PROP_NATIVE_WINDOW:
+        g_value_set_ulong(value, priv->native_window);
+        break;
 
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
@@ -432,6 +444,9 @@ static void vnc_connection_set_property(GObject *object,
     case PROP_FRAMEBUFFER:
         vnc_connection_set_framebuffer(conn, g_value_get_object(value));
         break;
+    case PROP_NATIVE_WINDOW:
+        vnc_connection_set_native_window(conn, g_value_get_ulong (value));
+        break;
 
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
@@ -2617,6 +2632,9 @@ static void vnc_connection_update(VncConnection *conn, int x, int y, int width,
     vnc_connection_emit_main_context(conn, VNC_FRAMEBUFFER_UPDATE, &sigdata);
 }
 
+#if WITH_LIBVA
+#include "vncencoding_h264.c"
+#endif
 
 static void vnc_connection_bell(VncConnection *conn)
 {
@@ -2877,6 +2895,17 @@ static gboolean vnc_connection_framebuffer_update(VncConnection *conn, gint32 et
         vnc_connection_tight_update(conn, x, y, width, height);
         vnc_connection_update(conn, x, y, width, height);
         break;
+#if WITH_LIBVA
+    case VNC_CONNECTION_ENCODING_VA_H264:
+        if (!vnc_connection_validate_boundary(conn, x, y, width, height))
+            break;
+        vnc_connection_h264_update(conn, x, y, width, height);
+        if (priv->native_window)
+        	vnc_connection_update(conn, x, y, 0, 0); /* Direct rendering was used */
+        else
+        	vnc_connection_update(conn, x, y, width, height);
+        break;
+#endif
     case VNC_CONNECTION_ENCODING_DESKTOP_RESIZE:
         vnc_connection_resize(conn, width, height);
         break;
@@ -4529,6 +4558,20 @@ static void vnc_connection_class_init(VncConnectionClass *klass)
                                                         G_PARAM_STATIC_NICK |
                                                         G_PARAM_STATIC_BLURB));
 
+    g_object_class_install_property (object_class,
+                                     PROP_NATIVE_WINDOW,
+                                     g_param_spec_ulong( "native-window",
+                                                         "Native window ID",
+                                                         "The native (e.g. X11) Window",
+                                                         0,
+                                                         G_MAXULONG,
+                                                         0,
+                                                         G_PARAM_READWRITE |
+                                                         G_PARAM_CONSTRUCT |
+                                                         G_PARAM_STATIC_NAME |
+                                                         G_PARAM_STATIC_NICK |
+                                                         G_PARAM_STATIC_BLURB));
+
     signals[VNC_CURSOR_CHANGED] =
         g_signal_new ("vnc-cursor-changed",
                       G_OBJECT_CLASS_TYPE (object_class),
@@ -5444,6 +5487,13 @@ gboolean vnc_connection_get_abs_pointer(VncConnection *conn)
     return priv->absPointer;
 }
 
+void vnc_connection_set_native_window(VncConnection *conn, gulong native_window)
+{
+    VncConnectionPrivate *priv = conn->priv;
+
+    priv->native_window = native_window;
+}
+
 /*
  * Local variables:
  *  c-indent-level: 4
diff --git a/src/vncconnection.h b/src/vncconnection.h
index cbfefd7..2e691ac 100644
--- a/src/vncconnection.h
+++ b/src/vncconnection.h
@@ -90,6 +90,7 @@ typedef enum {
     VNC_CONNECTION_ENCODING_HEXTILE = 5,
     VNC_CONNECTION_ENCODING_TIGHT = 7,
     VNC_CONNECTION_ENCODING_ZRLE = 16,
+    VNC_CONNECTION_ENCODING_VA_H264 = 0x48323634,
 
     /* Tight JPEG quality levels */
     VNC_CONNECTION_ENCODING_TIGHT_JPEG0 = -32,
@@ -214,6 +215,8 @@ const VncAudioFormat *vnc_connection_get_audio_format(VncConnection *conn);
 gboolean vnc_connection_audio_enable(VncConnection *conn);
 gboolean vnc_connection_audio_disable(VncConnection *conn);
 
+void vnc_connection_set_native_window(VncConnection *conn,
+                                      gulong native_window);
 
 G_END_DECLS
 
diff --git a/src/vncdisplay.c b/src/vncdisplay.c
index 14df066..9014b3b 100644
--- a/src/vncdisplay.c
+++ b/src/vncdisplay.c
@@ -40,6 +40,10 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
+#if WITH_LIBVA
+#include <gdk/gdkx.h>
+#endif
+
 #define VNC_DISPLAY_GET_PRIVATE(obj)                                    \
     (G_TYPE_INSTANCE_GET_PRIVATE((obj), VNC_TYPE_DISPLAY, VncDisplayPrivate))
 
@@ -74,6 +78,7 @@ struct _VncDisplayPrivate
     gboolean allow_scaling;
     gboolean shared_flag;
     gboolean force_size;
+    gboolean direct_render;
 
     GSList *preferable_auths;
     GSList *preferable_vencrypt_subauths;
@@ -973,64 +978,66 @@ static void on_framebuffer_update(VncConnection *conn G_GNUC_UNUSED,
     int ww, wh;
     int fbw, fbh;
 
-    fbw = vnc_framebuffer_get_width(VNC_FRAMEBUFFER(priv->fb));
-    fbh = vnc_framebuffer_get_height(VNC_FRAMEBUFFER(priv->fb));
+    if (w && h) {
+        fbw = vnc_framebuffer_get_width(VNC_FRAMEBUFFER(priv->fb));
+        fbh = vnc_framebuffer_get_height(VNC_FRAMEBUFFER(priv->fb));
 
-    gdk_drawable_get_size(gtk_widget_get_window(widget), &ww, &wh);
+        gdk_drawable_get_size(gtk_widget_get_window(widget), &ww, &wh);
 
-    /* If we have a pixmap, update the region which changed.
-     * If we don't have a pixmap, the entire thing will be
-     * created & rendered during the drawing handler
-     */
-    if (priv->fbCache) {
-        cairo_t *cr = cairo_create(priv->fbCache);
-        cairo_surface_t *surface = vnc_cairo_framebuffer_get_surface(priv->fb);
+        /* If we have a pixmap, update the region which changed.
+         * If we don't have a pixmap, the entire thing will be
+         * created & rendered during the drawing handler
+         */
+        if (priv->fbCache) {
+            cairo_t *cr = cairo_create(priv->fbCache);
+            cairo_surface_t *surface = vnc_cairo_framebuffer_get_surface(priv->fb);
 
-        cairo_rectangle(cr, x, y, w, h);
-        cairo_clip(cr);
-        cairo_set_source_surface(cr, surface, 0, 0);
-        cairo_paint(cr);
+            cairo_rectangle(cr, x, y, w, h);
+            cairo_clip(cr);
+            cairo_set_source_surface(cr, surface, 0, 0);
+            cairo_paint(cr);
 
-        cairo_destroy(cr);
-    }
+            cairo_destroy(cr);
+        }
 
-    if (priv->allow_scaling) {
-        double sx, sy;
+        if (priv->allow_scaling) {
+            double sx, sy;
 
-        /* Scale the VNC region to produce expose region */
+            /* Scale the VNC region to produce expose region */
+
+            sx = (double)ww / (double)fbw;
+            sy = (double)wh / (double)fbh;
 
-        sx = (double)ww / (double)fbw;
-        sy = (double)wh / (double)fbh;
+            x *= sx;
+            y *= sy;
+            w *= sx;
+            h *= sy;
 
-        x *= sx;
-        y *= sy;
-        w *= sx;
-        h *= sy;
+            /* Without this, we get horizontal & vertical line artifacts
+             * when drawing. This "fix" is somewhat dubious though. The
+             * true mistake & fix almost certainly lies elsewhere.
+             */
+            x -= 2;
+            y -= 2;
+            w += 4;
+            h += 4;
+        } else {
+            int mw = 0, mh = 0;
 
-        /* Without this, we get horizontal & vertical line artifacts
-         * when drawing. This "fix" is somewhat dubious though. The
-         * true mistake & fix almost certainly lies elsewhere.
-         */
-        x -= 2;
-        y -= 2;
-        w += 4;
-        h += 4;
-    } else {
-        int mw = 0, mh = 0;
+            /* Offset the VNC region to produce expose region */
 
-        /* Offset the VNC region to produce expose region */
+            if (ww > fbw)
+                mw = (ww - fbw) / 2;
+            if (wh > fbh)
+                mh = (wh - fbh) / 2;
 
-        if (ww > fbw)
-            mw = (ww - fbw) / 2;
-        if (wh > fbh)
-            mh = (wh - fbh) / 2;
+            x += mw;
+            y += mh;
+        }
 
-        x += mw;
-        y += mh;
+        gtk_widget_queue_draw_area(widget, x, y, w, h);
     }
 
-    gtk_widget_queue_draw_area(widget, x, y, w, h);
-
     vnc_connection_framebuffer_update_request(priv->conn, 1,
                                               0, 0,
                                               vnc_connection_get_width(priv->conn),
@@ -1056,6 +1063,27 @@ static void do_framebuffer_init(VncDisplay *obj,
         priv->fbCache = NULL;
     }
 
+#if WITH_LIBVA
+    /* When H.264 decoding with libva is used, it is much more efficient
+     * to instruct the GPU to blit the decoded buffer directly onto the screen
+     * (i.e. vaPutSurface) than to read it from the GPU to update the local
+     * framebuffer. However this requires a client native window id, so
+     * we try to ensure we have one:
+     */
+    GtkWidget *widget = GTK_WIDGET(obj);
+    GdkWindow* win = gtk_widget_get_window(widget);
+    if (gdk_window_ensure_native(win)) {
+    	unsigned long xid = GDK_WINDOW_XID(widget->window);
+    	VNC_DEBUG("X11 Window XID %lu", xid);
+        vnc_connection_set_native_window(priv->conn, xid);
+        priv->direct_render = TRUE;
+        //priv->local_pointer = TRUE; // Force local pointer
+    }
+    else {
+    	VNC_DEBUG("Failed to ensure_native() window!");
+    }
+#endif
+
     if (priv->null_cursor == NULL) {
         priv->null_cursor = create_null_cursor();
         if (priv->local_pointer)
@@ -1064,8 +1092,8 @@ static void do_framebuffer_init(VncDisplay *obj,
             do_pointer_hide(obj);
     }
 
-    priv->fb = vnc_cairo_framebuffer_new(width, height, remoteFormat);
-    vnc_connection_set_framebuffer(priv->conn, VNC_FRAMEBUFFER(priv->fb));
+	priv->fb = vnc_cairo_framebuffer_new(width, height, remoteFormat);
+	vnc_connection_set_framebuffer(priv->conn, VNC_FRAMEBUFFER(priv->fb));
 
     if (priv->force_size)
         gtk_widget_set_size_request(GTK_WIDGET(obj), width, height);
@@ -1426,7 +1454,11 @@ static void on_initialized(VncConnection *conn G_GNUC_UNUSED,
 
     /* The order determines which encodings the
      * server prefers when it has a choice to use */
-    gint32 encodings[] = {  VNC_CONNECTION_ENCODING_TIGHT_JPEG5,
+    gint32 encodings[] = {
+#if WITH_LIBVA
+                            VNC_CONNECTION_ENCODING_VA_H264,
+#endif
+                            VNC_CONNECTION_ENCODING_TIGHT_JPEG5,
                             VNC_CONNECTION_ENCODING_TIGHT,
                             VNC_CONNECTION_ENCODING_EXT_KEY_EVENT,
                             VNC_CONNECTION_ENCODING_DESKTOP_RESIZE,
diff --git a/src/vncencoding_h264.c b/src/vncencoding_h264.c
new file mode 100644
index 0000000..66b6308
--- /dev/null
+++ b/src/vncencoding_h264.c
@@ -0,0 +1,768 @@
+/*
+ * GTK VNC Widget - VA H.264 encoding
+ *
+ * Copyright (c) 2012-2013 Intel Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.0 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#include <va/va.h>
+#include <va/va_x11.h>
+
+#include <stdint.h>     /* for uint*_t types */
+
+/* Forward declarations */
+static void h264_decode_frame(h264_t* h264, int f_width, int f_height,
+                              guint8 *framedata, int framesize, int slice_type);
+static void h264_init_decoder(h264_t** pH264, int width, int height);
+static void h264_destroy_decoder(h264_t* h264);
+
+static void SetVAPictureParameterBufferH264(VAPictureParameterBufferH264 *p,
+                                            int width, int height);
+static void SetVASliceParameterBufferH264(VASliceParameterBufferH264 *p);
+static void SetVASliceParameterBufferH264_T2(VASliceParameterBufferH264 *p,
+                                             int first);
+static void h264_put_rectangle(VncConnectionPrivate *priv, int x, int y,
+                               int width, int height, int f_width, int f_height,
+                               int first_for_frame);
+static void nv12_to_rgba(VncConnectionPrivate *priv, const VAImage vaImage,
+                         int ch_x, int ch_y, int ch_w, int ch_h);
+
+
+#define SURFACE_NUM     3
+
+typedef struct h264_t {
+    VADisplay       va_dpy;
+    VAConfigID      va_config_id;
+    VAContextID     va_context_id;
+    VASurfaceID     va_surface_id[SURFACE_NUM];
+
+    VABufferID      va_pic_param_buf_id[SURFACE_NUM];
+    VABufferID      va_mat_param_buf_id[SURFACE_NUM];
+    VABufferID      va_sp_param_buf_id[SURFACE_NUM];
+    VABufferID      va_d_param_buf_id[SURFACE_NUM];
+
+    int             sid;            /* current surface index */
+    VASurfaceID     curr_surface;   /* current surface ID */
+
+    unsigned int    framecount;     /* frames decoded by the current pipeline */
+    unsigned int    frame_id;       /* identifier for current frame */
+    int             field_order_count;
+
+    int             cur_width;
+    int             cur_height;
+
+    VAPictureH264   curr_pic;
+    VAPictureH264   old_pic;
+    int             t2_first;
+
+    guint8         *rgb_buf;
+    unsigned int    rgb_buf_sz;
+
+    guint8         *compressed_buf;
+    unsigned int    compressed_buf_sz;
+} h264_t;
+
+
+#define CHECK_VASTATUS(va_status,func)                                                 \
+    if (va_status != VA_STATUS_SUCCESS) {                                              \
+        /*fprintf(stderr,"%s:%s (%d) failed,exit\n", __func__, func, __LINE__);*/      \
+        VNC_DEBUG("%s:%s:%d failed (0x%x),exit", __func__, func, __LINE__, va_status); \
+        exit(1);                                                                       \
+    } else  {                                                                          \
+        /*fprintf(stderr,">> SUCCESS for: %s:%s (%d)\n", __func__, func, __LINE__);*/  \
+        VNC_DEBUG("%s:%s:%d success", __func__, func, __LINE__);                       \
+    }
+
+
+static inline void CHECK_SURF(h264_t* h264, VASurfaceID surf_id) {
+    VAStatus va_status;
+    VASurfaceStatus surface_status;
+    va_status = vaQuerySurfaceStatus(h264->va_dpy, surf_id, &surface_status);
+    if (va_status != VA_STATUS_SUCCESS) {
+        VNC_DEBUG("Failed to query surface status (0x%x)", va_status);
+    }
+    if (surface_status != 4) {
+        VNC_DEBUG("Surface status: %d\n", surface_status);
+    }
+}
+
+static void vnc_connection_h264_update(VncConnection *conn,
+                                       guint16 x, guint16 y,
+                                       guint16 width, guint16 height)
+{
+    VNC_DEBUG("Update (%d-%d),(%d-%d)", x, y, width, height);
+
+    VncConnectionPrivate *priv = conn->priv;
+    h264_t* h264 = priv->h264;
+
+    /* The header for a H.264 encoded framebuffer update.
+     * The encoded data (nBytes bytes) follows immediately.
+     */
+    struct {
+        guint32 nBytes;
+        guint32 slice_type;
+        guint32 width;
+        guint32 height;
+    } hdr;
+
+    hdr.nBytes     = vnc_connection_read_u32(conn);
+    hdr.slice_type = vnc_connection_read_u32(conn);
+    hdr.width      = vnc_connection_read_u32(conn);
+    hdr.height     = vnc_connection_read_u32(conn);
+
+    VNC_DEBUG("H.264 header: %d bytes, slice_type: %d, width: %d, height: %d",
+              hdr.nBytes, hdr.slice_type, hdr.width, hdr.height);
+
+    /* Initialize decode pipeline if necessary */
+    if ( (h264 == NULL) ||
+         (hdr.width > h264->cur_width) || (hdr.height > h264->cur_height) ) {
+        if ((h264 != NULL) && (h264->va_dpy != NULL)) {
+            h264_destroy_decoder(h264);
+        }
+
+        h264_init_decoder(&h264, hdr.width, hdr.height);
+        priv->h264 = h264;
+
+        VNC_DEBUG("Decoder (re)initialized");
+    }
+
+    if ( (h264->compressed_buf == NULL) ||
+         (hdr.nBytes > h264->compressed_buf_sz) ) {
+        h264->compressed_buf = (guint8*) realloc(h264->compressed_buf, hdr.nBytes);
+        h264->compressed_buf_sz = hdr.nBytes;
+        if (h264->compressed_buf == NULL) {
+            fprintf(stderr, "Failed to (re)alloc input buffer of %d bytes\n",
+                    hdr.nBytes);
+            return;
+        }
+    }
+    if (vnc_connection_read(conn, h264->compressed_buf, hdr.nBytes)) {
+        VNC_DEBUG("Error reading H.264 data");
+        return;
+    }
+    VNC_DEBUG("Frame data received");
+
+    /* Decode frame if compressed data is sent. Frame data is only sent for
+     * the first framebuffer update message for particular framebuffer
+     * contents.
+     * If multiple rectangles are updated, the messages after the first one
+     * have nBytes == 0.
+     */
+    if (hdr.nBytes > 0) {
+        VNC_DEBUG("Decoding H.264 frame");
+        h264_decode_frame(priv->h264, hdr.width, hdr.height, h264->compressed_buf,
+                          hdr.nBytes, hdr.slice_type);
+    }
+
+    h264_put_rectangle(priv, x, y, width, height, hdr.width, hdr.height, hdr.nBytes != 0);
+}
+
+static void h264_decode_frame(h264_t* h264, int f_width, int f_height,
+                              guint8 *framedata, int framesize, int slice_type)
+{
+    VAStatus va_status;
+    char *slice_data_buf;
+    VAPictureParameterBufferH264 *pic_param_buf = NULL;
+    VAIQMatrixBufferH264 *iq_matrix_buf = NULL;
+    VABufferID buffer_ids[2];
+    VASliceParameterBufferH264 *slice_param_buf = NULL;
+    int sid_new;
+
+    /* The server should always send an I-frame when a new client connects
+     * or when the resolution of the framebuffer changes, but we check
+     * just in case.
+     */
+    if ((h264->framecount == 0) && (slice_type != 2)) {
+        VNC_DEBUG("Need an I-frame, got something different. Skipping!");
+        return;
+    }
+
+    VNC_DEBUG("frame_id: %d; va_surface_id[%d]: 0x%x; field_order_count: %d",
+            h264->frame_id, h264->sid,
+            h264->va_surface_id[h264->sid], h264->field_order_count);
+
+    h264->curr_pic.picture_id = h264->va_surface_id[h264->sid];
+    h264->curr_pic.frame_idx = h264->frame_id;
+    h264->curr_pic.flags = 0;
+    h264->curr_pic.BottomFieldOrderCnt = h264->field_order_count;
+    h264->curr_pic.TopFieldOrderCnt = h264->field_order_count;
+
+    /* Setup picture parameter buffer */
+    if (h264->va_pic_param_buf_id[h264->sid] == VA_INVALID_ID) {
+        va_status = vaCreateBuffer(h264->va_dpy, h264->va_context_id,
+                VAPictureParameterBufferType,
+                sizeof(VAPictureParameterBufferH264),
+                1, NULL,
+                &h264->va_pic_param_buf_id[h264->sid]);
+        CHECK_VASTATUS(va_status, "vaCreateBuffer(PicParam)");
+    }
+    CHECK_SURF(h264, h264->va_surface_id[h264->sid]);
+
+    va_status = vaMapBuffer(h264->va_dpy, h264->va_pic_param_buf_id[h264->sid],
+                            (void **)&pic_param_buf);
+    CHECK_VASTATUS(va_status, "vaMapBuffer(PicParam)");
+
+    SetVAPictureParameterBufferH264(pic_param_buf, f_width, f_height);
+    memcpy(&pic_param_buf->CurrPic, &h264->curr_pic, sizeof(VAPictureH264));
+
+    if (slice_type != 2) {
+        memcpy(&pic_param_buf->ReferenceFrames[0], &h264->old_pic, sizeof(VAPictureH264));
+        pic_param_buf->ReferenceFrames[0].flags = 0;
+    }
+    pic_param_buf->frame_num = h264->frame_id;
+
+    va_status = vaUnmapBuffer(h264->va_dpy, h264->va_pic_param_buf_id[h264->sid]);
+    CHECK_VASTATUS(va_status, "vaUnmapBuffer(PicParam)");
+
+    /* Set up IQ matrix buffer */
+    if (h264->va_mat_param_buf_id[h264->sid] == VA_INVALID_ID) {
+        va_status = vaCreateBuffer(h264->va_dpy, h264->va_context_id,
+                VAIQMatrixBufferType, sizeof(VAIQMatrixBufferH264),
+                1, NULL,
+                &h264->va_mat_param_buf_id[h264->sid]);
+        CHECK_VASTATUS(va_status, "vaCreateBuffer(IQMatrix)");
+    }
+    CHECK_SURF(h264, h264->va_surface_id[h264->sid]);
+
+    va_status = vaMapBuffer(h264->va_dpy, h264->va_mat_param_buf_id[h264->sid],
+                            (void **)&iq_matrix_buf);
+    CHECK_VASTATUS(va_status, "vaMapBuffer(IQMatrix)");
+
+    const unsigned char _MatrixBufferH264[]= {
+        /* ScalingList4x4[6][16] */
+        0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+        0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+        0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+        0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+        0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+        0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,
+        /* ScalingList8x8[2][64] */
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+    };
+
+    memcpy(iq_matrix_buf, _MatrixBufferH264, 224);
+    va_status = vaUnmapBuffer(h264->va_dpy, h264->va_mat_param_buf_id[h264->sid]);
+    CHECK_VASTATUS(va_status, "vaUnmapBuffer(IQMatrix)");
+
+    buffer_ids[0] = h264->va_pic_param_buf_id[h264->sid];
+    buffer_ids[1] = h264->va_mat_param_buf_id[h264->sid];
+
+    CHECK_SURF(h264, h264->va_surface_id[h264->sid]);
+    va_status = vaRenderPicture(h264->va_dpy, h264->va_context_id, buffer_ids, 2);
+    CHECK_VASTATUS(va_status, "vaRenderPicture");
+
+    /* Set up slice parameter buffer */
+    if (h264->va_sp_param_buf_id[h264->sid] == VA_INVALID_ID) {
+        va_status = vaCreateBuffer(h264->va_dpy, h264->va_context_id,
+                VASliceParameterBufferType,
+                sizeof(VASliceParameterBufferH264),
+                1, NULL,
+                &h264->va_sp_param_buf_id[h264->sid]);
+        CHECK_VASTATUS(va_status, "vaCreateBuffer(SliceParam)");
+    }
+    CHECK_SURF(h264, h264->va_surface_id[h264->sid]);
+
+    va_status = vaMapBuffer(h264->va_dpy, h264->va_sp_param_buf_id[h264->sid],
+                            (void **)&slice_param_buf);
+    CHECK_VASTATUS(va_status, "vaMapBuffer(SliceParam)");
+
+    if (slice_type == 2) {
+        SetVASliceParameterBufferH264_T2(slice_param_buf, h264->t2_first);
+        h264->t2_first = 0;
+    }
+    else {
+        SetVASliceParameterBufferH264(slice_param_buf);
+        memcpy(&slice_param_buf->RefPicList0[0], &h264->old_pic, sizeof(VAPictureH264));
+        slice_param_buf->RefPicList0[0].flags = 0;
+    }
+    slice_param_buf->slice_data_bit_offset = 0;
+    slice_param_buf->slice_data_size = framesize;
+
+    va_status = vaUnmapBuffer(h264->va_dpy, h264->va_sp_param_buf_id[h264->sid]);
+    CHECK_VASTATUS(va_status, "vaUnmapBuffer(SliceParam)");
+    CHECK_SURF(h264, h264->va_surface_id[h264->sid]);
+
+    /* Set up slice data buffer and copy H.264 encoded data */
+    if (h264->va_d_param_buf_id[h264->sid] == VA_INVALID_ID) {
+        /* TODO use correct size instead of this large value */
+        va_status = vaCreateBuffer(h264->va_dpy, h264->va_context_id,
+                VASliceDataBufferType,
+                4177920 /* 1080p size */,
+                1, NULL,
+                &h264->va_d_param_buf_id[h264->sid]);
+        CHECK_VASTATUS(va_status, "vaCreateBuffer(SliceData)");
+    }
+
+    va_status = vaMapBuffer(h264->va_dpy, h264->va_d_param_buf_id[h264->sid],
+                            (void **)&slice_data_buf);
+    CHECK_VASTATUS(va_status, "vaMapBuffer(SliceData)");
+    memcpy(slice_data_buf, framedata, framesize);
+
+    CHECK_SURF(h264, h264->va_surface_id[h264->sid]);
+    va_status = vaUnmapBuffer(h264->va_dpy, h264->va_d_param_buf_id[h264->sid]);
+    CHECK_VASTATUS(va_status, "vaUnmapBuffer(SliceData)");
+
+    buffer_ids[0] = h264->va_sp_param_buf_id[h264->sid];
+    buffer_ids[1] = h264->va_d_param_buf_id[h264->sid];
+
+    CHECK_SURF(h264, h264->va_surface_id[h264->sid]);
+    va_status = vaRenderPicture(h264->va_dpy, h264->va_context_id, buffer_ids, 2);
+    CHECK_VASTATUS(va_status, "vaRenderPicture");
+
+    va_status = vaEndPicture(h264->va_dpy, h264->va_context_id);
+    CHECK_VASTATUS(va_status, "vaEndPicture");
+
+    /* Prepare next one... */
+    sid_new = (h264->sid + 1) % SURFACE_NUM;
+    VNC_DEBUG("new Surface ID = %d", sid_new);
+    va_status = vaBeginPicture(h264->va_dpy, h264->va_context_id,
+                               h264->va_surface_id[sid_new]);
+    CHECK_VASTATUS(va_status, "vaBeginPicture");
+
+    va_status = vaSyncSurface(h264->va_dpy, h264->va_surface_id[h264->sid]);
+    CHECK_VASTATUS(va_status, "vaSyncSurface");
+    CHECK_SURF(h264, h264->va_surface_id[h264->sid]);
+
+    h264->curr_surface = h264->va_surface_id[h264->sid];
+    h264->sid = sid_new;
+    h264->field_order_count += 2;
+    ++h264->frame_id;
+    if (h264->frame_id > 15) {
+        h264->frame_id = 0;
+    }
+
+    ++h264->framecount;
+
+    memcpy(&h264->old_pic, &h264->curr_pic, sizeof(VAPictureH264));
+}
+
+static void h264_init_decoder(h264_t** pH264, int width, int height)
+{
+    VAStatus va_status;
+    Display *win_display;
+    int major_ver, minor_ver;
+    VAConfigAttrib attrib;
+    VAProfile profile;
+    int num_entrypoints;
+    VAEntrypoint entrypoints[5];
+    int vld_entrypoint_found;
+    int i;
+
+    h264_t* h264 = *pH264;
+
+    if ((h264 != NULL) && (h264->va_context_id != VA_INVALID_ID)) {
+        VNC_DEBUG("VA already initialized");
+    }
+
+    /* Initialize h264 data structure */
+    if (h264 == NULL) {
+        h264 = malloc(sizeof(struct h264_t));
+        *pH264 = h264;
+
+        memset(h264, 0, sizeof(struct h264_t));
+        h264->va_config_id = VA_INVALID_ID;
+        h264->va_context_id = VA_INVALID_ID;
+        for (i = 0; i < SURFACE_NUM; ++i) {
+            h264->va_surface_id[i] = VA_INVALID_ID;
+            h264->va_pic_param_buf_id[i] = VA_INVALID_ID;
+            h264->va_mat_param_buf_id[i] = VA_INVALID_ID;
+            h264->va_sp_param_buf_id[i] = VA_INVALID_ID;
+            h264->va_d_param_buf_id[i] = VA_INVALID_ID;
+        }
+    }
+
+    /* Get the VA display */
+    if (h264->va_dpy != NULL) {
+        VNC_DEBUG("Re-initializing H.264 decoder");
+    }
+    else {
+        VNC_DEBUG("Initializing H.264 decoder");
+
+        /* Attach VA display to local X display */
+        win_display = (Display *)XOpenDisplay(":0.0");
+        if (win_display == NULL) {
+            VNC_DEBUG("Can't connect to local display");
+            exit(-1);
+        }
+
+        h264->va_dpy = vaGetDisplay(win_display);
+        va_status = vaInitialize(h264->va_dpy, &major_ver, &minor_ver);
+        CHECK_VASTATUS(va_status, "vaInitialize");
+        VNC_DEBUG("libva version %d.%d found\n", major_ver, minor_ver);
+    }
+
+    /* Check for VLD entrypoint */
+    vld_entrypoint_found = 0;
+    profile = VAProfileH264High;
+    va_status = vaQueryConfigEntrypoints(h264->va_dpy, profile,
+                                         entrypoints, &num_entrypoints);
+    CHECK_VASTATUS(va_status, "vaQueryConfigEntrypoints");
+    for (i = 0; i < num_entrypoints; ++i) {
+        if (entrypoints[i] == VAEntrypointVLD) {
+            vld_entrypoint_found = 1;
+            break;
+        }
+    }
+
+    if (vld_entrypoint_found == 0) {
+        VNC_DEBUG("VLD entrypoint not found");
+        exit(1);
+    }
+
+    /* Create configuration for the decode pipeline */
+    attrib.type = VAConfigAttribRTFormat;
+    va_status = vaCreateConfig(h264->va_dpy, profile, VAEntrypointVLD, &attrib,
+                               1, &h264->va_config_id);
+    CHECK_VASTATUS(va_status, "vaCreateConfig");
+
+    /* Create VA surfaces */
+    for (i = 0; i < SURFACE_NUM; ++i) {
+        h264->va_surface_id[i]     = VA_INVALID_ID;
+        h264->va_pic_param_buf_id[i] = VA_INVALID_ID;
+        h264->va_mat_param_buf_id[i] = VA_INVALID_ID;
+        h264->va_sp_param_buf_id[i]  = VA_INVALID_ID;
+        h264->va_d_param_buf_id[i]   = VA_INVALID_ID;
+    }
+    va_status = vaCreateSurfaces(h264->va_dpy, width, height, VA_RT_FORMAT_YUV420,
+                                 SURFACE_NUM, &h264->va_surface_id[0]);
+    CHECK_VASTATUS(va_status, "vaCreateSurfaces");
+    for (i = 0; i < SURFACE_NUM; ++i) {
+        VNC_DEBUG("va_surface_id[%d] = 0x%x", i, h264->va_surface_id[i]);
+    }
+
+    /* Create VA context */
+    va_status = vaCreateContext(h264->va_dpy, h264->va_config_id,
+            width, height,
+            0,
+            &h264->va_surface_id[0], SURFACE_NUM,
+            &h264->va_context_id);
+    CHECK_VASTATUS(va_status, "vaCreateContext");
+    VNC_DEBUG("VA context created (id: %d)", h264->va_context_id);
+
+    h264->cur_width = width;
+    h264->cur_height = height;
+    h264->sid = 0;
+    h264->curr_surface = VA_INVALID_ID;
+    h264->frame_id = 0;
+    h264->field_order_count = 0;
+    h264->t2_first = 1;
+
+    /* Prepare decode pipeline */
+    va_status = vaBeginPicture(h264->va_dpy, h264->va_context_id, h264->va_surface_id[0]);
+    CHECK_VASTATUS(va_status, "vaBeginPicture");
+
+    VNC_DEBUG("H.264 decoder initialized");
+}
+
+
+static void h264_destroy_decoder(h264_t* h264)
+{
+    VAStatus va_status;
+
+    VNC_DEBUG("Destroying decode pipeline");
+
+    if (h264 == NULL) {
+        VNC_DEBUG("Pipeline was not instantiated.");
+        return;
+    }
+
+    if (h264->va_surface_id[0] != VA_INVALID_ID) {
+        va_status = vaDestroySurfaces(h264->va_dpy, &h264->va_surface_id[0], SURFACE_NUM);
+        CHECK_VASTATUS(va_status, "vaDestroySurfaces");
+    }
+
+    if (h264->va_context_id) {
+        va_status = vaDestroyContext(h264->va_dpy, h264->va_context_id);
+        CHECK_VASTATUS(va_status, "vaDestroyContext");
+        h264->va_context_id = 0;
+    }
+
+    if (h264->compressed_buf) {
+        free(h264->compressed_buf);
+        h264->compressed_buf = NULL;
+    }
+    h264->compressed_buf_sz = 0;
+
+    if (h264->rgb_buf) {
+        free(h264->rgb_buf);
+        h264->rgb_buf = NULL;
+    }
+    h264->rgb_buf_sz = 0;
+}
+
+
+static void SetVAPictureParameterBufferH264(VAPictureParameterBufferH264 *p,
+                                            int width, int height)
+{
+    unsigned int width_in_mbs = (width + 15) / 16;
+    unsigned int height_in_mbs = (height + 15) / 16;
+    int i;
+
+    memset(p, 0, sizeof(VAPictureParameterBufferH264));
+
+    p->picture_width_in_mbs_minus1 = width_in_mbs - 1;
+    p->picture_height_in_mbs_minus1 = height_in_mbs - 1;
+    p->num_ref_frames = 1;
+    p->seq_fields.value = 145;
+
+    p->pic_fields.value = 0x501;
+    for (i = 0; i < 16; ++i) {
+        p->ReferenceFrames[i].flags = VA_PICTURE_H264_INVALID;
+        p->ReferenceFrames[i].picture_id = 0xffffffff;
+    }
+}
+
+static void SetVASliceParameterBufferH264(VASliceParameterBufferH264 *p)
+{
+    int i;
+
+    memset(p, 0, sizeof(VASliceParameterBufferH264));
+
+    p->slice_data_size = 0;
+    p->slice_data_bit_offset = 64;
+    p->slice_alpha_c0_offset_div2 = 2;
+    p->slice_beta_offset_div2 = 2;
+    p->chroma_weight_l0_flag = 1;
+    p->chroma_weight_l0[0][0]=1;
+    p->chroma_offset_l0[0][0]=0;
+    p->chroma_weight_l0[0][1]=1;
+    p->chroma_offset_l0[0][1]=0;
+    p->luma_weight_l1_flag = 1;
+    p->chroma_weight_l1_flag = 1;
+    p->luma_weight_l0[0]=0x01;
+
+    for (i = 0; i < 32; ++i) {
+        p->RefPicList0[i].flags = VA_PICTURE_H264_INVALID;
+        p->RefPicList1[i].flags = VA_PICTURE_H264_INVALID;
+    }
+    p->RefPicList1[0].picture_id = 0xffffffff;
+}
+
+static void SetVASliceParameterBufferH264_T2(VASliceParameterBufferH264 *p, int first)
+{
+    int i;
+
+    memset(p, 0, sizeof(VASliceParameterBufferH264));
+
+    p->slice_data_size = 0;
+    p->slice_data_bit_offset = 64;
+    p->slice_alpha_c0_offset_div2 = 2;
+    p->slice_beta_offset_div2 = 2;
+    p->slice_type = 2;
+
+    if (first) {
+        p->luma_weight_l0_flag = 1;
+        p->chroma_weight_l0_flag = 1;
+        p->luma_weight_l1_flag = 1;
+        p->chroma_weight_l1_flag = 1;
+    }
+    else {
+        p->chroma_weight_l0_flag = 1;
+        p->chroma_weight_l0[0][0]=1;
+        p->chroma_offset_l0[0][0]=0;
+        p->chroma_weight_l0[0][1]=1;
+        p->chroma_offset_l0[0][1]=0;
+        p->luma_weight_l1_flag = 1;
+        p->chroma_weight_l1_flag = 1;
+        p->luma_weight_l0[0]=0x01;
+    }
+
+    for (i = 0; i < 32; ++i) {
+        p->RefPicList0[i].flags = VA_PICTURE_H264_INVALID;
+        p->RefPicList1[i].flags = VA_PICTURE_H264_INVALID;
+    }
+
+    p->RefPicList1[0].picture_id = 0xffffffff;
+    p->RefPicList0[0].picture_id = 0xffffffff;
+}
+
+static void h264_put_rectangle(VncConnectionPrivate *priv, int x, int y,
+                               int width, int height, int f_width, int f_height,
+                               int first_for_frame)
+{
+    VAStatus va_status;
+    VAImage decoded_image;
+    h264_t* h264 = priv->h264;
+
+    if (h264->curr_surface == VA_INVALID_ID) {
+        VNC_DEBUG("current surface is invalid");
+        return;
+    }
+
+    if (priv->native_window)
+    {
+        /* use efficient vaPutSurface() method of putting the framebuffer on the screen */
+        if (first_for_frame) {
+            VNC_DEBUG("Blitting updated rectangle to screen");
+            /* vaPutSurface() clears window contents outside the given destination
+             * rectangle => always update full screen. */
+            va_status = vaPutSurface(h264->va_dpy, h264->curr_surface,
+                                     priv->native_window,
+                                     0, 0, f_width, f_height,
+                                     0, 0, f_width, f_height,
+                                     NULL, 0, VA_FRAME_PICTURE);
+            CHECK_VASTATUS(va_status, "vaPutSurface");
+        }
+    }
+    else
+    {
+        /* ... or copy the changed framebuffer region manually as a fallback */
+        VNC_DEBUG("Blitting updated rectangle to framebuffer memory");
+
+        decoded_image.image_id = VA_INVALID_ID;
+        decoded_image.buf      = VA_INVALID_ID;
+        va_status = vaDeriveImage(h264->va_dpy, h264->curr_surface, &decoded_image);
+        CHECK_VASTATUS(va_status, "vaDeriveImage");
+
+        if ( (decoded_image.image_id == VA_INVALID_ID) ||
+             (decoded_image.buf == VA_INVALID_ID) ) {
+            VNC_DEBUG("vaDeriveImage() returned success but VA image is invalid"
+                      " (id: %d, buf: %d)", decoded_image.image_id, decoded_image.buf);
+        }
+
+        nv12_to_rgba(priv, decoded_image, x, y, width, height);
+
+        va_status = vaDestroyImage(h264->va_dpy, decoded_image.image_id);
+        CHECK_VASTATUS(va_status, "vaDestroyImage");
+    }
+}
+
+static void nv12_to_rgba(VncConnectionPrivate *priv, const VAImage vaImage,
+                         int ch_x, int ch_y, int ch_w, int ch_h)
+{
+    VAStatus va_status;
+    uint8_t *nv12_buf;
+    int src_x, src_y;
+    unsigned int rgb_size;
+    uint8_t *nv12_y;
+    uint8_t *nv12_uv;
+    h264_t* h264 = priv->h264;
+    guint32 *rgb_p;
+
+    /* TODO: determine output format, bit offsets */
+
+    va_status = vaMapBuffer(h264->va_dpy, vaImage.buf, (void **)&nv12_buf);
+    CHECK_VASTATUS(va_status, "vaMapBuffer(DecodedData)");
+
+    guint8 red_shift   = priv->fmt.red_shift;
+    guint8 green_shift = priv->fmt.green_shift;
+    guint8 blue_shift  = priv->fmt.blue_shift;
+
+    /* adjust x, y, width, height of the affected area so
+     * x, y, width and height are always even.
+     */
+    if (ch_x % 2) { --ch_x; ++ch_w; }
+    if (ch_y % 2) { --ch_y; ++ch_h; }
+    if ((ch_x + ch_w) % 2) { ++ch_w; }
+    if ((ch_y + ch_h) % 2) { ++ch_h; }
+
+    /* point nv12_buf and dst to upper left corner of changed area */
+    nv12_y  = &nv12_buf[vaImage.offsets[0] + vaImage.pitches[0] * ch_y + ch_x];
+    nv12_uv = &nv12_buf[vaImage.offsets[1] + vaImage.pitches[1] * (ch_y / 2) + ch_x];
+
+    /* Memory to hold the converted pixel data */
+    rgb_size = ch_w * ch_h * 4;
+    if ( (h264->rgb_buf == NULL) || (rgb_size > h264->rgb_buf_sz) ) {
+        h264->rgb_buf = realloc(h264->rgb_buf, rgb_size);
+        h264->rgb_buf_sz = rgb_size;
+    }
+    rgb_p = (guint32*)h264->rgb_buf;
+
+    /* TODO: optimize R, G, B calculation. Possible ways to do this:
+     *       - use lookup tables
+     *       - convert from floating point to integer arithmetic
+     *       - use MMX/SSE to vectorize calculations
+     */
+    for (src_y = 0; src_y < ch_h; src_y += 2) {
+        for (src_x = 0; src_x < ch_w; src_x += 2) {
+            uint8_t nv_u = nv12_uv[src_x    ];
+            uint8_t nv_v = nv12_uv[src_x + 1];
+            uint8_t nv_y[4] = { nv12_y[                     src_x    ],
+                                nv12_y[                     src_x + 1],
+                                nv12_y[vaImage.pitches[0] + src_x    ],
+                                nv12_y[vaImage.pitches[0] + src_x + 1] };
+
+            /* convert 4 pixels at a time. They share the same U and V values.
+             *
+             * src_x ----v                src_y
+             *     +---+---+---+---+---+    |
+             *     |   |   |   |   |   |    |
+             *     +---+===+===+---+---+    |
+             *     |   I 0 I 1 I   |   | <--+
+             *     +---+===+===+---+---+
+             *     |   I 2 I 3 I   |   |
+             *     +---+===+===+---+---+
+             *     |   |   |   |   |   |
+             *     +---+---+---+---+---+
+             * src_x and src_y point to the top left pixel in the Y plane of
+             * the square we want to convert.
+             * By iterating i from 0 to 3, we select the 4 pixels one by one.
+             * Hence the increment by 2 of src_x and src_y in the for loops.
+             */
+            for (int i = 0; i < 4; ++i) {
+                double R = 1.164 * (nv_y[i] - 16)                        + 1.596 * (nv_v - 128);
+                double G = 1.164 * (nv_y[i] - 16) - 0.391 * (nv_u - 128) - 0.813 * (nv_v - 128);
+                double B = 1.164 * (nv_y[i] - 16) + 2.018 * (nv_u - 128);
+
+                /* clamp R, G, B values. For some Y, U, V combinations,
+                 * the results of the above calculations fall outside of
+                 * the range 0-255.
+                 */
+                if (R < 0.0) R = 0.0;
+                if (G < 0.0) G = 0.0;
+                if (B < 0.0) B = 0.0;
+                if (R > 255.0) R = 255.0;
+                if (G > 255.0) G = 255.0;
+                if (B > 255.0) B = 255.0;
+
+                /* ch_w * (i / 2) : increase Y when i == 2 or i == 3
+                 *          i % 2 : increase X when i == 1 or i == 3
+                 * See diagram above.
+                 */
+                rgb_p[src_x + ch_w * (i / 2) + (i % 2)] = 0
+                               | ((unsigned int)(R + 0.5) << red_shift)
+                               | ((unsigned int)(G + 0.5) << green_shift)
+                               | ((unsigned int)(B + 0.5) << blue_shift);
+            }
+        }
+
+        nv12_y  += 2 * vaImage.pitches[0];
+        nv12_uv += vaImage.pitches[1];
+        rgb_p   += 2 * ch_w;
+    }
+
+    /* Blit RGB encoded pixels to the framebuffer */
+    vnc_framebuffer_blt(priv->fb, h264->rgb_buf, ch_w * 4, ch_x, ch_y, ch_w, ch_h);
+
+    CHECK_SURF(h264, h264->va_surface_id[h264->sid]);
+    va_status = vaUnmapBuffer(h264->va_dpy, vaImage.buf);
+    CHECK_VASTATUS(va_status, "vaUnmapBuffer(DecodedData)");
+}
-- 
1.7.9.5

Intel Corporation NV/SA
Kings Square, Veldkant 31
2550 Kontich
RPM (Bruxelles) 0415.497.718. 
Citibank, Brussels, account 570/1031255/09

This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). Any review or distribution by others is strictly prohibited. If you are not the intended recipient, please contact the sender and delete all copies.



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]