qemu-devel
[Top][All Lists]
Advanced

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

[Qemu-devel] [PATCH v2] ui/vnc: VA API based H.264 encoding for VNC


From: David Verbeiren
Subject: [Qemu-devel] [PATCH v2] ui/vnc: VA API based H.264 encoding for VNC
Date: Wed, 13 Feb 2013 11:45:04 +0100

This patch implements H.264 encoding of the VNC framebuffer updates
using hardware acceleration through the VA API.

This is experimental support to let the community explore the possibilities
offered by the potential bandwidth and latency reductions that H.264
encoding allows. This may be particularly useful for use cases such as
online gaming, hosted desktops, hosted set top boxes...
This patch provides the VNC server side support. Corresponding VNC
client side support is required. To this end, we are also contributing
patches to the gtk-vnc and libvncserver/libvncclient projects which can be
used to test this experimental feature.
See instructions below for how to build a test VNC client.

In case multiple regions are updated, only the first framebuffer
update message of the batch carries the H.264 frame data.
Subsequent update framebuffer messages will contain only the
coordinates and size of the other updated regions.

This is backwards compatible with standard VNC clients thanks to
the encoding scheme negotiation included in VNC. If the client doesn't
support H.264 encoding, the server will fall back to one of the usual
VNC encodings.

Instructions/Requirements:
* Currently only works with libva 1.0: use branch "v1.0-branch" for libva
and intel-driver. Those can be built as follows:
   cd libva
   git checkout v1.0-branch
   ./autogen.sh
   make
   sudo make install
   cd ..
   git clone git://anongit.freedesktop.org/vaapi/intel-driver
   cd intel-driver
   git checkout v1.0-branch
   ./autogen.sh
   make
   sudo make install
* A graphical environment must be running as the v1.0-branch of VA API
does not support headless operation.
* When using Intel integrated graphics, hardware encoding support requires
a 2nd generation (or later) i3, i5 or i7 processor ("Sandy Bridge" or
later), or similar, with enabled Intel(R) HD graphics.
See http://intellinuxgraphics.org/h264.html for details.

Instructions for building and using a gtk-vnc test client:
* Get gtk-vnc project
   git clone git://git.gnome.org/gtk-vnc
   cd gtk-vnc
   git checkout a4f1d1912090d5
* Download and apply (git apply <patch_file>) patch from:
   https://mail.gnome.org/archives/gtk-vnc-list/2013-February/msg00000.html
* Build
   ./autogen.sh --with-libva
   make -j4
* Run the client, for example:
   ./examples/gvncviewer <vm_host>:1

Instructions for building and using a libvncclient test client:
* Get LibVNCServer project
   git clone 
git://libvncserver.git.sourceforge.net/gitroot/libvncserver/libvncserver
   cd libvncserver
   git checkout 55bdab02574e3ac
* Download and apply (git apply <patch_file>) following two patches
  in sequence:
   http://sourceforge.net/mailarchive/message.php?msg_id=30323804
   http://sourceforge.net/mailarchive/message.php?msg_id=30327573
* Build:
   ./autogen.sh --with-libva
   make -j4
* Run the client, for example:
   ./client_examples/gtkvncviewer <vm_host>:5901

Signed-off-by: David Verbeiren <address@hidden>
---
 Changes for v1->v2:
  * No more statics; most moved into the VncDisplayH264 struct.
  * All variable declarations at top of funcs
  * VA encoder init now performed as part of set_encodings() so we can
    fallback to another encoding in case VA init fails.
  * configure script now defaults to libva=""
  * (no code change) "VA H.264" RFB encoding type number is now registered
    with IANA at http://www.iana.org/assignments/rfb/rfb.xml

 Also note that you can now use clients based on either libvncclient
 or gtk-vnc as a patch for the latter project was also submitted.


 configure         |   39 ++++
 ui/Makefile.objs  |    1 +
 ui/vnc-enc-h264.c |  616 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 ui/vnc-enc-h264.h |   74 +++++++
 ui/vnc-jobs.c     |    6 +
 ui/vnc.c          |   26 +++
 ui/vnc.h          |   18 ++
 7 files changed, 780 insertions(+)
 create mode 100644 ui/vnc-enc-h264.c
 create mode 100644 ui/vnc-enc-h264.h

diff --git a/configure b/configure
index 8789324..d742a6c 100755
--- a/configure
+++ b/configure
@@ -213,6 +213,7 @@ pie=""
 zero_malloc=""
 trace_backend="nop"
 trace_file="trace"
+libva=""
 spice=""
 rbd=""
 smartcard_nss=""
@@ -771,6 +772,10 @@ for opt do
   ;;
   --enable-spice) spice="yes"
   ;;
+  --disable-libva) libva="no"
+  ;;
+  --enable-libva) libva="yes"
+  ;;
   --disable-libiscsi) libiscsi="no"
   ;;
   --enable-libiscsi) libiscsi="yes"
@@ -1129,6 +1134,8 @@ echo "  --with-trace-file=NAME   Full PATH,NAME of file 
to store traces"
 echo "                           Default:trace-<pid>"
 echo "  --disable-spice          disable spice"
 echo "  --enable-spice           enable spice"
+echo "  --disable-libva          disable H.264 encoding with libva"
+echo "  --enable-libva           enable H.264 encoding with libva"
 echo "  --enable-rbd             enable building the rados block device (rbd)"
 echo "  --disable-libiscsi       disable iscsi support"
 echo "  --enable-libiscsi        enable iscsi support"
@@ -2842,6 +2849,33 @@ EOF
   fi
 fi
 
+##########################################
+# libva probe
+if test "$libva" != "no" ; then
+  cat > $TMPC << EOF
+#include <X11/Xlib.h>
+#include <va/va.h>
+#include <va/va_x11.h>
+int main(void) { int major_ver, minor_ver;
+ Display *win_display = (Display *)XOpenDisplay(":0.0");
+ VADisplay va_dpy = vaGetDisplay(win_display);
+ vaInitialize(va_dpy, &major_ver, &minor_ver); return 0;
+}
+EOF
+libva_cflags="$($pkg_config --cflags libva 2>/dev/null) $($pkg_config --cflags 
libva-x11 2>/dev/null) $($pkg_config --cflags x11 2>/dev/null)"
+libva_libs="$($pkg_config --libs libva 2>/dev/null) $($pkg_config --libs 
libva-x11 2>/dev/null) $($pkg_config --libs x11 2>/dev/null)"
+  if $pkg_config --atleast-version=0.26 libva >/dev/null 2>&1 && \
+     compile_prog "$libva_cflags" "$libva_libs" ; then
+    libva="yes"
+    libs_softmmu="$libs_softmmu $libva_libs"
+  else
+    if test "$libva" = "yes" ; then
+      feature_not_found "libva"
+    fi
+    libva="no"
+  fi
+fi
+
 # check for libcacard for smartcard support
 smartcard_cflags=""
 # TODO - what's the minimal nss version we support?
@@ -3336,6 +3370,7 @@ echo "xfsctl support    $xfs"
 echo "nss used          $smartcard_nss"
 echo "usb net redir     $usb_redir"
 echo "OpenGL support    $opengl"
+echo "libva support     $libva"
 echo "libiscsi support  $libiscsi"
 echo "build guest agent $guest_agent"
 echo "seccomp support   $seccomp"
@@ -3626,6 +3661,10 @@ if test "$spice" = "yes" ; then
   echo "CONFIG_SPICE=y" >> $config_host_mak
 fi
 
+if test "$libva" = "yes" ; then
+  echo "CONFIG_VNC_LIBVA=y" >> $config_host_mak
+fi
+
 if test "$smartcard_nss" = "yes" ; then
   echo "CONFIG_SMARTCARD_NSS=y" >> $config_host_mak
   echo "libcacard_libs=$libcacard_libs" >> $config_host_mak
diff --git a/ui/Makefile.objs b/ui/Makefile.objs
index d9db073..6b1bd1a 100644
--- a/ui/Makefile.objs
+++ b/ui/Makefile.objs
@@ -5,6 +5,7 @@ vnc-obj-y += vnc-enc-zrle.o
 vnc-obj-$(CONFIG_VNC_TLS) += vnc-tls.o vnc-auth-vencrypt.o
 vnc-obj-$(CONFIG_VNC_SASL) += vnc-auth-sasl.o
 vnc-obj-$(CONFIG_VNC_WS) += vnc-ws.o
+vnc-obj-$(CONFIG_VNC_LIBVA) += vnc-enc-h264.o
 vnc-obj-y += vnc-jobs.o
 
 common-obj-y += keymaps.o console.o cursor.o input.o qemu-pixman.o
diff --git a/ui/vnc-enc-h264.c b/ui/vnc-enc-h264.c
new file mode 100644
index 0000000..da9bcf0
--- /dev/null
+++ b/ui/vnc-enc-h264.c
@@ -0,0 +1,616 @@
+/*
+ * QEMU VNC display driver: VAAPI based H.264 encoding
+ *
+ * Copyright (c) 2012 Intel Corporation.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "vnc.h"
+
+#define CHECK_VASTATUS(va_status, func) do {                                   
\
+    if (va_status != VA_STATUS_SUCCESS) {                                      
\
+        fprintf(stderr, "%s:%s:%d failed (0x%x),exit\n", __func__, func,       
\
+                __LINE__, va_status);                                          
\
+        exit(1);                                                               
\
+    } else {                                                                   
\
+        /* fprintf(stderr, "%s:%s:%d success\n", __func__, func, __LINE__); */ 
\
+    }                                                                          
\
+} while (0);
+
+#define CHECK_VASTATUS_RET(va_status, func, retval) do {                       
\
+    if (va_status != VA_STATUS_SUCCESS) {                                      
\
+        fprintf(stderr, "%s:%s:%d failed (0x%x),exit\n", __func__, func,       
\
+                __LINE__, va_status);                                          
\
+        return retval;                                                         
\
+    } else {                                                                   
\
+        /* fprintf(stderr, "%s:%s:%d success\n", __func__, func, __LINE__); */ 
\
+    }                                                                          
\
+} while (0);
+
+
+/* Forward declarations */
+static int h264_encoder_reinit(VncDisplayH264 *h264, int w, int h, int pitch,
+                               int depth);
+static void h264_encoder_close(VncDisplayH264 *h264);
+
+static int h264_create_context(VncDisplayH264 *h264, int width, int height,
+                               int pitch, int depth);
+static void h264_release_context(VncDisplayH264 *h264);
+
+static void h264_prepare_input(VncDisplayH264 *h264,
+                               const unsigned char *const in_buf,
+                               const bool is_intra, const bool is_last);
+static int h264_get_coded_buf_size(VncDisplayH264 *h264);
+
+static void h264_encode_frame(VncDisplayH264 *h264,
+                              const unsigned char *const in_buf, const int 
last,
+                              const bool is_intra, int *out_length,
+                              unsigned char **out_buf);
+static void rgba_to_surface(const VncDisplayH264 *h264,
+                            const unsigned char *const inbuf,
+                            VASurfaceID surface_id);
+static int get_buffer_length(const unsigned char * const buffer,
+                             int buffer_length);
+
+
+int vnc_h264_encoder_init(VncDisplayH264 *h264)
+{
+    VAStatus va_status;
+    int va_major_ver, va_minor_ver;
+    VAEntrypoint entrypoints[5];
+    int num_entrypoints, entrypoint;
+    VAConfigAttrib attrib[2];
+    int i;
+
+    if (h264->initialized) {
+        return 0; /* Already initialized */
+    }
+
+    /* Initialize current frame data */
+    h264->cur_frame.data = NULL;
+    h264->cur_frame.length = -1;
+    h264->cur_frame.is_intra = -1;
+    h264->cur_frame.count = -1;
+    h264->cur_frame.width = -1;
+    h264->cur_frame.height = -1;
+
+    /* Open local X11 display and VA display */
+    h264->x11_dpy = XOpenDisplay(":0.0");
+    if (!h264->x11_dpy) {
+        fprintf(stderr, "Could not open display :0.0 for VA encoding\n");
+        return -1;
+    }
+    h264->va_dpy = vaGetDisplay(h264->x11_dpy);
+    va_status = vaInitialize(h264->va_dpy, &va_major_ver, &va_minor_ver);
+    CHECK_VASTATUS_RET(va_status, "vaInitialize", -1);
+
+    /* Check for Slice entrypoint */
+    vaQueryConfigEntrypoints(h264->va_dpy, VAProfileH264Baseline, entrypoints,
+                             &num_entrypoints);
+
+    for (entrypoint = 0; entrypoint < num_entrypoints; entrypoint++) {
+        if (entrypoints[entrypoint] == VAEntrypointEncSlice) {
+            break;
+        }
+    }
+
+    if (entrypoint == num_entrypoints) {
+        VNC_DEBUG("Could not find Encoder Slice entrypoint\n");
+        h264_encoder_close(h264);
+        return -1;
+    }
+
+    /* Set encode pipeline configuration */
+    attrib[0].type = VAConfigAttribRTFormat;
+    attrib[1].type = VAConfigAttribRateControl;
+    vaGetConfigAttributes(h264->va_dpy, VAProfileH264Baseline,
+                          VAEntrypointEncSlice, &attrib[0], 2);
+
+    if ((attrib[0].value & VA_RT_FORMAT_YUV420) == 0) {
+        VNC_DEBUG("YUV420 format is not supported by VA encoder\n");
+        h264_encoder_close(h264);
+        return -1;
+    }
+    if ((attrib[1].value & VA_RC_VBR) == 0) {
+        VNC_DEBUG("VBR mode is not supported by VA encoder\n");
+        h264_encoder_close(h264);
+        return -1;
+    }
+
+    attrib[0].value = VA_RT_FORMAT_YUV420;
+    attrib[1].value = VA_RC_VBR;
+    va_status = vaCreateConfig(h264->va_dpy, VAProfileH264Baseline,
+                               VAEntrypointEncSlice, &attrib[0], 2,
+                               &h264->config);
+    CHECK_VASTATUS_RET(va_status, "vaCreateConfig", -1);
+
+    /* Invalidate context */
+    h264->context = VA_INVALID_ID;
+    h264->seq_param = VA_INVALID_ID;
+    h264->pic_param = VA_INVALID_ID;
+    h264->slice_param = VA_INVALID_ID;
+    h264->coded_buf = VA_INVALID_ID;
+
+    for (i = 0; i < SID_NUMBER; ++i) {
+        h264->surface_ids[i] = VA_INVALID_ID;
+    }
+
+    /* Misc */
+    h264->frame_count = 0;
+    h264->initialized = true;
+
+    return 0;
+}
+
+static int h264_encoder_reinit(VncDisplayH264 *h264, int w, int h, int pitch,
+                               int depth)
+{
+    h264_release_context(h264);
+
+    return h264_create_context(h264, w, h, pitch, depth);
+}
+
+static void h264_encoder_close(VncDisplayH264 *h264)
+{
+    if (h264->config != VA_INVALID_ID) {
+        vaDestroyConfig(h264->va_dpy, h264->config);
+        h264->config = VA_INVALID_ID;
+    }
+    if (h264->va_dpy) {
+        vaTerminate(h264->va_dpy);
+        h264->va_dpy = NULL;
+    }
+    if (h264->x11_dpy) {
+        XCloseDisplay(h264->x11_dpy);
+        h264->x11_dpy = NULL;
+    }
+}
+
+int vnc_h264_send_framebuffer_update(VncState *vs, int ch_x,
+                                     int ch_y, int ch_w, int ch_h)
+{
+    int frame_w, frame_h;
+    int pitch, depth;
+    VncDisplayH264 *h264 = &vs->vd->h264;
+    VncH264Frame *cur_frame = &h264->cur_frame;
+    int bytes_to_send;
+
+    VNC_DEBUG("%s: x=%d; y=%d; w=%d; h=%d\n", __func__, ch_x, ch_y,
+              ch_w, ch_h);
+
+    frame_w = ds_get_width(vs->ds);
+    frame_h = ds_get_height(vs->ds);
+
+    /* first call for this frame: perform encoding */
+    if ((cur_frame->data == NULL) ||
+        (cur_frame->count != h264->frame_count)) {
+
+        if (!h264->va_dpy || (h264->config == VA_INVALID_ID)) {
+            fprintf(stderr, "VA H264 encoding selected but "
+                            "H264 encoder not initialized!\n");
+            return 0;
+        }
+
+        if ((frame_w != vs->vd->h264.cur_frame.width) ||
+            (frame_h != vs->vd->h264.cur_frame.height)) {
+            pitch = ds_get_linesize(vs->ds);
+            depth = ds_get_bytes_per_pixel(vs->ds);
+
+            VNC_DEBUG("%s: Resolution change: (%dx%d) => (%dx%d) "
+                      "(depth: %d, pitch: %d)\n", __func__,
+                      cur_frame->width, cur_frame->height,
+                      frame_w, frame_h, depth, pitch);
+
+            if (h264_encoder_reinit(h264, frame_w, frame_h,
+                                    pitch, depth) != 0) {
+                fprintf(stderr, "Error re-initializing the VA encoder!\n");
+                VNC_DEBUG("Error re-initializing the VA encoder\n");
+                return 0;
+            }
+
+            /* send full screen update on framebuffer resize/init */
+            h264->force_intra = true;
+
+            cur_frame->width = frame_w;
+            cur_frame->height = frame_h;
+        }
+
+        /* Encode the frame and get the encoded frame and its size */
+        cur_frame->is_intra = h264->force_intra ||
+                                 ((h264->frame_count % 30) == 0);
+        h264_encode_frame(h264, vnc_server_fb_ptr(vs->vd, 0, 0), 0,
+                          cur_frame->is_intra,
+                          &cur_frame->length, &cur_frame->data);
+
+        cur_frame->count = h264->frame_count;
+        h264->force_intra = false;
+    }
+
+    /* Send framebuffer update header for this rectangle */
+    vnc_framebuffer_update(vs, ch_x, ch_y, ch_w, ch_h, VNC_ENCODING_VA_H264);
+
+    /* Send frame contents only once. Multiple updates for the same frame
+     * are sent when there are multiple changed rectangles in the same
+     * framebuffer update run (see vnc_update_client() in vnc.c). */
+    bytes_to_send =
+        (vs->h264_last_sent_frame != cur_frame->count) ? cur_frame->length : 0;
+
+    /* VNC VA H264 Header: frame data size | frame_type | w | h | data */
+    vnc_write_s32(vs, bytes_to_send);
+    vnc_write_s32(vs, cur_frame->is_intra ? 2 : 0);
+    vnc_write_s32(vs, frame_w);
+    vnc_write_s32(vs, frame_h);
+    vnc_write(vs, cur_frame->data, bytes_to_send);
+
+    vs->h264_last_sent_frame = cur_frame->count;
+
+    return 1;
+}
+
+
+static int h264_get_coded_buf_size(VncDisplayH264 *h264)
+{
+    return h264->pic_width * h264->pic_height * 1.5;
+}
+
+static int h264_create_context(VncDisplayH264 *h264, int width, int height,
+                               int pitch, int depth)
+{
+    VAStatus va_status;
+    unsigned int coded_buf_size;
+    int i;
+
+    VNC_DEBUG("%s: width=%d; height=%d; pitch=%d; depth=%d\n", __func__,
+              width, height, pitch, depth);
+
+    if (h264->context != VA_INVALID_ID) {
+        VNC_DEBUG("%s: context already exists, doing nothing\n", __func__);
+        return 0;
+    }
+
+    /* Set picture related parameters */
+    h264->pic_width = width;
+    h264->pic_height = height;
+    h264->pic_depth = depth;
+    h264->pic_pitch = pitch;
+
+    /* Create context */
+    va_status = vaCreateContext(h264->va_dpy, h264->config, width, height,
+                                VA_PROGRESSIVE, 0, 0, &h264->context);
+    CHECK_VASTATUS_RET(va_status, "vaCreateContext", -1);
+
+    /* Sequence parameters */
+    VAEncSequenceParameterBufferH264 seq_h264 = {0};
+    seq_h264.level_idc = 30;
+    seq_h264.picture_width_in_mbs = (width + 15) / 16;
+    seq_h264.picture_height_in_mbs = (height + 15) / 16;
+    seq_h264.bits_per_second = 384 * 1000;
+    seq_h264.initial_qp = 26;
+    seq_h264.min_qp = 3;
+
+    va_status = vaCreateBuffer(h264->va_dpy, h264->context,
+                               VAEncSequenceParameterBufferType,
+                               sizeof(seq_h264), 1, &seq_h264,
+                               &h264->seq_param);
+    CHECK_VASTATUS_RET(va_status, "vaCreateBuffer(seq_param)", -1);
+
+    /* Surfaces */
+    va_status = vaCreateSurfaces(h264->va_dpy, width, height,
+                                 VA_RT_FORMAT_YUV420, SID_NUMBER,
+                                 &h264->surface_ids[0]);
+    CHECK_VASTATUS_RET(va_status, "vaCreateSurfaces", -1);
+    VNC_DEBUG("%s: Created surfaces:", __func__);
+    for (i = 0; i < SID_NUMBER; ++i) {
+        VNC_DEBUG(" [#%d -> 0x%x]", i, h264->surface_ids[i]);
+    }
+    VNC_DEBUG("\n");
+
+    /* Coded buffer */
+    coded_buf_size = h264_get_coded_buf_size(h264);
+    va_status = vaCreateBuffer(h264->va_dpy, h264->context,
+                               VAEncCodedBufferType, coded_buf_size, 1, NULL,
+                               &h264->coded_buf);
+    CHECK_VASTATUS_RET(va_status, "vaCreateBuffer(coded_buf)", -1);
+    VNC_DEBUG("%s: created coded_buf: id=0x%x, size=%d\n", __func__,
+              h264->coded_buf, coded_buf_size);
+
+    return 0;
+}
+
+static void h264_release_context(VncDisplayH264 *h264)
+{
+    VAStatus va_status;
+    VASurfaceStatus surface_status;
+    int i;
+
+    if (h264->context == VA_INVALID_ID) {
+        return;
+    }
+
+    /* Sync all surfaces */
+    VNC_DEBUG("%s: Syncing surfaces: ", __func__);
+    for (i = 0; i < SID_NUMBER; ++i) {
+        if (h264->surface_ids[i] != VA_INVALID_ID) {
+            VNC_DEBUG("[#%d", i);
+
+            va_status = vaSyncSurface(h264->va_dpy, h264->surface_ids[i]);
+            CHECK_VASTATUS(va_status, "vaSyncSurface");
+
+            surface_status = (VASurfaceStatus)0;
+            va_status = vaQuerySurfaceStatus(h264->va_dpy, 
h264->surface_ids[i],
+                                             &surface_status);
+            CHECK_VASTATUS(va_status, "vaQuerySurfaceStatus");
+
+            VNC_DEBUG(" -> 0x%x] ", surface_status);
+        }
+    }
+    VNC_DEBUG("done!\n");
+
+    /* Release parameter buffers */
+    VNC_DEBUG("%s: seq_param = 0x%x\n", __func__, h264->seq_param);
+    if (h264->seq_param != VA_INVALID_ID) {
+        va_status = vaDestroyBuffer(h264->va_dpy, h264->seq_param);
+        CHECK_VASTATUS(va_status, "vaDestroyBuffer(seq_param)");
+        h264->seq_param = VA_INVALID_ID;
+    }
+
+    VNC_DEBUG("%s: pic_param = 0x%x\n", __func__, h264->pic_param);
+    if (h264->pic_param != VA_INVALID_ID) {
+        va_status = vaDestroyBuffer(h264->va_dpy, h264->pic_param);
+        CHECK_VASTATUS(va_status, "vaDestroyBuffer(pic_param)");
+        h264->pic_param = VA_INVALID_ID;
+    }
+
+    VNC_DEBUG("%s: slice_param = 0x%x\n", __func__, h264->slice_param);
+    if (h264->slice_param != VA_INVALID_ID) {
+        va_status = vaDestroyBuffer(h264->va_dpy, h264->slice_param);
+        CHECK_VASTATUS(va_status, "vaDestroyBuffer(slice_param)");
+        h264->slice_param = VA_INVALID_ID;
+    }
+
+    VNC_DEBUG("%s: coded_buf = 0x%x\n", __func__, h264->coded_buf);
+    if (h264->coded_buf != VA_INVALID_ID) {
+        va_status = vaDestroyBuffer(h264->va_dpy, h264->coded_buf);
+        CHECK_VASTATUS(va_status, "vaDestroyBuffer(coded_buf)");
+        h264->coded_buf = VA_INVALID_ID;
+    }
+
+    /* Release surfaces */
+    if (h264->surface_ids[0] != VA_INVALID_ID) {
+        va_status = vaDestroySurfaces(h264->va_dpy, &h264->surface_ids[0],
+                                      SID_NUMBER);
+        CHECK_VASTATUS(va_status, "vaDestroySurfaces");
+    }
+    for (i = 0; i < SID_NUMBER; ++i) {
+        h264->surface_ids[i] = VA_INVALID_ID;
+    }
+
+    /* Release context */
+    va_status = vaDestroyContext(h264->va_dpy, h264->context);
+    CHECK_VASTATUS(va_status, "vaDestroyContext");
+    h264->context = VA_INVALID_ID;
+}
+
+
+static void h264_encode_frame(VncDisplayH264 *h264,
+                              const unsigned char *const in_buf, const int 
last,
+                              const bool is_intra, int *out_length,
+                              unsigned char **out_buf)
+{
+    VAStatus va_status;
+    VASurfaceStatus surface_status;
+    VACodedBufferSegment *coded_buf_segment = NULL;
+    int coded_buf_size;
+
+    va_status = vaBeginPicture(h264->va_dpy, h264->context,
+                               h264->surface_ids[SID_INPUT_PICTURE]);
+    CHECK_VASTATUS(va_status, "vaBeginPicture");
+
+    /* Set parameters and put picture on the input picture surface */
+    h264_prepare_input(h264, in_buf, is_intra, last);
+
+    /* Sync input surface */
+    va_status = vaEndPicture(h264->va_dpy, h264->context);
+    CHECK_VASTATUS(va_status, "vaEndPicture");
+
+    va_status = vaSyncSurface(h264->va_dpy,
+                              h264->surface_ids[SID_INPUT_PICTURE]);
+    CHECK_VASTATUS(va_status, "vaSyncSurface");
+
+    surface_status = (VASurfaceStatus)0;
+    va_status = vaQuerySurfaceStatus(h264->va_dpy,
+                                     h264->surface_ids[SID_INPUT_PICTURE],
+                                     &surface_status);
+    CHECK_VASTATUS(va_status, "vaQuerySurfaceStatus");
+
+    /* Get H.264 encoded data */
+    if (h264->coded_buf != VA_INVALID_ID) {
+        va_status = vaUnmapBuffer(h264->va_dpy, h264->coded_buf);
+        CHECK_VASTATUS(va_status, "vaUnmapBuffer(coded_buf)");
+    }
+
+    va_status = vaMapBuffer(h264->va_dpy, h264->coded_buf,
+                            (void **)(&coded_buf_segment));
+    CHECK_VASTATUS(va_status, "vaMapBuffer");
+
+    *out_buf = (unsigned char *)coded_buf_segment->buf;
+
+    if (coded_buf_segment->next) {
+        VNC_DEBUG("%s: Unsupported: multiple segments detected. "
+                  "Skipping next segments\n", __func__);
+    }
+
+    coded_buf_size = h264_get_coded_buf_size(h264);
+
+    *out_length = get_buffer_length(*out_buf, coded_buf_size);
+}
+
+
+static void h264_prepare_input(VncDisplayH264 *h264,
+                               const unsigned char *const in_buf,
+                               const bool is_intra, const bool is_last)
+{
+    VAStatus va_status;
+    VAEncPictureParameterBufferH264 pic_h264;
+    VAEncSliceParameterBuffer slice_h264;
+    VACodedBufferSegment *coded_buf_segment = NULL;
+    VABufferID tempID;
+
+    VNC_DEBUG("%s: in_buf=%p; is_intra=%d; is_last=%d\n", __func__, in_buf,
+              is_intra, is_last);
+
+    /* Set sequence parameters */
+    va_status = vaRenderPicture(h264->va_dpy, h264->context,
+                                &h264->seq_param, 1);
+    CHECK_VASTATUS(va_status, "vaRenderPicture(seq_param)");
+
+    /* Set picture parameters */
+    pic_h264.reference_picture = h264->surface_ids[SID_REFERENCE_PICTURE];
+    pic_h264.reconstructed_picture = h264->surface_ids[SID_RECON_PICTURE];
+    pic_h264.coded_buf = h264->coded_buf;
+    pic_h264.picture_width = h264->pic_width;
+    pic_h264.picture_height = h264->pic_height;
+    pic_h264.last_picture = is_last;
+
+    if (h264->pic_param != VA_INVALID_ID) {
+        va_status = vaDestroyBuffer(h264->va_dpy, h264->pic_param);
+        CHECK_VASTATUS(va_status, "vaDestroyBuffer(pic_parameter)");
+    }
+
+    va_status = vaCreateBuffer(h264->va_dpy, h264->context,
+                               VAEncPictureParameterBufferType,
+                               sizeof(pic_h264), 1, &pic_h264,
+                               &h264->pic_param);
+    CHECK_VASTATUS(va_status, "vaCreateBuffer(pic_parameter)");
+
+    va_status = vaRenderPicture(h264->va_dpy, h264->context,
+                                &h264->pic_param, 1);
+    CHECK_VASTATUS(va_status, "vaRenderPicture(pic_parameter)");
+
+    /* Allocate and zero out coded buffer segment.
+     * See also get_buffer_length(). */
+    va_status = vaMapBuffer(h264->va_dpy, h264->coded_buf,
+                            (void **)(&coded_buf_segment));
+    CHECK_VASTATUS(va_status, "vaMapBuffer(coded_buf_segment)");
+
+    /* FIXME: Is this (still) needed? */
+    memset((unsigned char *)coded_buf_segment->buf, 0, 
coded_buf_segment->size);
+
+    va_status = vaUnmapBuffer(h264->va_dpy, h264->coded_buf);
+    CHECK_VASTATUS(va_status, "vaUnmapBuffer(coded_buf)");
+
+    /* Set slice parameters */
+    slice_h264.start_row_number = 0;
+    slice_h264.slice_height = h264->pic_height / 16;    /* Measured in MB */
+    slice_h264.slice_flags.bits.is_intra = is_intra;
+    slice_h264.slice_flags.bits.disable_deblocking_filter_idc = 0;
+
+    if (h264->slice_param != VA_INVALID_ID) {
+        va_status = vaDestroyBuffer(h264->va_dpy, h264->slice_param);
+        CHECK_VASTATUS(va_status, "vaDestroyBuffer(slice_parameter)");
+    }
+
+    va_status = vaCreateBuffer(h264->va_dpy, h264->context,
+                               VAEncSliceParameterBufferType,
+                               sizeof(slice_h264), 1, &slice_h264,
+                               &h264->slice_param);
+    CHECK_VASTATUS(va_status, "vaCreateBuffer(slice_parameter)");
+
+    va_status = vaRenderPicture(h264->va_dpy, h264->context,
+                                &h264->slice_param, 1);
+    CHECK_VASTATUS(va_status, "vaRenderPicture(slice_parameter)");
+
+    /* Copy RGBA input buffer to VA surface */
+    rgba_to_surface(h264, in_buf, h264->surface_ids[SID_INPUT_PICTURE]);
+
+    /* Prepare for next picture */
+    tempID = h264->surface_ids[SID_RECON_PICTURE];
+    h264->surface_ids[SID_RECON_PICTURE] =
+                                      h264->surface_ids[SID_REFERENCE_PICTURE];
+    h264->surface_ids[SID_REFERENCE_PICTURE] = tempID;
+}
+
+static void rgba_to_surface(const VncDisplayH264 *h264,
+                            const unsigned char *const inbuf,
+                            VASurfaceID surface_id)
+{
+    VAStatus va_status;
+    VAImage image;
+    void *pbuffer = NULL;
+    const unsigned char *psrc = inbuf;
+    const unsigned char *s;
+    unsigned char *pdst = NULL;
+    unsigned char *dst_y, *dst_uv;
+    unsigned char *dst_uv_line = NULL;
+    int i, j;
+
+    VNC_DEBUG("%s: inbuf=%p; width=%d; height=%d; frame=%ld\n", __func__,
+              inbuf, h264->pic_width, h264->pic_height, h264->frame_count);
+
+    va_status = vaDeriveImage(h264->va_dpy, surface_id, &image);
+    CHECK_VASTATUS(va_status, "vaDeriveImage");
+
+    va_status = vaMapBuffer(h264->va_dpy, image.buf, &pbuffer);
+    CHECK_VASTATUS(va_status, "vaMapBuffer(image.buf)");
+
+    pdst = (unsigned char *)pbuffer;
+    dst_uv_line = pdst + image.offsets[1];
+
+    /* RGBA => NV12 */
+    for (i = 0; i < h264->pic_height; ++i) {
+        dst_y = (pdst + image.offsets[0]) + i*image.pitches[0];
+        dst_uv = dst_uv_line;
+        s = psrc;
+        for (j = 0; j < h264->pic_width; j++) {
+            *dst_y++ = ((66*s[0] + 129*s[1] + 25*s[2] + 128) >> 8) + 16;
+            /* NV12 means subsampling by 2 in x and y */
+            if ((0 == i%2) && (0 == j%2)) {
+                *dst_uv++ = ((112*s[0] - 94*s[1] - 18*s[2] + 128) >> 8) + 128;
+                *dst_uv++ = ((-38*s[0] - 74*s[1] + 112*s[2] + 128) >> 8) + 128;
+            }
+            s += h264->pic_depth;
+        }
+        psrc += h264->pic_pitch;
+        if (0 == i%2) {
+            dst_uv_line += image.pitches[1];
+        }
+    }
+
+    va_status = vaUnmapBuffer(h264->va_dpy, image.buf);
+    CHECK_VASTATUS(va_status, "vaUnmapBuffer(image.buf)");
+
+    va_status = vaDestroyImage(h264->va_dpy, image.image_id);
+    CHECK_VASTATUS(va_status, "vaDestroyImage");
+}
+
+static int get_buffer_length(const unsigned char * const buffer,
+                             int buffer_length)
+{
+    int i;
+    for (i = buffer_length - 1; i >= 0; i--) {
+        if (buffer[i]) {
+            break;
+        }
+    }
+
+    return i + 1;
+}
diff --git a/ui/vnc-enc-h264.h b/ui/vnc-enc-h264.h
new file mode 100644
index 0000000..0047d9b
--- /dev/null
+++ b/ui/vnc-enc-h264.h
@@ -0,0 +1,74 @@
+/*
+ * QEMU VNC display driver: VAAPI based H.264 encoding
+ *
+ * Copyright (c) 2012 Intel Corporation.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __QEMU_VNC_ENC_H264_H__
+#define __QEMU_VNC_ENC_H264_H__
+
+#include <X11/Xlib.h>
+#include <va/va_x11.h>
+
+#define SID_NUMBER                              3
+#define SID_INPUT_PICTURE                       0
+#define SID_REFERENCE_PICTURE                   1
+#define SID_RECON_PICTURE                       2
+
+typedef struct VncH264Frame {
+    unsigned char *data;
+    int length;
+    int is_intra;
+    unsigned long count;
+    int width;
+    int height;
+} VncH264Frame;
+
+typedef struct VncDisplayH264 {
+    /* Encoding context related */
+    Display *x11_dpy;
+    VADisplay va_dpy;
+    VAConfigID config;
+    VAContextID context;
+    VASurfaceID surface_ids[SID_NUMBER];
+    VABufferID seq_param;       /* Sequence parameters */
+    VABufferID pic_param;       /* Picture parameters */
+    VABufferID slice_param;     /* Slice parameters */
+    VABufferID coded_buf;       /* Output buffer, encoded data */
+
+    /* Picture related */
+    int pic_width;
+    int pic_height;
+    int pic_depth;
+    int pic_pitch;
+
+    /* Data for last encoded frame, e.g. to use it for multiple clients */
+    VncH264Frame cur_frame;
+
+    /* Misc */
+    bool initialized;
+    unsigned long frame_count;
+    bool force_intra;
+} VncDisplayH264;
+
+#endif /* __QEMU_VNC_ENC_H264_H__ */
diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c
index 0bfc0c5..ad02e37 100644
--- a/ui/vnc-jobs.c
+++ b/ui/vnc-jobs.c
@@ -194,6 +194,9 @@ static void vnc_async_encoding_start(VncState *orig, 
VncState *local)
     local->hextile = orig->hextile;
     local->zrle = orig->zrle;
     local->output =  queue->buffer;
+#ifdef CONFIG_VNC_LIBVA
+    local->h264_last_sent_frame = orig->h264_last_sent_frame;
+#endif
     local->csock = -1; /* Don't do any network work on this thread */
 
     buffer_reset(&local->output);
@@ -206,6 +209,9 @@ static void vnc_async_encoding_end(VncState *orig, VncState 
*local)
     orig->hextile = local->hextile;
     orig->zrle = local->zrle;
     orig->lossy_rect = local->lossy_rect;
+#ifdef CONFIG_VNC_LIBVA
+    orig->h264_last_sent_frame = local->h264_last_sent_frame;
+#endif
 
     queue->buffer = local->output;
 }
diff --git a/ui/vnc.c b/ui/vnc.c
index ff4e2ae..d977563 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -713,6 +713,11 @@ int vnc_send_framebuffer_update(VncState *vs, int x, int 
y, int w, int h)
         case VNC_ENCODING_ZYWRLE:
             n = vnc_zywrle_send_framebuffer_update(vs, x, y, w, h);
             break;
+#ifdef CONFIG_VNC_LIBVA
+        case VNC_ENCODING_VA_H264:
+            n = vnc_h264_send_framebuffer_update(vs, x, y, w, h);
+            break;
+#endif
         default:
             vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_RAW);
             n = vnc_raw_send_framebuffer_update(vs, x, y, w, h);
@@ -1864,6 +1869,16 @@ static void set_encodings(VncState *vs, int32_t 
*encodings, size_t n_encodings)
             vs->features |= VNC_FEATURE_ZYWRLE_MASK;
             vs->vnc_encoding = enc;
             break;
+#ifdef CONFIG_VNC_LIBVA
+        case VNC_ENCODING_VA_H264:
+            if (vnc_h264_encoder_init(&vs->vd->h264) == 0) {
+                vs->features |= VNC_FEATURE_VA_H264_MASK;
+                vs->vnc_encoding = enc;
+            } else {
+                fprintf(stderr, "Failed to initialize VA H264 encoder\n");
+            }
+            break;
+#endif
         case VNC_ENCODING_DESKTOPRESIZE:
             vs->features |= VNC_FEATURE_RESIZE_MASK;
             break;
@@ -2659,6 +2674,12 @@ static void vnc_refresh(void *opaque)
     has_dirty = vnc_refresh_server_surface(vd);
     vnc_unlock_display(vd);
 
+#ifdef CONFIG_VNC_LIBVA
+    if (has_dirty) {
+        ++vd->h264.frame_count;
+    }
+#endif
+
     QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) {
         rects += vnc_update_client(vs, has_dirty);
         /* vs might be free()ed here */
@@ -2770,6 +2791,11 @@ void vnc_init_state(VncState *vs)
 
     QTAILQ_INSERT_HEAD(&vd->clients, vs, next);
 
+#ifdef CONFIG_VNC_LIBVA
+    vs->h264_last_sent_frame = vd->h264.frame_count - 1;
+    vd->h264.force_intra = true;
+#endif
+
     vga_hw_update();
 
     vnc_write(vs, "RFB 003.008\n", 12);
diff --git a/ui/vnc.h b/ui/vnc.h
index 45d7686..89c5027 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -102,6 +102,9 @@ typedef struct VncDisplay VncDisplay;
 #ifdef CONFIG_VNC_WS
 #include "vnc-ws.h"
 #endif
+#ifdef CONFIG_VNC_LIBVA
+#include "vnc-enc-h264.h"
+#endif
 
 struct VncRectStat
 {
@@ -175,6 +178,9 @@ struct VncDisplay
 #ifdef CONFIG_VNC_SASL
     VncDisplaySASL sasl;
 #endif
+#ifdef CONFIG_VNC_LIBVA
+    VncDisplayH264 h264;
+#endif
 };
 
 typedef struct VncTight {
@@ -320,6 +326,10 @@ struct VncState
     VncZrle zrle;
     VncZywrle zywrle;
 
+#ifdef CONFIG_VNC_LIBVA
+    unsigned long h264_last_sent_frame;
+#endif
+
     Notifier mouse_mode_notifier;
 
     QTAILQ_ENTRY(VncState) next;
@@ -375,6 +385,7 @@ enum {
 #define VNC_ENCODING_TRLE                 0x0000000f
 #define VNC_ENCODING_ZRLE                 0x00000010
 #define VNC_ENCODING_ZYWRLE               0x00000011
+#define VNC_ENCODING_VA_H264              0x48323634
 #define VNC_ENCODING_COMPRESSLEVEL0       0xFFFFFF00 /* -256 */
 #define VNC_ENCODING_QUALITYLEVEL0        0xFFFFFFE0 /* -32  */
 #define VNC_ENCODING_XCURSOR              0xFFFFFF10 /* -240 */
@@ -424,6 +435,7 @@ enum {
 #define VNC_FEATURE_TIGHT_PNG                8
 #define VNC_FEATURE_ZRLE                     9
 #define VNC_FEATURE_ZYWRLE                  10
+#define VNC_FEATURE_VA_H264                 11
 
 #define VNC_FEATURE_RESIZE_MASK              (1 << VNC_FEATURE_RESIZE)
 #define VNC_FEATURE_HEXTILE_MASK             (1 << VNC_FEATURE_HEXTILE)
@@ -436,6 +448,7 @@ enum {
 #define VNC_FEATURE_TIGHT_PNG_MASK           (1 << VNC_FEATURE_TIGHT_PNG)
 #define VNC_FEATURE_ZRLE_MASK                (1 << VNC_FEATURE_ZRLE)
 #define VNC_FEATURE_ZYWRLE_MASK              (1 << VNC_FEATURE_ZYWRLE)
+#define VNC_FEATURE_VA_H264_MASK             (1 << VNC_FEATURE_VA_H264)
 
 
 /* Client -> Server message IDs */
@@ -581,4 +594,9 @@ int vnc_zrle_send_framebuffer_update(VncState *vs, int x, 
int y, int w, int h);
 int vnc_zywrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int 
h);
 void vnc_zrle_clear(VncState *vs);
 
+#ifdef CONFIG_VNC_LIBVA
+int vnc_h264_encoder_init(VncDisplayH264 *h264);
+int vnc_h264_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
+#endif
+
 #endif /* __QEMU_VNC_H */
-- 
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.




reply via email to

[Prev in Thread] Current Thread [Next in Thread]