Currently when gstreamer is used to decode a full-screen stream sent from the server, the decoding frames are being forced to RBGA format and pushed using appsink to be scaled and rendered to screen. Today most of the gstreamer sinks supports the GstVideoOverlay interface which allows to render directly from the pipeline to a window by a given windowing system id (i.e. xid). This patch makes playbin to use this feature if possible. Setting the DISABLE_GSTVIDEOOVERLAY environment variable will make gstreamer to avoid of using the gstvideooverlay interface. --- src/channel-display-gst.c | 99 ++++++++++++++++++++++++++++++++++++++--------- src/channel-display.c | 55 ++++++++++++++++++++++++++ src/channel-display.h | 3 ++ src/spice-widget-priv.h | 1 + src/spice-widget.c | 40 ++++++++++++++++++- 5 files changed, 179 insertions(+), 19 deletions(-) diff --git a/src/channel-display-gst.c b/src/channel-display-gst.c index 8b23036..a29af3f 100644 --- a/src/channel-display-gst.c +++ b/src/channel-display-gst.c @@ -49,6 +49,8 @@ typedef struct SpiceGstDecoder { GQueue *decoding_queue; GQueue *display_queue; guint timer_id; + + gboolean gstvideooverlay; } SpiceGstDecoder; #define VALID_VIDEO_CODEC_TYPE(codec) \ @@ -89,6 +91,12 @@ static SpiceGstFrame *create_gst_frame(GstBuffer *buffer, SpiceFrame *frame) return gstframe; } +static void free_spice_frame(SpiceFrame *frame) +{ + frame->unref_data(frame->data_opaque); + frame->free(frame); +} + static void free_gst_frame(SpiceGstFrame *gstframe) { gstframe->frame->free(gstframe->frame); @@ -265,7 +273,8 @@ static void free_pipeline(SpiceGstDecoder *decoder) gst_element_set_state(decoder->pipeline, GST_STATE_NULL); gst_object_unref(decoder->appsrc); - gst_object_unref(decoder->appsink); + if (decoder->appsink) + gst_object_unref(decoder->appsink); gst_object_unref(decoder->pipeline); gst_object_unref(decoder->clock); decoder->pipeline = NULL; @@ -306,6 +315,20 @@ static gboolean handle_pipeline_message(GstBus *bus, GstMessage *msg, gpointer v g_free(filename); break; } + case GST_MESSAGE_ELEMENT: { + if (gst_is_video_overlay_prepare_window_handle_message(msg)) { + GstVideoOverlay *overlay; + guintptr handle = 0; + + g_object_get(decoder->base.stream->channel, "handle", &handle, NULL); + SPICE_DEBUG("prepare-window-handle msg received (handle: %lu)", handle); + if (handle != 0) { + overlay = GST_VIDEO_OVERLAY(GST_MESSAGE_SRC(msg)); + gst_video_overlay_set_window_handle(overlay, handle); + } + } + break; + } default: /* not being handled */ break; @@ -347,6 +370,7 @@ static gboolean create_pipeline(SpiceGstDecoder *decoder) #if GST_CHECK_VERSION(1,9,0) GstElement *playbin, *sink; SpiceGstPlayFlags flags; + guintptr handle; GstCaps *caps; playbin = gst_element_factory_make("playbin", "playbin"); @@ -355,26 +379,53 @@ static gboolean create_pipeline(SpiceGstDecoder *decoder) return FALSE; } - sink = gst_element_factory_make("appsink", "sink"); - if (sink == NULL) { - spice_warning("error upon creation of 'appsink' element"); - gst_object_unref(playbin); - return FALSE; - } - - caps = gst_caps_from_string("video/x-raw,format=BGRx"); - g_object_set(sink, + g_object_get(decoder->base.stream->channel, "handle", &handle, NULL); + decoder->gstvideooverlay = (handle != 0); + SPICE_DEBUG("Creating Gstreamer pipline (handle for overlay %s)\n", + handle ? "received" : "not received"); + if (handle == 0) { + sink = gst_element_factory_make("appsink", "sink"); + if (sink == NULL) { + spice_warning("error upon creation of 'appsink' element"); + gst_object_unref(playbin); + return FALSE; + } + caps = gst_caps_from_string("video/x-raw,format=BGRx"); + g_object_set(sink, "caps", caps, "sync", FALSE, "drop", FALSE, NULL); - gst_caps_unref(caps); + gst_caps_unref(caps); + g_object_set(playbin, + "video-sink", gst_object_ref(sink), + NULL); + + decoder->appsink = GST_APP_SINK(sink); + } else { + /* handle has received, it means playbin will render directly into + * widget using the gstvideoooverlay interface instead of app-sink. + * Also avoid using vaapisink if exist since vaapisink could be + * buggy when it is combined with playbin. changing its rank to + * none will make playbin to avoid of using it. + */ + GstRegistry *registry = NULL; + GstPluginFeature *vaapisink = NULL; + + registry = gst_registry_get(); + if (registry) { + vaapisink = gst_registry_lookup_feature(registry, "vaapisink"); + } + if (vaapisink) { + gst_plugin_feature_set_rank(vaapisink, GST_RANK_NONE); + gst_object_unref(vaapisink); + } + } g_signal_connect(playbin, "source-setup", G_CALLBACK(app_source_setup), decoder); g_object_set(playbin, "uri", "appsrc://", - "video-sink", gst_object_ref(sink), NULL); /* Disable audio in playbin */ @@ -383,7 +434,6 @@ static gboolean create_pipeline(SpiceGstDecoder *decoder) g_object_set(playbin, "flags", flags, NULL); g_warn_if_fail(decoder->appsrc == NULL); - decoder->appsink = GST_APP_SINK(sink); decoder->pipeline = playbin; #else gchar *desc; @@ -416,7 +466,9 @@ static gboolean create_pipeline(SpiceGstDecoder *decoder) #endif appsink_cbs.new_sample = new_sample; - gst_app_sink_set_callbacks(decoder->appsink, &appsink_cbs, decoder, NULL); + if (decoder->appsink) { + gst_app_sink_set_callbacks(decoder->appsink, &appsink_cbs, decoder, NULL); + } bus = gst_pipeline_get_bus(GST_PIPELINE(decoder->pipeline)); gst_bus_add_watch(bus, handle_pipeline_message, decoder); gst_object_unref(bus); @@ -510,6 +562,8 @@ static gboolean spice_gst_decoder_queue_frame(VideoDecoder *video_decoder, SpiceFrame *frame, int latency) { SpiceGstDecoder *decoder = (SpiceGstDecoder*)video_decoder; + gpointer data_opaque; + GDestroyNotify data_unref; if (frame->size == 0) { SPICE_DEBUG("got an empty frame buffer!"); @@ -551,17 +605,26 @@ static gboolean spice_gst_decoder_queue_frame(VideoDecoder *video_decoder, /* ref() the frame data for the buffer */ frame->ref_data(frame->data_opaque); + if (decoder->gstvideooverlay) { + data_opaque = frame; + data_unref = (void*)free_spice_frame; + } else { + data_opaque = frame->data_opaque; + data_unref = frame->unref_data; + } GstBuffer *buffer = gst_buffer_new_wrapped_full(GST_MEMORY_FLAG_PHYSICALLY_CONTIGUOUS, frame->data, frame->size, 0, frame->size, - frame->data_opaque, frame->unref_data); + data_opaque, data_unref); GST_BUFFER_DURATION(buffer) = GST_CLOCK_TIME_NONE; GST_BUFFER_DTS(buffer) = GST_CLOCK_TIME_NONE; GST_BUFFER_PTS(buffer) = gst_clock_get_time(decoder->clock) - gst_element_get_base_time(decoder->pipeline) + ((uint64_t)MAX(0, latency)) * 1000 * 1000; - g_mutex_lock(&decoder->queues_mutex); - g_queue_push_tail(decoder->decoding_queue, create_gst_frame(buffer, frame)); - g_mutex_unlock(&decoder->queues_mutex); + if (!decoder->gstvideooverlay) { + g_mutex_lock(&decoder->queues_mutex); + g_queue_push_tail(decoder->decoding_queue, create_gst_frame(buffer, frame)); + g_mutex_unlock(&decoder->queues_mutex); + } if (gst_app_src_push_buffer(decoder->appsrc, buffer) != GST_FLOW_OK) { SPICE_DEBUG("GStreamer error: unable to push frame of size %u", frame->size); diff --git a/src/channel-display.c b/src/channel-display.c index d0d977f..ac040bc 100644 --- a/src/channel-display.c +++ b/src/channel-display.c @@ -70,6 +70,7 @@ struct _SpiceDisplayChannelPrivate { GArray *monitors; guint monitors_max; gboolean enable_adaptive_streaming; + gpointer handle; SpiceGlScanout scanout; }; @@ -83,6 +84,7 @@ enum { PROP_MONITORS, PROP_MONITORS_MAX, PROP_GL_SCANOUT, + PROP_HANDLE, }; enum { @@ -91,6 +93,7 @@ enum { SPICE_DISPLAY_INVALIDATE, SPICE_DISPLAY_MARK, SPICE_DISPLAY_GL_DRAW, + SPICE_DISPLAY_STREAMING_MODE, SPICE_DISPLAY_LAST_SIGNAL, }; @@ -227,6 +230,10 @@ static void spice_display_get_property(GObject *object, g_value_set_static_boxed(value, spice_display_channel_get_gl_scanout(channel)); break; } + case PROP_HANDLE: { + g_value_set_pointer(value, c->handle); + break; + } default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; @@ -238,7 +245,14 @@ static void spice_display_set_property(GObject *object, const GValue *value, GParamSpec *pspec) { + SpiceDisplayChannel *channel = SPICE_DISPLAY_CHANNEL(object); + SpiceDisplayChannelPrivate *c = channel->priv; + switch (prop_id) { + case PROP_HANDLE: { + c->handle = g_value_get_pointer(value); + break; + } default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; @@ -338,6 +352,22 @@ static void spice_display_channel_class_init(SpiceDisplayChannelClass *klass) G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + /** + * SpiceDisplayChannel:handle: + * + * The handle for current window. + * + * Since: 0.35 + */ + g_object_class_install_property + (gobject_class, PROP_HANDLE, + g_param_spec_pointer("handle", + "Display handle", + "Display handle", + G_PARAM_WRITABLE | + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + /** * SpiceDisplayChannel::display-primary-create: * @display: the #SpiceDisplayChannel that emitted the signal @@ -454,6 +484,28 @@ static void spice_display_channel_class_init(SpiceDisplayChannelClass *klass) 4, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT); + /** + * SpiceDisplayChannel::streaming-mode: + * @display: the #SpiceDisplayChannel that emitted the signal + * @stream_enable: %TRUE when it's streaming mode + * + * The #SpiceDisplayChannel::streaming_mode signal is emitted when + * spice server is working in streaming mode. + * + * Since 0.35 + **/ + signals[SPICE_DISPLAY_STREAMING_MODE] = + g_signal_new("streaming-mode", + G_OBJECT_CLASS_TYPE(gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(SpiceDisplayChannelClass, + streaming_mode), + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); + g_type_class_add_private(klass, sizeof(SpiceDisplayChannelPrivate)); channel_set_handlers(SPICE_CHANNEL_CLASS(klass)); @@ -1779,6 +1831,9 @@ static void display_handle_surface_create(SpiceChannel *channel, SpiceMsgIn *in) if (create->flags & SPICE_SURFACE_FLAGS_PRIMARY) { SPICE_DEBUG("primary flags: %x", create->flags); surface->primary = true; + if (create->flags & SPICE_SURFACE_FLAGS_STREAMING_MODE) { + g_coroutine_signal_emit(channel, signals[SPICE_DISPLAY_STREAMING_MODE], 0, TRUE); + } create_canvas(channel, surface); if (c->mark_false_event_id != 0) { g_source_remove(c->mark_false_event_id); diff --git a/src/channel-display.h b/src/channel-display.h index 5b48d2f..9c51aa2 100644 --- a/src/channel-display.h +++ b/src/channel-display.h @@ -125,6 +125,7 @@ struct _SpiceDisplayChannel { * @display_primary_destroy: Signal class handler for the #SpiceDisplayChannel::display-primary-destroy signal. * @display_invalidate: Signal class handler for the #SpiceDisplayChannel::display-invalidate signal. * @display_mark: Signal class handler for the #SpiceDisplayChannel::display-mark signal. + * @streaming_mode: Signal class handler for the #SpiceDisplayChannel::streaming-mode signal. * * Class structure for #SpiceDisplayChannel. */ @@ -140,6 +141,8 @@ struct _SpiceDisplayChannelClass { gint x, gint y, gint w, gint h); void (*display_mark)(SpiceChannel *channel, gboolean mark); + void (*streaming_mode)(SpiceChannel *channel, + gboolean streaming_mode); /*< private >*/ }; diff --git a/src/spice-widget-priv.h b/src/spice-widget-priv.h index 1189cbb..9930bab 100644 --- a/src/spice-widget-priv.h +++ b/src/spice-widget-priv.h @@ -70,6 +70,7 @@ struct _SpiceDisplayPrivate { bool resize_guest_enable; /* state */ + gboolean streaming_mode; gboolean ready; gboolean monitor_ready; struct { diff --git a/src/spice-widget.c b/src/spice-widget.c index 8a6b5ab..abfbb32 100644 --- a/src/spice-widget.c +++ b/src/spice-widget.c @@ -615,14 +615,42 @@ G_GNUC_END_IGNORE_DEPRECATIONS static void drawing_area_realize(GtkWidget *area, gpointer user_data) { -#if defined(GDK_WINDOWING_X11) && defined(HAVE_EGL) +#ifdef GDK_WINDOWING_X11 SpiceDisplay *display = SPICE_DISPLAY(user_data); + SpiceDisplayPrivate *d = display->priv; + + /* GstVideoOverlay will currently be used only under x */ + if (d->streaming_mode && + !g_getenv("DISABLE_GSTVIDEOOVERLAY") && + GDK_IS_X11_DISPLAY(gdk_display_get_default())) { + GdkWindow *window; + GtkWidget *area; + + window = gtk_widget_get_window(GTK_WIDGET(display)); + if (window) { +#if GTK_CHECK_VERSION(2,18,0) + if (gdk_window_ensure_native (window)) +#endif + { + g_object_set(G_OBJECT (d->display), + "handle", GDK_WINDOW_XID(window), + NULL); + area = gtk_stack_get_visible_child(d->stack); + g_object_disconnect(area, + "any_signal::draw", draw_event, display, // to avoid spice drawing + NULL); + return; + } + } + } +#ifdef HAVE_EGL if (GDK_IS_X11_DISPLAY(gdk_display_get_default()) && spice_display_channel_get_gl_scanout(display->priv->display) != NULL) { spice_display_widget_gl_scanout(display); } #endif +#endif } static void spice_display_init(SpiceDisplay *display) @@ -2589,6 +2617,14 @@ static void queue_draw_area(SpiceDisplay *display, gint x, gint y, x, y, width, height); } +static void set_streaming_mode(SpiceChannel *channel, guint streaming, gpointer data) +{ + SpiceDisplay *display = data; + SpiceDisplayPrivate *d = display->priv; + + d->streaming_mode = streaming; +} + static void invalidate(SpiceChannel *channel, gint x, gint y, gint w, gint h, gpointer data) { @@ -2957,6 +2993,8 @@ static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data) spice_g_signal_connect_object(channel, "notify::monitors", G_CALLBACK(spice_display_widget_update_monitor_area), display, G_CONNECT_AFTER | G_CONNECT_SWAPPED); + spice_g_signal_connect_object(channel, "streaming-mode", + G_CALLBACK(set_streaming_mode), display, 0); if (spice_display_channel_get_primary(channel, 0, &primary)) { primary_create(channel, primary.format, primary.width, primary.height, primary.stride, primary.shmid, primary.data, display); -- 2.14.3 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel