[Spice-devel] [PATCH v5 04/20] server: Add VP8 support, a video codec preference list and compatibility checks with the Spice client.
Francois Gouget
fgouget at codeweavers.com
Thu Aug 27 12:00:50 PDT 2015
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 at codeweavers.com>
---
***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
More information about the Spice-devel
mailing list