diff --git a/server/gstreamer-encoder.c b/server/gstreamer-encoder.c index 40882f69..be8e3111 100644 --- a/server/gstreamer-encoder.c +++ b/server/gstreamer-encoder.c @@ -38,6 +38,7 @@ #include "red-common.h" #include "video-encoder.h" #include "utils.h" +#include "common/udev.h" #define SPICE_GST_DEFAULT_FPS 30 @@ -861,6 +862,116 @@ static const gchar* get_gst_codec_name(const SpiceGstEncoder *encoder) } } +static const char video_codecs[][8] = { + { "" }, + { "mjpeg" }, + { "vp8" }, + { "h264" }, + { "vp9" }, + { "h265" }, +}; + +static bool gst_features_lookup(const gchar *feature_name) +{ + GstRegistry *registry; + GstPluginFeature *feature; + + registry = gst_registry_get(); + if (!registry) { + return false; + } + + feature = gst_registry_lookup_feature(registry, feature_name); + if (!feature) { + return false; + } + + gst_object_unref(feature); + return true; +} + +static gchar *find_best_hw_plugin(const gchar *codec_name) +{ + static const char plugins[][8] = {"msdk", "va", "vaapi"}; + gchar *feature_name; + int i; + + for (i = 0; i < G_N_ELEMENTS(plugins); i++) { + feature_name = !codec_name ? g_strconcat(plugins[i], "postproc", NULL) : + g_strconcat(plugins[i], codec_name, "enc", NULL); + if (!gst_features_lookup(feature_name)) { + g_free(feature_name); + feature_name = NULL; + continue; + } + break; + } + return feature_name; +} + +static gchar *get_hw_gstenc_opts(gchar *encoder, const gchar *codec_name) +{ + gchar *gstenc_opts; + + if (strcmp(codec_name, "mjpeg") == 0) { + return g_strdup(""); + } + + if (g_str_has_prefix(encoder, "msdk")) { + if (strcmp(codec_name, "vp9") == 0) { + gstenc_opts = g_strdup("async-depth=1 b-frames=0 rate-control=3 target-usage=7"); + } else { + gstenc_opts = g_strdup("async-depth=1 rate-control=3 gop-size=1 tune=16 b-frames=0 target-usage=7 min-qp=15 max-qp=35"); + } + } else if (g_str_has_prefix(encoder, "vaapi")) { + if (strcmp(codec_name, "vp9") == 0) { + gstenc_opts = g_strdup("tune=3 rate-control=1"); + } else { + gstenc_opts = g_strdup("rate-control=cqp max-bframes=0 min-qp=15 max-qp=35"); + } + } else { + if (strcmp(codec_name, "vp9") == 0) { + gstenc_opts = g_strdup("min-qp=15 max-qp=35 rate-control=16 ref-frames=0 target-usage=7"); + } else { + gstenc_opts = g_strdup("rate-control=16 b-frames=0 target-usage=7 min-qp=15 max-qp=35"); + } + } + return gstenc_opts; +} + +static void try_intel_hw_plugins(const gchar *codec_name, gchar **converter, + gchar **gstenc_name, gchar **gstenc_opts) +{ + gchar *encoder, *vpp; + + if (strcmp(codec_name, "vp8") == 0) { + return; + } + + encoder = find_best_hw_plugin(codec_name); + if (!encoder) { + return; + } + vpp = find_best_hw_plugin(NULL); + if (!vpp) { + g_free(encoder); + return; + } + + g_free(*converter); + g_free(*gstenc_name); + g_free(*gstenc_opts); + *gstenc_name = encoder; + *gstenc_opts = get_hw_gstenc_opts(encoder, codec_name); + + if (g_str_has_prefix(vpp, "vaapi")) { + *converter = g_strconcat(vpp, " ! video/x-raw(memory:VASurface),format=NV12", NULL); + } else { + *converter = g_strconcat(vpp, " ! video/x-raw(memory:VAMemory),format=NV12", NULL); + } + g_free(vpp); +} + /* At this time, only the following formats are supported by x264enc. */ static const char valid_formats[][10] = { { "Y444" }, @@ -900,7 +1011,7 @@ static gchar *get_gst_converter(void) static gboolean create_pipeline(SpiceGstEncoder *encoder) { - const gchar* gstenc_name = get_gst_codec_name(encoder); + gchar* gstenc_name = g_strdup(get_gst_codec_name(encoder)); if (!gstenc_name) { return FALSE; } @@ -947,10 +1058,18 @@ static gboolean create_pipeline(SpiceGstEncoder *encoder) default: /* gstreamer_encoder_new() should have rejected this codec type */ spice_warning("unsupported codec type %d", encoder->base.codec_type); + g_free(gstenc_name); g_free(converter); return FALSE; } + const char *codec_name = video_codecs[encoder->base.codec_type]; + GpuVendor vendor = spice_udev_detect_gpu(INTEL_VENDOR_ID); + if (vendor == VENDOR_GPU_DETECTED) { + try_intel_hw_plugins(codec_name, &converter, &gstenc_name, + &gstenc_opts); + } + GError *err = NULL; gchar *desc = g_strdup_printf("appsrc is-live=true format=time do-timestamp=true name=src !" " %s ! %s name=encoder %s ! appsink name=sink", @@ -967,6 +1086,7 @@ static gboolean create_pipeline(SpiceGstEncoder *encoder) gst_object_unref(encoder->pipeline); encoder->pipeline = NULL; } + g_free(gstenc_name); return FALSE; } encoder->appsrc = GST_APP_SRC(gst_bin_get_by_name(GST_BIN(encoder->pipeline), "src")); @@ -1003,6 +1123,7 @@ static gboolean create_pipeline(SpiceGstEncoder *encoder) SPICE_GST_VIDEO_PIPELINE_BITRATE | SPICE_GST_VIDEO_PIPELINE_CAPS); + g_free(gstenc_name); return TRUE; }