[Spice-devel,v16,09/23] streaming: Add VP8 support to the GStreamer video encoder

Submitted by Francois Gouget on June 7, 2016, 1:59 p.m.

Details

Message ID 9fcb52907775f528ee1b38f2a0b732050df3754d.1465306176.git.fgouget@free.fr
State New
Headers show
Series "Add GStreamer support for video streaming" ( rev: 22 21 20 19 18 ) in Spice

Not browsing as part of any series.

Commit Message

Francois Gouget June 7, 2016, 1:59 p.m.
Signed-off-by: Francois Gouget <fgouget@codeweavers.com>
---
 configure.ac               |  1 +
 server/gstreamer-encoder.c | 67 ++++++++++++++++++++++++++++++++++++++++------
 server/reds.c              |  4 ++-
 3 files changed, 63 insertions(+), 9 deletions(-)

Patch hide | download patch | download mbox

diff --git a/configure.ac b/configure.ac
index 79fe0b8..ac08410 100644
--- a/configure.ac
+++ b/configure.ac
@@ -78,6 +78,7 @@  if test "x$enable_gstreamer" != "xno"; then
         [enable_gstreamer="yes"
          SPICE_CHECK_GSTREAMER_ELEMENTS($GST_INSPECT_1_0, [gst-plugins-base 1.0], [appsrc videoconvert appsink])
          SPICE_CHECK_GSTREAMER_ELEMENTS($GST_INSPECT_1_0, [gstreamer-libav 1.0], [avenc_mjpeg])
+         SPICE_CHECK_GSTREAMER_ELEMENTS($GST_INSPECT_1_0, [gst-plugins-good 1.0], [vp8enc])
          ],
          [if test "x$enable_gstreamer" = "xyes"; then
               AC_MSG_ERROR([GStreamer 1.0 support requested but not found. You may set GSTREAMER_1_0_CFLAGS and GSTREAMER_1_0_LIBS to avoid the need to call pkg-config.])
diff --git a/server/gstreamer-encoder.c b/server/gstreamer-encoder.c
index 67a757f..e0d4d3a 100644
--- a/server/gstreamer-encoder.c
+++ b/server/gstreamer-encoder.c
@@ -192,11 +192,44 @@  static void set_appsrc_caps(SpiceGstEncoder *encoder)
 /* A helper for spice_gst_encoder_encode_frame() */
 static gboolean create_pipeline(SpiceGstEncoder *encoder)
 {
+    gchar *gstenc;
+    switch (encoder->base.codec_type)
+    {
+    case SPICE_VIDEO_CODEC_TYPE_MJPEG:
+        /* Set max-threads to ensure zero-frame latency */
+        gstenc = g_strdup("avenc_mjpeg max-threads=1");
+        break;
+    case SPICE_VIDEO_CODEC_TYPE_VP8: {
+        /* See http://www.webmproject.org/docs/encoder-parameters/
+         * - Set end-usage to get a constant bitrate to help with streaming.
+         * - min-quantizer ensures the bitrate does not get needlessly high.
+         * - resize-allowed would be useful for low bitrate situations but
+         *   the decoder does not return a frame of the expected size so
+         *   avoid it.
+         * - error-resilient minimises artifacts in case the client drops a
+         *   frame.
+         * - Set lag-in-frames, deadline and cpu-used to match
+         *   "Profile Realtime". lag-in-frames ensures zero-frame latency,
+         *   deadline turns on realtime behavior, and cpu-used targets a 75%
+         *   CPU usage.
+         * - deadline is supposed to be set in microseconds but in practice
+         *   it behaves like a boolean.
+         */
+        gstenc = g_strdup_printf("vp8enc end-usage=cbr min-quantizer=10 error-resilient=default lag-in-frames=0 deadline=1 cpu-used=4");
+        break;
+        }
+    default:
+        /* gstreamer_encoder_new() should have rejected this codec type */
+        spice_warning("unsupported codec type %d", encoder->base.codec_type);
+        return FALSE;
+    }
+
     GError *err = NULL;
-    /* Set max-threads to ensure zero-frame latency */
-    const gchar *desc = "appsrc is-live=true format=time do-timestamp=true name=src ! videoconvert ! avenc_mjpeg max-threads=1 name=encoder ! appsink name=sink";
+    gchar *desc = g_strdup_printf("appsrc is-live=true format=time do-timestamp=true name=src ! videoconvert ! %s name=encoder ! appsink name=sink", gstenc);
     spice_debug("GStreamer pipeline: %s", desc);
     encoder->pipeline = gst_parse_launch_full(desc, NULL, GST_PARSE_FLAG_FATAL_ERRORS, &err);
+    g_free(gstenc);
+    g_free(desc);
     if (!encoder->pipeline || err) {
         spice_warning("GStreamer error: %s", err->message);
         g_clear_error(&err);
@@ -210,9 +243,11 @@  static gboolean create_pipeline(SpiceGstEncoder *encoder)
     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"));
 
-    /* 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);
+    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);
+    }
 
     set_pipeline_changes(encoder, SPICE_GST_VIDEO_PIPELINE_STATE |
                                   SPICE_GST_VIDEO_PIPELINE_BITRATE |
@@ -225,8 +260,23 @@  static gboolean create_pipeline(SpiceGstEncoder *encoder)
 static void set_gstenc_bitrate(SpiceGstEncoder *encoder)
 {
     adjust_bit_rate(encoder);
-    g_object_set(G_OBJECT(encoder->gstenc),
-                 "bitrate", (gint)encoder->bit_rate, NULL);
+    switch (encoder->base.codec_type)
+    {
+    case SPICE_VIDEO_CODEC_TYPE_MJPEG:
+        g_object_set(G_OBJECT(encoder->gstenc),
+                     "bitrate", (gint)encoder->bit_rate,
+                     NULL);
+        break;
+    case SPICE_VIDEO_CODEC_TYPE_VP8:
+        g_object_set(G_OBJECT(encoder->gstenc),
+                     "target-bitrate", (gint)encoder->bit_rate,
+                     NULL);
+        break;
+    default:
+        /* gstreamer_encoder_new() should have rejected this codec type */
+        spice_warning("unsupported codec type %d", encoder->base.codec_type);
+        free_pipeline(encoder);
+    }
 }
 
 /* A helper for spice_gst_encoder_encode_frame() */
@@ -529,7 +579,8 @@  VideoEncoder *gstreamer_encoder_new(SpiceVideoCodecType codec_type,
                                     uint64_t starting_bit_rate,
                                     VideoEncoderRateControlCbs *cbs)
 {
-    spice_return_val_if_fail(codec_type == SPICE_VIDEO_CODEC_TYPE_MJPEG, NULL);
+    spice_return_val_if_fail(codec_type == SPICE_VIDEO_CODEC_TYPE_MJPEG ||
+                             codec_type == SPICE_VIDEO_CODEC_TYPE_VP8, NULL);
 
     GError *err = NULL;
     if (!gst_init_check(NULL, NULL, &err)) {
diff --git a/server/reds.c b/server/reds.c
index 43fd5c3..21d3835 100644
--- a/server/reds.c
+++ b/server/reds.c
@@ -3499,7 +3499,7 @@  err:
 }
 
 static const char default_renderer[] = "sw";
-static const char default_video_codecs[] = "spice:mjpeg;gstreamer:mjpeg";
+static const char default_video_codecs[] = "spice:mjpeg;gstreamer:mjpeg;gstreamer:vp8";
 
 /* new interface */
 SPICE_GNUC_VISIBLE SpiceServer *spice_server_new(void)
@@ -3586,11 +3586,13 @@  static new_video_encoder_t video_encoder_procs[] = {
 
 static const EnumNames video_codec_names[] = {
     {SPICE_VIDEO_CODEC_TYPE_MJPEG, "mjpeg"},
+    {SPICE_VIDEO_CODEC_TYPE_VP8, "vp8"},
     {0, NULL},
 };
 
 static int video_codec_caps[] = {
     SPICE_DISPLAY_CAP_CODEC_MJPEG,
+    SPICE_DISPLAY_CAP_CODEC_VP8,
 };