[Spice-devel,7/12] server: Add VP8 support, a video codec preference list and compatibility checks with the Spice client. (take 3b)

Submitted by Francois Gouget on June 11, 2015, 10:24 p.m.

Details

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

Not browsing as part of any series.

Commit Message

Francois Gouget June 11, 2015, 10:24 p.m.
The video encoder preferences can be expressed by building a semi-colon separated list of
encoder:codec pairs. For instance 'gstreamer:vp8;spice:mjpeg' to pick first the GStreamer VP8
video encoder first and used the original MJPEG video encoder one as a fallback.
The server has a default preference list which can also be selected by specifying 'auto' as the
preferences list.
The client compatibility check relies on the following new capabilities:
 * SPICE_DISPLAY_CAP_MULTI_CODEC which denotes a recent client that supports multiple codecs.
This capability is needed to not have to hardcode that MJPEG is supported. This makes it possible
to write clients that don't support MJPEG.
 * SPICE_DISPLAY_CAP_CODEC_XXX, where XXX is a supported codec, for now MJPEG and VP8.
    
Signed-off-by: Francois Gouget <fgouget@codeweavers.com>
---

On Thu, 11 Jun 2015, Fabio Fantoni wrote:
[...]
> > ../../server/.libs/libspice-server.so: undefined reference to
> > `g_get_num_processors'
>
> toso in spice chat told that was added in glib 2.36....check 
pkg-config
> --modversion glib-2.0 (this should probably bump the configure version
> for glib tho)

I think it's better to check directly for the presence of 
g_get_num_processors(). That way it works even if some distribution 
decides to backport it or removes it later on. Here is an updated patch. 
Note that this will impact the GStreamer 1.0 and h264 patches that 
follow.

Patch hide | download patch | download mbox

diff --git a/configure.ac b/configure.ac
index 34e1a4a..ed3a337 100644
--- a/configure.ac
+++ b/configure.ac
@@ -154,6 +154,10 @@  fi
 PKG_CHECK_MODULES([GLIB2], [glib-2.0 >= 2.22])
 AS_VAR_APPEND([SPICE_REQUIRES], [" glib-2.0 >= 2.22"])
 
+AC_CHECK_LIB(glib-2.0, g_get_num_processors,
+             AC_DEFINE([HAVE_G_GET_NUMPROCESSORS], 1, [Defined if we have g_get_num_processors()]),,
+             $GLIB2_LIBS)
+
 PKG_CHECK_MODULES(PIXMAN, pixman-1 >= 0.17.7)
 AC_SUBST(PIXMAN_CFLAGS)
 AC_SUBST(PIXMAN_LIBS)
diff --git a/server/gstreamer_encoder.c b/server/gstreamer_encoder.c
index 3600b1b..8fa9071 100644
--- a/server/gstreamer_encoder.c
+++ b/server/gstreamer_encoder.c
@@ -178,10 +178,27 @@  static gboolean set_appsrc_caps(GstEncoder *encoder)
 
 static gboolean construct_pipeline(GstEncoder *encoder, const SpiceBitmap *bitmap)
 {
+    gboolean no_clock = FALSE;
+    const gchar* gstenc_name;
+    switch (encoder->base.codec_type)
+    {
+    case SPICE_VIDEO_CODEC_TYPE_MJPEG:
+        gstenc_name = "ffenc_mjpeg";
+        no_clock = TRUE;
+        break;
+    case SPICE_VIDEO_CODEC_TYPE_VP8:
+        gstenc_name = "vp8enc";
+        break;
+    default:
+        spice_warning("unsupported codec type %d", encoder->base.codec_type);
+        return FALSE;
+    }
+
     GError *err = NULL;
-    const gchar* desc = "appsrc name=src format=2 do-timestamp=true ! ffmpegcolorspace ! ffenc_mjpeg name=encoder ! appsink name=sink";
+    gchar *desc = g_strdup_printf("appsrc name=src format=2 do-timestamp=true ! ffmpegcolorspace ! %s name=encoder ! appsink name=sink", gstenc_name);
     spice_debug("GStreamer pipeline: %s", desc);
     encoder->pipeline = gst_parse_launch_full(desc, NULL, GST_PARSE_FLAG_FATAL_ERRORS, &err);
+    g_free(desc);
     if (!encoder->pipeline) {
         spice_warning("GStreamer error: %s", err->message);
         g_clear_error(&err);
@@ -191,19 +208,36 @@  static gboolean construct_pipeline(GstEncoder *encoder, const SpiceBitmap *bitma
     encoder->gstenc = gst_bin_get_by_name(GST_BIN(encoder->pipeline), "encoder");
     encoder->appsink = GST_APP_SINK(gst_bin_get_by_name(GST_BIN(encoder->pipeline), "sink"));
 
+    /* Set the encoder initial bit rate, and ask for a low latency */
+    adjust_bit_rate(encoder);
+    g_object_set(G_OBJECT(encoder->gstenc), "bitrate", encoder->bit_rate, NULL);
+    if (encoder->base.codec_type == SPICE_VIDEO_CODEC_TYPE_VP8) {
+        /* See http://www.webmproject.org/docs/encoder-parameters/ */
+#ifdef HAVE_G_GET_NUMPROCESSORS
+        int core_count = g_get_num_processors();
+#else
+        int core_count = 1;
+#endif
+        g_object_set(G_OBJECT(encoder->gstenc),
+                     "mode", 1, /* CBR */
+                     "max-latency", 0,
+                     "speed", 7,
+                     "resize-allowed", TRUE,
+                     "threads", core_count - 1,
+                     NULL);
+   }
+
     /* Set the source caps */
     encoder->src_caps = NULL;
     if (!set_appsrc_caps(encoder)) {
         return FALSE;
     }
 
-    /* Set the encoder initial bit rate */
-    adjust_bit_rate(encoder);
-    g_object_set(G_OBJECT(encoder->gstenc), "bitrate", encoder->bit_rate, NULL);
-
     /* Start playing */
-    spice_debug("removing the pipeline clock");
-    gst_pipeline_use_clock(GST_PIPELINE(encoder->pipeline), NULL);
+    if (no_clock) {
+        spice_debug("removing the pipeline clock");
+        gst_pipeline_use_clock(GST_PIPELINE(encoder->pipeline), NULL);
+    }
     spice_debug("setting state to PLAYING");
     if (gst_element_set_state(encoder->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
         spice_warning("GStreamer error: unable to set the pipeline to the playing state");
@@ -215,9 +249,13 @@  static gboolean construct_pipeline(GstEncoder *encoder, const SpiceBitmap *bitma
 
 static void reconfigure_pipeline(GstEncoder *encoder)
 {
-    if (gst_element_set_state(encoder->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE ||
-        !set_appsrc_caps(encoder) ||
-        gst_element_set_state(encoder->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
+    if (encoder->base.codec_type == SPICE_VIDEO_CODEC_TYPE_VP8) {
+        /* vp8enc gets confused if we try to reconfigure the pipeline */
+        reset_pipeline(encoder);
+
+    } else if (gst_element_set_state(encoder->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE ||
+             !set_appsrc_caps(encoder) ||
+             gst_element_set_state(encoder->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
         spice_debug("GStreamer error: the pipeline reconfiguration failed, rebuilding it instead");
         reset_pipeline(encoder); /* we can rebuild it... */
     }
@@ -405,11 +443,16 @@  void gst_encoder_get_stats(GstEncoder *encoder, VideoEncoderStats *stats)
     }
 }
 
-GstEncoder *create_gstreamer_encoder(uint64_t starting_bit_rate, VideoEncoderRateControlCbs *cbs, void *cbs_opaque)
+GstEncoder *create_gstreamer_encoder(SpiceVideoCodecType codec_type, uint64_t starting_bit_rate, VideoEncoderRateControlCbs *cbs, void *cbs_opaque)
 {
     GstEncoder *encoder;
 
     spice_assert(!cbs || (cbs && cbs->get_roundtrip_ms && cbs->get_source_fps));
+    if (codec_type != SPICE_VIDEO_CODEC_TYPE_MJPEG &&
+        codec_type != SPICE_VIDEO_CODEC_TYPE_VP8) {
+        spice_warning("unsupported codec type %d", codec_type);
+        return NULL;
+    }
 
     gst_init(NULL, NULL);
 
@@ -420,6 +463,7 @@  GstEncoder *create_gstreamer_encoder(uint64_t starting_bit_rate, VideoEncoderRat
     encoder->base.notify_server_frame_drop = &gst_encoder_notify_server_frame_drop;
     encoder->base.get_bit_rate = &gst_encoder_get_bit_rate;
     encoder->base.get_stats = &gst_encoder_get_stats;
+    encoder->base.codec_type = codec_type;
     encoder->pipeline = NULL;
 
     if (cbs) {
diff --git a/server/mjpeg_encoder.c b/server/mjpeg_encoder.c
index d734520..7cdad81 100644
--- a/server/mjpeg_encoder.c
+++ b/server/mjpeg_encoder.c
@@ -210,7 +210,8 @@  static inline int rate_control_is_active(MJpegEncoder* encoder)
     return encoder->cbs.get_roundtrip_ms != NULL;
 }
 
-MJpegEncoder *create_mjpeg_encoder(uint64_t starting_bit_rate,
+MJpegEncoder *create_mjpeg_encoder(SpiceVideoCodecType codec_type,
+                                   uint64_t starting_bit_rate,
                                    VideoEncoderRateControlCbs *cbs,
                                    void *cbs_opaque)
 {
@@ -218,6 +219,9 @@  MJpegEncoder *create_mjpeg_encoder(uint64_t starting_bit_rate,
 
     spice_assert(!cbs || (cbs && cbs->get_roundtrip_ms && cbs->get_source_fps));
 
+    if (codec_type != SPICE_VIDEO_CODEC_TYPE_MJPEG) {
+        return NULL;
+    }
     enc = spice_new0(MJpegEncoder, 1);
 
     enc->base.destroy = &mjpeg_encoder_destroy;
@@ -226,6 +230,7 @@  MJpegEncoder *create_mjpeg_encoder(uint64_t starting_bit_rate,
     enc->base.notify_server_frame_drop = &mjpeg_encoder_notify_server_frame_drop;
     enc->base.get_bit_rate = &mjpeg_encoder_get_bit_rate;
     enc->base.get_stats = &mjpeg_encoder_get_stats;
+    enc->base.codec_type = codec_type;
     enc->first_frame = TRUE;
     enc->rate_control.byte_rate = starting_bit_rate / 8;
     enc->starting_bit_rate = starting_bit_rate;
diff --git a/server/red_dispatcher.c b/server/red_dispatcher.c
index f5f3e52..4330f2e 100644
--- a/server/red_dispatcher.c
+++ b/server/red_dispatcher.c
@@ -27,6 +27,7 @@ 
 #include <sys/socket.h>
 #include <signal.h>
 #include <inttypes.h>
+#include <ctype.h>
 
 #include <spice/qxl_dev.h>
 #include "common/quic.h"
@@ -204,43 +205,161 @@  static void red_dispatcher_cursor_migrate(RedChannelClient *rcc)
                             &payload);
 }
 
-typedef struct RendererInfo {
-    int id;
+typedef struct {
+    uint32_t id;
     const char *name;
-} RendererInfo;
+} EnumNames;
 
-static RendererInfo renderers_info[] = {
+static int name_to_enum(const EnumNames names[], const char *string, uint32_t *id)
+{
+    if (string) {
+        int i = 0;
+        while (names[i].name) {
+            if (strcmp(string, names[i].name) == 0) {
+                *id = names[i].id;
+                return TRUE;
+            }
+            i++;
+        }
+    }
+    return FALSE;
+}
+
+static const EnumNames renderer_names[] = {
     {RED_RENDERER_SW, "sw"},
 #ifdef USE_OPENGL
     {RED_RENDERER_OGL_PBUF, "oglpbuf"},
     {RED_RENDERER_OGL_PIXMAP, "oglpixmap"},
 #endif
-    {RED_RENDERER_INVALID, NULL},
+    {0, NULL},
 };
 
 static uint32_t renderers[RED_MAX_RENDERERS];
 static uint32_t num_renderers = 0;
 
-static RendererInfo *find_renderer(const char *name)
+int red_dispatcher_add_renderer(const char *name)
+{
+    uint32_t renderer;
+
+    if (num_renderers == RED_MAX_RENDERERS ||
+        !name_to_enum(renderer_names, name, &renderer)) {
+        return FALSE;
+    }
+    renderers[num_renderers++] = renderer;
+    return TRUE;
+}
+
+static const EnumNames video_encoder_names[] = {
+    {0, "spice"},
+    {1, "gstreamer"},
+    {0, NULL},
+};
+
+static create_video_encoder_proc video_encoder_procs[] = {
+    &create_mjpeg_encoder,
+#ifdef HAVE_GSTREAMER_0_10
+    &create_gstreamer_encoder,
+#else
+    NULL,
+#endif
+};
+
+static const EnumNames video_codec_names[] = {
+    {SPICE_VIDEO_CODEC_TYPE_MJPEG, "mjpeg"},
+    {SPICE_VIDEO_CODEC_TYPE_VP8, "vp8"},
+    {0, NULL},
+};
+
+static const EnumNames video_cap_names[] = {
+    {SPICE_DISPLAY_CAP_CODEC_MJPEG, "mjpeg"},
+    {SPICE_DISPLAY_CAP_CODEC_VP8, "vp8"},
+    {0, NULL},
+};
+
+
+static RedVideoCodec video_codecs[RED_MAX_VIDEO_CODECS];
+static int num_video_codecs = -1;
+
+/* Expected string:  encoder:codec;encoder:codec */
+static const char* parse_video_codecs(const char *codecs, char **encoder,
+                                      char **codec)
 {
-    RendererInfo *inf = renderers_info;
-    while (inf->name) {
-        if (strcmp(name, inf->name) == 0) {
-            return inf;
+    const char* e;
+    while (*codecs == ';') {
+        codecs++;
+    }
+    if (!*codecs) {
+        return NULL;
+    }
+    e = codecs;
+    while (isalnum(*codecs) || *codecs == '_') {
+        codecs++;
+    }
+    if (*codecs == ':') {
+        const char* c = ++codecs;
+        while (isalnum(*codecs) || *codecs == '_') {
+            codecs++;
+        }
+        if (!*codecs || *codecs == ';') {
+            int len = c - e - 1;
+            *encoder = malloc(len + 1);
+            strncpy(*encoder, e, len);
+            (*encoder)[len] = '\0';
+
+            len =  codecs - c;
+            *codec = malloc(len + 1);
+            strncpy(*codec, c, len);
+            (*codec)[len] = '\0';
+            return codecs;
         }
-        inf++;
     }
+    spice_warning("spice: invalid encoder:codec value: %s", e);
     return NULL;
 }
 
-int red_dispatcher_add_renderer(const char *name)
+int red_dispatcher_set_video_codecs(const char *codecs)
 {
-    RendererInfo *inf;
+    uint32_t encoder;
+    SpiceVideoCodecType type;
+    uint32_t cap;
+    char *encoder_name, *codec_name;
+    static RedVideoCodec new_codecs[RED_MAX_VIDEO_CODECS];
+    int count;
+    const char* c;
 
-    if (num_renderers == RED_MAX_RENDERERS || !(inf = find_renderer(name))) {
-        return FALSE;
+    if (strcmp(codecs, "auto") == 0) {
+        codecs = VIDEO_ENCODER_DEFAULT_PREFERENCE;
+    }
+
+    c = codecs;
+    count = 0;
+    while ( (c = parse_video_codecs(c, &encoder_name, &codec_name)) ) {
+        int convert_failed = FALSE;
+        if (count == RED_MAX_VIDEO_CODECS ||
+            !name_to_enum(video_encoder_names, encoder_name, &encoder) ||
+            !name_to_enum(video_codec_names, codec_name, &type) ||
+            !name_to_enum(video_cap_names, codec_name, &cap)) {
+            convert_failed = TRUE;
+        }
+
+        free(encoder_name);
+        free(codec_name);
+
+        if (convert_failed) {
+            return FALSE;
+        }
+        if (!video_encoder_procs[encoder]) {
+            /* A valid but unsupported video encoder, skip it */
+            continue;
+        }
+
+        new_codecs[count].create = video_encoder_procs[encoder];
+        new_codecs[count].type = type;
+        new_codecs[count].cap = cap;
+        count++;
     }
-    renderers[num_renderers++] = inf->id;
+    num_video_codecs = count;
+    memcpy(video_codecs, new_codecs, sizeof(video_codecs));
     return TRUE;
 }
 
@@ -783,6 +902,22 @@  void red_dispatcher_on_sv_change(void)
     }
 }
 
+void red_dispatcher_on_vc_change(void)
+{
+    RedWorkerMessageSetVideoCodecs payload;
+    int compression_level = calc_compression_level();
+    RedDispatcher *now = dispatchers;
+    while (now) {
+        now->qxl->st->qif->set_compression_level(now->qxl, compression_level);
+        payload.num_video_codecs = num_video_codecs;
+        payload.video_codecs = video_codecs;
+        dispatcher_send_message(&now->dispatcher,
+                                RED_WORKER_MESSAGE_SET_VIDEO_CODECS,
+                                &payload);
+        now = now->next;
+    }
+}
+
 void red_dispatcher_set_mouse_mode(uint32_t mode)
 {
     RedWorkerMessageSetMouseMode payload;
@@ -1084,6 +1219,11 @@  void red_dispatcher_init(QXLInstance *qxl)
     init_data.pending = &red_dispatcher->pending;
     init_data.num_renderers = num_renderers;
     memcpy(init_data.renderers, renderers, sizeof(init_data.renderers));
+    if (num_video_codecs < 0) {
+        red_dispatcher_set_video_codecs("auto");
+    }
+    init_data.num_video_codecs = num_video_codecs;
+    memcpy(init_data.video_codecs, video_codecs, sizeof(init_data.video_codecs));
 
     pthread_mutex_init(&red_dispatcher->async_lock, NULL);
     init_data.image_compression = image_compression;
diff --git a/server/red_dispatcher.h b/server/red_dispatcher.h
index 907b7c7..eb3aee4 100644
--- a/server/red_dispatcher.h
+++ b/server/red_dispatcher.h
@@ -22,6 +22,7 @@ 
 
 struct RedChannelClient;
 struct RedDispatcher;
+typedef struct RedVideoCodec RedVideoCodec;
 typedef struct AsyncCommand AsyncCommand;
 
 void red_dispatcher_init(QXLInstance *qxl);
@@ -29,11 +30,13 @@  void red_dispatcher_init(QXLInstance *qxl);
 void red_dispatcher_set_mm_time(uint32_t);
 void red_dispatcher_on_ic_change(void);
 void red_dispatcher_on_sv_change(void);
+void red_dispatcher_on_vc_change(void);
 void red_dispatcher_set_mouse_mode(uint32_t mode);
 void red_dispatcher_on_vm_stop(void);
 void red_dispatcher_on_vm_start(void);
 int red_dispatcher_count(void);
 int red_dispatcher_add_renderer(const char *name);
+int red_dispatcher_set_video_codecs(const char* codecs);
 uint32_t red_dispatcher_qxl_ram_size(void);
 int red_dispatcher_qxl_count(void);
 void red_dispatcher_async_complete(struct RedDispatcher *, AsyncCommand *);
@@ -174,6 +177,11 @@  typedef struct RedWorkerMessageSetStreamingVideo {
     uint32_t streaming_video;
 } RedWorkerMessageSetStreamingVideo;
 
+typedef struct RedWorkerMessageSetVideoCodecs {
+    uint32_t num_video_codecs;
+    RedVideoCodec* video_codecs;
+} RedWorkerMessageSetVideoCodecs;
+
 typedef struct RedWorkerMessageSetMouseMode {
     uint32_t mode;
 } RedWorkerMessageSetMouseMode;
diff --git a/server/red_worker.c b/server/red_worker.c
index 19e27c5..3fa48dd 100644
--- a/server/red_worker.c
+++ b/server/red_worker.c
@@ -1,6 +1,7 @@ 
 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
 /*
    Copyright (C) 2009 Red Hat, Inc.
+   Copyright (C) 2015 Francois Gouget
 
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
@@ -994,6 +995,8 @@  typedef struct RedWorker {
     uint32_t mouse_mode;
 
     uint32_t streaming_video;
+    uint32_t num_video_codecs;
+    RedVideoCodec video_codecs[RED_MAX_VIDEO_CODECS];
     Stream streams_buf[NUM_STREAMS];
     Stream *free_streams;
     Ring streams;
@@ -3074,10 +3077,44 @@  static void red_stream_update_client_playback_latency(void *opaque, uint32_t del
     main_dispatcher_set_mm_time_latency(agent->dcc->common.base.client, agent->dcc->streams_max_latency);
 }
 
+static VideoEncoder* red_display_create_video_encoder(DisplayChannelClient *dcc, uint64_t starting_bit_rate, VideoEncoderRateControlCbs *cbs, void *cbs_opaque)
+{
+    RedWorker* worker = dcc->common.worker;
+    int i;
+    int client_has_multi_codec = red_channel_client_test_remote_cap(&dcc->common.base, SPICE_DISPLAY_CAP_MULTI_CODEC);
+
+    for (i = 0; i < worker->num_video_codecs; i++) {
+        RedVideoCodec* video_codec = &worker->video_codecs[i];
+        VideoEncoder* video_encoder;
+
+        if (!client_has_multi_codec &&
+            video_codec->type != SPICE_VIDEO_CODEC_TYPE_MJPEG) {
+            /* Old clients only support MJPEG */
+            continue;
+        }
+        if (client_has_multi_codec &&
+            !red_channel_client_test_remote_cap(&dcc->common.base, video_codec->cap)) {
+            /* The client is recent but does not support this codec */
+            continue;
+        }
+
+        video_encoder = video_codec->create(video_codec->type, starting_bit_rate, cbs, cbs_opaque);
+        if (video_encoder) {
+            return video_encoder;
+        }
+    }
+
+    /* Try to use the builtin MJPEG video encoder as a fallback */
+    if (!client_has_multi_codec || red_channel_client_test_remote_cap(&dcc->common.base, SPICE_DISPLAY_CAP_CODEC_MJPEG)) {
+        return create_mjpeg_encoder(SPICE_VIDEO_CODEC_TYPE_MJPEG, starting_bit_rate, cbs, cbs_opaque);
+    }
+
+    return NULL;
+}
+
 static void red_display_create_stream(DisplayChannelClient *dcc, Stream *stream)
 {
     StreamAgent *agent = &dcc->stream_agents[get_stream_id(dcc->common.worker, stream)];
-    create_video_encoder_proc create_video_encoder;
 
     stream->refs++;
     spice_assert(region_is_empty(&agent->vis_region));
@@ -3092,12 +3129,6 @@  static void red_display_create_stream(DisplayChannelClient *dcc, Stream *stream)
     agent->fps = MAX_FPS;
     agent->dcc = dcc;
 
-#ifdef HAVE_GSTREAMER_0_10
-    create_video_encoder = &create_gstreamer_encoder;
-#else
-    create_video_encoder = &create_mjpeg_encoder;
-#endif
-
     if (dcc->use_video_encoder_rate_control) {
         VideoEncoderRateControlCbs video_cbs;
         uint64_t initial_bit_rate;
@@ -3107,10 +3138,13 @@  static void red_display_create_stream(DisplayChannelClient *dcc, Stream *stream)
         video_cbs.update_client_playback_delay = red_stream_update_client_playback_latency;
 
         initial_bit_rate = red_stream_get_initial_bit_rate(dcc, stream);
-        agent->video_encoder = create_video_encoder(initial_bit_rate, &video_cbs, agent);
+        agent->video_encoder = red_display_create_video_encoder(dcc, initial_bit_rate, &video_cbs, agent);
     } else {
-        agent->video_encoder = create_video_encoder(0, NULL, NULL);
+        agent->video_encoder = red_display_create_video_encoder(dcc, 0, NULL, NULL);
     }
+    /* FIXME: We may have failed to create a video encoder which will cause
+     *        a crash!
+     */
     red_channel_client_pipe_add(&dcc->common.base, &agent->create_item);
 
     if (red_channel_client_test_remote_cap(&dcc->common.base, SPICE_DISPLAY_CAP_STREAM_REPORT)) {
@@ -8938,7 +8972,7 @@  static void red_display_marshall_stream_start(RedChannelClient *rcc,
     stream_create.surface_id = 0;
     stream_create.id = get_stream_id(dcc->common.worker, stream);
     stream_create.flags = stream->top_down ? SPICE_STREAM_FLAGS_TOP_DOWN : 0;
-    stream_create.codec_type = SPICE_VIDEO_CODEC_TYPE_MJPEG;
+    stream_create.codec_type = agent->video_encoder->codec_type;
 
     stream_create.src_width = stream->width;
     stream_create.src_height = stream->height;
@@ -11729,6 +11763,15 @@  void handle_dev_set_streaming_video(void *opaque, void *payload)
     }
 }
 
+void handle_dev_set_video_codecs(void *opaque, void *payload)
+{
+    RedWorkerMessageSetVideoCodecs *msg = payload;
+    RedWorker *worker = opaque;
+
+    worker->num_video_codecs = msg->num_video_codecs;
+    memcpy(worker->video_codecs, msg->video_codecs, sizeof(worker->video_codecs));
+}
+
 void handle_dev_set_mouse_mode(void *opaque, void *payload)
 {
     RedWorkerMessageSetMouseMode *msg = payload;
@@ -12035,6 +12078,8 @@  static void red_init(RedWorker *worker, WorkerInitData *init_data)
     worker->jpeg_state = init_data->jpeg_state;
     worker->zlib_glz_state = init_data->zlib_glz_state;
     worker->streaming_video = init_data->streaming_video;
+    worker->num_video_codecs = init_data->num_video_codecs;
+    memcpy(worker->video_codecs, init_data->video_codecs, sizeof(worker->video_codecs));
     worker->driver_cap_monitors_config = 0;
     ring_init(&worker->current_list);
     image_cache_init(&worker->image_cache);
diff --git a/server/red_worker.h b/server/red_worker.h
index 272661f..f732a3e 100644
--- a/server/red_worker.h
+++ b/server/red_worker.h
@@ -21,6 +21,7 @@ 
 #include <unistd.h>
 #include <errno.h>
 #include "red_common.h"
+#include "video_encoder.h"
 
 enum {
     RED_WORKER_PENDING_WAKEUP,
@@ -69,6 +70,7 @@  enum {
 
     RED_WORKER_MESSAGE_MONITORS_CONFIG_ASYNC,
     RED_WORKER_MESSAGE_DRIVER_UNLOAD,
+    RED_WORKER_MESSAGE_SET_VIDEO_CODECS,
 
     RED_WORKER_MESSAGE_COUNT // LAST
 };
@@ -84,6 +86,20 @@  enum {
     RED_RENDERER_OGL_PIXMAP,
 };
 
+#define RED_MAX_VIDEO_CODECS 8
+
+typedef struct RedVideoCodec {
+    create_video_encoder_proc create;
+    SpiceVideoCodecType type;
+    uint32_t cap;
+} RedVideoCodec;
+
+enum {
+    SPICE_STREAMING_INVALID,
+    SPICE_STREAMING_SPICE,
+    SPICE_STREAMING_GSTREAMER
+};
+
 typedef struct RedDispatcher RedDispatcher;
 
 typedef struct WorkerInitData {
@@ -96,6 +112,8 @@  typedef struct WorkerInitData {
     spice_wan_compression_t jpeg_state;
     spice_wan_compression_t zlib_glz_state;
     int streaming_video;
+    uint32_t num_video_codecs;
+    RedVideoCodec video_codecs[RED_MAX_VIDEO_CODECS];
     uint32_t num_memslots;
     uint32_t num_memslots_groups;
     uint8_t memslot_gen_bits;
diff --git a/server/reds.c b/server/reds.c
index 3a38258..b8d845b 100644
--- a/server/reds.c
+++ b/server/reds.c
@@ -111,6 +111,7 @@  static int ticketing_enabled = 1; //Ticketing is enabled by default
 static pthread_mutex_t *lock_cs;
 static long *lock_count;
 uint32_t streaming_video = STREAM_VIDEO_FILTER;
+
 spice_image_compression_t image_compression = SPICE_IMAGE_COMPRESS_AUTO_GLZ;
 spice_wan_compression_t jpeg_state = SPICE_WAN_COMPRESSION_AUTO;
 spice_wan_compression_t zlib_glz_state = SPICE_WAN_COMPRESSION_AUTO;
@@ -3679,6 +3680,18 @@  SPICE_GNUC_VISIBLE int spice_server_set_streaming_video(SpiceServer *s, int valu
     return 0;
 }
 
+SPICE_GNUC_VISIBLE int spice_server_set_video_codecs(SpiceServer *s, const char* video_codecs)
+{
+    spice_assert(reds == s);
+
+    if (!red_dispatcher_set_video_codecs(video_codecs)) {
+        return -1;
+    }
+
+    red_dispatcher_on_vc_change();
+    return 0;
+}
+
 SPICE_GNUC_VISIBLE int spice_server_set_playback_compression(SpiceServer *s, int enable)
 {
     spice_assert(reds == s);
diff --git a/server/spice-server.h b/server/spice-server.h
index bca0da6..7d39e2e 100644
--- a/server/spice-server.h
+++ b/server/spice-server.h
@@ -107,6 +107,7 @@  enum {
 };
 
 int spice_server_set_streaming_video(SpiceServer *s, int value);
+int spice_server_set_video_codecs(SpiceServer *s, const char* video_codecs);
 int spice_server_set_playback_compression(SpiceServer *s, int enable);
 int spice_server_set_agent_mouse(SpiceServer *s, int enable);
 int spice_server_set_agent_copypaste(SpiceServer *s, int enable);
diff --git a/server/spice-server.syms b/server/spice-server.syms
index 2193811..9094a26 100644
--- a/server/spice-server.syms
+++ b/server/spice-server.syms
@@ -32,6 +32,7 @@  global:
     spice_server_set_playback_compression;
     spice_server_set_port;
     spice_server_set_streaming_video;
+    spice_server_set_video_codecs;
     spice_server_set_ticket;
     spice_server_set_tls;
     spice_server_set_zlib_glz_compression;
diff --git a/server/video_encoder.h b/server/video_encoder.h
index 70547b0..ab20443 100644
--- a/server/video_encoder.h
+++ b/server/video_encoder.h
@@ -119,6 +119,9 @@  struct VideoEncoder {
      *              statistics.
      */
     void (*get_stats)(VIDEO_ENCODER_T *encoder, VideoEncoderStats *stats);
+
+    /* The codec being used by the video encoder */
+    SpiceVideoCodecType codec_type;
 };
 
 
@@ -146,23 +149,28 @@  typedef struct VideoEncoderRateControlCbs {
 } VideoEncoderRateControlCbs;
 
 
-/* Instantiates the builtin MJPEG video encoder.
+/* Instantiates the a video encoder for the specified codec.
  *
- * @starting_bit_rate: An initial estimate of the available stream bit rate .
+ * @codec_type:        The codec to use.
+ * @starting_bit_rate: An initial estimate of the available stream bit rate.
  * @bit_rate_control:  True if the client supports rate control.
  * @cbs:               A set of callback methods to be used for rate control.
  * @cbs_opaque:        A pointer to be passed to the rate control callbacks.
  * @return:            A pointer to a structure implementing the VideoEncoder
  *                     methods.
  */
-typedef VIDEO_ENCODER_T* (*create_video_encoder_proc)(uint64_t starting_bit_rate, VideoEncoderRateControlCbs *cbs, void *cbs_opaque);
+typedef VIDEO_ENCODER_T* (*create_video_encoder_proc)(SpiceVideoCodecType codec_type, uint64_t starting_bit_rate, VideoEncoderRateControlCbs *cbs, void *cbs_opaque);
 
-VIDEO_ENCODER_T* create_mjpeg_encoder(uint64_t starting_bit_rate,
+VIDEO_ENCODER_T* create_mjpeg_encoder(SpiceVideoCodecType codec_type,
+                                      uint64_t starting_bit_rate,
                                       VideoEncoderRateControlCbs *cbs,
                                       void *cbs_opaque);
 
-VIDEO_ENCODER_T* create_gstreamer_encoder(uint64_t starting_bit_rate,
+VIDEO_ENCODER_T* create_gstreamer_encoder(SpiceVideoCodecType codec_type,
+                                          uint64_t starting_bit_rate,
                                           VideoEncoderRateControlCbs *cbs,
                                           void *cbs_opaque);
 
+#define VIDEO_ENCODER_DEFAULT_PREFERENCE "spice:mjpeg;gstreamer:mjpeg;gstreamer:vp8"
+
 #endif