From 4d70fac89099ac7d3cee93acce8e86a00f04191d Mon Sep 17 00:00:00 2001 From: Jeffy Chen Date: Tue, 30 Mar 2021 07:08:06 +0800 Subject: [PATCH 21/49] waylandsink: Support window fill-mode property Tested with: gst-launch-1.0 videotestsrc ! waylandsink fullscreen=1 fill-mode=crop Signed-off-by: Jeffy Chen --- ext/wayland/gstwaylandsink.c | 56 +++++++++++++++ ext/wayland/gstwaylandsink.h | 1 + gst-libs/gst/wayland/gstwlwindow.c | 111 ++++++++++++++++++++++++----- gst-libs/gst/wayland/gstwlwindow.h | 11 +++ 4 files changed, 163 insertions(+), 16 deletions(-) diff --git a/ext/wayland/gstwaylandsink.c b/ext/wayland/gstwaylandsink.c index 71b1c1c..f8032fc 100644 --- a/ext/wayland/gstwaylandsink.c +++ b/ext/wayland/gstwaylandsink.c @@ -63,9 +63,12 @@ enum PROP_ROTATE_METHOD, PROP_LAYER, PROP_ALPHA, + PROP_FILL_MODE, PROP_LAST }; +static GstWlWindowFillMode DEFAULT_FILL_MODE = GST_WL_WINDOW_FIT; + GST_DEBUG_CATEGORY (gstwayland_debug); #define GST_CAT_DEFAULT gstwayland_debug @@ -136,6 +139,24 @@ gst_wl_window_layer_get_type (void) return layer; } +#define GST_TYPE_WL_WINDOW_FILL_MODE (gst_wl_window_fill_mode_get_type ()) +static GType +gst_wl_window_fill_mode_get_type (void) +{ + static GType mode = 0; + + if (!mode) { + static const GEnumValue modes[] = { + {GST_WL_WINDOW_STRETCH, "Ignore aspect ratio", "stretch"}, + {GST_WL_WINDOW_FIT, "Keep aspect ratio", "fit"}, + {GST_WL_WINDOW_CROP, "Keep aspect ratio by expanding", "crop"}, + {0, NULL, NULL} + }; + mode = g_enum_register_static ("GstWlWindowFillMode", modes); + } + return mode; +} + static void gst_wayland_sink_class_init (GstWaylandSinkClass * klass) { @@ -208,6 +229,15 @@ gst_wayland_sink_class_init (GstWaylandSinkClass * klass) "Wayland window alpha", 0.0, 1.0, 1.0, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); + if (g_getenv ("WAYLANDSINK_STRETCH")) + DEFAULT_FILL_MODE = GST_WL_WINDOW_STRETCH; + + g_object_class_install_property (gobject_class, PROP_FILL_MODE, + g_param_spec_enum ("fill-mode", "Window fill mode", + "Wayland window fill mode", + GST_TYPE_WL_WINDOW_FILL_MODE, DEFAULT_FILL_MODE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** * waylandsink:render-rectangle: * @@ -228,6 +258,7 @@ gst_wayland_sink_init (GstWaylandSink * self) self->window_handle = 1; self->layer = GST_WL_WINDOW_LAYER_NORMAL; self->alpha = 1.0; + self->fill_mode = DEFAULT_FILL_MODE; } static void @@ -303,6 +334,19 @@ gst_wayland_sink_set_alpha (GstWaylandSink * self, gdouble alpha) g_mutex_unlock (&self->render_lock); } +static void +gst_wayland_sink_set_fill_mode (GstWaylandSink * self, + GstWlWindowFillMode fill_mode) +{ + if (fill_mode == self->fill_mode) + return; + + g_mutex_lock (&self->render_lock); + self->fill_mode = fill_mode; + gst_wl_window_ensure_fill_mode (self->window, fill_mode); + g_mutex_unlock (&self->render_lock); +} + static void gst_wayland_sink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) @@ -335,6 +379,11 @@ gst_wayland_sink_get_property (GObject * object, g_value_set_double (value, self->alpha); GST_OBJECT_UNLOCK (self); break; + case PROP_FILL_MODE: + GST_OBJECT_LOCK (self); + g_value_set_enum (value, self->fill_mode); + GST_OBJECT_UNLOCK (self); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -372,6 +421,11 @@ gst_wayland_sink_set_property (GObject * object, gst_wayland_sink_set_alpha (self, g_value_get_double (value)); GST_OBJECT_UNLOCK (self); break; + case PROP_FILL_MODE: + GST_OBJECT_LOCK (self); + gst_wayland_sink_set_fill_mode (self, g_value_get_enum (value)); + GST_OBJECT_UNLOCK (self); + break; default: if (!gst_video_overlay_set_property (object, PROP_LAST, prop_id, value)) G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -855,6 +909,7 @@ gst_wayland_sink_show_frame (GstVideoSink * vsink, GstBuffer * buffer) gst_wl_window_set_rotate_method (self->window, self->current_rotate_method); gst_wl_window_ensure_alpha (self->window, self->alpha); + gst_wl_window_ensure_fill_mode (self->window, self->fill_mode); } } @@ -1114,6 +1169,7 @@ gst_wayland_sink_set_window_handle (GstVideoOverlay * overlay, guintptr handle) gst_wl_window_set_rotate_method (self->window, self->current_rotate_method); gst_wl_window_ensure_alpha (self->window, self->alpha); + gst_wl_window_ensure_fill_mode (self->window, self->fill_mode); if (self->last_buffer) { /* Resend video info to force resize video surface */ diff --git a/ext/wayland/gstwaylandsink.h b/ext/wayland/gstwaylandsink.h index 0339ef7..7abfea1 100644 --- a/ext/wayland/gstwaylandsink.h +++ b/ext/wayland/gstwaylandsink.h @@ -59,6 +59,7 @@ struct _GstWaylandSink gboolean fullscreen; GstWlWindowLayer layer; gdouble alpha; + GstWlWindowFillMode fill_mode; gchar *display_name; diff --git a/gst-libs/gst/wayland/gstwlwindow.c b/gst-libs/gst/wayland/gstwlwindow.c index a102b65..61ec525 100644 --- a/gst-libs/gst/wayland/gstwlwindow.c +++ b/gst-libs/gst/wayland/gstwlwindow.c @@ -66,14 +66,16 @@ typedef struct _GstWlWindowPrivate /* the size of the video in the buffers */ gint video_width, video_height; - /* video width scaled according to par */ - gint scaled_width; + gint par_n; + gint par_d; 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; + + GstWlWindowFillMode fill_mode; } GstWlWindowPrivate; G_DEFINE_TYPE_WITH_CODE (GstWlWindow, gst_wl_window, G_TYPE_OBJECT, @@ -260,6 +262,19 @@ gst_wl_window_new_internal (GstWlDisplay * display, GMutex * render_lock) return self; } +void +gst_wl_window_ensure_fill_mode (GstWlWindow * self, + GstWlWindowFillMode fill_mode) +{ + GstWlWindowPrivate *priv; + + if (!self) + return; + + priv = gst_wl_window_get_instance_private (self); + priv->fill_mode = fill_mode; +} + static void gst_wl_window_set_config (GstWlWindow * self, const char *config) { @@ -400,7 +415,22 @@ gst_wl_window_new_toplevel (GstWlDisplay * display, const GstVideoInfo * info, /* set the initial size to be the same as the reported video size */ gint width = gst_util_uint64_scale_int_round (info->width, info->par_n, info->par_d); - gst_wl_window_set_render_rectangle (self, 0, 0, width, info->height, FALSE); + 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: + gst_wl_window_set_render_rectangle (self, 0, 0, + width, info->height, FALSE); + break; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + gst_wl_window_set_render_rectangle (self, 0, 0, + info->height, width, FALSE); + break; + } } return self; @@ -494,21 +524,29 @@ gst_wl_window_resize_video_surface (GstWlWindow * self, gboolean commit) GstVideoRectangle src = { 0, }; GstVideoRectangle dst = { 0, }; GstVideoRectangle res; - + gint64 video_x = 0, video_y = 0; + gint64 video_width = priv->video_width; + gint64 video_height = priv->video_height; + guint64 scaled_width = + gst_util_uint64_scale_int_round (video_width, priv->par_n, priv->par_d); + gboolean swapped = FALSE; + + /* Use scaled size for centering */ 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->scaled_width; - src.h = priv->video_height; + src.w = scaled_width; + src.h = 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->scaled_width; + src.w = video_height; + src.h = scaled_width; + swapped = TRUE; break; } @@ -517,10 +555,51 @@ gst_wl_window_resize_video_surface (GstWlWindow * self, gboolean commit) /* center the video_subsurface inside area_subsurface */ if (priv->video_viewport) { - gst_video_center_rect (&src, &dst, &res, TRUE); - wp_viewport_set_source (priv->video_viewport, wl_fixed_from_int (0), - wl_fixed_from_int (0), wl_fixed_from_int (priv->video_width), - wl_fixed_from_int (priv->video_height)); + if (priv->fill_mode == GST_WL_WINDOW_STRETCH) { + res = dst; + } else if (priv->fill_mode == GST_WL_WINDOW_FIT) { + gst_video_center_rect (&src, &dst, &res, TRUE); + } else if (priv->fill_mode == GST_WL_WINDOW_CROP) { + gdouble src_ratio, dst_ratio; + gint new_width, new_height; + + src_ratio = (gdouble) src.w / src.h; + dst_ratio = (gdouble) dst.w / dst.h; + + if (src_ratio < dst_ratio) + src.h = src.w / dst_ratio; + else if (src_ratio > dst_ratio) + src.w = src.h * dst_ratio; + + /* Calculate original video size from the scaled one */ + if (!swapped) { + new_width = + gst_util_uint64_scale_int_round (src.w, priv->par_d, priv->par_n); + new_height = src.h; + } else { + new_width = + gst_util_uint64_scale_int_round (src.h, priv->par_d, priv->par_n); + new_height = src.w; + } + + video_x += (video_width - new_width) / 2; + video_width = new_width; + video_y += (video_height - new_height) / 2; + video_height = new_height; + + res = dst; + } + + if (!swapped) { + wp_viewport_set_source (priv->video_viewport, + wl_fixed_from_int (video_x), wl_fixed_from_int (video_y), + wl_fixed_from_int (video_width), wl_fixed_from_int (video_height)); + } else { + wp_viewport_set_source (priv->video_viewport, + wl_fixed_from_int (video_y), wl_fixed_from_int (video_x), + wl_fixed_from_int (video_height), wl_fixed_from_int (video_width)); + } + wp_viewport_set_destination (priv->video_viewport, res.w, res.h); } else { gst_video_center_rect (&src, &dst, &res, FALSE); @@ -566,8 +645,8 @@ gst_wl_window_render (GstWlWindow * self, GstWlBuffer * buffer, GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self); if (G_UNLIKELY (info)) { - priv->scaled_width = - gst_util_uint64_scale_int_round (info->width, info->par_n, info->par_d); + priv->par_n = info->par_n; + priv->par_d = info->par_d; priv->video_width = info->width; priv->video_height = info->height; @@ -681,14 +760,14 @@ gst_wl_window_update_geometry (GstWlWindow * self) if (!priv->configured) return; - if (priv->scaled_width != 0) { + if (priv->par_n != 0) { wl_subsurface_set_sync (priv->video_subsurface); gst_wl_window_resize_video_surface (self, TRUE); } wl_surface_commit (priv->area_surface_wrapper); - if (priv->scaled_width != 0) + if (priv->par_n != 0) wl_subsurface_set_desync (priv->video_subsurface); } diff --git a/gst-libs/gst/wayland/gstwlwindow.h b/gst-libs/gst/wayland/gstwlwindow.h index 2672e0f..1571ece 100644 --- a/gst-libs/gst/wayland/gstwlwindow.h +++ b/gst-libs/gst/wayland/gstwlwindow.h @@ -35,11 +35,22 @@ typedef enum GST_WL_WINDOW_LAYER_BOTTOM = 2, } GstWlWindowLayer; +typedef enum +{ + GST_WL_WINDOW_STRETCH = 0, + GST_WL_WINDOW_FIT = 1, + GST_WL_WINDOW_CROP = 2, +} GstWlWindowFillMode; + struct _GstWlWindow { GObject parent_instance; }; +GST_WL_API +void gst_wl_window_ensure_fill_mode (GstWlWindow * self, + GstWlWindowFillMode fill_mode); + GST_WL_API void gst_wl_window_ensure_alpha (GstWlWindow * self, gdouble alpha); -- 2.20.1