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@xxxxxxxxxxxxxxx> --- ***WARNNIG*** This is the first server patch that needs the protocol constants defined in patch 01. Changes since take 4: - Avoid introducing the no_clock variable so it's not removed in later patches. - Set the error-resilient vp8enc property to minimize artifacts on the client when a frame is lost. - Convert a codec type check to a spice_assert(). - Use sscanf() in parse_video_codecs(). - Make the video_codecs parsing more forgiving. - Use VIDEO_ENCODER_DEFAULT_PREFERENCE instead of "auto". - Introduce red_display_create_video_encoder() now instead of in later patches. - Fix a typo in a comment. configure.ac | 4 ++ server/gstreamer_encoder.c | 78 ++++++++++++++++++--- server/mjpeg_encoder.c | 5 +- server/red_dispatcher.c | 167 ++++++++++++++++++++++++++++++++++++++++----- server/red_dispatcher.h | 8 +++ server/red_worker.c | 80 ++++++++++++++++++---- server/red_worker.h | 18 +++++ server/reds.c | 12 ++++ server/spice-server.h | 1 + server/spice-server.syms | 1 + server/video_encoder.h | 18 +++-- 11 files changed, 345 insertions(+), 47 deletions(-) diff --git a/configure.ac b/configure.ac index db18761..d775bc1 100644 --- a/configure.ac +++ b/configure.ac @@ -140,6 +140,10 @@ AC_SUBST([SPICE_PROTOCOL_MIN_VER]) 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 13b24cc..2318f40 100644 --- a/server/gstreamer_encoder.c +++ b/server/gstreamer_encoder.c @@ -110,6 +110,8 @@ static void reset_pipeline(GstEncoder *encoder) gst_element_set_state(encoder->pipeline, GST_STATE_NULL); gst_caps_unref(encoder->src_caps); + encoder->src_caps = NULL; + gst_object_unref(encoder->appsrc); gst_object_unref(encoder->gstenc); gst_object_unref(encoder->appsink); @@ -174,6 +176,7 @@ static void set_appsrc_caps(GstEncoder *encoder) gst_caps_unref(encoder->src_caps); } encoder->src_caps = gst_caps_new_simple( +#ifdef HAVE_GSTREAMER_0_10 "video/x-raw-rgb", "bpp", G_TYPE_INT, encoder->format->bpp, "depth", G_TYPE_INT, encoder->format->depth, @@ -181,6 +184,10 @@ static void set_appsrc_caps(GstEncoder *encoder) "red_mask", G_TYPE_INT, encoder->format->red_mask, "green_mask", G_TYPE_INT, encoder->format->green_mask, "blue_mask", G_TYPE_INT, encoder->format->blue_mask, +#else + "video/x-raw", + "format", G_TYPE_STRING, encoder->format->format, +#endif "width", G_TYPE_INT, encoder->width, "height", G_TYPE_INT, encoder->height, "framerate", GST_TYPE_FRACTION, get_source_fps(encoder), 1, @@ -191,29 +198,67 @@ static void set_appsrc_caps(GstEncoder *encoder) /* A helper for gst_encoder_encode_frame(). */ static gboolean construct_pipeline(GstEncoder *encoder, const SpiceBitmap *bitmap) { + const gchar* gstenc_name; + switch (encoder->base.codec_type) + { + case SPICE_VIDEO_CODEC_TYPE_MJPEG: + gstenc_name = "ffenc_mjpeg"; + 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); - if (!encoder->pipeline) { + g_free(desc); + if (!encoder->pipeline || err) { spice_warning("GStreamer error: %s", err->message); g_clear_error(&err); + if (encoder->pipeline) { + gst_object_unref(encoder->pipeline); + encoder->pipeline = NULL; + } return FALSE; } encoder->appsrc = GST_APP_SRC(gst_bin_get_by_name(GST_BIN(encoder->pipeline), "src")); 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 source caps */ - set_appsrc_caps(encoder); - - /* Set the encoder initial bit rate */ + /* Configure the encoders for a zero-frame latency, and real-time speed */ 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), + "resize-allowed", TRUE, /* for very low bit rates */ + "mode", 1, /* CBR */ + "max-latency", 0, /* zero-frame latency */ + "error-resilient", TRUE, /* for client frame drops */ + "speed", 7, /* ultrafast */ + "threads", core_count - 1, + NULL); + } + + /* Set the source caps */ + set_appsrc_caps(encoder); /* Start playing */ - spice_debug("removing the pipeline clock"); - gst_pipeline_use_clock(GST_PIPELINE(encoder->pipeline), NULL); + if (encoder->base.codec_type == SPICE_VIDEO_CODEC_TYPE_MJPEG) { + /* See https://bugzilla.gnome.org/show_bug.cgi?id=753257 */ + 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"); @@ -227,6 +272,12 @@ static gboolean construct_pipeline(GstEncoder *encoder, const SpiceBitmap *bitma /* A helper for gst_encoder_encode_frame(). */ static void reconfigure_pipeline(GstEncoder *encoder) { + if (encoder->base.codec_type == SPICE_VIDEO_CODEC_TYPE_VP8) { + /* vp8enc gets confused if we try to reconfigure the pipeline */ + reset_pipeline(encoder); + return; + } + if (gst_element_set_state(encoder->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) { spice_debug("GStreamer error: could not pause the pipeline, rebuilding it instead"); reset_pipeline(encoder); @@ -440,11 +491,19 @@ static 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); @@ -455,6 +514,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; if (cbs) { encoder->cbs = *cbs; diff --git a/server/mjpeg_encoder.c b/server/mjpeg_encoder.c index d064fd2..9f1b392 100644 --- a/server/mjpeg_encoder.c +++ b/server/mjpeg_encoder.c @@ -1337,10 +1337,12 @@ static void mjpeg_encoder_get_stats(MJpegEncoder *encoder, VideoEncoderStats *st stats->avg_quality = (double)encoder->avg_quality / encoder->num_frames; } -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) { + spice_assert(codec_type == SPICE_VIDEO_CODEC_TYPE_MJPEG); spice_assert(!cbs || (cbs && cbs->get_roundtrip_ms && cbs->get_source_fps)); MJpegEncoder *encoder = spice_new0(MJpegEncoder, 1); @@ -1351,6 +1353,7 @@ MJpegEncoder *create_mjpeg_encoder(uint64_t starting_bit_rate, encoder->base.notify_server_frame_drop = &mjpeg_encoder_notify_server_frame_drop; encoder->base.get_bit_rate = &mjpeg_encoder_get_bit_rate; encoder->base.get_stats = &mjpeg_encoder_get_stats; + encoder->base.codec_type = codec_type; encoder->first_frame = TRUE; encoder->rate_control.byte_rate = starting_bit_rate / 8; encoder->starting_bit_rate = starting_bit_rate; diff --git a/server/red_dispatcher.c b/server/red_dispatcher.c index 113848a..acceb53 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" @@ -205,43 +206,152 @@ 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) { - RendererInfo *inf = renderers_info; - while (inf->name) { - if (strcmp(name, inf->name) == 0) { - return inf; - } - inf++; + uint32_t renderer; + + if (num_renderers == RED_MAX_RENDERERS || + !name_to_enum(renderer_names, name, &renderer)) { + return FALSE; } - return NULL; + renderers[num_renderers++] = renderer; + return TRUE; } -int red_dispatcher_add_renderer(const char *name) +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; + while (*codecs == ';') { + codecs++; + } + if (!*codecs) { + return NULL; + } + int n; + *encoder = *codec = NULL; + if (sscanf(codecs, "%m[0-9a-zA-Z_]:%m[0-9a-zA-Z_]%n", encoder, codec, &n) != 2) { + spice_warning("spice: invalid encoder:codec value at %s", codecs); + return strchr(codecs, ';'); + } + return codecs + n; +} - if (num_renderers == RED_MAX_RENDERERS || !(inf = find_renderer(name))) { - return FALSE; +int red_dispatcher_set_video_codecs(const char *codecs) +{ + 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 (strcmp(codecs, "auto") == 0) { + codecs = VIDEO_ENCODER_DEFAULT_PREFERENCE; + } + + c = codecs; + count = 0; + while ( (c = parse_video_codecs(c, &encoder_name, &codec_name)) ) { + int skip = FALSE; + if (!encoder_name || !codec_name) { + skip = TRUE; + + } else if (!name_to_enum(video_encoder_names, encoder_name, &encoder)) { + spice_warning("spice: unknown video encoder %s", encoder_name); + skip = TRUE; + + } else if (!name_to_enum(video_codec_names, codec_name, &type) || + !name_to_enum(video_cap_names, codec_name, &cap)) { + spice_warning("spice: unknown video codec %s", codec_name); + skip = TRUE; + + } else if (!video_encoder_procs[encoder]) { + spice_warning("spice: unsupported video encoder %s", encoder_name); + skip = TRUE; + } + + free(encoder_name); + free(codec_name); + if (skip) { + continue; + } + + if (count == RED_MAX_VIDEO_CODECS) { + spice_warning("spice: cannot add more than %d video codec preferences", count); + break; + } + 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; } @@ -785,6 +895,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; @@ -1092,6 +1218,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(VIDEO_ENCODER_DEFAULT_PREFERENCE); + } + 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 320b7e3..82aed9f 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 c2b7631..6587047 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 @@ -989,6 +990,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; @@ -3078,21 +3081,45 @@ 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(uint64_t starting_bit_rate, +static VideoEncoder* red_display_create_video_encoder(DisplayChannelClient *dcc, + uint64_t starting_bit_rate, VideoEncoderRateControlCbs *cbs, void *cbs_opaque) { -#ifdef HAVE_GSTREAMER_0_10 - VideoEncoder* video_encoder = create_gstreamer_encoder(starting_bit_rate, cbs, cbs_opaque); - if (video_encoder) { - return video_encoder; + 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; + } } -#endif - /* Use the builtin MJPEG video encoder as a fallback */ - return create_mjpeg_encoder(starting_bit_rate, cbs, cbs_opaque); + + /* 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) +static int red_display_create_stream(DisplayChannelClient *dcc, Stream *stream) { StreamAgent *agent = &dcc->stream_agents[get_stream_id(dcc->common.worker, stream)]; @@ -3118,10 +3145,15 @@ 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 = red_display_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 = red_display_create_video_encoder(0, NULL, NULL); + agent->video_encoder = red_display_create_video_encoder(dcc, 0, NULL, NULL); } + if (agent->video_encoder == NULL) { + stream->refs--; + return FALSE; + } + 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)) { @@ -3139,6 +3171,7 @@ static void red_display_create_stream(DisplayChannelClient *dcc, Stream *stream) agent->stats.start = stream->current->red_drawable->mm_time; } #endif + return TRUE; } static void red_create_stream(RedWorker *worker, Drawable *drawable) @@ -3173,7 +3206,12 @@ static void red_create_stream(RedWorker *worker, Drawable *drawable) worker->streams_size_total += stream->width * stream->height; worker->stream_count++; WORKER_FOREACH_DCC_SAFE(worker, dcc_ring_item, next, dcc) { - red_display_create_stream(dcc, stream); + if (!red_display_create_stream(dcc, stream)) { + drawable->stream = NULL; + stream->current = NULL; + red_stop_stream(worker, stream); + return; + } } spice_debug("stream %d %dx%d (%d, %d) (%d, %d)", (int)(stream - worker->streams_buf), stream->width, stream->height, stream->dest_area.left, stream->dest_area.top, @@ -3188,7 +3226,10 @@ static void red_disply_start_streams(DisplayChannelClient *dcc) while ((item = ring_next(ring, item))) { Stream *stream = SPICE_CONTAINEROF(item, Stream, link); - red_display_create_stream(dcc, stream); + if (!red_display_create_stream(dcc, stream)) { + red_stop_stream(dcc->common.worker, stream); + return; + } } } @@ -8938,7 +8979,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; @@ -11751,6 +11792,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; @@ -12080,6 +12130,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 c34369a..c13d225 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 5d2ad9b..174f103 100644 --- a/server/reds.c +++ b/server/reds.c @@ -3689,6 +3689,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 c2ff61d..198dcf0 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 d65e14d..8da649c 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 64af9ae..ac96589 100644 --- a/server/video_encoder.h +++ b/server/video_encoder.h @@ -122,6 +122,9 @@ struct VideoEncoder { * statistics. */ void (*get_stats)(VIDEO_ENCODER_T *encoder, VideoEncoderStats *stats); + + /* The codec being used by the video encoder */ + SpiceVideoCodecType codec_type; }; @@ -149,23 +152,28 @@ typedef struct VideoEncoderRateControlCbs { } VideoEncoderRateControlCbs; -/* Instantiates the builtin MJPEG video encoder. +/* Instantiates the 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 -- 2.5.0 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel