streaming: Give up after a while if GStreamer cannot handle the video

This typically happens when sending very small frames (less than
16 pixels in one dimension) to the x264enc encoder.
This avoids repeatedly wasting time rebuilding the pipeline.

Signed-off-by: Francois Gouget <fgouget@codeweavers.com>
This commit is contained in:
Francois Gouget 2015-07-22 18:42:23 +02:00 committed by Christophe Fergeau
parent ef6d6b063d
commit c6f6471eb8

View File

@ -82,6 +82,9 @@ typedef struct SpiceGstEncoder {
const SpiceFormatForGStreamer *format;
SpiceBitmapFmt spice_format;
/* Number of consecutive frame encoding errors. */
uint32_t errors;
/* ---------- GStreamer pipeline ---------- */
/* Pointers to the GStreamer pipeline elements. If pipeline is NULL the
@ -811,14 +814,35 @@ static GstFlowReturn new_sample(GstAppSink *gstappsink, gpointer video_encoder)
return GST_FLOW_OK;
}
static const gchar* get_gst_codec_name(SpiceGstEncoder *encoder)
{
switch (encoder->base.codec_type)
{
case SPICE_VIDEO_CODEC_TYPE_MJPEG:
return "avenc_mjpeg";
case SPICE_VIDEO_CODEC_TYPE_VP8:
return "vp8enc";
case SPICE_VIDEO_CODEC_TYPE_H264:
return "x264enc";
default:
/* gstreamer_encoder_new() should have rejected this codec type */
spice_warning("unsupported codec type %d", encoder->base.codec_type);
return NULL;
}
}
static gboolean create_pipeline(SpiceGstEncoder *encoder)
{
gchar *gstenc;
const gchar* gstenc_name = get_gst_codec_name(encoder);
if (!gstenc_name) {
return FALSE;
}
gchar* gstenc_opts;
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");
gstenc_opts = g_strdup("max-threads=1");
break;
case SPICE_VIDEO_CODEC_TYPE_VP8: {
/* See http://www.webmproject.org/docs/encoder-parameters/
@ -836,7 +860,7 @@ static gboolean create_pipeline(SpiceGstEncoder *encoder)
* - 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");
gstenc_opts = g_strdup_printf("end-usage=cbr min-quantizer=10 error-resilient=default lag-in-frames=0 deadline=1 cpu-used=4");
break;
}
case SPICE_VIDEO_CODEC_TYPE_H264:
@ -846,7 +870,7 @@ static gboolean create_pipeline(SpiceGstEncoder *encoder)
* - Set intra-refresh to get more uniform compressed frame sizes,
* thus helping with streaming.
*/
gstenc = g_strdup("x264enc byte-stream=true aud=true qp-min=15 tune=4 sliced-threads=true speed-preset=ultrafast intra-refresh=true");
gstenc_opts = g_strdup("byte-stream=true aud=true qp-min=15 tune=4 sliced-threads=true speed-preset=ultrafast intra-refresh=true");
break;
default:
/* gstreamer_encoder_new() should have rejected this codec type */
@ -855,10 +879,10 @@ static gboolean create_pipeline(SpiceGstEncoder *encoder)
}
GError *err = NULL;
gchar *desc = g_strdup_printf("appsrc is-live=true format=time do-timestamp=true name=src ! videoconvert ! %s name=encoder ! appsink name=sink", gstenc);
gchar *desc = g_strdup_printf("appsrc is-live=true format=time do-timestamp=true name=src ! videoconvert ! %s %s name=encoder ! appsink name=sink", gstenc_name, gstenc_opts);
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(gstenc_opts);
g_free(desc);
if (!encoder->pipeline || err) {
spice_warning("GStreamer error: %s", err->message);
@ -873,20 +897,12 @@ 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"));
#ifdef HAVE_GSTREAMER_0_10
GstAppSinkCallbacks appsink_cbs = {NULL, NULL, &new_sample, NULL, {NULL}};
#else
GstAppSinkCallbacks appsink_cbs = {NULL, NULL, &new_sample, {NULL}};
#endif
gst_app_sink_set_callbacks(encoder->appsink, &appsink_cbs, encoder, NULL);
/* Hook into the bus so we can handle errors */
GstBus *bus = gst_element_get_bus(encoder->pipeline);
#ifdef HAVE_GSTREAMER_0_10
gst_bus_set_sync_handler(bus, handle_pipeline_message, encoder);
#else
gst_bus_set_sync_handler(bus, handle_pipeline_message, encoder, NULL);
#endif
gst_object_unref(bus);
if (encoder->base.codec_type == SPICE_VIDEO_CODEC_TYPE_MJPEG) {
@ -1301,6 +1317,7 @@ static int spice_gst_encoder_encode_frame(VideoEncoder *video_encoder,
encoder->format = map_format(bitmap->format);
if (!encoder->format) {
spice_warning("unable to map format type %d", bitmap->format);
encoder->errors = 4;
return VIDEO_ENCODER_FRAME_UNSUPPORTED;
}
encoder->spice_format = bitmap->format;
@ -1316,6 +1333,19 @@ static int spice_gst_encoder_encode_frame(VideoEncoder *video_encoder,
} else if (encoder->pipeline) {
set_pipeline_changes(encoder, SPICE_GST_VIDEO_PIPELINE_CAPS);
}
encoder->errors = 0;
} else if (encoder->errors >= 3) {
/* The pipeline keeps failing to handle the frames we send it, which
* is usually because they are too small (mouse pointer-sized).
* So give up until something changes.
*/
if (encoder->errors == 3) {
spice_debug("%s cannot compress %dx%d:%dbpp frames",
get_gst_codec_name(encoder), encoder->width,
encoder->height, encoder->format->bpp);
encoder->errors++;
}
return VIDEO_ENCODER_FRAME_UNSUPPORTED;
}
if (rate_control_is_active(encoder) &&
@ -1326,6 +1356,7 @@ static int spice_gst_encoder_encode_frame(VideoEncoder *video_encoder,
}
if (!configure_pipeline(encoder)) {
encoder->errors++;
return VIDEO_ENCODER_FRAME_UNSUPPORTED;
}
@ -1340,6 +1371,7 @@ static int spice_gst_encoder_encode_frame(VideoEncoder *video_encoder,
* from scratch.
*/
free_pipeline(encoder);
encoder->errors++;
}
}