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>
"readable": false,
"type": "GstValueArray",
"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"
PROP_0,
PROP_DISPLAY,
PROP_FULLSCREEN,
+ PROP_ROTATE_METHOD,
PROP_LAST
};
static void gst_wayland_sink_set_context (GstElement * element,
GstContext * context);
+static gboolean gst_wayland_sink_event (GstBaseSink * sink, GstEvent * event);
static GstCaps *gst_wayland_sink_get_caps (GstBaseSink * bsink,
GstCaps * filter);
static gboolean gst_wayland_sink_set_caps (GstBaseSink * bsink, GstCaps * caps);
gstelement_class->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->set_caps = GST_DEBUG_FUNCPTR (gst_wayland_sink_set_caps);
gstbasesink_class->propose_allocation =
"Whether the surface should be made fullscreen ", FALSE,
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:
*
}
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
gst_wayland_sink_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec)
{
g_value_set_boolean (value, sink->fullscreen);
GST_OBJECT_UNLOCK (sink);
break;
+ case PROP_ROTATE_METHOD:
+ GST_OBJECT_LOCK (sink);
+ g_value_set_enum (value, sink->current_rotate_method);
+ GST_OBJECT_UNLOCK (sink);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
gst_wayland_sink_set_fullscreen (sink, g_value_get_boolean (value));
GST_OBJECT_UNLOCK (sink);
break;
+ case PROP_ROTATE_METHOD:
+ gst_wayland_sink_set_rotate_method (sink, g_value_get_enum (value),
+ FALSE);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
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 *
gst_wayland_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
{
&sink->video_info, sink->fullscreen, &sink->render_lock);
g_signal_connect_object (sink->window, "closed",
G_CALLBACK (on_window_closed), sink, 0);
+ gst_wl_window_set_rotate_method (sink->window,
+ sink->current_rotate_method);
}
}
} else {
sink->window = gst_wl_window_new_in_surface (sink->display, surface,
&sink->render_lock);
+ gst_wl_window_set_rotate_method (sink->window,
+ sink->current_rotate_method);
}
} else {
GST_ERROR_OBJECT (sink, "Failed to find display handle, "
GMutex render_lock;
GstBuffer *last_buffer;
+ GstVideoOrientationMethod sink_rotate_method;
+ GstVideoOrientationMethod tag_rotate_method;
+ GstVideoOrientationMethod current_rotate_method;
+
struct wl_callback *callback;
};
/* the size of the video in the buffers */
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
* visible and certain steps should be skipped */
gboolean is_area_surface_mapped;
GstVideoRectangle dst = { 0, };
GstVideoRectangle res;
- /* center the video_subsurface inside area_subsurface */
- src.w = priv->video_width;
- src.h = priv->video_height;
+ switch (priv->buffer_transform) {
+ case WL_OUTPUT_TRANSFORM_NORMAL:
+ 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.h = priv->render_rectangle.h;
+ /* center the video_subsurface inside area_subsurface */
if (priv->video_viewport) {
gst_video_center_rect (&src, &dst, &res, TRUE);
wp_viewport_set_destination (priv->video_viewport, res.w, res.h);
}
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)
wl_surface_commit (priv->video_surface_wrapper);
g_object_unref (alloc);
}
-void
-gst_wl_window_set_render_rectangle (GstWlWindow * self, gint x, gint y,
- gint w, gint h)
+static void
+gst_wl_window_update_geometry (GstWlWindow * 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 */
- if (priv->area_subsurface)
- wl_subsurface_set_position (priv->area_subsurface, x, y);
+ if (priv->area_subsurface) {
+ wl_subsurface_set_position (priv->area_subsurface, priv->render_rectangle.x,
+ priv->render_rectangle.y);
+ }
if (priv->is_area_surface_mapped)
gst_wl_window_update_borders (self);
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 *
gst_wl_window_get_render_rectangle (GstWlWindow * self)
{
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);
+}
GST_WL_API
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