[Spice-devel] [spice v10 08/27] server: Add VP8 support to the GStreamer video encoder
Frediano Ziglio
fziglio at redhat.com
Thu Mar 3 14:50:26 UTC 2016
>
> Signed-off-by: Francois Gouget <fgouget at codeweavers.com>
> ---
>
> Changed the VP8 encoder parameters based on the realtime profile to
> improve performance. The patch does not use the realtime profile
> directly because profiles don't seem to be supported from 'gst-launch'
> pipeline strings yet.
>
> Furthermore it turns out that it is important to get the number of
> physical cores right, and that's something the encoder does not know how
> to do itself.
>
>
> configure.ac | 4 ++
> server/gstreamer-encoder.c | 98
> ++++++++++++++++++++++++++++++++++++++++++----
> server/reds.c | 2 +-
> 3 files changed, 95 insertions(+), 9 deletions(-)
>
> diff --git a/configure.ac b/configure.ac
> index d9aa073..9865240 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -130,6 +130,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([GOBJECT2], [gobject-2.0 >= 2.22])
> AS_VAR_APPEND([SPICE_REQUIRES], [" gobject-2.0 >= 2.22"])
>
> diff --git a/server/gstreamer-encoder.c b/server/gstreamer-encoder.c
> index aae47c7..3dfbdfb 100644
> --- a/server/gstreamer-encoder.c
> +++ b/server/gstreamer-encoder.c
> @@ -188,14 +188,70 @@ static void set_appsrc_caps(SpiceGstEncoder *encoder)
> g_object_set(G_OBJECT(encoder->appsrc), "caps", encoder->src_caps,
> NULL);
> }
>
> +static int physical_core_count = 0;
> +static int get_physical_core_count(void)
> +{
> + if (!physical_core_count) {
> +#ifdef HAVE_G_GET_NUMPROCESSORS
> + physical_core_count = g_get_num_processors();
> +#endif
you could use sysconf if g_get_num_processors is not detected.
> + if (system("egrep -l '^flags\\b.*: .*\\bht\\b' /proc/cpuinfo
> >/dev/null 2>&1") == 0) {
> + /* Hyperthreading is enabled so divide by two to get the number
> + * of physical cores.
> + */
> + physical_core_count = physical_core_count / 2;
> + }
Or as you are using /proc/cpuinfo detect from it.
I'm not sure we should consider compatibility with other processors (like ARM).
> + if (physical_core_count == 0)
> + physical_core_count = 1;
> + }
> + return physical_core_count;
> +}
> +
> /* 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.
> + * - resize-allowed allows trading resolution for low bitrates while
> + * min-quantizer ensures the bitrate does not get needlessly high.
> + * - 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.
> + * - At least up to GStreamer 1.6.2, vp8enc cannot be trusted to
> pick
> + * the optimal number of threads. Also exceeding the number of
> + * physical core really degrades image quality.
> + * - token-partitions parallelizes more operations.
> + */
> + int threads = get_physical_core_count();
> + int parts = threads < 2 ? 0 : threads < 4 ? 1 : threads < 8 ? 2 : 3;
> + gstenc = g_strdup_printf("vp8enc end-usage=cbr min-quantizer=10
> resize-allowed=true error-resilient=true lag-in-frames=0 deadline=1
> cpu-used=4 threads=%d token-partitions=%d", threads, parts);
> + 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);
> @@ -221,11 +277,27 @@ static gboolean configure_pipeline(SpiceGstEncoder
> *encoder,
>
> /* Configure the encoder bitrate */
> adjust_bit_rate(encoder);
> - g_object_set(G_OBJECT(encoder->gstenc), "bitrate", encoder->bit_rate,
> NULL);
> -
> - /* 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);
> + switch (encoder->base.codec_type)
> + {
> + case SPICE_VIDEO_CODEC_TYPE_MJPEG:
> + g_object_set(G_OBJECT(encoder->gstenc),
> + "bitrate", encoder->bit_rate,
> + NULL);
> + /* 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);
> + break;
> + case SPICE_VIDEO_CODEC_TYPE_VP8:
> + g_object_set(G_OBJECT(encoder->gstenc),
> + "target-bitrate", 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);
> + return FALSE;
> + }
>
> /* Set the source caps */
> set_appsrc_caps(encoder);
> @@ -247,6 +319,15 @@ static void reconfigure_pipeline(SpiceGstEncoder
> *encoder)
> if (!is_pipeline_configured(encoder)) {
> return;
> }
> + if (encoder->base.codec_type == SPICE_VIDEO_CODEC_TYPE_VP8) {
> + /* vp8enc fails to account for caps changes that modify the frame
> + * size and complains about the buffer size.
> + * So recreate the pipeline from scratch.
> + */
> + free_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");
> free_pipeline(encoder);
> @@ -501,7 +582,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 e97038b..de98509 100644
> --- a/server/reds.c
> +++ b/server/reds.c
> @@ -3427,7 +3427,7 @@ err:
> static const char default_renderer[] = "sw";
>
> #define RED_MAX_VIDEO_CODECS 8
> -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)
Frediano
More information about the Spice-devel
mailing list