[Spice-devel,v6,23/26] spice-gtk: Add a GStreamer video decoder for MJPEG, VP8 and h264

Submitted by Francois Gouget on Oct. 14, 2015, 3:34 p.m.

Details

Message ID alpine.DEB.2.20.1510141614340.17509@amboise
State New
Headers show

Not browsing as part of any series.

Commit Message

Francois Gouget Oct. 14, 2015, 3:34 p.m.
Based on a patch by Jeremy White.

Signed-off-by: Francois Gouget <fgouget@codeweavers.com>
---
 configure.ac               |  27 ++++-
 src/Makefile.am            |   8 ++
 src/channel-display-gst.c  | 238 +++++++++++++++++++++++++++++++++++++++++++++
 src/channel-display-priv.h |   6 ++
 src/channel-display.c      |  10 ++
 5 files changed, 285 insertions(+), 4 deletions(-)
 create mode 100644 src/channel-display-gst.c


Changes since the previous version:
 * Moved the GSTAUDIO_XXX changes to a separate patch.
 * Removed an unneeded PROTOCOL_CFLAGS autoconf change.
 * Renamed GstDecoder to SpiceGstDecoder.
 * Fixed a couple of GstBuffer coding issues.
 * Free the GstSample earlier.
 * Replaced gst_init() with gst_init_check().
 * Calling it earlier and using that to decide whether to advertise 
   support for VP8 and H264 or not.
 * Use gst_new0() to allocate the Gstreamer decoder.
 * Added HAVE_GSTVIDEO checks.

Patch hide | download patch | download mbox

diff --git a/configure.ac b/configure.ac
index 85da9a4..60a43f6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -321,9 +321,9 @@  AS_IF([test "x$with_audio" = "xauto"],
 AS_IF([test "x$with_audio" = "xpulse"],
       [AS_IF([test "x$have_pulse" = "xyes"],
              [AC_DEFINE([WITH_PULSE], 1, [Have pulseaudio?])],
-             [AC_MSG_ERROR([PulseAudio requested but not found])
-      ])
-])
+             [AC_MSG_ERROR([PulseAudio requested but not found])]
+      )]
+)
 AM_CONDITIONAL([WITH_PULSE], [test "x$have_pulse" = "xyes"])
 AC_SUBST(PULSE_CFLAGS)
 AC_SUBST(PULSE_LIBS)
@@ -338,6 +338,25 @@  AM_CONDITIONAL([WITH_GSTAUDIO], [test "x$have_gst_audio" = "xyes"])
 AC_SUBST(GSTAUDIO_CFLAGS)
 AC_SUBST(GSTAUDIO_LIBS)
 
+AC_ARG_ENABLE([gst-video],
+              AS_HELP_STRING([--enable-gst-video=@<:@auto/yes/no@:>@],
+                             [Enable GStreamer video support @<:@default=auto@:>@]),
+              [],
+              [enable_gst_video="auto"])
+
+AS_IF([test "x$enable_gst_video" != "xno"],
+      [PKG_CHECK_MODULES(GSTVIDEO, gstreamer-1.0 gstreamer-base-1.0 gstreamer-app-1.0 gstreamer-video-1.0, [have_gst_video=yes], [have_gst_video=no])
+       AS_IF([test "x$have_gst_video" = "xyes"],
+             [AC_DEFINE([HAVE_GSTVIDEO], 1, [Have GStreamer 1.0 video?])],
+             [AS_IF([test "x$enable_gst_video" = "xyes"],
+                    [AC_MSG_ERROR([GStreamer 1.0 video requested but not found])]
+              )]
+       )]
+)
+AM_CONDITIONAL([HAVE_GSTVIDEO], [test "x$have_gst_video" = "xyes"])]
+AC_SUBST(GSTVIDEO_CFLAGS)
+AC_SUBST(GSTVIDEO_LIBS)
+
 AC_CHECK_LIB(jpeg, jpeg_destroy_decompress,
     AC_MSG_CHECKING([for jpeglib.h])
     AC_TRY_CPP(
@@ -702,7 +721,7 @@  SPICE_CFLAGS="$SPICE_CFLAGS $WARN_CFLAGS"
 
 AC_SUBST(SPICE_CFLAGS)
 
-SPICE_GLIB_CFLAGS="$PIXMAN_CFLAGS $PULSE_CFLAGS $GSTAUDIO_CFLAGS $GLIB2_CFLAGS $GIO_CFLAGS $GOBJECT2_CFLAGS $SSL_CFLAGS $SASL_CFLAGS"
+SPICE_GLIB_CFLAGS="$PIXMAN_CFLAGS $PULSE_CFLAGS $GSTVIDEO_CFLAGS $GSTAUDIO_CFLAGS $GLIB2_CFLAGS $GIO_CFLAGS $GOBJECT2_CFLAGS $SSL_CFLAGS $SASL_CFLAGS"
 SPICE_GTK_CFLAGS="$SPICE_GLIB_CFLAGS $GTK_CFLAGS "
 
 AC_SUBST(SPICE_GLIB_CFLAGS)
diff --git a/src/Makefile.am b/src/Makefile.am
index 99a8a1f..ae43b13 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -98,6 +98,7 @@  SPICE_COMMON_CPPFLAGS =						\
 	$(SSL_CFLAGS)						\
 	$(SASL_CFLAGS)						\
 	$(GSTAUDIO_CFLAGS)					\
+	$(GSTVIDEO_CFLAGS)					\
 	$(SMARTCARD_CFLAGS)					\
 	$(USBREDIR_CFLAGS)					\
 	$(GUDEV_CFLAGS)						\
@@ -211,6 +212,7 @@  libspice_client_glib_2_0_la_LIBADD =					\
 	$(SSL_LIBS)							\
 	$(PULSE_LIBS)							\
 	$(GSTAUDIO_LIBS)						\
+	$(GSTVIDEO_LIBS)						\
 	$(SASL_LIBS)							\
 	$(SMARTCARD_LIBS)						\
 	$(USBREDIR_LIBS)						\
@@ -344,6 +346,12 @@  libspice_client_glib_2_0_la_SOURCES +=	\
 	$(NULL)
 endif
 
+if HAVE_GSTVIDEO
+libspice_client_glib_2_0_la_SOURCES +=	\
+	channel-display-gst.c		\
+	$(NULL)
+endif
+
 if WITH_PHODAV
 libspice_client_glib_2_0_la_SOURCES +=	\
 	giopipe.c			\
diff --git a/src/channel-display-gst.c b/src/channel-display-gst.c
new file mode 100644
index 0000000..c5ad2e9
--- /dev/null
+++ b/src/channel-display-gst.c
@@ -0,0 +1,238 @@ 
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2015 CodeWeavers, Inc
+
+   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.1 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, see <http://www.gnu.org/licenses/>.
+*/
+#include "config.h"
+
+#include "spice-client.h"
+#include "spice-common.h"
+#include "spice-channel-priv.h"
+
+#include "channel-display-priv.h"
+
+#include <gst/gst.h>
+#include <gst/app/gstappsrc.h>
+#include <gst/app/gstappsink.h>
+
+
+/* GStreamer decoder implementation */
+
+typedef struct SpiceGstDecoder {
+    VideoDecoder base;
+
+    /* ---------- Video characteristics ---------- */
+
+    int width;
+    int height;
+
+    /* ---------- GStreamer pipeline ---------- */
+
+    GstElement *pipeline;
+    GstAppSrc *appsrc;
+    GstAppSink *appsink;
+
+    /* ---------- Output frame data ---------- */
+
+    GstBuffer *buffer;
+    GstMapInfo mapinfo;
+} SpiceGstDecoder;
+
+
+/* ---------- GStreamer pipeline ---------- */
+
+static void reset_pipeline(SpiceGstDecoder *decoder)
+{
+    if (!decoder->pipeline) {
+        return;
+    }
+
+    gst_element_set_state(decoder->pipeline, GST_STATE_NULL);
+    gst_object_unref(decoder->appsrc);
+    gst_object_unref(decoder->appsink);
+    gst_object_unref(decoder->pipeline);
+    decoder->pipeline = NULL;
+}
+
+static gboolean construct_pipeline(SpiceGstDecoder *decoder)
+{
+    const gchar *src_caps, *gstdec_name;
+    switch (decoder->base.codec_type) {
+    case SPICE_VIDEO_CODEC_TYPE_MJPEG:
+        src_caps = "caps=image/jpeg";
+        gstdec_name = "jpegdec";
+        break;
+    case SPICE_VIDEO_CODEC_TYPE_VP8:
+        src_caps = "caps=video/x-vp8";
+        gstdec_name = "vp8dec";
+        break;
+    case SPICE_VIDEO_CODEC_TYPE_H264:
+        src_caps = "caps=video/x-h264";
+        gstdec_name = "h264parse ! avdec_h264";
+        break;
+    default:
+        spice_warning("Unknown codec type %d", decoder->base.codec_type);
+        return -1;
+    }
+
+    GError *err = NULL;
+    gchar *desc = g_strdup_printf("appsrc name=src format=2 do-timestamp=1 %s ! %s ! videoconvert ! appsink name=sink caps=video/x-raw,format=BGRx", src_caps, gstdec_name);
+    SPICE_DEBUG("GStreamer pipeline: %s", desc);
+    decoder->pipeline = gst_parse_launch_full(desc, NULL, GST_PARSE_FLAG_FATAL_ERRORS, &err);
+    g_free(desc);
+    if (!decoder->pipeline) {
+        spice_warning("GStreamer error: %s", err->message);
+        g_clear_error(&err);
+        return FALSE;
+    }
+
+    decoder->appsrc = GST_APP_SRC(gst_bin_get_by_name(GST_BIN(decoder->pipeline), "src"));
+    decoder->appsink = GST_APP_SINK(gst_bin_get_by_name(GST_BIN(decoder->pipeline), "sink"));
+
+    if (gst_element_set_state(decoder->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
+        SPICE_DEBUG("GStreamer error: Unable to set the pipeline to the playing state.");
+        reset_pipeline(decoder);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static gboolean push_compressed_buffer(SpiceGstDecoder *decoder,
+                                       SpiceMsgIn *frame_msg)
+{
+    uint8_t *data;
+    uint32_t size = spice_msg_in_frame_data(frame_msg, &data);
+    if (size == 0) {
+        SPICE_DEBUG("got an empty frame buffer!");
+        return FALSE;
+    }
+
+    GstBuffer *buffer = gst_buffer_new_allocate(NULL, size, NULL);
+    gst_buffer_fill(buffer, 0, data, size);
+    if (gst_app_src_push_buffer(decoder->appsrc, buffer) != GST_FLOW_OK) {
+        SPICE_DEBUG("GStreamer error: unable to push frame of size %d", size);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static void release_last_frame(SpiceGstDecoder *decoder)
+{
+    if (decoder->buffer) {
+        if (decoder->mapinfo.memory) {
+            gst_buffer_unmap(decoder->buffer, &decoder->mapinfo);
+            decoder->mapinfo.memory = NULL;
+        }
+        gst_buffer_unref(decoder->buffer);
+        decoder->buffer = NULL;
+    }
+}
+
+static uint8_t* pull_raw_frame(SpiceGstDecoder *decoder)
+{
+    GstSample *sample = gst_app_sink_pull_sample(decoder->appsink);
+    if (!sample) {
+        SPICE_DEBUG("GStreamer error: could not pull sample");
+        return NULL;
+    }
+    decoder->buffer = gst_sample_get_buffer(sample);
+    gst_buffer_ref(decoder->buffer);
+    gst_sample_unref(sample);
+
+    if (gst_buffer_map(decoder->buffer, &decoder->mapinfo, GST_MAP_READ)) {
+        return decoder->mapinfo.data;
+    }
+
+    SPICE_DEBUG("GStreamer error: could not map the buffer");
+    gst_buffer_unref(decoder->buffer);
+    decoder->buffer = NULL;
+    decoder->mapinfo.memory = NULL;
+    return NULL;
+}
+
+
+/* ---------- VideoDecoder's public API ---------- */
+
+static void gst_decoder_destroy(VideoDecoder *video_decoder)
+{
+    SpiceGstDecoder *decoder = (SpiceGstDecoder*)video_decoder;
+    release_last_frame(decoder);
+    reset_pipeline(decoder);
+    g_free(decoder);
+    /* Don't call gst_deinit() as other parts may still be using GStreamer */
+}
+
+static uint8_t* gst_decoder_decode_frame(VideoDecoder *video_decoder,
+                                         SpiceMsgIn *frame_msg)
+{
+    SpiceGstDecoder *decoder = (SpiceGstDecoder*)video_decoder;
+    int width, height;
+
+    stream_get_dimensions(decoder->base.stream, frame_msg, &width, &height);
+    if (width != decoder->width || height != decoder->height) {
+        SPICE_DEBUG("video format change: width %d -> %d, height %d -> %d", decoder->width, width, decoder->height, height);
+        decoder->width = width;
+        decoder->height = height;
+        reset_pipeline(decoder);
+    }
+    if (!decoder->pipeline && !construct_pipeline(decoder)) {
+        return NULL;
+    }
+
+    /* Release the output frame buffer early so the pipeline can reuse it.
+     * This also simplifies error handling.
+     */
+    release_last_frame(decoder);
+
+    if (push_compressed_buffer(decoder, frame_msg)) {
+        return pull_raw_frame(decoder);
+    }
+    return NULL;
+}
+
+G_GNUC_INTERNAL
+gboolean gstvideo_init(void)
+{
+    static int success = 0;
+    if (!success) {
+        GError *err = NULL;
+        if (gst_init_check(NULL, NULL, &err)) {
+            success = 1;
+        } else {
+            spice_warning("Disabling GStreamer video support: %s", err->message);
+            g_clear_error(&err);
+            success = -1;
+        }
+    }
+    return success > 0;
+}
+
+G_GNUC_INTERNAL
+VideoDecoder* create_gstreamer_decoder(int codec_type, display_stream *stream)
+{
+    SpiceGstDecoder *decoder = NULL;
+
+    if (gstvideo_init()) {
+        decoder = spice_new0(SpiceGstDecoder, 1);
+        decoder->base.destroy = &gst_decoder_destroy;
+        decoder->base.decode_frame = &gst_decoder_decode_frame;
+        decoder->base.codec_type = codec_type;
+        decoder->base.stream = stream;
+    }
+
+    return (VideoDecoder*)decoder;
+}
diff --git a/src/channel-display-priv.h b/src/channel-display-priv.h
index 4ca80b6..d8a03e9 100644
--- a/src/channel-display-priv.h
+++ b/src/channel-display-priv.h
@@ -66,6 +66,12 @@  struct VideoDecoder {
  * @return:     A pointer to a structure implementing the VideoDecoder methods.
  */
 VideoDecoder* create_mjpeg_decoder(int codec_type, display_stream *stream);
+#ifdef HAVE_GSTVIDEO
+VideoDecoder* create_gstreamer_decoder(int codec_type, display_stream *stream);
+gboolean gstvideo_init(void);
+#else
+# define gstvideo_init() FALSE
+#endif
 
 
 typedef struct display_surface {
diff --git a/src/channel-display.c b/src/channel-display.c
index 9dd51fe..86d8869 100644
--- a/src/channel-display.c
+++ b/src/channel-display.c
@@ -596,6 +596,12 @@  static void spice_display_channel_reset_capabilities(SpiceChannel *channel)
     if (SPICE_DISPLAY_CHANNEL(channel)->priv->enable_adaptive_streaming) {
         spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_STREAM_REPORT);
     }
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_MULTI_CODEC);
+    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_CODEC_MJPEG);
+    if (gstvideo_init()) {
+        spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_CODEC_VP8);
+        spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_CODEC_H264);
+    }
 }
 
 static void destroy_surface(gpointer data)
@@ -1009,7 +1015,11 @@  static void display_handle_stream_create(SpiceChannel *channel, SpiceMsgIn *in)
         st->video_decoder = create_mjpeg_decoder(op->codec_type, st);
         break;
     default:
+#ifdef HAVE_GSTVIDEO
+        st->video_decoder = create_gstreamer_decoder(op->codec_type, st);
+#else
         st->video_decoder = NULL;
+#endif
     }
     if (st->video_decoder == NULL) {
         spice_printerr("could not create a video decoder for codec %d", op->codec_type);

Comments

Hi,

On Wed, Oct 14, 2015 at 05:34:29PM +0200, Francois Gouget wrote:
> Based on a patch by Jeremy White.
>
> Signed-off-by: Francois Gouget <fgouget@codeweavers.com>
> ---
>  configure.ac               |  27 ++++-
>  src/Makefile.am            |   8 ++
>  src/channel-display-gst.c  | 238 +++++++++++++++++++++++++++++++++++++++++++++
>  src/channel-display-priv.h |   6 ++
>  src/channel-display.c      |  10 ++
>  5 files changed, 285 insertions(+), 4 deletions(-)
>  create mode 100644 src/channel-display-gst.c
>
>
> Changes since the previous version:
>  * Moved the GSTAUDIO_XXX changes to a separate patch.
>  * Removed an unneeded PROTOCOL_CFLAGS autoconf change.
>  * Renamed GstDecoder to SpiceGstDecoder.
>  * Fixed a couple of GstBuffer coding issues.
>  * Free the GstSample earlier.
>  * Replaced gst_init() with gst_init_check().
>  * Calling it earlier and using that to decide whether to advertise
>    support for VP8 and H264 or not.
>  * Use gst_new0() to allocate the Gstreamer decoder.
>  * Added HAVE_GSTVIDEO checks.
>
>
>
> diff --git a/configure.ac b/configure.ac
> index 85da9a4..60a43f6 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -321,9 +321,9 @@ AS_IF([test "x$with_audio" = "xauto"],
>  AS_IF([test "x$with_audio" = "xpulse"],
>        [AS_IF([test "x$have_pulse" = "xyes"],
>               [AC_DEFINE([WITH_PULSE], 1, [Have pulseaudio?])],
> -             [AC_MSG_ERROR([PulseAudio requested but not found])
> -      ])
> -])
> +             [AC_MSG_ERROR([PulseAudio requested but not found])]
> +      )]
> +)
>  AM_CONDITIONAL([WITH_PULSE], [test "x$have_pulse" = "xyes"])
>  AC_SUBST(PULSE_CFLAGS)
>  AC_SUBST(PULSE_LIBS)
> @@ -338,6 +338,25 @@ AM_CONDITIONAL([WITH_GSTAUDIO], [test "x$have_gst_audio" = "xyes"])
>  AC_SUBST(GSTAUDIO_CFLAGS)
>  AC_SUBST(GSTAUDIO_LIBS)
>
> +AC_ARG_ENABLE([gst-video],
> +              AS_HELP_STRING([--enable-gst-video=@<:@auto/yes/no@:>@],
> +                             [Enable GStreamer video support @<:@default=auto@:>@]),
> +              [],
> +              [enable_gst_video="auto"])
> +
> +AS_IF([test "x$enable_gst_video" != "xno"],
> +      [PKG_CHECK_MODULES(GSTVIDEO, gstreamer-1.0 gstreamer-base-1.0 gstreamer-app-1.0 gstreamer-video-1.0, [have_gst_video=yes], [have_gst_video=no])
> +       AS_IF([test "x$have_gst_video" = "xyes"],
> +             [AC_DEFINE([HAVE_GSTVIDEO], 1, [Have GStreamer 1.0 video?])],
> +             [AS_IF([test "x$enable_gst_video" = "xyes"],
> +                    [AC_MSG_ERROR([GStreamer 1.0 video requested but not found])]
> +              )]
> +       )]
> +)
> +AM_CONDITIONAL([HAVE_GSTVIDEO], [test "x$have_gst_video" = "xyes"])]
> +AC_SUBST(GSTVIDEO_CFLAGS)
> +AC_SUBST(GSTVIDEO_LIBS)
> +

It would be good to have a with_video = GStreamer and set it in the
summary as we do in with_audio.

It would also be good to check for the dec elements we use like totem
does:

https://git.gnome.org/browse/totem/tree/configure.ac#n131

Patch looks fine otherwise,
  toso

>  AC_CHECK_LIB(jpeg, jpeg_destroy_decompress,
>      AC_MSG_CHECKING([for jpeglib.h])
>      AC_TRY_CPP(
> @@ -702,7 +721,7 @@ SPICE_CFLAGS="$SPICE_CFLAGS $WARN_CFLAGS"
>
>  AC_SUBST(SPICE_CFLAGS)
>
> -SPICE_GLIB_CFLAGS="$PIXMAN_CFLAGS $PULSE_CFLAGS $GSTAUDIO_CFLAGS $GLIB2_CFLAGS $GIO_CFLAGS $GOBJECT2_CFLAGS $SSL_CFLAGS $SASL_CFLAGS"
> +SPICE_GLIB_CFLAGS="$PIXMAN_CFLAGS $PULSE_CFLAGS $GSTVIDEO_CFLAGS $GSTAUDIO_CFLAGS $GLIB2_CFLAGS $GIO_CFLAGS $GOBJECT2_CFLAGS $SSL_CFLAGS $SASL_CFLAGS"
>  SPICE_GTK_CFLAGS="$SPICE_GLIB_CFLAGS $GTK_CFLAGS "
>
>  AC_SUBST(SPICE_GLIB_CFLAGS)
> diff --git a/src/Makefile.am b/src/Makefile.am
> index 99a8a1f..ae43b13 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -98,6 +98,7 @@ SPICE_COMMON_CPPFLAGS =						\
>  	$(SSL_CFLAGS)						\
>  	$(SASL_CFLAGS)						\
>  	$(GSTAUDIO_CFLAGS)					\
> +	$(GSTVIDEO_CFLAGS)					\
>  	$(SMARTCARD_CFLAGS)					\
>  	$(USBREDIR_CFLAGS)					\
>  	$(GUDEV_CFLAGS)						\
> @@ -211,6 +212,7 @@ libspice_client_glib_2_0_la_LIBADD =					\
>  	$(SSL_LIBS)							\
>  	$(PULSE_LIBS)							\
>  	$(GSTAUDIO_LIBS)						\
> +	$(GSTVIDEO_LIBS)						\
>  	$(SASL_LIBS)							\
>  	$(SMARTCARD_LIBS)						\
>  	$(USBREDIR_LIBS)						\
> @@ -344,6 +346,12 @@ libspice_client_glib_2_0_la_SOURCES +=	\
>  	$(NULL)
>  endif
>
> +if HAVE_GSTVIDEO
> +libspice_client_glib_2_0_la_SOURCES +=	\
> +	channel-display-gst.c		\
> +	$(NULL)
> +endif
> +
>  if WITH_PHODAV
>  libspice_client_glib_2_0_la_SOURCES +=	\
>  	giopipe.c			\
> diff --git a/src/channel-display-gst.c b/src/channel-display-gst.c
> new file mode 100644
> index 0000000..c5ad2e9
> --- /dev/null
> +++ b/src/channel-display-gst.c
> @@ -0,0 +1,238 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> +   Copyright (C) 2015 CodeWeavers, Inc
> +
> +   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.1 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, see <http://www.gnu.org/licenses/>.
> +*/
> +#include "config.h"
> +
> +#include "spice-client.h"
> +#include "spice-common.h"
> +#include "spice-channel-priv.h"
> +
> +#include "channel-display-priv.h"
> +
> +#include <gst/gst.h>
> +#include <gst/app/gstappsrc.h>
> +#include <gst/app/gstappsink.h>
> +
> +
> +/* GStreamer decoder implementation */
> +
> +typedef struct SpiceGstDecoder {
> +    VideoDecoder base;
> +
> +    /* ---------- Video characteristics ---------- */
> +
> +    int width;
> +    int height;
> +
> +    /* ---------- GStreamer pipeline ---------- */
> +
> +    GstElement *pipeline;
> +    GstAppSrc *appsrc;
> +    GstAppSink *appsink;
> +
> +    /* ---------- Output frame data ---------- */
> +
> +    GstBuffer *buffer;
> +    GstMapInfo mapinfo;
> +} SpiceGstDecoder;
> +
> +
> +/* ---------- GStreamer pipeline ---------- */
> +
> +static void reset_pipeline(SpiceGstDecoder *decoder)
> +{
> +    if (!decoder->pipeline) {
> +        return;
> +    }
> +
> +    gst_element_set_state(decoder->pipeline, GST_STATE_NULL);
> +    gst_object_unref(decoder->appsrc);
> +    gst_object_unref(decoder->appsink);
> +    gst_object_unref(decoder->pipeline);
> +    decoder->pipeline = NULL;
> +}
> +
> +static gboolean construct_pipeline(SpiceGstDecoder *decoder)
> +{
> +    const gchar *src_caps, *gstdec_name;
> +    switch (decoder->base.codec_type) {
> +    case SPICE_VIDEO_CODEC_TYPE_MJPEG:
> +        src_caps = "caps=image/jpeg";
> +        gstdec_name = "jpegdec";
> +        break;
> +    case SPICE_VIDEO_CODEC_TYPE_VP8:
> +        src_caps = "caps=video/x-vp8";
> +        gstdec_name = "vp8dec";
> +        break;
> +    case SPICE_VIDEO_CODEC_TYPE_H264:
> +        src_caps = "caps=video/x-h264";
> +        gstdec_name = "h264parse ! avdec_h264";
> +        break;
> +    default:
> +        spice_warning("Unknown codec type %d", decoder->base.codec_type);
> +        return -1;
> +    }
> +
> +    GError *err = NULL;
> +    gchar *desc = g_strdup_printf("appsrc name=src format=2 do-timestamp=1 %s ! %s ! videoconvert ! appsink name=sink caps=video/x-raw,format=BGRx", src_caps, gstdec_name);
> +    SPICE_DEBUG("GStreamer pipeline: %s", desc);
> +    decoder->pipeline = gst_parse_launch_full(desc, NULL, GST_PARSE_FLAG_FATAL_ERRORS, &err);
> +    g_free(desc);
> +    if (!decoder->pipeline) {
> +        spice_warning("GStreamer error: %s", err->message);
> +        g_clear_error(&err);
> +        return FALSE;
> +    }
> +
> +    decoder->appsrc = GST_APP_SRC(gst_bin_get_by_name(GST_BIN(decoder->pipeline), "src"));
> +    decoder->appsink = GST_APP_SINK(gst_bin_get_by_name(GST_BIN(decoder->pipeline), "sink"));
> +
> +    if (gst_element_set_state(decoder->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
> +        SPICE_DEBUG("GStreamer error: Unable to set the pipeline to the playing state.");
> +        reset_pipeline(decoder);
> +        return FALSE;
> +    }
> +
> +    return TRUE;
> +}
> +
> +static gboolean push_compressed_buffer(SpiceGstDecoder *decoder,
> +                                       SpiceMsgIn *frame_msg)
> +{
> +    uint8_t *data;
> +    uint32_t size = spice_msg_in_frame_data(frame_msg, &data);
> +    if (size == 0) {
> +        SPICE_DEBUG("got an empty frame buffer!");
> +        return FALSE;
> +    }
> +
> +    GstBuffer *buffer = gst_buffer_new_allocate(NULL, size, NULL);
> +    gst_buffer_fill(buffer, 0, data, size);
> +    if (gst_app_src_push_buffer(decoder->appsrc, buffer) != GST_FLOW_OK) {
> +        SPICE_DEBUG("GStreamer error: unable to push frame of size %d", size);
> +        return FALSE;
> +    }
> +
> +    return TRUE;
> +}
> +
> +static void release_last_frame(SpiceGstDecoder *decoder)
> +{
> +    if (decoder->buffer) {
> +        if (decoder->mapinfo.memory) {
> +            gst_buffer_unmap(decoder->buffer, &decoder->mapinfo);
> +            decoder->mapinfo.memory = NULL;
> +        }
> +        gst_buffer_unref(decoder->buffer);
> +        decoder->buffer = NULL;
> +    }
> +}
> +
> +static uint8_t* pull_raw_frame(SpiceGstDecoder *decoder)
> +{
> +    GstSample *sample = gst_app_sink_pull_sample(decoder->appsink);
> +    if (!sample) {
> +        SPICE_DEBUG("GStreamer error: could not pull sample");
> +        return NULL;
> +    }
> +    decoder->buffer = gst_sample_get_buffer(sample);
> +    gst_buffer_ref(decoder->buffer);
> +    gst_sample_unref(sample);
> +
> +    if (gst_buffer_map(decoder->buffer, &decoder->mapinfo, GST_MAP_READ)) {
> +        return decoder->mapinfo.data;
> +    }
> +
> +    SPICE_DEBUG("GStreamer error: could not map the buffer");
> +    gst_buffer_unref(decoder->buffer);
> +    decoder->buffer = NULL;
> +    decoder->mapinfo.memory = NULL;
> +    return NULL;
> +}
> +
> +
> +/* ---------- VideoDecoder's public API ---------- */
> +
> +static void gst_decoder_destroy(VideoDecoder *video_decoder)
> +{
> +    SpiceGstDecoder *decoder = (SpiceGstDecoder*)video_decoder;
> +    release_last_frame(decoder);
> +    reset_pipeline(decoder);
> +    g_free(decoder);
> +    /* Don't call gst_deinit() as other parts may still be using GStreamer */
> +}
> +
> +static uint8_t* gst_decoder_decode_frame(VideoDecoder *video_decoder,
> +                                         SpiceMsgIn *frame_msg)
> +{
> +    SpiceGstDecoder *decoder = (SpiceGstDecoder*)video_decoder;
> +    int width, height;
> +
> +    stream_get_dimensions(decoder->base.stream, frame_msg, &width, &height);
> +    if (width != decoder->width || height != decoder->height) {
> +        SPICE_DEBUG("video format change: width %d -> %d, height %d -> %d", decoder->width, width, decoder->height, height);
> +        decoder->width = width;
> +        decoder->height = height;
> +        reset_pipeline(decoder);
> +    }
> +    if (!decoder->pipeline && !construct_pipeline(decoder)) {
> +        return NULL;
> +    }
> +
> +    /* Release the output frame buffer early so the pipeline can reuse it.
> +     * This also simplifies error handling.
> +     */
> +    release_last_frame(decoder);
> +
> +    if (push_compressed_buffer(decoder, frame_msg)) {
> +        return pull_raw_frame(decoder);
> +    }
> +    return NULL;
> +}
> +
> +G_GNUC_INTERNAL
> +gboolean gstvideo_init(void)
> +{
> +    static int success = 0;
> +    if (!success) {
> +        GError *err = NULL;
> +        if (gst_init_check(NULL, NULL, &err)) {
> +            success = 1;
> +        } else {
> +            spice_warning("Disabling GStreamer video support: %s", err->message);
> +            g_clear_error(&err);
> +            success = -1;
> +        }
> +    }
> +    return success > 0;
> +}
> +
> +G_GNUC_INTERNAL
> +VideoDecoder* create_gstreamer_decoder(int codec_type, display_stream *stream)
> +{
> +    SpiceGstDecoder *decoder = NULL;
> +
> +    if (gstvideo_init()) {
> +        decoder = spice_new0(SpiceGstDecoder, 1);
> +        decoder->base.destroy = &gst_decoder_destroy;
> +        decoder->base.decode_frame = &gst_decoder_decode_frame;
> +        decoder->base.codec_type = codec_type;
> +        decoder->base.stream = stream;
> +    }
> +
> +    return (VideoDecoder*)decoder;
> +}
> diff --git a/src/channel-display-priv.h b/src/channel-display-priv.h
> index 4ca80b6..d8a03e9 100644
> --- a/src/channel-display-priv.h
> +++ b/src/channel-display-priv.h
> @@ -66,6 +66,12 @@ struct VideoDecoder {
>   * @return:     A pointer to a structure implementing the VideoDecoder methods.
>   */
>  VideoDecoder* create_mjpeg_decoder(int codec_type, display_stream *stream);
> +#ifdef HAVE_GSTVIDEO
> +VideoDecoder* create_gstreamer_decoder(int codec_type, display_stream *stream);
> +gboolean gstvideo_init(void);
> +#else
> +# define gstvideo_init() FALSE
> +#endif
>  
>  
>  typedef struct display_surface {
> diff --git a/src/channel-display.c b/src/channel-display.c
> index 9dd51fe..86d8869 100644
> --- a/src/channel-display.c
> +++ b/src/channel-display.c
> @@ -596,6 +596,12 @@ static void spice_display_channel_reset_capabilities(SpiceChannel *channel)
>      if (SPICE_DISPLAY_CHANNEL(channel)->priv->enable_adaptive_streaming) {
>          spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_STREAM_REPORT);
>      }
> +    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_MULTI_CODEC);
> +    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_CODEC_MJPEG);
> +    if (gstvideo_init()) {
> +        spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_CODEC_VP8);
> +        spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_CODEC_H264);
> +    }
>  }
>  
>  static void destroy_surface(gpointer data)
> @@ -1009,7 +1015,11 @@ static void display_handle_stream_create(SpiceChannel *channel, SpiceMsgIn *in)
>          st->video_decoder = create_mjpeg_decoder(op->codec_type, st);
>          break;
>      default:
> +#ifdef HAVE_GSTVIDEO
> +        st->video_decoder = create_gstreamer_decoder(op->codec_type, st);
> +#else
>          st->video_decoder = NULL;
> +#endif
>      }
>      if (st->video_decoder == NULL) {
>          spice_printerr("could not create a video decoder for codec %d", op->codec_type);
> -- 
> 2.6.1
> 
> _______________________________________________
> Spice-devel mailing list
> Spice-devel@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/spice-devel
On Wed, Oct 14, 2015 at 05:34:29PM +0200, Francois Gouget wrote:
> Based on a patch by Jeremy White.
> 
> Signed-off-by: Francois Gouget <fgouget@codeweavers.com>
> ---
>  configure.ac               |  27 ++++-
>  src/Makefile.am            |   8 ++
>  src/channel-display-gst.c  | 238 +++++++++++++++++++++++++++++++++++++++++++++
>  src/channel-display-priv.h |   6 ++
>  src/channel-display.c      |  10 ++
>  5 files changed, 285 insertions(+), 4 deletions(-)
>  create mode 100644 src/channel-display-gst.c
> 
> 
> Changes since the previous version:
>  * Moved the GSTAUDIO_XXX changes to a separate patch.
>  * Removed an unneeded PROTOCOL_CFLAGS autoconf change.
>  * Renamed GstDecoder to SpiceGstDecoder.
>  * Fixed a couple of GstBuffer coding issues.
>  * Free the GstSample earlier.
>  * Replaced gst_init() with gst_init_check().
>  * Calling it earlier and using that to decide whether to advertise 
>    support for VP8 and H264 or not.
>  * Use gst_new0() to allocate the Gstreamer decoder.
>  * Added HAVE_GSTVIDEO checks.
> 
> 
> 
> diff --git a/configure.ac b/configure.ac
> index 85da9a4..60a43f6 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -321,9 +321,9 @@ AS_IF([test "x$with_audio" = "xauto"],
>  AS_IF([test "x$with_audio" = "xpulse"],
>        [AS_IF([test "x$have_pulse" = "xyes"],
>               [AC_DEFINE([WITH_PULSE], 1, [Have pulseaudio?])],
> -             [AC_MSG_ERROR([PulseAudio requested but not found])
> -      ])
> -])
> +             [AC_MSG_ERROR([PulseAudio requested but not found])]
> +      )]
> +)

Order is inverted )] ?

checking for GSTVIDEO... yes
./configure: line 17978: ]: command not found

<snip>
17970 fi
17971  if test "x$have_gst_video" = "xyes"; then
17972   HAVE_GSTVIDEO_TRUE=
17973   HAVE_GSTVIDEO_FALSE='#'
17974 else
17975   HAVE_GSTVIDEO_TRUE='#'
17976   HAVE_GSTVIDEO_FALSE=
17977 fi
17978 ]
</snip>

>  AM_CONDITIONAL([WITH_PULSE], [test "x$have_pulse" = "xyes"])
>  AC_SUBST(PULSE_CFLAGS)
>  AC_SUBST(PULSE_LIBS)
> @@ -338,6 +338,25 @@ AM_CONDITIONAL([WITH_GSTAUDIO], [test "x$have_gst_audio" = "xyes"])
>  AC_SUBST(GSTAUDIO_CFLAGS)
>  AC_SUBST(GSTAUDIO_LIBS)
>  
> +AC_ARG_ENABLE([gst-video],
> +              AS_HELP_STRING([--enable-gst-video=@<:@auto/yes/no@:>@],
> +                             [Enable GStreamer video support @<:@default=auto@:>@]),
> +              [],
> +              [enable_gst_video="auto"])
> +
> +AS_IF([test "x$enable_gst_video" != "xno"],
> +      [PKG_CHECK_MODULES(GSTVIDEO, gstreamer-1.0 gstreamer-base-1.0 gstreamer-app-1.0 gstreamer-video-1.0, [have_gst_video=yes], [have_gst_video=no])
> +       AS_IF([test "x$have_gst_video" = "xyes"],
> +             [AC_DEFINE([HAVE_GSTVIDEO], 1, [Have GStreamer 1.0 video?])],
> +             [AS_IF([test "x$enable_gst_video" = "xyes"],
> +                    [AC_MSG_ERROR([GStreamer 1.0 video requested but not found])]
> +              )]
> +       )]
> +)
> +AM_CONDITIONAL([HAVE_GSTVIDEO], [test "x$have_gst_video" = "xyes"])]
> +AC_SUBST(GSTVIDEO_CFLAGS)
> +AC_SUBST(GSTVIDEO_LIBS)
> +
>  AC_CHECK_LIB(jpeg, jpeg_destroy_decompress,
>      AC_MSG_CHECKING([for jpeglib.h])
>      AC_TRY_CPP(
> @@ -702,7 +721,7 @@ SPICE_CFLAGS="$SPICE_CFLAGS $WARN_CFLAGS"
>  
>  AC_SUBST(SPICE_CFLAGS)
>  
> -SPICE_GLIB_CFLAGS="$PIXMAN_CFLAGS $PULSE_CFLAGS $GSTAUDIO_CFLAGS $GLIB2_CFLAGS $GIO_CFLAGS $GOBJECT2_CFLAGS $SSL_CFLAGS $SASL_CFLAGS"
> +SPICE_GLIB_CFLAGS="$PIXMAN_CFLAGS $PULSE_CFLAGS $GSTVIDEO_CFLAGS $GSTAUDIO_CFLAGS $GLIB2_CFLAGS $GIO_CFLAGS $GOBJECT2_CFLAGS $SSL_CFLAGS $SASL_CFLAGS"
>  SPICE_GTK_CFLAGS="$SPICE_GLIB_CFLAGS $GTK_CFLAGS "
>  
>  AC_SUBST(SPICE_GLIB_CFLAGS)
> diff --git a/src/Makefile.am b/src/Makefile.am
> index 99a8a1f..ae43b13 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -98,6 +98,7 @@ SPICE_COMMON_CPPFLAGS =						\
>  	$(SSL_CFLAGS)						\
>  	$(SASL_CFLAGS)						\
>  	$(GSTAUDIO_CFLAGS)					\
> +	$(GSTVIDEO_CFLAGS)					\
>  	$(SMARTCARD_CFLAGS)					\
>  	$(USBREDIR_CFLAGS)					\
>  	$(GUDEV_CFLAGS)						\
> @@ -211,6 +212,7 @@ libspice_client_glib_2_0_la_LIBADD =					\
>  	$(SSL_LIBS)							\
>  	$(PULSE_LIBS)							\
>  	$(GSTAUDIO_LIBS)						\
> +	$(GSTVIDEO_LIBS)						\
>  	$(SASL_LIBS)							\
>  	$(SMARTCARD_LIBS)						\
>  	$(USBREDIR_LIBS)						\
> @@ -344,6 +346,12 @@ libspice_client_glib_2_0_la_SOURCES +=	\
>  	$(NULL)
>  endif
>  
> +if HAVE_GSTVIDEO
> +libspice_client_glib_2_0_la_SOURCES +=	\
> +	channel-display-gst.c		\
> +	$(NULL)
> +endif
> +
>  if WITH_PHODAV
>  libspice_client_glib_2_0_la_SOURCES +=	\
>  	giopipe.c			\
> diff --git a/src/channel-display-gst.c b/src/channel-display-gst.c
> new file mode 100644
> index 0000000..c5ad2e9
> --- /dev/null
> +++ b/src/channel-display-gst.c
> @@ -0,0 +1,238 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> +   Copyright (C) 2015 CodeWeavers, Inc
> +
> +   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.1 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, see <http://www.gnu.org/licenses/>.
> +*/
> +#include "config.h"
> +
> +#include "spice-client.h"
> +#include "spice-common.h"
> +#include "spice-channel-priv.h"
> +
> +#include "channel-display-priv.h"
> +
> +#include <gst/gst.h>
> +#include <gst/app/gstappsrc.h>
> +#include <gst/app/gstappsink.h>
> +
> +
> +/* GStreamer decoder implementation */
> +
> +typedef struct SpiceGstDecoder {
> +    VideoDecoder base;
> +
> +    /* ---------- Video characteristics ---------- */
> +
> +    int width;
> +    int height;
> +
> +    /* ---------- GStreamer pipeline ---------- */
> +
> +    GstElement *pipeline;
> +    GstAppSrc *appsrc;
> +    GstAppSink *appsink;
> +
> +    /* ---------- Output frame data ---------- */
> +
> +    GstBuffer *buffer;
> +    GstMapInfo mapinfo;
> +} SpiceGstDecoder;
> +
> +
> +/* ---------- GStreamer pipeline ---------- */
> +
> +static void reset_pipeline(SpiceGstDecoder *decoder)
> +{
> +    if (!decoder->pipeline) {
> +        return;
> +    }
> +
> +    gst_element_set_state(decoder->pipeline, GST_STATE_NULL);
> +    gst_object_unref(decoder->appsrc);
> +    gst_object_unref(decoder->appsink);
> +    gst_object_unref(decoder->pipeline);
> +    decoder->pipeline = NULL;
> +}
> +
> +static gboolean construct_pipeline(SpiceGstDecoder *decoder)
> +{
> +    const gchar *src_caps, *gstdec_name;
> +    switch (decoder->base.codec_type) {
> +    case SPICE_VIDEO_CODEC_TYPE_MJPEG:
> +        src_caps = "caps=image/jpeg";
> +        gstdec_name = "jpegdec";
> +        break;
> +    case SPICE_VIDEO_CODEC_TYPE_VP8:
> +        src_caps = "caps=video/x-vp8";
> +        gstdec_name = "vp8dec";
> +        break;
> +    case SPICE_VIDEO_CODEC_TYPE_H264:
> +        src_caps = "caps=video/x-h264";
> +        gstdec_name = "h264parse ! avdec_h264";
> +        break;
> +    default:
> +        spice_warning("Unknown codec type %d", decoder->base.codec_type);
> +        return -1;
> +    }
> +
> +    GError *err = NULL;
> +    gchar *desc = g_strdup_printf("appsrc name=src format=2 do-timestamp=1 %s ! %s ! videoconvert ! appsink name=sink caps=video/x-raw,format=BGRx", src_caps, gstdec_name);
> +    SPICE_DEBUG("GStreamer pipeline: %s", desc);
> +    decoder->pipeline = gst_parse_launch_full(desc, NULL, GST_PARSE_FLAG_FATAL_ERRORS, &err);
> +    g_free(desc);
> +    if (!decoder->pipeline) {
> +        spice_warning("GStreamer error: %s", err->message);
> +        g_clear_error(&err);
> +        return FALSE;
> +    }
> +
> +    decoder->appsrc = GST_APP_SRC(gst_bin_get_by_name(GST_BIN(decoder->pipeline), "src"));
> +    decoder->appsink = GST_APP_SINK(gst_bin_get_by_name(GST_BIN(decoder->pipeline), "sink"));
> +
> +    if (gst_element_set_state(decoder->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
> +        SPICE_DEBUG("GStreamer error: Unable to set the pipeline to the playing state.");
> +        reset_pipeline(decoder);
> +        return FALSE;
> +    }
> +
> +    return TRUE;
> +}
> +
> +static gboolean push_compressed_buffer(SpiceGstDecoder *decoder,
> +                                       SpiceMsgIn *frame_msg)
> +{
> +    uint8_t *data;
> +    uint32_t size = spice_msg_in_frame_data(frame_msg, &data);
> +    if (size == 0) {
> +        SPICE_DEBUG("got an empty frame buffer!");
> +        return FALSE;
> +    }
> +
> +    GstBuffer *buffer = gst_buffer_new_allocate(NULL, size, NULL);
> +    gst_buffer_fill(buffer, 0, data, size);
> +    if (gst_app_src_push_buffer(decoder->appsrc, buffer) != GST_FLOW_OK) {
> +        SPICE_DEBUG("GStreamer error: unable to push frame of size %d", size);
> +        return FALSE;
> +    }
> +
> +    return TRUE;
> +}
> +
> +static void release_last_frame(SpiceGstDecoder *decoder)
> +{
> +    if (decoder->buffer) {
> +        if (decoder->mapinfo.memory) {
> +            gst_buffer_unmap(decoder->buffer, &decoder->mapinfo);
> +            decoder->mapinfo.memory = NULL;
> +        }
> +        gst_buffer_unref(decoder->buffer);
> +        decoder->buffer = NULL;
> +    }
> +}
> +
> +static uint8_t* pull_raw_frame(SpiceGstDecoder *decoder)
> +{
> +    GstSample *sample = gst_app_sink_pull_sample(decoder->appsink);
> +    if (!sample) {
> +        SPICE_DEBUG("GStreamer error: could not pull sample");
> +        return NULL;
> +    }
> +    decoder->buffer = gst_sample_get_buffer(sample);
> +    gst_buffer_ref(decoder->buffer);
> +    gst_sample_unref(sample);
> +
> +    if (gst_buffer_map(decoder->buffer, &decoder->mapinfo, GST_MAP_READ)) {
> +        return decoder->mapinfo.data;
> +    }
> +
> +    SPICE_DEBUG("GStreamer error: could not map the buffer");
> +    gst_buffer_unref(decoder->buffer);
> +    decoder->buffer = NULL;
> +    decoder->mapinfo.memory = NULL;
> +    return NULL;
> +}
> +
> +
> +/* ---------- VideoDecoder's public API ---------- */
> +
> +static void gst_decoder_destroy(VideoDecoder *video_decoder)
> +{
> +    SpiceGstDecoder *decoder = (SpiceGstDecoder*)video_decoder;
> +    release_last_frame(decoder);
> +    reset_pipeline(decoder);
> +    g_free(decoder);
> +    /* Don't call gst_deinit() as other parts may still be using GStreamer */
> +}
> +
> +static uint8_t* gst_decoder_decode_frame(VideoDecoder *video_decoder,
> +                                         SpiceMsgIn *frame_msg)
> +{
> +    SpiceGstDecoder *decoder = (SpiceGstDecoder*)video_decoder;
> +    int width, height;
> +
> +    stream_get_dimensions(decoder->base.stream, frame_msg, &width, &height);
> +    if (width != decoder->width || height != decoder->height) {
> +        SPICE_DEBUG("video format change: width %d -> %d, height %d -> %d", decoder->width, width, decoder->height, height);
> +        decoder->width = width;
> +        decoder->height = height;
> +        reset_pipeline(decoder);
> +    }
> +    if (!decoder->pipeline && !construct_pipeline(decoder)) {
> +        return NULL;
> +    }
> +
> +    /* Release the output frame buffer early so the pipeline can reuse it.
> +     * This also simplifies error handling.
> +     */
> +    release_last_frame(decoder);
> +
> +    if (push_compressed_buffer(decoder, frame_msg)) {
> +        return pull_raw_frame(decoder);
> +    }
> +    return NULL;
> +}
> +
> +G_GNUC_INTERNAL
> +gboolean gstvideo_init(void)
> +{
> +    static int success = 0;
> +    if (!success) {
> +        GError *err = NULL;
> +        if (gst_init_check(NULL, NULL, &err)) {
> +            success = 1;
> +        } else {
> +            spice_warning("Disabling GStreamer video support: %s", err->message);
> +            g_clear_error(&err);
> +            success = -1;
> +        }
> +    }
> +    return success > 0;
> +}
> +
> +G_GNUC_INTERNAL
> +VideoDecoder* create_gstreamer_decoder(int codec_type, display_stream *stream)
> +{
> +    SpiceGstDecoder *decoder = NULL;
> +
> +    if (gstvideo_init()) {
> +        decoder = spice_new0(SpiceGstDecoder, 1);
> +        decoder->base.destroy = &gst_decoder_destroy;
> +        decoder->base.decode_frame = &gst_decoder_decode_frame;
> +        decoder->base.codec_type = codec_type;
> +        decoder->base.stream = stream;
> +    }
> +
> +    return (VideoDecoder*)decoder;
> +}
> diff --git a/src/channel-display-priv.h b/src/channel-display-priv.h
> index 4ca80b6..d8a03e9 100644
> --- a/src/channel-display-priv.h
> +++ b/src/channel-display-priv.h
> @@ -66,6 +66,12 @@ struct VideoDecoder {
>   * @return:     A pointer to a structure implementing the VideoDecoder methods.
>   */
>  VideoDecoder* create_mjpeg_decoder(int codec_type, display_stream *stream);
> +#ifdef HAVE_GSTVIDEO
> +VideoDecoder* create_gstreamer_decoder(int codec_type, display_stream *stream);
> +gboolean gstvideo_init(void);
> +#else
> +# define gstvideo_init() FALSE
> +#endif
>  
>  
>  typedef struct display_surface {
> diff --git a/src/channel-display.c b/src/channel-display.c
> index 9dd51fe..86d8869 100644
> --- a/src/channel-display.c
> +++ b/src/channel-display.c
> @@ -596,6 +596,12 @@ static void spice_display_channel_reset_capabilities(SpiceChannel *channel)
>      if (SPICE_DISPLAY_CHANNEL(channel)->priv->enable_adaptive_streaming) {
>          spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_STREAM_REPORT);
>      }
> +    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_MULTI_CODEC);
> +    spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_CODEC_MJPEG);
> +    if (gstvideo_init()) {
> +        spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_CODEC_VP8);
> +        spice_channel_set_capability(SPICE_CHANNEL(channel), SPICE_DISPLAY_CAP_CODEC_H264);
> +    }
>  }
>  
>  static void destroy_surface(gpointer data)
> @@ -1009,7 +1015,11 @@ static void display_handle_stream_create(SpiceChannel *channel, SpiceMsgIn *in)
>          st->video_decoder = create_mjpeg_decoder(op->codec_type, st);
>          break;
>      default:
> +#ifdef HAVE_GSTVIDEO
> +        st->video_decoder = create_gstreamer_decoder(op->codec_type, st);
> +#else
>          st->video_decoder = NULL;
> +#endif
>      }
>      if (st->video_decoder == NULL) {
>          spice_printerr("could not create a video decoder for codec %d", op->codec_type);
> -- 
> 2.6.1
> 
> _______________________________________________
> Spice-devel mailing list
> Spice-devel@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/spice-devel