The Spice server administrator can specify the preferred encoder and codec preferences to optimize for CPU or bandwidth usage. Preferences are described in a semi-colon separated list of encoder:codec pairs. The server has a default preference list which can explicitly be selected by specifying 'auto'. The server then picks a codec supported by the client based on the following new client 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. Note that for now the server only supports the MJPEG codec. Signed-off-by: Francois Gouget <fgouget@xxxxxxxxxxxxxxx> --- server/dcc-send.c | 2 +- server/display-channel.c | 13 ++++ server/display-channel.h | 5 ++ server/gstreamer-encoder.c | 6 +- server/mjpeg-encoder.c | 6 +- server/red-dispatcher.c | 17 +++++ server/red-dispatcher.h | 8 +++ server/red-worker.c | 14 ++++ server/reds.c | 155 ++++++++++++++++++++++++++++++++++++++++----- server/reds.h | 13 ++++ server/spice-server.h | 1 + server/spice-server.syms | 5 ++ server/stream.c | 29 +++++++-- server/video-encoder.h | 23 ++++++- 14 files changed, 269 insertions(+), 28 deletions(-) diff --git a/server/dcc-send.c b/server/dcc-send.c index fd11b09..e13f04f 100644 --- a/server/dcc-send.c +++ b/server/dcc-send.c @@ -2189,7 +2189,7 @@ static void marshall_stream_start(RedChannelClient *rcc, stream_create.surface_id = 0; stream_create.id = get_stream_id(DCC_TO_DC(dcc), 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; diff --git a/server/display-channel.c b/server/display-channel.c index ec4ca10..76d2a41 100644 --- a/server/display-channel.c +++ b/server/display-channel.c @@ -219,6 +219,17 @@ void display_channel_set_stream_video(DisplayChannel *display, int stream_video) display->stream_video = stream_video; } +void display_channel_set_video_codecs(DisplayChannel *display, + int num_video_codecs, + RedVideoCodec *video_codecs) +{ + spice_return_if_fail(display); + spice_return_if_fail(num_video_codecs > RED_MAX_VIDEO_CODECS); + + display->num_video_codecs = num_video_codecs; + memcpy(display->video_codecs, video_codecs, sizeof(display->video_codecs)); +} + static void stop_streams(DisplayChannel *display) { Ring *ring = &display->streams; @@ -2072,6 +2083,8 @@ DisplayChannel* display_channel_new(RedWorker *worker, int migrate, int stream_v drawables_init(display); image_cache_init(&display->image_cache); display->stream_video = stream_video; + display->num_video_codecs = num_video_codecs; + memcpy(display->video_codecs, video_codecs, sizeof(display->video_codecs)); display_channel_init_streams(display); return display; diff --git a/server/display-channel.h b/server/display-channel.h index bf29cd3..f55d23b 100644 --- a/server/display-channel.h +++ b/server/display-channel.h @@ -185,6 +185,8 @@ struct DisplayChannel { uint32_t glz_drawable_count; int stream_video; + uint32_t num_video_codecs; + RedVideoCodec video_codecs[RED_MAX_VIDEO_CODECS]; uint32_t stream_count; Stream streams_buf[NUM_STREAMS]; Stream *free_streams; @@ -273,6 +275,9 @@ void display_channel_update (DisplayCha void display_channel_free_some (DisplayChannel *display); void display_channel_set_stream_video (DisplayChannel *display, int stream_video); +void display_channel_set_video_codecs (DisplayChannel *display, + int num_video_codecs, + RedVideoCodec *video_codecs); int display_channel_get_streams_timeout (DisplayChannel *display); void display_channel_compress_stats_print (const DisplayChannel *display); void display_channel_compress_stats_reset (DisplayChannel *display); diff --git a/server/gstreamer-encoder.c b/server/gstreamer-encoder.c index e934180..edf87df 100644 --- a/server/gstreamer-encoder.c +++ b/server/gstreamer-encoder.c @@ -494,10 +494,13 @@ static void spice_gst_encoder_get_stats(VideoEncoder *video_encoder, } } -VideoEncoder *gstreamer_encoder_new(uint64_t starting_bit_rate, +VideoEncoder *gstreamer_encoder_new(SpiceVideoCodecType codec_type, + uint64_t starting_bit_rate, VideoEncoderRateControlCbs *cbs, void *cbs_opaque) { + spice_return_val_if_fail(codec_type == SPICE_VIDEO_CODEC_TYPE_MJPEG, NULL); + GError *err = NULL; if (!gst_init_check(NULL, NULL, &err)) { spice_warning("GStreamer error: %s", err->message); @@ -512,6 +515,7 @@ VideoEncoder *gstreamer_encoder_new(uint64_t starting_bit_rate, encoder->base.notify_server_frame_drop = &spice_gst_encoder_notify_server_frame_drop; encoder->base.get_bit_rate = &spice_gst_encoder_get_bit_rate; encoder->base.get_stats = &spice_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 eb1a333..607bea8 100644 --- a/server/mjpeg-encoder.c +++ b/server/mjpeg-encoder.c @@ -1344,18 +1344,22 @@ static void mjpeg_encoder_get_stats(VideoEncoder *video_encoder, stats->avg_quality = (double)encoder->avg_quality / encoder->num_frames; } -VideoEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate, +VideoEncoder *mjpeg_encoder_new(SpiceVideoCodecType codec_type, + uint64_t starting_bit_rate, VideoEncoderRateControlCbs *cbs, void *cbs_opaque) { MJpegEncoder *encoder = spice_new0(MJpegEncoder, 1); + spice_return_val_if_fail(codec_type == SPICE_VIDEO_CODEC_TYPE_MJPEG, NULL); + encoder->base.destroy = &mjpeg_encoder_destroy; encoder->base.encode_frame = &mjpeg_encoder_encode_frame; encoder->base.client_stream_report = &mjpeg_encoder_client_stream_report; 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 0810798..c81039b 100644 --- a/server/red-dispatcher.c +++ b/server/red-dispatcher.c @@ -26,6 +26,7 @@ #include <pthread.h> #include <sys/socket.h> #include <inttypes.h> +#include <ctype.h> #include <spice/qxl_dev.h> #include "common/quic.h" @@ -744,6 +745,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; diff --git a/server/red-dispatcher.h b/server/red-dispatcher.h index d99695d..b46668e 100644 --- a/server/red-dispatcher.h +++ b/server/red-dispatcher.h @@ -19,6 +19,7 @@ #define _H_RED_DISPATCHER #include "red-channel.h" +#include "video-encoder.h" typedef struct RedDispatcher RedDispatcher; @@ -28,6 +29,7 @@ void red_dispatcher_init(QXLInstance *qxl); 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); @@ -88,6 +90,7 @@ enum { RED_WORKER_MESSAGE_MONITORS_CONFIG_ASYNC, RED_WORKER_MESSAGE_DRIVER_UNLOAD, + RED_WORKER_MESSAGE_SET_VIDEO_CODECS, RED_WORKER_MESSAGE_COUNT // LAST }; @@ -225,6 +228,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 dfaf4ba..416cb44 100644 --- a/server/red-worker.c +++ b/server/red-worker.c @@ -1237,6 +1237,15 @@ static void handle_dev_set_streaming_video(void *opaque, void *payload) display_channel_set_stream_video(worker->display_channel, msg->streaming_video); } +void handle_dev_set_video_codecs(void *opaque, void *payload) +{ + RedWorkerMessageSetVideoCodecs *msg = payload; + RedWorker *worker = opaque; + + display_channel_set_video_codecs(worker->display_channel, + msg->num_video_codecs, msg->video_codecs); +} + static void handle_dev_set_mouse_mode(void *opaque, void *payload) { RedWorkerMessageSetMouseMode *msg = payload; @@ -1494,6 +1503,11 @@ static void register_callbacks(Dispatcher *dispatcher) sizeof(RedWorkerMessageSetStreamingVideo), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, + RED_WORKER_MESSAGE_SET_VIDEO_CODECS, + handle_dev_set_video_codecs, + sizeof(RedWorkerMessageSetVideoCodecs), + DISPATCHER_NONE); + dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_SET_MOUSE_MODE, handle_dev_set_mouse_mode, sizeof(RedWorkerMessageSetMouseMode), diff --git a/server/reds.c b/server/reds.c index e8cf168..b4bb4d1 100644 --- a/server/reds.c +++ b/server/reds.c @@ -3364,12 +3364,27 @@ SPICE_GNUC_VISIBLE SpiceServer *spice_server_new(void) return reds; } -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 EnumNames renderer_names[] = { {RED_RENDERER_SW, "sw"}, {RED_RENDERER_INVALID, NULL}, }; @@ -3377,26 +3392,120 @@ static RendererInfo renderers_info[] = { uint32_t renderers[RED_RENDERER_LAST]; uint32_t num_renderers = 0; -static RendererInfo *find_renderer(const char *name) +static int red_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_RENDERER_LAST || + !name_to_enum(renderer_names, name, &renderer)) { + return FALSE; } - return NULL; + renderers[num_renderers++] = renderer; + return TRUE; } -static int red_add_renderer(const char *name) +static const EnumNames video_encoder_names[] = { + {0, "spice"}, + {1, "gstreamer"}, + {0, NULL}, +}; + +static new_video_encoder_t video_encoder_procs[] = { + &mjpeg_encoder_new, +#ifdef HAVE_GSTREAMER_1_0 + &gstreamer_encoder_new, +#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}, +}; + + +RedVideoCodec video_codecs[RED_MAX_VIDEO_CODECS]; +uint32_t num_video_codecs = 0; + +/* 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_RENDERER_LAST || !(inf = find_renderer(name))) { - return FALSE; +static int red_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; } @@ -3725,6 +3834,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_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/reds.h b/server/reds.h index 4d1b631..afad933 100644 --- a/server/reds.h +++ b/server/reds.h @@ -29,6 +29,7 @@ #include "spice.h" #include "red-channel.h" #include "migration-protocol.h" +#include "video-encoder.h" struct QXLState { QXLInterface *qif; @@ -70,7 +71,19 @@ extern uint32_t renderers[RED_RENDERER_LAST]; extern uint32_t num_renderers; extern struct SpiceCoreInterface *core; + +#define RED_MAX_VIDEO_CODECS 8 + +enum { + SPICE_STREAMING_INVALID, + SPICE_STREAMING_SPICE, + SPICE_STREAMING_GSTREAMER +}; + extern uint32_t streaming_video; +uint32_t num_video_codecs; +RedVideoCodec video_codecs[RED_MAX_VIDEO_CODECS]; + extern SpiceImageCompression image_compression; extern spice_wan_compression_t jpeg_state; extern spice_wan_compression_t zlib_glz_state; 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..b8f06a2 100644 --- a/server/spice-server.syms +++ b/server/spice-server.syms @@ -162,3 +162,8 @@ global: spice_replay_next_cmd; spice_replay_free_cmd; } SPICE_SERVER_0.12.5; + +SPICE_SERVER_0.12.7 { +global: + spice_server_set_video_codecs; +} SPICE_SERVER_0.12.6; diff --git a/server/stream.c b/server/stream.c index bbc7003..0db9c58 100644 --- a/server/stream.c +++ b/server/stream.c @@ -705,17 +705,34 @@ static VideoEncoder* dcc_create_video_encoder(DisplayChannelClient *dcc, VideoEncoderRateControlCbs *cbs, void *cbs_opaque) { + DisplayChannel *display = DCC_TO_DC(dcc); RedChannelClient *rcc = RED_CHANNEL_CLIENT(dcc); int client_has_multi_codec = red_channel_client_test_remote_cap(rcc, SPICE_DISPLAY_CAP_MULTI_CODEC); - if (!client_has_multi_codec || red_channel_client_test_remote_cap(rcc, SPICE_DISPLAY_CAP_CODEC_MJPEG)) { -#ifdef HAVE_GSTREAMER_1_0 - VideoEncoder* video_encoder = gstreamer_encoder_new(starting_bit_rate, cbs, cbs_opaque); + int i; + + for (i = 0; i < display->num_video_codecs; i++) { + RedVideoCodec* video_codec = display->video_codecs+i; + + 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(rcc, video_codec->cap)) { + /* The client is recent but does not support this codec */ + continue; + } + + VideoEncoder* 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 mjpeg_encoder_new(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(rcc, SPICE_DISPLAY_CAP_CODEC_MJPEG)) { + return mjpeg_encoder_new(SPICE_VIDEO_CODEC_TYPE_MJPEG, starting_bit_rate, cbs, cbs_opaque); } return NULL; diff --git a/server/video-encoder.h b/server/video-encoder.h index 1c6ceb3..b8a21e1 100644 --- a/server/video-encoder.h +++ b/server/video-encoder.h @@ -117,6 +117,9 @@ struct VideoEncoder { * statistics. */ void (*get_stats)(VideoEncoder *encoder, VideoEncoderStats *stats); + + /* The codec being used by the video encoder */ + SpiceVideoCodecType codec_type; }; @@ -146,6 +149,7 @@ typedef struct VideoEncoderRateControlCbs { /* Instantiates the video encoder. * + * @codec_type: The codec to use. * @starting_bit_rate: An initial estimate of the available stream bit rate or * zero if the client does not support rate control. * @cbs: A set of callback methods to be used for rate control. @@ -153,13 +157,28 @@ typedef struct VideoEncoderRateControlCbs { * @return: A pointer to a structure implementing the VideoEncoder * methods. */ -VideoEncoder* mjpeg_encoder_new(uint64_t starting_bit_rate, +typedef VideoEncoder* (*new_video_encoder_t)(SpiceVideoCodecType codec_type, + uint64_t starting_bit_rate, + VideoEncoderRateControlCbs *cbs, + void *cbs_opaque); + +typedef struct RedVideoCodec { + new_video_encoder_t create; + SpiceVideoCodecType type; + uint32_t cap; +} RedVideoCodec; + +VideoEncoder* mjpeg_encoder_new(SpiceVideoCodecType codec_type, + uint64_t starting_bit_rate, VideoEncoderRateControlCbs *cbs, void *cbs_opaque); #ifdef HAVE_GSTREAMER_1_0 -VideoEncoder* gstreamer_encoder_new(uint64_t starting_bit_rate, +VideoEncoder* gstreamer_encoder_new(SpiceVideoCodecType codec_type, + uint64_t starting_bit_rate, VideoEncoderRateControlCbs *cbs, void *cbs_opaque); #endif +#define VIDEO_ENCODER_DEFAULT_PREFERENCE "spice:mjpeg;gstreamer:mjpeg" + #endif -- 2.6.2 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel