<div dir="ltr"><br><div class="gmail_extra"><br><div class="gmail_quote">On Wed, May 13, 2015 at 10:28 PM, Francois Gouget <span dir="ltr"><<a href="mailto:fgouget@codeweavers.com" target="_blank">fgouget@codeweavers.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Preferences can be expressed by sending a semicolon-separated<br>
encoder:codec list. For instance spice:mjpeg for the original video<br>
encoder, and gstreamer:mjpeg for the new one.<br>
The client compatibility check relies on the following new capabilities:<br>
 * SPICE_DISPLAY_CAP_MULTI_CODEC which denotes a recent client that<br>
   supports multiple codecs. This capability is needed to not have to<br>
   hardcode that MJPEG is supported. This makes it possible to write<br>
   clients that don't support MJPEG.<br>
 * SPICE_DISPLAY_CAP_CODEC_XXX, where XXX is a supported codec, for now<br>
   MJPEG and VP8.<br>
---<br>
 server/gstreamer_encoder.c |  75 +++++++++++++++------<br>
 server/mjpeg_encoder.c     |   6 +-<br>
 server/red_dispatcher.c    | 159 ++++++++++++++++++++++++++++++++++++++++-----<br>
 server/red_dispatcher.h    |   8 +++<br>
 server/red_worker.c        |  62 +++++++++++++++---<br>
 server/red_worker.h        |  18 +++++<br>
 server/reds.c              |   8 +++<br>
 server/spice-server.h      |   1 +<br>
 server/spice-server.syms   |   1 +<br>
 server/video_encoder.h     |  16 +++--<br>
 10 files changed, 301 insertions(+), 53 deletions(-)<br>
<br>
diff --git a/server/gstreamer_encoder.c b/server/gstreamer_encoder.c<br>
index ef71b73..dcebc95 100644<br>
--- a/server/gstreamer_encoder.c<br>
+++ b/server/gstreamer_encoder.c<br>
@@ -42,6 +42,7 @@ typedef struct {<br>
<br>
 struct GstEncoder {<br>
     VideoEncoder base;<br>
+    const gchar* gstenc_name;<br>
<br>
     /* The GStreamer pipeline. If pipeline is NULL then the other pointers are<br>
      * invalid.<br>
@@ -139,11 +140,11 @@ static void adjust_bit_rate(GstEncoder *encoder)<br>
     {<br>
         /* Even MJPEG should achieve good quality with a 10x compression level */<br>
         encoder->bit_rate = raw_bit_rate / 10;<br>
-        spice_debug("capping the bit rate to %.2fMbps for a 10x compression level", ((double)encoder->bit_rate) / 1024 / 1024);<br>
+        spice_debug("capping the %s bit rate to %.2fMbps for a 10x compression level", encoder->gstenc_name, ((double)encoder->bit_rate) / 1024 / 1024);<br>
     }<br>
     else<br>
     {<br>
-        spice_debug("setting the bit rate to %.2fMbps for a %dx compression level", ((double)encoder->bit_rate) / 1024 / 1024, compression);<br>
+        spice_debug("setting the %s bit rate to %.2fMbps for a %dx compression level", encoder->gstenc_name, ((double)encoder->bit_rate) / 1024 / 1024, compression);<br>
     }<br>
 }<br>
<br>
@@ -177,9 +178,12 @@ static int construct_pipeline(GstEncoder *encoder, const SpiceBitmap *bitmap)<br>
 {<br>
     GstStateChangeReturn ret;<br>
     GError *err;<br>
+    gchar *desc;<br>
<br>
     err = NULL;<br>
-    encoder->pipeline = gst_parse_launch_full("appsrc name=src is-live=1 ! ffmpegcolorspace ! ffenc_mjpeg name=encoder ! appsink name=sink", NULL, GST_PARSE_FLAG_FATAL_ERRORS, &err);<br>
+    desc = g_strdup_printf("appsrc name=src is-live=1 ! ffmpegcolorspace ! %s name=encoder ! appsink name=sink", encoder->gstenc_name);<br>
+    encoder->pipeline = gst_parse_launch_full(desc, NULL, GST_PARSE_FLAG_FATAL_ERRORS, &err);<br>
+    g_free(desc);<br>
     if (!encoder->pipeline)<br>
     {<br>
         spice_warning("GStreamer error: %s", err->message);<br>
@@ -190,15 +194,18 @@ static int construct_pipeline(GstEncoder *encoder, const SpiceBitmap *bitmap)<br>
     encoder->gstenc = gst_bin_get_by_name(GST_BIN(encoder->pipeline), "encoder");<br>
     encoder->appsink = GST_APP_SINK(gst_bin_get_by_name(GST_BIN(encoder->pipeline), "sink"));<br>
<br>
+    /* Set the encoder initial bit rate, and ask for a low latency */<br>
+    adjust_bit_rate(encoder);<br>
+    g_object_set(G_OBJECT(encoder->gstenc), "bitrate", encoder->bit_rate, NULL);<br>
+    g_object_set(G_OBJECT(encoder->gstenc), "max-latency", 0, NULL);<br>
+    g_object_set(G_OBJECT(encoder->gstenc), "max-keyframe-distance", 0, NULL);<br>
+    g_object_set(G_OBJECT(encoder->gstenc), "lag-in-frames", 0, NULL);<br>
+<br></blockquote><div><br></div><div>Those paremeters do not exist with all encoders, notably ffenc_mjpeg. We could rather use or define encodebin profiles.<br> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
     /* Set the source caps */<br>
     encoder->src_caps = NULL;<br>
     if (!set_appsrc_caps(encoder))<br>
         return FALSE;<br>
<br>
-    /* Set the encoder initial bit rate */<br>
-    adjust_bit_rate(encoder);<br>
-    g_object_set(G_OBJECT(encoder->gstenc), "bitrate", encoder->bit_rate, NULL);<br>
-<br>
     /* Start playing */<br>
     gst_pipeline_use_clock(GST_PIPELINE(encoder->pipeline), NULL);<br>
     ret = gst_element_set_state(encoder->pipeline, GST_STATE_PLAYING);<br>
@@ -210,16 +217,18 @@ static int construct_pipeline(GstEncoder *encoder, const SpiceBitmap *bitmap)<br>
     return TRUE;<br>
 }<br>
<br>
-static int reconfigure_pipeline(GstEncoder *encoder)<br>
+static void reconfigure_pipeline(GstEncoder *encoder)<br>
 {<br>
-    if (gst_element_set_state(encoder->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE ||<br>
-        !set_appsrc_caps(encoder) ||<br>
-        gst_element_set_state(encoder->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {<br>
-        spice_warning("GStreamer error: the pipeline reconfiguration failed");<br>
+    if (encoder->base.codec_type == SPICE_VIDEO_CODEC_TYPE_VP8) {<br>
+        /* vp8enc gets confused if we try to reconfigure the pipeline */<br></blockquote><div><br></div><div>You'll easily reach this condition with CAP_SIZED_STREAM streams. Tbh, I don't think the video stream should be able to change every frame, but it's something we need to fix in the video handling/detection code first.<br></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
         reset_pipeline(encoder);<br>
-        return FALSE;<br>
     }<br>
-    return TRUE;<br>
+    else if (gst_element_set_state(encoder->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE ||<br>
+             !set_appsrc_caps(encoder) ||<br>
+             gst_element_set_state(encoder->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {<br>
+        spice_debug("GStreamer error: the pipeline reconfiguration failed, rebuilding it instead");<br>
+        reset_pipeline(encoder); /* we can rebuild it... */<br>
+    }<br>
 }<br>
<br>
 static inline uint8_t *get_image_line(SpiceChunks *chunks, size_t *offset,<br>
@@ -250,7 +259,7 @@ static inline uint8_t *get_image_line(SpiceChunks *chunks, size_t *offset,<br>
<br>
<br>
 static int push_raw_frame(GstEncoder *encoder, const SpiceBitmap *bitmap,<br>
-                          const SpiceRect *src, int top_down)<br>
+                          const SpiceRect *src, int top_down, uint32_t frame_time)<br>
 {<br>
     SpiceChunks *chunks;<br>
     uint32_t image_stride;<br>
@@ -298,8 +307,17 @@ static int push_raw_frame(GstEncoder *encoder, const SpiceBitmap *bitmap,<br>
     /* The GStreamer buffer timestamps and framerate are irrelevant and would<br>
      * be hard to set right because they can arrive a bit irregularly<br>
      */<br>
-    GST_BUFFER_TIMESTAMP(buffer) = GST_CLOCK_TIME_NONE;<br>
-    GST_BUFFER_DURATION(buffer) = GST_CLOCK_TIME_NONE;<br>
+    if (encoder->base.codec_type == SPICE_VIDEO_CODEC_TYPE_VP8)<br>
+    {<br>
+        /* FIXME: Maybe try drop-frame = 0 instead? */<br>
+        GST_BUFFER_TIMESTAMP(buffer) = frame_time;<br>
+        GST_BUFFER_DURATION(buffer) = 1;<br>
+    }<br>
+    else<br>
+    {<br>
+        GST_BUFFER_TIMESTAMP(buffer) = GST_CLOCK_TIME_NONE;<br>
+        GST_BUFFER_DURATION(buffer) = GST_CLOCK_TIME_NONE;<br>
+    }<br></blockquote><div><br></div><div>What is this supposed to change? Did you consider appsrc do-timestamp instead?<br> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
     GST_BUFFER_OFFSET(buffer) = encoder->frame++;<br>
     gst_buffer_set_caps(buffer, encoder->src_caps);<br>
<br>
@@ -364,13 +382,13 @@ static int gst_encoder_encode_frame(GstEncoder *encoder,<br>
         encoder->spice_format = bitmap->format;<br>
         encoder->width = width;<br>
         encoder->height = height;<br>
-        if (encoder->pipeline && !reconfigure_pipeline(encoder))<br>
-            return VIDEO_ENCODER_FRAME_UNSUPPORTED;<br>
+        if (encoder->pipeline)<br>
+            reconfigure_pipeline(encoder);<br></blockquote><div><br></div><div>Why this change?<br> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
     }<br>
     if (!encoder->pipeline && !construct_pipeline(encoder, bitmap))<br>
         return VIDEO_ENCODER_FRAME_DROP;<br>
<br>
-    rc = push_raw_frame(encoder, bitmap, src, top_down);<br>
+    rc = push_raw_frame(encoder, bitmap, src, top_down, frame_mm_time);<br>
     if (rc == VIDEO_ENCODER_FRAME_ENCODE_DONE)<br>
         rc = pull_compressed_buffer(encoder, outbuf, outbuf_size, data_size);<br>
     return rc;<br>
@@ -413,11 +431,24 @@ void gst_encoder_get_stats(GstEncoder *encoder, VideoEncoderStats *stats)<br>
         stats->avg_quality = 0;<br>
 }<br>
<br>
-GstEncoder *create_gstreamer_encoder(uint64_t starting_bit_rate, VideoEncoderRateControlCbs *cbs, void *cbs_opaque)<br>
+GstEncoder *create_gstreamer_encoder(SpiceVideoCodecType codec_type, uint64_t starting_bit_rate, VideoEncoderRateControlCbs *cbs, void *cbs_opaque)<br>
 {<br>
     GstEncoder *encoder;<br>
+    const gchar *gstenc_name;<br>
<br>
     spice_assert(!cbs || (cbs && cbs->get_roundtrip_ms && cbs->get_source_fps));<br>
+    switch (codec_type)<br>
+    {<br>
+    case SPICE_VIDEO_CODEC_TYPE_MJPEG:<br>
+        gstenc_name = "ffenc_mjpeg";<br>
+        break;<br>
+    case SPICE_VIDEO_CODEC_TYPE_VP8:<br>
+        gstenc_name = "vp8enc";<br>
+        break;<br>
+    default:<br>
+        spice_warning("unsupported codec type %d", codec_type);<br>
+        return NULL;<br>
+    }<br>
<br>
     gst_init(NULL, NULL);<br>
<br>
@@ -428,6 +459,8 @@ GstEncoder *create_gstreamer_encoder(uint64_t starting_bit_rate, VideoEncoderRat<br>
     encoder->base.notify_server_frame_drop = &gst_encoder_notify_server_frame_drop;<br>
     encoder->base.get_bit_rate = &gst_encoder_get_bit_rate;<br>
     encoder->base.get_stats = &gst_encoder_get_stats;<br>
+    encoder->base.codec_type = codec_type;<br>
+    encoder->gstenc_name = gstenc_name;<br>
     encoder->pipeline = NULL;<br>
<br>
     if (cbs)<br>
diff --git a/server/mjpeg_encoder.c b/server/mjpeg_encoder.c<br>
index d734520..479e120 100644<br>
--- a/server/mjpeg_encoder.c<br>
+++ b/server/mjpeg_encoder.c<br>
@@ -210,7 +210,8 @@ static inline int rate_control_is_active(MJpegEncoder* encoder)<br>
     return encoder->cbs.get_roundtrip_ms != NULL;<br>
 }<br>
<br>
-MJpegEncoder *create_mjpeg_encoder(uint64_t starting_bit_rate,<br>
+MJpegEncoder *create_mjpeg_encoder(SpiceVideoCodecType codec_type,<br>
+                                   uint64_t starting_bit_rate,<br>
                                    VideoEncoderRateControlCbs *cbs,<br>
                                    void *cbs_opaque)<br>
 {<br>
@@ -218,6 +219,8 @@ MJpegEncoder *create_mjpeg_encoder(uint64_t starting_bit_rate,<br>
<br>
     spice_assert(!cbs || (cbs && cbs->get_roundtrip_ms && cbs->get_source_fps));<br>
<br>
+    if (codec_type != SPICE_VIDEO_CODEC_TYPE_MJPEG)<br>
+        return NULL;<br>
     enc = spice_new0(MJpegEncoder, 1);<br>
<br>
     enc->base.destroy = &mjpeg_encoder_destroy;<br>
@@ -226,6 +229,7 @@ MJpegEncoder *create_mjpeg_encoder(uint64_t starting_bit_rate,<br>
     enc->base.notify_server_frame_drop = &mjpeg_encoder_notify_server_frame_drop;<br>
     enc->base.get_bit_rate = &mjpeg_encoder_get_bit_rate;<br>
     enc->base.get_stats = &mjpeg_encoder_get_stats;<br>
+    enc->base.codec_type = codec_type;<br>
     enc->first_frame = TRUE;<br>
     enc->rate_control.byte_rate = starting_bit_rate / 8;<br>
     enc->starting_bit_rate = starting_bit_rate;<br>
diff --git a/server/red_dispatcher.c b/server/red_dispatcher.c<br>
index f5f3e52..89ffa72 100644<br>
--- a/server/red_dispatcher.c<br>
+++ b/server/red_dispatcher.c<br>
@@ -27,6 +27,7 @@<br>
 #include <sys/socket.h><br>
 #include <signal.h><br>
 #include <inttypes.h><br>
+#include <ctype.h><br>
<br>
 #include <spice/qxl_dev.h><br>
 #include "common/quic.h"<br>
@@ -204,43 +205,151 @@ static void red_dispatcher_cursor_migrate(RedChannelClient *rcc)<br>
                             &payload);<br>
 }<br>
<br>
-typedef struct RendererInfo {<br>
-    int id;<br>
+typedef struct {<br>
+    uint32_t id;<br>
     const char *name;<br>
-} RendererInfo;<br>
+} EnumNames;<br>
<br>
-static RendererInfo renderers_info[] = {<br>
+static int name_to_enum(const EnumNames names[], const char *string, uint32_t *id)<br>
+{<br>
+    if (string) {<br>
+        int i = 0;<br>
+        while (names[i].name) {<br>
+            if (strcmp(string, names[i].name) == 0)<br>
+            {<br>
+                *id = names[i].id;<br>
+                return TRUE;<br>
+            }<br>
+            i++;<br>
+        }<br>
+    }<br>
+    return FALSE;<br>
+}<br>
+<br>
+static const EnumNames renderer_names[] = {<br>
     {RED_RENDERER_SW, "sw"},<br>
 #ifdef USE_OPENGL<br>
     {RED_RENDERER_OGL_PBUF, "oglpbuf"},<br>
     {RED_RENDERER_OGL_PIXMAP, "oglpixmap"},<br>
 #endif<br>
-    {RED_RENDERER_INVALID, NULL},<br>
+    {0, NULL},<br>
 };<br>
<br>
 static uint32_t renderers[RED_MAX_RENDERERS];<br>
 static uint32_t num_renderers = 0;<br>
<br>
-static RendererInfo *find_renderer(const char *name)<br>
+int red_dispatcher_add_renderer(const char *name)<br>
 {<br>
-    RendererInfo *inf = renderers_info;<br>
-    while (inf->name) {<br>
-        if (strcmp(name, inf->name) == 0) {<br>
-            return inf;<br>
+    uint32_t renderer;<br>
+<br>
+    if (num_renderers == RED_MAX_RENDERERS ||<br>
+        !name_to_enum(renderer_names, name, &renderer))<br>
+        return FALSE;<br>
+    renderers[num_renderers++] = renderer;<br>
+    return TRUE;<br>
+}<br>
+<br>
+static const EnumNames video_encoder_names[] = {<br>
+    {0, "spice"},<br>
+    {1, "gstreamer"},<br>
+    {0, NULL},<br>
+};<br>
+<br>
+static create_video_encoder_proc video_encoder_procs[] = {<br>
+    &create_mjpeg_encoder,<br>
+#ifdef HAVE_GSTREAMER_0_10<br>
+    &create_gstreamer_encoder,<br>
+#else<br>
+    NULL,<br>
+#endif<br>
+};<br>
+<br>
+static const EnumNames video_codec_names[] = {<br>
+    {SPICE_VIDEO_CODEC_TYPE_MJPEG, "mjpeg"},<br>
+    {SPICE_VIDEO_CODEC_TYPE_VP8, "vp8"},<br>
+    {0, NULL},<br>
+};<br>
+<br>
+static const EnumNames video_cap_names[] = {<br>
+    {SPICE_DISPLAY_CAP_CODEC_MJPEG, "mjpeg"},<br>
+    {SPICE_DISPLAY_CAP_CODEC_VP8, "vp8"},<br>
+    {0, NULL},<br>
+};<br>
+<br>
+<br>
+static RedVideoCodec video_codecs[RED_MAX_VIDEO_CODECS];<br>
+static uint32_t num_video_codecs = 0;<br>
+<br>
+/* Expected string:  encoder:codec;encoder:codec */<br>
+static const char* parse_video_codecs(const char *codecs, char **encoder,<br>
+                                      char **codec)<br>
+{<br>
+    const char* e;<br>
+    while (*codecs == ';')<br>
+        codecs++;<br>
+    if (!*codecs)<br>
+        return NULL;<br>
+    e = codecs;<br>
+    while (isalnum(*codecs) || *codecs == '_')<br>
+        codecs++;<br>
+    if (*codecs == ':')<br>
+    {<br>
+        const char* c = ++codecs;<br>
+        while (isalnum(*codecs) || *codecs == '_')<br>
+            codecs++;<br>
+        if (!*codecs || *codecs == ';')<br>
+        {<br>
+            int len = c - e - 1;<br>
+            *encoder = malloc(len + 1);<br>
+            strncpy(*encoder, e, len);<br>
+            (*encoder)[len] = '\0';<br>
+<br>
+            len =  codecs - c;<br>
+            *codec = malloc(len + 1);<br>
+            strncpy(*codec, c, len);<br>
+            (*codec)[len] = '\0';<br>
+            return codecs;<br>
         }<br>
-        inf++;<br>
     }<br>
+    spice_warning("spice: invalid encoder:codec value: %s", e);<br>
     return NULL;<br>
 }<br>
<br>
-int red_dispatcher_add_renderer(const char *name)<br>
+int red_dispatcher_set_video_codecs(const char *codecs)<br>
 {<br>
-    RendererInfo *inf;<br>
+    uint32_t encoder;<br>
+    SpiceVideoCodecType type;<br>
+    uint32_t cap;<br>
+    char *encoder_name, *codec_name;<br>
+    static RedVideoCodec new_codecs[RED_MAX_VIDEO_CODECS];<br>
+    int count;<br>
+    const char* c = codecs;<br>
<br>
-    if (num_renderers == RED_MAX_RENDERERS || !(inf = find_renderer(name))) {<br>
-        return FALSE;<br>
+    count = 0;<br>
+    while ( (c = parse_video_codecs(c, &encoder_name, &codec_name)) ) {<br>
+        int convert_failed = FALSE;<br>
+        if (count == RED_MAX_VIDEO_CODECS ||<br>
+            !name_to_enum(video_encoder_names, encoder_name, &encoder) ||<br>
+            !name_to_enum(video_codec_names, codec_name, &type) ||<br>
+            !name_to_enum(video_cap_names, codec_name, &cap) ||<br>
+            !video_encoder_procs[encoder])<br>
+            convert_failed = TRUE;<br>
+<br>
+        free(encoder_name);<br>
+        free(codec_name);<br>
+<br>
+        if (convert_failed)<br>
+            return FALSE;<br>
+<br>
+        new_codecs[count].create = video_encoder_procs[encoder];<br>
+        new_codecs[count].type = type;<br>
+        new_codecs[count].cap = cap;<br>
+        count++;<br>
     }<br>
-    renderers[num_renderers++] = inf->id;<br>
+    num_video_codecs = count;<br>
+    memcpy(video_codecs, new_codecs, sizeof(video_codecs));<br>
+    red_dispatcher_on_vc_change();<br>
+<br>
     return TRUE;<br>
 }<br>
<br>
@@ -783,6 +892,22 @@ void red_dispatcher_on_sv_change(void)<br>
     }<br>
 }<br>
<br>
+void red_dispatcher_on_vc_change(void)<br>
+{<br>
+    RedWorkerMessageSetVideoCodecs payload;<br>
+    int compression_level = calc_compression_level();<br>
+    RedDispatcher *now = dispatchers;<br>
+    while (now) {<br>
+        now->qxl->st->qif->set_compression_level(now->qxl, compression_level);<br>
+        payload.num_video_codecs = num_video_codecs;<br>
+        payload.video_codecs = video_codecs;<br>
+        dispatcher_send_message(&now->dispatcher,<br>
+                                RED_WORKER_MESSAGE_SET_VIDEO_CODECS,<br>
+                                &payload);<br>
+        now = now->next;<br>
+    }<br>
+}<br>
+<br>
 void red_dispatcher_set_mouse_mode(uint32_t mode)<br>
 {<br>
     RedWorkerMessageSetMouseMode payload;<br>
@@ -1084,6 +1209,8 @@ void red_dispatcher_init(QXLInstance *qxl)<br>
     init_data.pending = &red_dispatcher->pending;<br>
     init_data.num_renderers = num_renderers;<br>
     memcpy(init_data.renderers, renderers, sizeof(init_data.renderers));<br>
+    init_data.num_video_codecs = num_video_codecs;<br>
+    memcpy(init_data.video_codecs, video_codecs, sizeof(init_data.video_codecs));<br>
<br>
     pthread_mutex_init(&red_dispatcher->async_lock, NULL);<br>
     init_data.image_compression = image_compression;<br>
diff --git a/server/red_dispatcher.h b/server/red_dispatcher.h<br>
index 907b7c7..eb3aee4 100644<br>
--- a/server/red_dispatcher.h<br>
+++ b/server/red_dispatcher.h<br>
@@ -22,6 +22,7 @@<br>
<br>
 struct RedChannelClient;<br>
 struct RedDispatcher;<br>
+typedef struct RedVideoCodec RedVideoCodec;<br>
 typedef struct AsyncCommand AsyncCommand;<br>
<br>
 void red_dispatcher_init(QXLInstance *qxl);<br>
@@ -29,11 +30,13 @@ void red_dispatcher_init(QXLInstance *qxl);<br>
 void red_dispatcher_set_mm_time(uint32_t);<br>
 void red_dispatcher_on_ic_change(void);<br>
 void red_dispatcher_on_sv_change(void);<br>
+void red_dispatcher_on_vc_change(void);<br>
 void red_dispatcher_set_mouse_mode(uint32_t mode);<br>
 void red_dispatcher_on_vm_stop(void);<br>
 void red_dispatcher_on_vm_start(void);<br>
 int red_dispatcher_count(void);<br>
 int red_dispatcher_add_renderer(const char *name);<br>
+int red_dispatcher_set_video_codecs(const char* codecs);<br>
 uint32_t red_dispatcher_qxl_ram_size(void);<br>
 int red_dispatcher_qxl_count(void);<br>
 void red_dispatcher_async_complete(struct RedDispatcher *, AsyncCommand *);<br>
@@ -174,6 +177,11 @@ typedef struct RedWorkerMessageSetStreamingVideo {<br>
     uint32_t streaming_video;<br>
 } RedWorkerMessageSetStreamingVideo;<br>
<br>
+typedef struct RedWorkerMessageSetVideoCodecs {<br>
+    uint32_t num_video_codecs;<br>
+    RedVideoCodec* video_codecs;<br>
+} RedWorkerMessageSetVideoCodecs;<br>
+<br>
 typedef struct RedWorkerMessageSetMouseMode {<br>
     uint32_t mode;<br>
 } RedWorkerMessageSetMouseMode;<br>
diff --git a/server/red_worker.c b/server/red_worker.c<br>
index 19e27c5..8e94e26 100644<br>
--- a/server/red_worker.c<br>
+++ b/server/red_worker.c<br>
@@ -1,6 +1,7 @@<br>
 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */<br>
 /*<br>
    Copyright (C) 2009 Red Hat, Inc.<br>
+   Copyright (C) 2015 Francois Gouget<br>
<br>
    This library is free software; you can redistribute it and/or<br>
    modify it under the terms of the GNU Lesser General Public<br>
@@ -994,6 +995,8 @@ typedef struct RedWorker {<br>
     uint32_t mouse_mode;<br>
<br>
     uint32_t streaming_video;<br>
+    uint32_t num_video_codecs;<br>
+    RedVideoCodec video_codecs[RED_MAX_VIDEO_CODECS];<br>
     Stream streams_buf[NUM_STREAMS];<br>
     Stream *free_streams;<br>
     Ring streams;<br>
@@ -3074,10 +3077,41 @@ static void red_stream_update_client_playback_latency(void *opaque, uint32_t del<br>
     main_dispatcher_set_mm_time_latency(agent->dcc->common.base.client, agent->dcc->streams_max_latency);<br>
 }<br>
<br>
+static VideoEncoder* red_display_create_video_encoder(DisplayChannelClient *dcc, uint64_t starting_bit_rate, VideoEncoderRateControlCbs *cbs, void *cbs_opaque)<br>
+{<br>
+    RedWorker* worker = dcc->common.worker;<br>
+    int i;<br>
+    int client_has_multi_codec = red_channel_client_test_remote_cap(&dcc->common.base, SPICE_DISPLAY_CAP_MULTI_CODEC);<br>
+<br>
+    for (i = 0; i < worker->num_video_codecs; i++)<br>
+    {<br>
+        RedVideoCodec* video_codec = &worker->video_codecs[i];<br>
+        VideoEncoder* video_encoder;<br>
+<br>
+        if (!client_has_multi_codec &&<br>
+            video_codec->type != SPICE_VIDEO_CODEC_TYPE_MJPEG)<br>
+            /* Old clients only support MJPEG */<br>
+            continue;<br>
+<br>
+        if (client_has_multi_codec && !red_channel_client_test_remote_cap(&dcc->common.base, video_codec->cap))<br>
+            /* The client is recent but does not support this codec */<br>
+            continue;<br>
+<br>
+        video_encoder = video_codec->create(video_codec->type, starting_bit_rate, cbs, cbs_opaque);<br>
+        if (video_encoder)<br>
+            return video_encoder;<br>
+    }<br>
+<br>
+    /* Try to use the builtin MJPEG video encoder as a fallback */<br>
+    if (!client_has_multi_codec || red_channel_client_test_remote_cap(&dcc->common.base, SPICE_DISPLAY_CAP_CODEC_MJPEG))<br>
+        return create_mjpeg_encoder(SPICE_VIDEO_CODEC_TYPE_MJPEG, starting_bit_rate, cbs, cbs_opaque);<br>
+<br>
+    return NULL;<br>
+}<br>
+<br>
 static void red_display_create_stream(DisplayChannelClient *dcc, Stream *stream)<br>
 {<br>
     StreamAgent *agent = &dcc->stream_agents[get_stream_id(dcc->common.worker, stream)];<br>
-    create_video_encoder_proc create_video_encoder;<br>
<br>
     stream->refs++;<br>
     spice_assert(region_is_empty(&agent->vis_region));<br>
@@ -3092,12 +3126,6 @@ static void red_display_create_stream(DisplayChannelClient *dcc, Stream *stream)<br>
     agent->fps = MAX_FPS;<br>
     agent->dcc = dcc;<br>
<br>
-#ifdef HAVE_GSTREAMER_0_10<br>
-    create_video_encoder = &create_gstreamer_encoder;<br>
-#else<br>
-    create_video_encoder = &create_mjpeg_encoder;<br>
-#endif<br>
-<br>
     if (dcc->use_video_encoder_rate_control) {<br>
         VideoEncoderRateControlCbs video_cbs;<br>
         uint64_t initial_bit_rate;<br>
@@ -3107,10 +3135,13 @@ static void red_display_create_stream(DisplayChannelClient *dcc, Stream *stream)<br>
         video_cbs.update_client_playback_delay = red_stream_update_client_playback_latency;<br>
<br>
         initial_bit_rate = red_stream_get_initial_bit_rate(dcc, stream);<br>
-        agent->video_encoder = create_video_encoder(initial_bit_rate, &video_cbs, agent);<br>
+        agent->video_encoder = red_display_create_video_encoder(dcc, initial_bit_rate, &video_cbs, agent);<br>
     } else {<br>
-        agent->video_encoder = create_video_encoder(0, NULL, NULL);<br>
+        agent->video_encoder = red_display_create_video_encoder(dcc, 0, NULL, NULL);<br>
     }<br>
+    /* FIXME: We may have failed to create a video encoder which will cause<br>
+     *        a crash!<br>
+     */<br></blockquote><div><br></div><div>You may disable video streaming in this case? (worker->streaming_video == STREAM_VIDEO_OFF)<br> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
     red_channel_client_pipe_add(&dcc->common.base, &agent->create_item);<br>
<br>
     if (red_channel_client_test_remote_cap(&dcc->common.base, SPICE_DISPLAY_CAP_STREAM_REPORT)) {<br>
@@ -8938,7 +8969,7 @@ static void red_display_marshall_stream_start(RedChannelClient *rcc,<br>
     stream_create.surface_id = 0;<br>
     <a href="http://stream_create.id" target="_blank">stream_create.id</a> = get_stream_id(dcc->common.worker, stream);<br>
     stream_create.flags = stream->top_down ? SPICE_STREAM_FLAGS_TOP_DOWN : 0;<br>
-    stream_create.codec_type = SPICE_VIDEO_CODEC_TYPE_MJPEG;<br>
+    stream_create.codec_type = agent->video_encoder->codec_type;<br>
<br>
     stream_create.src_width = stream->width;<br>
     stream_create.src_height = stream->height;<br>
@@ -11729,6 +11760,15 @@ void handle_dev_set_streaming_video(void *opaque, void *payload)<br>
     }<br>
 }<br>
<br>
+void handle_dev_set_video_codecs(void *opaque, void *payload)<br>
+{<br>
+    RedWorkerMessageSetVideoCodecs *msg = payload;<br>
+    RedWorker *worker = opaque;<br>
+<br>
+    worker->num_video_codecs = msg->num_video_codecs;<br>
+    memcpy(worker->video_codecs, msg->video_codecs, sizeof(worker->video_codecs));<br>
+}<br>
+<br>
 void handle_dev_set_mouse_mode(void *opaque, void *payload)<br>
 {<br>
     RedWorkerMessageSetMouseMode *msg = payload;<br>
@@ -12035,6 +12075,8 @@ static void red_init(RedWorker *worker, WorkerInitData *init_data)<br>
     worker->jpeg_state = init_data->jpeg_state;<br>
     worker->zlib_glz_state = init_data->zlib_glz_state;<br>
     worker->streaming_video = init_data->streaming_video;<br>
+    worker->num_video_codecs = init_data->num_video_codecs;<br>
+    memcpy(worker->video_codecs, init_data->video_codecs, sizeof(worker->video_codecs));<br>
     worker->driver_cap_monitors_config = 0;<br>
     ring_init(&worker->current_list);<br>
     image_cache_init(&worker->image_cache);<br>
diff --git a/server/red_worker.h b/server/red_worker.h<br>
index 272661f..f732a3e 100644<br>
--- a/server/red_worker.h<br>
+++ b/server/red_worker.h<br>
@@ -21,6 +21,7 @@<br>
 #include <unistd.h><br>
 #include <errno.h><br>
 #include "red_common.h"<br>
+#include "video_encoder.h"<br>
<br>
 enum {<br>
     RED_WORKER_PENDING_WAKEUP,<br>
@@ -69,6 +70,7 @@ enum {<br>
<br>
     RED_WORKER_MESSAGE_MONITORS_CONFIG_ASYNC,<br>
     RED_WORKER_MESSAGE_DRIVER_UNLOAD,<br>
+    RED_WORKER_MESSAGE_SET_VIDEO_CODECS,<br>
<br>
     RED_WORKER_MESSAGE_COUNT // LAST<br>
 };<br>
@@ -84,6 +86,20 @@ enum {<br>
     RED_RENDERER_OGL_PIXMAP,<br>
 };<br>
<br>
+#define RED_MAX_VIDEO_CODECS 8<br>
+<br>
+typedef struct RedVideoCodec {<br>
+    create_video_encoder_proc create;<br>
+    SpiceVideoCodecType type;<br>
+    uint32_t cap;<br>
+} RedVideoCodec;<br>
+<br>
+enum {<br>
+    SPICE_STREAMING_INVALID,<br>
+    SPICE_STREAMING_SPICE,<br>
+    SPICE_STREAMING_GSTREAMER<br>
+};<br>
+<br>
 typedef struct RedDispatcher RedDispatcher;<br>
<br>
 typedef struct WorkerInitData {<br>
@@ -96,6 +112,8 @@ typedef struct WorkerInitData {<br>
     spice_wan_compression_t jpeg_state;<br>
     spice_wan_compression_t zlib_glz_state;<br>
     int streaming_video;<br>
+    uint32_t num_video_codecs;<br>
+    RedVideoCodec video_codecs[RED_MAX_VIDEO_CODECS];<br>
     uint32_t num_memslots;<br>
     uint32_t num_memslots_groups;<br>
     uint8_t memslot_gen_bits;<br>
diff --git a/server/reds.c b/server/reds.c<br>
index 6d70b68..93d8141 100644<br>
--- a/server/reds.c<br>
+++ b/server/reds.c<br>
@@ -111,6 +111,7 @@ static int ticketing_enabled = 1; //Ticketing is enabled by default<br>
 static pthread_mutex_t *lock_cs;<br>
 static long *lock_count;<br>
 uint32_t streaming_video = STREAM_VIDEO_FILTER;<br>
+<br>
 spice_image_compression_t image_compression = SPICE_IMAGE_COMPRESS_AUTO_GLZ;<br>
 spice_wan_compression_t jpeg_state = SPICE_WAN_COMPRESSION_AUTO;<br>
 spice_wan_compression_t zlib_glz_state = SPICE_WAN_COMPRESSION_AUTO;<br>
@@ -3671,6 +3672,13 @@ SPICE_GNUC_VISIBLE int spice_server_set_streaming_video(SpiceServer *s, int valu<br>
     return 0;<br>
 }<br>
<br>
+SPICE_GNUC_VISIBLE int spice_server_set_video_codecs(SpiceServer *s, const char* video_codecs)<br>
+{<br>
+    spice_assert(reds == s);<br>
+<br>
+    return red_dispatcher_set_video_codecs(video_codecs) ? 0 : -1;<br>
+}<br>
+<br>
 SPICE_GNUC_VISIBLE int spice_server_set_playback_compression(SpiceServer *s, int enable)<br>
 {<br>
     spice_assert(reds == s);<br>
diff --git a/server/spice-server.h b/server/spice-server.h<br>
index bca0da6..7d39e2e 100644<br>
--- a/server/spice-server.h<br>
+++ b/server/spice-server.h<br>
@@ -107,6 +107,7 @@ enum {<br>
 };<br>
<br>
 int spice_server_set_streaming_video(SpiceServer *s, int value);<br>
+int spice_server_set_video_codecs(SpiceServer *s, const char* video_codecs);<br>
 int spice_server_set_playback_compression(SpiceServer *s, int enable);<br>
 int spice_server_set_agent_mouse(SpiceServer *s, int enable);<br>
 int spice_server_set_agent_copypaste(SpiceServer *s, int enable);<br>
diff --git a/server/spice-server.syms b/server/spice-server.syms<br>
index 2193811..9094a26 100644<br>
--- a/server/spice-server.syms<br>
+++ b/server/spice-server.syms<br>
@@ -32,6 +32,7 @@ global:<br>
     spice_server_set_playback_compression;<br>
     spice_server_set_port;<br>
     spice_server_set_streaming_video;<br>
+    spice_server_set_video_codecs;<br>
     spice_server_set_ticket;<br>
     spice_server_set_tls;<br>
     spice_server_set_zlib_glz_compression;<br>
diff --git a/server/video_encoder.h b/server/video_encoder.h<br>
index d5f7fb8..422b6c3 100644<br>
--- a/server/video_encoder.h<br>
+++ b/server/video_encoder.h<br>
@@ -119,6 +119,9 @@ struct VideoEncoder {<br>
      *              statistics.<br>
      */<br>
     void (*get_stats)(VIDEO_ENCODER_T *encoder, VideoEncoderStats *stats);<br>
+<br>
+    /* The codec being used by the video encoder */<br>
+    SpiceVideoCodecType codec_type;<br>
 };<br>
<br>
<br>
@@ -146,22 +149,25 @@ typedef struct VideoEncoderRateControlCbs {<br>
 } VideoEncoderRateControlCbs;<br>
<br>
<br>
-/* Instantiates the builtin MJPEG video encoder.<br>
+/* Instantiates the a video encoder for the specified codec.<br>
  *<br>
- * @starting_bit_rate: An initial estimate of the available stream bit rate .<br>
+ * @codec_type:        The codec to use.<br>
+ * @starting_bit_rate: An initial estimate of the available stream bit rate.<br>
  * @bit_rate_control:  True if the client supports rate control.<br>
  * @cbs:               A set of callback methods to be used for rate control.<br>
  * @cbs_opaque:        A pointer to be passed to the rate control callbacks.<br>
  * @return:            A pointer to a structure implementing the VideoEncoder<br>
  *                     methods.<br>
  */<br>
-typedef VIDEO_ENCODER_T* (*create_video_encoder_proc)(uint64_t starting_bit_rate, VideoEncoderRateControlCbs *cbs, void *cbs_opaque);<br>
+typedef VIDEO_ENCODER_T* (*create_video_encoder_proc)(SpiceVideoCodecType codec_type, uint64_t starting_bit_rate, VideoEncoderRateControlCbs *cbs, void *cbs_opaque);<br>
<br>
-VIDEO_ENCODER_T* create_mjpeg_encoder(uint64_t starting_bit_rate,<br>
+VIDEO_ENCODER_T* create_mjpeg_encoder(SpiceVideoCodecType codec_type,<br>
+                                      uint64_t starting_bit_rate,<br>
                                       VideoEncoderRateControlCbs *cbs,<br>
                                       void *cbs_opaque);<br>
<br>
-VIDEO_ENCODER_T* create_gstreamer_encoder(uint64_t starting_bit_rate,<br>
+VIDEO_ENCODER_T* create_gstreamer_encoder(SpiceVideoCodecType codec_type,<br>
+                                          uint64_t starting_bit_rate,<br>
                                           VideoEncoderRateControlCbs *cbs,<br>
                                           void *cbs_opaque);<br>
<span class=""><font color="#888888"><br>
--<br>
2.1.4<br>
_______________________________________________<br>
Spice-devel mailing list<br>
<a href="mailto:Spice-devel@lists.freedesktop.org">Spice-devel@lists.freedesktop.org</a><br>
<a href="http://lists.freedesktop.org/mailman/listinfo/spice-devel" target="_blank">http://lists.freedesktop.org/mailman/listinfo/spice-devel</a><br>
</font></span></blockquote></div><br></div><div class="gmail_extra"><br>-- <br><div class="gmail_signature">Marc-André Lureau</div>
</div></div>