From 0d53611c59d1c29f1d1cf22f62a50ae1ee21096b Mon Sep 17 00:00:00 2001
From: Haihua Hu <jared.hu@nxp.com>
Date: Mon, 9 May 2016 20:26:51 +0800
Subject: [PATCH] glimagesink: support video rotation using transform matrix

Add "rotate-method" to glimagesink and apply transform matrix
to vertex coordinate to control rotation.

Upstream-Status: Accepted[1.9.1]

https://bugzilla.gnome.org/show_bug.cgi?id=765795

Signed-off-by: Haihua Hu <jared.hu@nxp.com>
---
 ext/gl/gstglimagesink.c              | 266 ++++++++++++++++++++++++++++++++---
 ext/gl/gstglimagesink.h              |  18 +++
 gst-libs/gst/gl/gstglshaderstrings.c |  11 ++
 gst-libs/gst/gl/gstglshaderstrings.h |   1 +
 gst-libs/gst/gl/gstglutils.c         |  60 ++++++++
 gst-libs/gst/gl/gstglutils.h         |   5 +
 6 files changed, 344 insertions(+), 17 deletions(-)

diff --git a/ext/gl/gstglimagesink.c b/ext/gl/gstglimagesink.c
index 185577f2a07e..fb60468b361e 100644
--- a/ext/gl/gstglimagesink.c
+++ b/ext/gl/gstglimagesink.c
@@ -120,6 +120,7 @@ G_DEFINE_TYPE (GstGLImageSinkBin, gst_gl_image_sink_bin, GST_TYPE_GL_SINK_BIN);
 enum
 {
   PROP_BIN_0,
+  PROP_BIN_ROTATE_METHOD,
   PROP_BIN_FORCE_ASPECT_RATIO,
   PROP_BIN_PIXEL_ASPECT_RATIO,
   PROP_BIN_HANDLE_EVENTS,
@@ -181,6 +182,39 @@ _on_client_draw (GstGLImageSink * sink, GstGLContext * context,
   return ret;
 }
 
+#define DEFAULT_ROTATE_METHOD GST_GL_ROTATE_METHOD_IDENTITY
+
+#define GST_TYPE_GL_ROTATE_METHOD (gst_gl_rotate_method_get_type())
+
+static const GEnumValue rotate_methods[] = {
+  {GST_GL_ROTATE_METHOD_IDENTITY, "Identity (no rotation)", "none"},
+  {GST_GL_ROTATE_METHOD_90R, "Rotate clockwise 90 degrees", "clockwise"},
+  {GST_GL_ROTATE_METHOD_180, "Rotate 180 degrees", "rotate-180"},
+  {GST_GL_ROTATE_METHOD_90L, "Rotate counter-clockwise 90 degrees",
+      "counterclockwise"},
+  {GST_GL_ROTATE_METHOD_FLIP_HORIZ, "Flip horizontally", "horizontal-flip"},
+  {GST_GL_ROTATE_METHOD_FLIP_VERT, "Flip vertically", "vertical-flip"},
+  {GST_GL_ROTATE_METHOD_FLIP_UL_LR,
+      "Flip across upper left/lower right diagonal", "upper-left-diagonal"},
+  {GST_GL_ROTATE_METHOD_FLIP_UR_LL,
+      "Flip across upper right/lower left diagonal", "upper-right-diagonal"},
+  {GST_GL_ROTATE_METHOD_AUTO,
+      "Select rotate method based on image-orientation tag", "automatic"},
+  {0, NULL, NULL},
+};
+
+static GType
+gst_gl_rotate_method_get_type (void)
+{
+  static GType rotate_method_type = 0;
+
+  if (!rotate_method_type) {
+    rotate_method_type = g_enum_register_static ("GstGLRotateMethod",
+        rotate_methods);
+  }
+  return rotate_method_type;
+}
+
 static void
 gst_gl_image_sink_bin_init (GstGLImageSinkBin * self)
 {
@@ -203,6 +237,12 @@ gst_gl_image_sink_bin_class_init (GstGLImageSinkBinClass * klass)
   gobject_class->set_property = gst_gl_image_sink_bin_set_property;
 
   /* gl sink */
+  g_object_class_install_property (gobject_class, PROP_BIN_ROTATE_METHOD,
+      g_param_spec_enum ("rotate-method",
+          "rotate method",
+          "rotate method",
+          GST_TYPE_GL_ROTATE_METHOD, DEFAULT_ROTATE_METHOD,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
   g_object_class_install_property (gobject_class, PROP_BIN_FORCE_ASPECT_RATIO,
       g_param_spec_boolean ("force-aspect-ratio",
           "Force aspect ratio",
@@ -291,6 +331,7 @@ static void gst_glimage_sink_set_property (GObject * object, guint prop_id,
 static void gst_glimage_sink_get_property (GObject * object, guint prop_id,
     GValue * value, GParamSpec * param_spec);
 
+static gboolean gst_glimage_sink_event (GstBaseSink *sink, GstEvent * event);
 static gboolean gst_glimage_sink_query (GstBaseSink * bsink, GstQuery * query);
 static void gst_glimage_sink_set_context (GstElement * element,
     GstContext * context);
@@ -347,6 +388,7 @@ enum
 {
   ARG_0,
   ARG_DISPLAY,
+  PROP_ROTATE_METHOD,
   PROP_FORCE_ASPECT_RATIO,
   PROP_PIXEL_ASPECT_RATIO,
   PROP_CONTEXT,
@@ -404,6 +446,128 @@ _display_size_to_stream_size (GstGLImageSink * gl_sink, gdouble x,
   GST_TRACE ("transform %fx%f into %fx%f", x, y, *stream_x, *stream_y);
 }
 
+/* rotate 90 */
+static const gfloat clockwise_matrix[] = {
+   0.0f, -1.0f,  0.0,  0.0f,
+   1.0f,  0.0f,  0.0,  0.0f,
+   0.0f,  0.0f,  1.0,  0.0f,
+   0.0f,  0.0f,  0.0,  1.0f,
+};
+
+/* rotate 180 */
+static const gfloat clockwise_180_matrix[] = {
+  -1.0f,  0.0f,  0.0,  0.0f,
+   0.0f, -1.0f,	 0.0,  0.0f,
+   0.0f,  0.0f,  1.0,  0.0f,
+   0.0f,  0.0f,  0.0,  1.0f,
+};
+
+/* rotate 270 */
+static const gfloat counterclockwise_matrix[] = {
+   0.0f,  1.0f,  0.0,  0.0f,
+  -1.0f,  0.0f,  0.0,  0.0f,
+   0.0f,  0.0f,  1.0,  0.0f,
+   0.0f,  0.0f,  0.0,  1.0f,
+};
+
+/* horizontal-flip */
+static const gfloat horizontal_flip_matrix[] = {
+  1.0f, 0.0f, 0.0f, 0.0f,
+  0.0f, -1.0f, 0.0f, 0.0f,
+  0.0f, 0.0f, 1.0f, 0.0f,
+  0.0f, 0.0f, 0.0f, 1.0f,
+};
+
+/* vertical-flip */
+static const gfloat vertical_flip_matrix[] = {
+  -1.0f, 0.0f, 0.0f, 0.0f,
+  0.0f, 1.0f, 0.0f, 0.0f,
+  0.0f, 0.0f, 1.0f, 0.0f,
+  0.0f, 0.0f, 0.0f, 1.0f,
+};
+
+/* upper-left-diagonal */
+static const gfloat upper_left_matrix[] = {
+  0.0f, 1.0f, 0.0f, 0.0f,
+  1.0f, 0.0f, 0.0f, 0.0f,
+  0.0f, 0.0f, 1.0f, 0.0f,
+  0.0f, 0.0f, 0.0f, 1.0f,
+};
+
+/* upper-right-diagonal */
+static const gfloat upper_right_matrix[] = {
+  0.0f, -1.0f, 0.0f, 0.0f,
+  -1.0f, 0.0f, 0.0f, 0.0f,
+  0.0f, 0.0f, 1.0f, 0.0f,
+  0.0f, 0.0f, 0.0f, 1.0f,
+};
+
+static void
+gst_glimage_sink_set_rotate_method(GstGLImageSink *gl_sink,
+    GstGLRotateMethod method, gboolean from_tag)
+{
+  GstGLRotateMethod tag_method;
+  GST_GLIMAGE_SINK_LOCK (gl_sink);
+  if (from_tag)
+    tag_method = method;
+  else
+    gl_sink->rotate_method = method;
+
+  if (gl_sink->rotate_method == GST_GL_ROTATE_METHOD_AUTO)
+    method = tag_method;
+  else
+    method = gl_sink->rotate_method;
+
+  if (method != gl_sink->current_rotate_method)
+  {
+    GST_DEBUG_OBJECT (gl_sink, "Changing method from %s to %s",
+      rotate_methods[gl_sink->current_rotate_method].value_nick,
+      rotate_methods[method].value_nick);
+
+    switch (method)
+    {
+      case GST_GL_ROTATE_METHOD_IDENTITY:
+        gl_sink->transform_matrix = NULL;
+        gl_sink->output_mode_changed = TRUE;
+        break;
+      case GST_GL_ROTATE_METHOD_90R:
+        gl_sink->transform_matrix = clockwise_matrix;
+        gl_sink->output_mode_changed = TRUE;
+        break;
+      case GST_GL_ROTATE_METHOD_180:
+        gl_sink->transform_matrix = clockwise_180_matrix;
+        gl_sink->output_mode_changed = TRUE;
+        break;
+      case GST_GL_ROTATE_METHOD_90L:
+        gl_sink->transform_matrix = counterclockwise_matrix;
+        gl_sink->output_mode_changed = TRUE;
+        break;
+      case GST_GL_ROTATE_METHOD_FLIP_HORIZ:
+        gl_sink->transform_matrix = horizontal_flip_matrix;
+        gl_sink->output_mode_changed = TRUE;
+        break;
+      case GST_GL_ROTATE_METHOD_FLIP_VERT:
+        gl_sink->transform_matrix = vertical_flip_matrix;
+        gl_sink->output_mode_changed = TRUE;
+        break;
+      case GST_GL_ROTATE_METHOD_FLIP_UL_LR:
+        gl_sink->transform_matrix = upper_left_matrix;
+        gl_sink->output_mode_changed = TRUE;
+        break;
+      case GST_GL_ROTATE_METHOD_FLIP_UR_LL:
+        gl_sink->transform_matrix = upper_right_matrix;
+        gl_sink->output_mode_changed = TRUE;
+        break;
+      default:
+	g_assert_not_reached();
+        break;
+    }
+
+    gl_sink->current_rotate_method = method;
+  }
+  GST_GLIMAGE_SINK_UNLOCK (gl_sink);
+}
+
 static void
 gst_glimage_sink_navigation_send_event (GstNavigation * navigation, GstStructure
     * structure)
@@ -485,6 +649,13 @@ gst_glimage_sink_class_init (GstGLImageSinkClass * klass)
   gobject_class->set_property = gst_glimage_sink_set_property;
   gobject_class->get_property = gst_glimage_sink_get_property;
 
+  g_object_class_install_property (gobject_class, PROP_ROTATE_METHOD,
+      g_param_spec_enum ("rotate-method",
+          "rotate method",
+          "rotate method",
+          GST_TYPE_GL_ROTATE_METHOD, DEFAULT_ROTATE_METHOD,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
       g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
           "When enabled, scaling will respect original aspect ratio",
@@ -577,6 +748,7 @@ gst_glimage_sink_class_init (GstGLImageSinkClass * klass)
 
   gstelement_class->change_state = gst_glimage_sink_change_state;
   gstelement_class->set_context = gst_glimage_sink_set_context;
+  gstbasesink_class->event = gst_glimage_sink_event;
   gstbasesink_class->query = GST_DEBUG_FUNCPTR (gst_glimage_sink_query);
   gstbasesink_class->set_caps = gst_glimage_sink_set_caps;
   gstbasesink_class->get_caps = gst_glimage_sink_get_caps;
@@ -610,6 +782,9 @@ gst_glimage_sink_init (GstGLImageSink * glimage_sink)
   glimage_sink->mview_output_flags = DEFAULT_MULTIVIEW_FLAGS;
   glimage_sink->mview_downmix_mode = DEFAULT_MULTIVIEW_DOWNMIX;
 
+  glimage_sink->current_rotate_method = DEFAULT_ROTATE_METHOD;
+  glimage_sink->transform_matrix = NULL; 
+
   g_mutex_init (&glimage_sink->drawing_lock);
 }
 
@@ -624,6 +799,9 @@ gst_glimage_sink_set_property (GObject * object, guint prop_id,
   glimage_sink = GST_GLIMAGE_SINK (object);
 
   switch (prop_id) {
+    case PROP_ROTATE_METHOD:
+      gst_glimage_sink_set_rotate_method (glimage_sink, g_value_get_enum (value), FALSE);
+      break;
     case PROP_FORCE_ASPECT_RATIO:
     {
       glimage_sink->keep_aspect_ratio = g_value_get_boolean (value);
@@ -691,6 +869,9 @@ gst_glimage_sink_get_property (GObject * object, guint prop_id,
   glimage_sink = GST_GLIMAGE_SINK (object);
 
   switch (prop_id) {
+    case PROP_ROTATE_METHOD:
+      g_value_set_enum (value, glimage_sink->current_rotate_method);
+      break;
     case PROP_FORCE_ASPECT_RATIO:
       g_value_set_boolean (value, glimage_sink->keep_aspect_ratio);
       break;
@@ -848,6 +1029,50 @@ context_error:
 }
 
 static gboolean
+gst_glimage_sink_event (GstBaseSink *sink, GstEvent * event)
+{
+  GstGLImageSink *gl_sink =  GST_GLIMAGE_SINK (sink);
+  GstTagList *taglist;
+  gchar *orientation;
+  gboolean ret;
+
+  GST_DEBUG_OBJECT (gl_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_tag_list_get_string (taglist, "image-orientation", &orientation)) {
+        if (!g_strcmp0 ("rotate-0", orientation))
+          gst_glimage_sink_set_rotate_method (gl_sink, GST_GL_ROTATE_METHOD_IDENTITY, TRUE);
+        else if (!g_strcmp0 ("rotate-90", orientation))
+          gst_glimage_sink_set_rotate_method (gl_sink, GST_GL_ROTATE_METHOD_90R, TRUE);
+        else if (!g_strcmp0 ("rotate-180", orientation))
+          gst_glimage_sink_set_rotate_method (gl_sink, GST_GL_ROTATE_METHOD_180, TRUE);
+        else if (!g_strcmp0 ("rotate-270", orientation))
+          gst_glimage_sink_set_rotate_method (gl_sink, GST_GL_ROTATE_METHOD_90L, TRUE);
+        else if (!g_strcmp0 ("flip-rotate-0", orientation))
+          gst_glimage_sink_set_rotate_method (gl_sink, GST_GL_ROTATE_METHOD_FLIP_HORIZ, TRUE);
+        else if (!g_strcmp0 ("flip-rotate-90", orientation))
+          gst_glimage_sink_set_rotate_method (gl_sink, GST_GL_ROTATE_METHOD_FLIP_UR_LL, TRUE);
+        else if (!g_strcmp0 ("flip-rotate-180", orientation))
+          gst_glimage_sink_set_rotate_method (gl_sink, GST_GL_ROTATE_METHOD_FLIP_VERT, TRUE);
+        else if (!g_strcmp0 ("flip-rotate-270", orientation))
+          gst_glimage_sink_set_rotate_method (gl_sink, GST_GL_ROTATE_METHOD_FLIP_UL_LR, TRUE);
+
+        g_free (orientation);
+      }
+      break;
+    default:
+      break;
+  }
+
+  ret = GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
+
+  return ret;
+}
+
+static gboolean
 gst_glimage_sink_query (GstBaseSink * bsink, GstQuery * query)
 {
   GstGLImageSink *glimage_sink = GST_GLIMAGE_SINK (bsink);
@@ -1817,7 +2042,10 @@ gst_glimage_sink_thread_init_redisplay (GstGLImageSink * gl_sink)
         GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
         gst_gl_shader_string_fragment_external_oes_default);
   } else {
-    vert_stage = gst_glsl_stage_new_default_vertex (gl_sink->context);
+    vert_stage = gst_glsl_stage_new_with_string (gl_sink->context,
+        GL_VERTEX_SHADER, GST_GLSL_VERSION_NONE,
+        GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
+        gst_gl_shader_string_vertex_mat4_vertex_transform);
     frag_stage = gst_glsl_stage_new_default_fragment (gl_sink->context);
   }
   if (!vert_stage || !frag_stage) {
@@ -1943,8 +2171,16 @@ gst_glimage_sink_on_resize (GstGLImageSink * gl_sink, gint width, gint height)
 
       src.x = 0;
       src.y = 0;
-      src.w = GST_VIDEO_SINK_WIDTH (gl_sink);
-      src.h = GST_VIDEO_SINK_HEIGHT (gl_sink);
+      if (gl_sink->current_rotate_method == GST_GL_ROTATE_METHOD_90R
+          || gl_sink->current_rotate_method == GST_GL_ROTATE_METHOD_90L
+          || gl_sink->current_rotate_method == GST_GL_ROTATE_METHOD_FLIP_UL_LR
+          || gl_sink->current_rotate_method == GST_GL_ROTATE_METHOD_FLIP_UR_LL) {
+        src.h = GST_VIDEO_SINK_WIDTH (gl_sink);
+        src.w = GST_VIDEO_SINK_HEIGHT (gl_sink);
+      } else {
+        src.w = GST_VIDEO_SINK_WIDTH (gl_sink);
+        src.h = GST_VIDEO_SINK_HEIGHT (gl_sink);
+      }
 
       dst.x = 0;
       dst.y = 0;
@@ -1974,13 +2210,6 @@ gst_glimage_sink_on_resize (GstGLImageSink * gl_sink, gint width, gint height)
   GST_GLIMAGE_SINK_UNLOCK (gl_sink);
 }
 
-static const gfloat identity_matrix[] = {
-  1.0f, 0.0f, 0.0, 0.0f,
-  0.0f, 1.0f, 0.0, 0.0f,
-  0.0f, 0.0f, 1.0, 0.0f,
-  0.0f, 0.0f, 0.0, 1.0f,
-};
-
 static void
 gst_glimage_sink_on_draw (GstGLImageSink * gl_sink)
 {
@@ -2109,18 +2338,21 @@ gst_glimage_sink_on_draw (GstGLImageSink * gl_sink)
     gl->ActiveTexture (GL_TEXTURE0);
     gl->BindTexture (gl_target, gl_sink->redisplay_texture);
     gst_gl_shader_set_uniform_1i (gl_sink->redisplay_shader, "tex", 0);
-    if (gl_sink->texture_target == GST_GL_TEXTURE_TARGET_EXTERNAL_OES) {
+    {
       GstVideoAffineTransformationMeta *af_meta;
+      gfloat matrix[16];
 
       af_meta =
           gst_buffer_get_video_affine_transformation_meta
           (gl_sink->stored_buffer[0]);
-      if (af_meta)
-        gst_gl_shader_set_uniform_matrix_4fv (gl_sink->redisplay_shader,
-            "u_transformation", 1, FALSE, af_meta->matrix);
-      else
-        gst_gl_shader_set_uniform_matrix_4fv (gl_sink->redisplay_shader,
-            "u_transformation", 1, FALSE, identity_matrix);
+
+      gst_gl_get_affine_transformation_meta_as_ndc (af_meta, matrix);
+
+      if (gl_sink->transform_matrix)
+        gst_gl_multiply_matrix4 (gl_sink->transform_matrix, matrix, matrix);
+
+      gst_gl_shader_set_uniform_matrix_4fv (gl_sink->redisplay_shader,
+          "u_transformation", 1, FALSE, matrix);
     }
 
     gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
diff --git a/ext/gl/gstglimagesink.h b/ext/gl/gstglimagesink.h
index f9a052a12ed8..c0ab3ffb3b47 100644
--- a/ext/gl/gstglimagesink.h
+++ b/ext/gl/gstglimagesink.h
@@ -44,6 +44,19 @@ GST_DEBUG_CATEGORY_EXTERN (gst_debug_glimage_sink);
 #define GST_IS_GLIMAGE_SINK_CLASS(klass) \
     (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GLIMAGE_SINK))
 
+typedef enum
+{
+  GST_GL_ROTATE_METHOD_IDENTITY,
+  GST_GL_ROTATE_METHOD_90R,
+  GST_GL_ROTATE_METHOD_180,
+  GST_GL_ROTATE_METHOD_90L,
+  GST_GL_ROTATE_METHOD_FLIP_HORIZ,
+  GST_GL_ROTATE_METHOD_FLIP_VERT,
+  GST_GL_ROTATE_METHOD_FLIP_UL_LR,
+  GST_GL_ROTATE_METHOD_FLIP_UR_LL,
+  GST_GL_ROTATE_METHOD_AUTO
+}GstGLRotateMethod;
+
 typedef struct _GstGLImageSink GstGLImageSink;
 typedef struct _GstGLImageSinkClass GstGLImageSinkClass;
 
@@ -126,6 +139,11 @@ struct _GstGLImageSink
 
     GstGLOverlayCompositor *overlay_compositor;
 
+    /* current video flip method */
+    GstGLRotateMethod current_rotate_method;
+    GstGLRotateMethod rotate_method;
+    const gfloat *transform_matrix;
+
     /* fps print support */
     guint64 frame_showed;
     GstClockTime run_time;
diff --git a/gst-libs/gst/gl/gstglshaderstrings.c b/gst-libs/gst/gl/gstglshaderstrings.c
index 729be6f75100..10186f1b86ec 100644
--- a/gst-libs/gst/gl/gstglshaderstrings.c
+++ b/gst-libs/gst/gl/gstglshaderstrings.c
@@ -35,6 +35,17 @@ const gchar *gst_gl_shader_string_vertex_default =
     "   v_texcoord = a_texcoord;\n"
     "}\n";
 
+const gchar *gst_gl_shader_string_vertex_mat4_vertex_transform =
+    "uniform mat4 u_transformation;\n"
+    "attribute vec4 a_position;\n"
+    "attribute vec2 a_texcoord;\n"
+    "varying vec2 v_texcoord;\n"
+    "void main()\n"
+    "{\n"
+    "   gl_Position = u_transformation * a_position;\n"
+    "   v_texcoord = a_texcoord;\n"
+    "}\n";
+
 const gchar *gst_gl_shader_string_vertex_mat4_texture_transform =
     "uniform mat4 u_transformation;\n"
     "attribute vec4 a_position;\n"
diff --git a/gst-libs/gst/gl/gstglshaderstrings.h b/gst-libs/gst/gl/gstglshaderstrings.h
index 49ea8de427c5..f9a13c8b3d92 100644
--- a/gst-libs/gst/gl/gstglshaderstrings.h
+++ b/gst-libs/gst/gl/gstglshaderstrings.h
@@ -28,6 +28,7 @@ G_BEGIN_DECLS
 extern const gchar *gst_gl_shader_string_vertex_default;
 extern const gchar *gst_gl_shader_string_fragment_default;
 
+extern const gchar *gst_gl_shader_string_vertex_mat4_vertex_transform;
 extern const gchar *gst_gl_shader_string_vertex_mat4_texture_transform;
 extern const gchar *gst_gl_shader_string_fragment_external_oes_default;
 
diff --git a/gst-libs/gst/gl/gstglutils.c b/gst-libs/gst/gl/gstglutils.c
index e2e04e407ed7..a38772f10be3 100644
--- a/gst-libs/gst/gl/gstglutils.c
+++ b/gst-libs/gst/gl/gstglutils.c
@@ -1067,3 +1067,63 @@ gst_gl_value_set_texture_target_from_mask (GValue * value,
     return ret;
   }
 }
+
+static const gfloat identity_matrix[] = {
+  1.0f, 0.0f, 0.0, 0.0f,
+  0.0f, 1.0f, 0.0, 0.0f,
+  0.0f, 0.0f, 1.0, 0.0f,
+  0.0f, 0.0f, 0.0, 1.0f,
+};
+
+static const gfloat from_ndc_matrix[] = {
+  0.5f, 0.0f, 0.0, 0.5f,
+  0.0f, 0.5f, 0.0, 0.5f,
+  0.0f, 0.0f, 0.5, 0.5f,
+  0.0f, 0.0f, 0.0, 1.0f,
+};
+
+static const gfloat to_ndc_matrix[] = {
+  2.0f, 0.0f, 0.0, -1.0f,
+  0.0f, 2.0f, 0.0, -1.0f,
+  0.0f, 0.0f, 2.0, -1.0f,
+  0.0f, 0.0f, 0.0, 1.0f,
+};
+
+void
+gst_gl_multiply_matrix4 (const gfloat * a, const gfloat * b, gfloat * result)
+{
+  int i, j, k;
+  gfloat tmp[16] = { 0.0f };
+
+  if (!a || !b || !result)
+    return;
+
+  for (i = 0; i < 4; i++) {
+    for (j = 0; j < 4; j++) {
+      for (k = 0; k < 4; k++) {
+        tmp[i + (j * 4)] += a[i + (k * 4)] * b[k + (j * 4)];
+      }
+    }
+  }
+
+  for (i = 0; i < 16; i++)
+    result[i] = tmp[i];
+}
+
+void
+gst_gl_get_affine_transformation_meta_as_ndc (GstVideoAffineTransformationMeta *
+    meta, gfloat * matrix)
+{
+  if (!meta) {
+    int i;
+
+    for (i = 0; i < 16; i++) {
+      matrix[i] = identity_matrix[i];
+    }
+  } else {
+    gfloat tmp[16] = { 0.0f };
+
+    gst_gl_multiply_matrix4 (from_ndc_matrix, meta->matrix, tmp);
+    gst_gl_multiply_matrix4 (tmp, to_ndc_matrix, matrix);
+  }
+}
diff --git a/gst-libs/gst/gl/gstglutils.h b/gst-libs/gst/gl/gstglutils.h
index 1c5ab12344fe..fc12801978e9 100644
--- a/gst-libs/gst/gl/gstglutils.h
+++ b/gst-libs/gst/gl/gstglutils.h
@@ -24,6 +24,7 @@
 #include <gst/video/video.h>
 
 #include <gst/gl/gstgl_fwd.h>
+#include <gst/video/gstvideoaffinetransformationmeta.h>
 
 G_BEGIN_DECLS
 
@@ -116,6 +117,10 @@ gboolean gst_gl_value_set_texture_target_from_mask (GValue * value,
 gboolean gst_gl_value_set_texture_target (GValue * value, GstGLTextureTarget target);
 GstGLTextureTarget gst_gl_value_get_texture_target_mask (const GValue * value);
 
+void gst_gl_multiply_matrix4 (const gfloat * a, const gfloat * b, gfloat * result);
+void gst_gl_get_affine_transformation_meta_as_ndc (GstVideoAffineTransformationMeta *
+    meta, gfloat * matrix);
+
 G_END_DECLS
 
 #endif /* __GST_GL_UTILS_H__ */
