gstwaylandsink: Add rotate-method property

Similar to and inspired by glimagesink and gtkglsink.

Using the Wayland buffer transform API allows to offload
rotate operations to the Wayland compositor. This can have
several advantages:
 - The Wayland compositor may be able to use hardware plane
   capabilities to do the rotation.
 - In case of pre-rotated content on rotated outputs the
   rotations may equal out, potentially allowing the
   compositor to use hardware planes even if they don't
   support rotate operations.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2543>
This commit is contained in:
Robert Mader 2022-06-02 14:22:21 +02:00 committed by GStreamer Marge Bot
parent 5fc953e838
commit 6aa0b0cae2
5 changed files with 195 additions and 17 deletions

View file

@ -230601,6 +230601,18 @@
"readable": false, "readable": false,
"type": "GstValueArray", "type": "GstValueArray",
"writable": true "writable": true
},
"rotate-method": {
"blurb": "rotate method",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "identity (0)",
"mutable": "null",
"readable": true,
"type": "GstVideoOrientationMethod",
"writable": true
} }
}, },
"rank": "marginal" "rank": "marginal"

View file

@ -60,6 +60,7 @@ enum
PROP_0, PROP_0,
PROP_DISPLAY, PROP_DISPLAY,
PROP_FULLSCREEN, PROP_FULLSCREEN,
PROP_ROTATE_METHOD,
PROP_LAST PROP_LAST
}; };
@ -90,6 +91,7 @@ static GstStateChangeReturn gst_wayland_sink_change_state (GstElement * element,
static void gst_wayland_sink_set_context (GstElement * element, static void gst_wayland_sink_set_context (GstElement * element,
GstContext * context); GstContext * context);
static gboolean gst_wayland_sink_event (GstBaseSink * sink, GstEvent * event);
static GstCaps *gst_wayland_sink_get_caps (GstBaseSink * bsink, static GstCaps *gst_wayland_sink_get_caps (GstBaseSink * bsink,
GstCaps * filter); GstCaps * filter);
static gboolean gst_wayland_sink_set_caps (GstBaseSink * bsink, GstCaps * caps); static gboolean gst_wayland_sink_set_caps (GstBaseSink * bsink, GstCaps * caps);
@ -144,6 +146,7 @@ gst_wayland_sink_class_init (GstWaylandSinkClass * klass)
gstelement_class->set_context = gstelement_class->set_context =
GST_DEBUG_FUNCPTR (gst_wayland_sink_set_context); GST_DEBUG_FUNCPTR (gst_wayland_sink_set_context);
gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_wayland_sink_event);
gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_wayland_sink_get_caps); gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_wayland_sink_get_caps);
gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_wayland_sink_set_caps); gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_wayland_sink_set_caps);
gstbasesink_class->propose_allocation = gstbasesink_class->propose_allocation =
@ -162,6 +165,18 @@ gst_wayland_sink_class_init (GstWaylandSinkClass * klass)
"Whether the surface should be made fullscreen ", FALSE, "Whether the surface should be made fullscreen ", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* waylandsink:rotate-method:
*
* Since: 1.22
*/
g_object_class_install_property (gobject_class, PROP_ROTATE_METHOD,
g_param_spec_enum ("rotate-method",
"rotate method",
"rotate method",
GST_TYPE_VIDEO_ORIENTATION_METHOD, GST_VIDEO_ORIENTATION_IDENTITY,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/** /**
* waylandsink:render-rectangle: * waylandsink:render-rectangle:
* *
@ -192,6 +207,43 @@ gst_wayland_sink_set_fullscreen (GstWaylandSink * sink, gboolean fullscreen)
g_mutex_unlock (&sink->render_lock); g_mutex_unlock (&sink->render_lock);
} }
static void
gst_wayland_sink_set_rotate_method (GstWaylandSink * sink,
GstVideoOrientationMethod method, gboolean from_tag)
{
GstVideoOrientationMethod new_method;
if (method == GST_VIDEO_ORIENTATION_CUSTOM) {
GST_WARNING_OBJECT (sink, "unsupported custom orientation");
return;
}
GST_OBJECT_LOCK (sink);
if (from_tag)
sink->tag_rotate_method = method;
else
sink->sink_rotate_method = method;
if (sink->sink_rotate_method == GST_VIDEO_ORIENTATION_AUTO)
new_method = sink->tag_rotate_method;
else
new_method = sink->sink_rotate_method;
if (new_method != sink->current_rotate_method) {
GST_DEBUG_OBJECT (sink, "Changing method from %d to %d",
sink->current_rotate_method, new_method);
if (sink->window) {
g_mutex_lock (&sink->render_lock);
gst_wl_window_set_rotate_method (sink->window, new_method);
g_mutex_unlock (&sink->render_lock);
}
sink->current_rotate_method = new_method;
}
GST_OBJECT_UNLOCK (sink);
}
static void static void
gst_wayland_sink_get_property (GObject * object, gst_wayland_sink_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec) guint prop_id, GValue * value, GParamSpec * pspec)
@ -209,6 +261,11 @@ gst_wayland_sink_get_property (GObject * object,
g_value_set_boolean (value, sink->fullscreen); g_value_set_boolean (value, sink->fullscreen);
GST_OBJECT_UNLOCK (sink); GST_OBJECT_UNLOCK (sink);
break; break;
case PROP_ROTATE_METHOD:
GST_OBJECT_LOCK (sink);
g_value_set_enum (value, sink->current_rotate_method);
GST_OBJECT_UNLOCK (sink);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -232,6 +289,10 @@ gst_wayland_sink_set_property (GObject * object,
gst_wayland_sink_set_fullscreen (sink, g_value_get_boolean (value)); gst_wayland_sink_set_fullscreen (sink, g_value_get_boolean (value));
GST_OBJECT_UNLOCK (sink); GST_OBJECT_UNLOCK (sink);
break; break;
case PROP_ROTATE_METHOD:
gst_wayland_sink_set_rotate_method (sink, g_value_get_enum (value),
FALSE);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -420,6 +481,34 @@ gst_wayland_sink_set_context (GstElement * element, GstContext * context)
GST_ELEMENT_CLASS (parent_class)->set_context (element, context); GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
} }
static gboolean
gst_wayland_sink_event (GstBaseSink * bsink, GstEvent * event)
{
GstWaylandSink *sink = GST_WAYLAND_SINK (bsink);
GstTagList *taglist;
GstVideoOrientationMethod method;
gboolean ret;
GST_DEBUG_OBJECT (sink, "handling %s event", GST_EVENT_TYPE_NAME (event));
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_TAG:
gst_event_parse_tag (event, &taglist);
if (gst_video_orientation_from_tag (taglist, &method)) {
gst_wayland_sink_set_rotate_method (sink, method, TRUE);
}
break;
default:
break;
}
ret = GST_BASE_SINK_CLASS (parent_class)->event (bsink, event);
return ret;
}
static GstCaps * static GstCaps *
gst_wayland_sink_get_caps (GstBaseSink * bsink, GstCaps * filter) gst_wayland_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
{ {
@ -679,6 +768,8 @@ gst_wayland_sink_show_frame (GstVideoSink * vsink, GstBuffer * buffer)
&sink->video_info, sink->fullscreen, &sink->render_lock); &sink->video_info, sink->fullscreen, &sink->render_lock);
g_signal_connect_object (sink->window, "closed", g_signal_connect_object (sink->window, "closed",
G_CALLBACK (on_window_closed), sink, 0); G_CALLBACK (on_window_closed), sink, 0);
gst_wl_window_set_rotate_method (sink->window,
sink->current_rotate_method);
} }
} }
@ -922,6 +1013,8 @@ gst_wayland_sink_set_window_handle (GstVideoOverlay * overlay, guintptr handle)
} else { } else {
sink->window = gst_wl_window_new_in_surface (sink->display, surface, sink->window = gst_wl_window_new_in_surface (sink->display, surface,
&sink->render_lock); &sink->render_lock);
gst_wl_window_set_rotate_method (sink->window,
sink->current_rotate_method);
} }
} else { } else {
GST_ERROR_OBJECT (sink, "Failed to find display handle, " GST_ERROR_OBJECT (sink, "Failed to find display handle, "

View file

@ -63,6 +63,10 @@ struct _GstWaylandSink
GMutex render_lock; GMutex render_lock;
GstBuffer *last_buffer; GstBuffer *last_buffer;
GstVideoOrientationMethod sink_rotate_method;
GstVideoOrientationMethod tag_rotate_method;
GstVideoOrientationMethod current_rotate_method;
struct wl_callback *callback; struct wl_callback *callback;
}; };

View file

@ -63,6 +63,8 @@ typedef struct _GstWlWindowPrivate
/* the size of the video in the buffers */ /* the size of the video in the buffers */
gint video_width, video_height; gint video_width, video_height;
enum wl_output_transform buffer_transform;
/* when this is not set both the area_surface and the video_surface are not /* when this is not set both the area_surface and the video_surface are not
* visible and certain steps should be skipped */ * visible and certain steps should be skipped */
gboolean is_area_surface_mapped; gboolean is_area_surface_mapped;
@ -420,12 +422,27 @@ gst_wl_window_resize_video_surface (GstWlWindow * self, gboolean commit)
GstVideoRectangle dst = { 0, }; GstVideoRectangle dst = { 0, };
GstVideoRectangle res; GstVideoRectangle res;
/* center the video_subsurface inside area_subsurface */ switch (priv->buffer_transform) {
src.w = priv->video_width; case WL_OUTPUT_TRANSFORM_NORMAL:
src.h = priv->video_height; case WL_OUTPUT_TRANSFORM_180:
case WL_OUTPUT_TRANSFORM_FLIPPED:
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
src.w = priv->video_width;
src.h = priv->video_height;
break;
case WL_OUTPUT_TRANSFORM_90:
case WL_OUTPUT_TRANSFORM_270:
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
src.w = priv->video_height;
src.h = priv->video_width;
break;
}
dst.w = priv->render_rectangle.w; dst.w = priv->render_rectangle.w;
dst.h = priv->render_rectangle.h; dst.h = priv->render_rectangle.h;
/* center the video_subsurface inside area_subsurface */
if (priv->video_viewport) { if (priv->video_viewport) {
gst_video_center_rect (&src, &dst, &res, TRUE); gst_video_center_rect (&src, &dst, &res, TRUE);
wp_viewport_set_destination (priv->video_viewport, res.w, res.h); wp_viewport_set_destination (priv->video_viewport, res.w, res.h);
@ -434,6 +451,8 @@ gst_wl_window_resize_video_surface (GstWlWindow * self, gboolean commit)
} }
wl_subsurface_set_position (priv->video_subsurface, res.x, res.y); wl_subsurface_set_position (priv->video_subsurface, res.x, res.y);
wl_surface_set_buffer_transform (priv->video_surface_wrapper,
priv->buffer_transform);
if (commit) if (commit)
wl_surface_commit (priv->video_surface_wrapper); wl_surface_commit (priv->video_surface_wrapper);
@ -567,24 +586,16 @@ gst_wl_window_update_borders (GstWlWindow * self)
g_object_unref (alloc); g_object_unref (alloc);
} }
void static void
gst_wl_window_set_render_rectangle (GstWlWindow * self, gint x, gint y, gst_wl_window_update_geometry (GstWlWindow * self)
gint w, gint h)
{ {
GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self); GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);
if (priv->render_rectangle.x == x && priv->render_rectangle.y == y &&
priv->render_rectangle.w == w && priv->render_rectangle.h == h)
return;
priv->render_rectangle.x = x;
priv->render_rectangle.y = y;
priv->render_rectangle.w = w;
priv->render_rectangle.h = h;
/* position the area inside the parent - needs a parent commit to apply */ /* position the area inside the parent - needs a parent commit to apply */
if (priv->area_subsurface) if (priv->area_subsurface) {
wl_subsurface_set_position (priv->area_subsurface, x, y); wl_subsurface_set_position (priv->area_subsurface, priv->render_rectangle.x,
priv->render_rectangle.y);
}
if (priv->is_area_surface_mapped) if (priv->is_area_surface_mapped)
gst_wl_window_update_borders (self); gst_wl_window_update_borders (self);
@ -603,6 +614,24 @@ gst_wl_window_set_render_rectangle (GstWlWindow * self, gint x, gint y,
wl_subsurface_set_desync (priv->video_subsurface); wl_subsurface_set_desync (priv->video_subsurface);
} }
void
gst_wl_window_set_render_rectangle (GstWlWindow * self, gint x, gint y,
gint w, gint h)
{
GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);
if (priv->render_rectangle.x == x && priv->render_rectangle.y == y &&
priv->render_rectangle.w == w && priv->render_rectangle.h == h)
return;
priv->render_rectangle.x = x;
priv->render_rectangle.y = y;
priv->render_rectangle.w = w;
priv->render_rectangle.h = h;
gst_wl_window_update_geometry (self);
}
const GstVideoRectangle * const GstVideoRectangle *
gst_wl_window_get_render_rectangle (GstWlWindow * self) gst_wl_window_get_render_rectangle (GstWlWindow * self)
{ {
@ -610,3 +639,39 @@ gst_wl_window_get_render_rectangle (GstWlWindow * self)
return &priv->render_rectangle; return &priv->render_rectangle;
} }
static enum wl_output_transform
output_transform_from_orientation_method (GstVideoOrientationMethod method)
{
switch (method) {
case GST_VIDEO_ORIENTATION_IDENTITY:
return WL_OUTPUT_TRANSFORM_NORMAL;
case GST_VIDEO_ORIENTATION_90R:
return WL_OUTPUT_TRANSFORM_90;
case GST_VIDEO_ORIENTATION_180:
return WL_OUTPUT_TRANSFORM_180;
case GST_VIDEO_ORIENTATION_90L:
return WL_OUTPUT_TRANSFORM_270;
case GST_VIDEO_ORIENTATION_HORIZ:
return WL_OUTPUT_TRANSFORM_FLIPPED;
case GST_VIDEO_ORIENTATION_VERT:
return WL_OUTPUT_TRANSFORM_FLIPPED_180;
case GST_VIDEO_ORIENTATION_UL_LR:
return WL_OUTPUT_TRANSFORM_FLIPPED_90;
case GST_VIDEO_ORIENTATION_UR_LL:
return WL_OUTPUT_TRANSFORM_FLIPPED_270;
default:
g_assert_not_reached ();
}
}
void
gst_wl_window_set_rotate_method (GstWlWindow * self,
GstVideoOrientationMethod method)
{
GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);
priv->buffer_transform = output_transform_from_orientation_method (method);
gst_wl_window_update_geometry (self);
}

View file

@ -68,4 +68,8 @@ void gst_wl_window_set_render_rectangle (GstWlWindow * self, gint x, gint y,
GST_WL_API GST_WL_API
const GstVideoRectangle *gst_wl_window_get_render_rectangle (GstWlWindow * self); const GstVideoRectangle *gst_wl_window_get_render_rectangle (GstWlWindow * self);
GST_WL_API
void gst_wl_window_set_rotate_method (GstWlWindow *self,
GstVideoOrientationMethod rotate_method);
G_END_DECLS G_END_DECLS