videosink: CB1 videosink support ge2d rotation  [1/1]

PD#SWPL-164873

Problem:
videosink support ge2d rotation

Solution:
videosink support ge2d rotation

Verify:
AH212

Change-Id: Idf255b8c8ea8f63e5999f1575706e5f7057cb77a
Signed-off-by: kaiqiang.xiang <kaiqiang.xiang@amlogic.com>
diff --git a/src/Makefile.am b/src/Makefile.am
index 820c7f6..8aa2125 100755
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -14,13 +14,13 @@
 ##############################################################################
 
 # sources used to compile this plug-in
-libgstamlvideosink_la_SOURCES = gstamlvideosink.c
+libgstamlvideosink_la_SOURCES = gstamlvideosink.c ge2drotation.c
 # compiler and linker flags used to compile this plugin, set in configure.ac
 libgstamlvideosink_la_CFLAGS = $(GST_CFLAGS)
 libgstamlvideosink_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
 
 libgstamlvideosink_la_LIBADD = $(GST_LIBS)
-libgstamlvideosink_la_LIBADD += -L$(TARGET_DIR)/usr/lib  -lmediahal_videorender 
+libgstamlvideosink_la_LIBADD += -L$(TARGET_DIR)/usr/lib  -lmediahal_videorender -lge2d
 libgstamlvideosink_la_LIBADD += -L$(TARGET_DIR)/usr/lib/gstreamer-1.0 -lmediahal_videorender -lgstvideo-1.0 -lgstdrmbufferpool -lgstdrmallocator 
 libgstamlvideosink_la_LIBTOOLFLAGS = --tag=disable-static
 
diff --git a/src/ge2drotation.c b/src/ge2drotation.c
new file mode 100644
index 0000000..66afd0a
--- /dev/null
+++ b/src/ge2drotation.c
@@ -0,0 +1,387 @@
+/* GStreamer
+ * Copyright (C) 2022 amlogic
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+ * Boston, MA 02110-1335, USA.
+ */
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdbool.h>
+#include <sys/utsname.h>
+#include "resourcemanage.h"
+#include "meson_drm.h"
+#include <errno.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <drm/drm_fourcc.h>
+#include "ge2drotation.h"
+//#include <gst/gstdrmbufferpool.h>
+#include <gst/allocators/gstdmabuf.h>
+
+#define DUMP_ROTATION_DATA 0
+
+static int mdrmFd = -1;
+
+static void destroy_gem_buffer(int drmFd, GemBuffer *gemBuf)
+{
+   int rc;
+   struct drm_gem_close gclose;
+
+   for (int i= 0; i < gemBuf->planeCount; ++i)
+   {
+      if (gemBuf->fd[i] >= 0)
+      {
+         close(gemBuf->fd[i]);
+         gemBuf->fd[i]= -1;
+      }
+      if (gemBuf->handle[i] > 0)
+      {
+         memset(&gclose, 0, sizeof(gclose));
+         gclose.handle= gemBuf->handle[i];
+         rc= ioctl(drmFd, DRM_IOCTL_GEM_CLOSE, &gclose);
+         if (rc < 0)
+         {
+            GST_ERROR("Failed to release gem buffer handle %d: DRM_IOCTL_MODE_DESTROY_DUMB rc %d errno %d",
+                      gemBuf->handle[i], rc, errno );
+         }
+         gemBuf->handle[i]= 0;
+      }
+   }
+}
+
+void rotation_buffer_teardown(ROTBuffer *ROTbuf, int numBuffer)
+{
+   if (NULL == ROTbuf || 0 == numBuffer)
+   {
+     return;
+   }
+
+   for (int i= 0; i < numBuffer; ++i)
+   {
+     destroy_gem_buffer(mdrmFd, &ROTbuf[i].gemBuf);
+   }
+   free(ROTbuf);
+   ROTbuf = NULL;
+
+   if (mdrmFd >= 0)
+   {
+     close(mdrmFd);
+     mdrmFd= -1;
+   }
+
+}
+
+#define DEFAULT_DRM_NAME "/dev/dri/renderD128"
+static bool creat_gem_buffer(int drmFd, GemBuffer *gemBuf)
+{
+   bool result= false;
+   int rc;
+   struct drm_meson_gem_create gc;
+
+   for (int i= 0; i < gemBuf->planeCount; ++i)
+   {
+      gemBuf->fd[i]= -1;
+      memset(&gc, 0, sizeof(gc));
+      // NV12M
+      {
+         gc.flags= MESON_USE_VIDEO_PLANE;
+         if (i == 0)
+         {
+            gc.size= gemBuf->width*gemBuf->height;
+         }
+         else
+         {
+            gc.size= gemBuf->width*gemBuf->height/2;
+         }
+         gemBuf->stride[i]= gemBuf->width;
+      }
+      gemBuf->size[i]= gc.size;
+
+      rc= ioctl(drmFd, DRM_IOCTL_MESON_GEM_CREATE, &gc);
+      if (rc < 0)
+      {
+         GST_ERROR("Failed to create gem buffer: plane %d DRM_IOCTL_MESON_GEM_CREATE rc %d", i, rc);
+         goto exit;
+      }
+
+      gemBuf->handle[i]= gc.handle;
+      gemBuf->offset[i]= 0;
+
+      rc= drmPrimeHandleToFD(drmFd, gemBuf->handle[i], DRM_CLOEXEC | DRM_RDWR, &gemBuf->fd[i]);
+      if (rc < 0)
+      {
+         GST_ERROR("Failed to get fd for gem buf plane %d: handle %d drmPrimeHandleToFD rc %d",
+                   i, gemBuf->handle[i], rc );
+         goto exit;
+      }
+
+      GST_DEBUG("create gem buf plane %d size (%dx%d) %u bytes stride %d offset %d handle %d fd %d",
+                i, gemBuf->width, gemBuf->height, gemBuf->size[i],
+                 gemBuf->stride[i], gemBuf->offset[i], gemBuf->handle[i], gemBuf->fd[i] );
+   }
+
+   result= true;
+
+exit:
+
+   return result;
+}
+
+
+
+ROTBuffer* rotation_buffer_setup(int numPlanes, int width, int height, int numBuffer)
+{
+   bool result= false;
+   if (0 == numBuffer)
+   {
+     return NULL;
+   }
+   ROTBuffer* ROTbuf = (ROTBuffer*)calloc(numBuffer, sizeof(ROTBuffer));
+   if (!ROTbuf)
+   {
+     GST_ERROR("no memory for GemBuffer" );
+     return NULL;
+   }
+
+   char *drmName = DEFAULT_DRM_NAME;
+   mdrmFd= open(drmName, O_RDWR|O_CLOEXEC);
+   if (mdrmFd < 0)
+   {
+     GST_ERROR("Failed to open drm render node: %d", errno);
+     goto exit;
+   }
+
+   for (int i= 0; i < numBuffer; ++i)
+   {
+     ROTbuf[i].used = FALSE;
+     ROTbuf[i].gemBuf.planeCount= numPlanes;
+     ROTbuf[i].gemBuf.width= width;
+     ROTbuf[i].gemBuf.height= height;
+     for (int j= 0; j < MAX_PLANES; ++j)
+     {
+       ROTbuf[i].gemBuf.fd[j]= -1;
+     }
+     if (!creat_gem_buffer(mdrmFd, &ROTbuf[i].gemBuf))
+     {
+       GST_ERROR("Failed to allocate gem buffer");
+       goto exit;
+     }
+   }
+
+   result= true;
+
+exit:
+   if (!result)
+   {
+     rotation_buffer_teardown(ROTbuf, numBuffer);
+     ROTbuf = NULL;
+   }
+   return ROTbuf;
+}
+
+gboolean rotation_init(aml_ge2d_t* pAmlge2d, aml_ge2d_info_t **pge2dinfo)
+{
+   int ret = 0;
+   if (NULL == pAmlge2d)
+   {
+     return FALSE;
+   }
+
+   *pge2dinfo = &(pAmlge2d->ge2dinfo);
+   memset(pAmlge2d, 0, sizeof(aml_ge2d_t));
+
+   /* ge2d init */
+   ret = aml_ge2d_init(pAmlge2d);
+   if (ret < 0)
+   {
+      GST_ERROR("ge2d init failed");
+      return FALSE;
+   }
+   return TRUE;
+}
+
+gboolean rotation_exit(aml_ge2d_t* pAmlge2d)
+{
+   if (NULL == pAmlge2d)
+   {
+     return FALSE;
+   }
+
+   //ge2d release exit
+   aml_ge2d_exit(pAmlge2d);
+   return TRUE;
+}
+
+#if DUMP_ROTATION_DATA
+static int aml_write_file(int shared_fd, const char* file_name, int write_bytes)
+{
+   int fd = -1;
+   int write_num = 0;
+   char *vaddr = NULL;
+
+   if (shared_fd < 0 || !file_name || !write_bytes) {
+     printf("wrong params, read failed");
+     return -1;
+   }
+
+   vaddr = (char*)mmap(NULL, write_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, shared_fd, 0);
+   if (!vaddr) {
+     printf("%s,%d,mmap failed,Not enough memory\n",__func__, __LINE__);
+     return -1;
+   }
+
+   fd = open(file_name, O_RDWR | O_CREAT | O_APPEND, 0660);
+   if (fd < 0) {
+     printf("write file:%s open error\n", file_name);
+     return -1;
+   }
+
+   write_num = write(fd, vaddr, write_bytes);
+   if (write_num <= 0) {
+     printf("write file write_num=%d error\n", write_num);
+   }
+
+   close(fd);
+   munmap(vaddr, write_bytes);
+
+   return 0;
+}
+#endif
+
+gboolean get_rotation_size(int src_w, int src_h, int* dst_w, int* dst_h)
+{
+  if (NULL == dst_w || NULL == dst_h)
+  {
+    return FALSE;
+  }
+
+  int dst_width = src_w;
+  int dst_height = src_h;
+  // reduce to 720p
+  if (dst_width > 1280 || dst_height > 720)
+  {
+    int ratio = dst_width * 100 / dst_height;
+    if (ratio > 100)
+    {
+      dst_width = 1280;
+      dst_height = dst_width *100 / ratio;
+    }
+    else
+    {
+      dst_height = 720;
+      dst_width = dst_height * ratio / 100;
+    }
+    GST_DEBUG("ratio:%d, w:%d, h:%d\n", ratio, dst_width, dst_height);
+  }
+  *dst_w = dst_width/32*32;
+  *dst_h = dst_height;
+  return TRUE;
+}
+
+GstFlowReturn rotation_transform(aml_ge2d_info_t *pge2dinfo, GstBuffer *inbuf, ROTBuffer *ROTbuf, ROTATION_DEGREE ROTDegree)
+{
+    GstFlowReturn ret = GST_FLOW_ERROR;
+    GstMemory *mem;
+    int i;
+    int plane = 2;
+
+    GstVideoMeta *meta_data = NULL;
+
+    if (NULL == pge2dinfo || NULL == inbuf || NULL == ROTbuf)
+    {
+      return GST_FLOW_ERROR;
+    }
+
+    plane = gst_buffer_n_memory(inbuf);
+    for (i = 0; i < plane; i++)
+    {
+        mem = gst_buffer_peek_memory(inbuf, i);
+        g_return_val_if_fail(gst_is_drm_memory(mem), ret);
+        pge2dinfo->src_info[0].shared_fd[i] = gst_fd_memory_get_fd(mem);
+        pge2dinfo->dst_info.shared_fd[i] = ROTbuf->gemBuf.fd[i];
+    }
+
+    meta_data = gst_buffer_get_video_meta(inbuf);
+    // pge2dinfo->mem_sec = plugin->secure ? 1 : 0;
+    int src_width = meta_data->width;
+    int src_height = meta_data->height;
+
+    int dst_width = ROTbuf->gemBuf.width;
+    int dst_height = ROTbuf->gemBuf.height;
+
+    // Align to 32-bit
+    dst_width = dst_width/32*32;
+
+    pge2dinfo->src_info[0].memtype = GE2D_CANVAS_ALLOC;
+    pge2dinfo->src_info[0].mem_alloc_type = AML_GE2D_MEM_DMABUF;
+    pge2dinfo->src_info[0].plane_number = plane;                             /* When allocating memory, it is a continuous block or separate multiple blocks */
+    pge2dinfo->src_info[0].canvas_w = src_width;                             /* input width */
+    pge2dinfo->src_info[0].canvas_h = src_height;                            /* input height */
+    pge2dinfo->src_info[0].format = PIXEL_FORMAT_YCbCr_420_SP_NV12;
+    pge2dinfo->src_info[0].rect.x = 0;                                       /* input process area x */
+    pge2dinfo->src_info[0].rect.y = 0;                                       /* input process area y */
+    pge2dinfo->src_info[0].rect.w = src_width;                               /* input process area w */
+    pge2dinfo->src_info[0].rect.h = src_height;                              /* input process area h */
+    pge2dinfo->src_info[0].plane_alpha = 0xFF;                               /* global plane alpha*/
+
+    pge2dinfo->dst_info.memtype = GE2D_CANVAS_ALLOC;
+    pge2dinfo->dst_info.mem_alloc_type = AML_GE2D_MEM_DMABUF;
+    pge2dinfo->dst_info.plane_number = plane;                                /* When allocating memory, it is a continuous block or separate multiple blocks */
+    pge2dinfo->dst_info.canvas_w = dst_width;                                /* output width */
+    pge2dinfo->dst_info.canvas_h = dst_height;                               /* output height */
+    pge2dinfo->dst_info.format = PIXEL_FORMAT_YCbCr_420_SP_NV12;
+    pge2dinfo->dst_info.rect.x = 0;                                          /* output process area x */
+    pge2dinfo->dst_info.rect.y = 0;                                          /* output process area y */
+    pge2dinfo->dst_info.rect.w = dst_width;                                  /* output process area w */
+    pge2dinfo->dst_info.rect.h = dst_height;                                 /* output process area h */
+    pge2dinfo->dst_info.plane_alpha = 0xFF;                                  /* global plane alpha*/
+
+    pge2dinfo->dst_info.rotation = ROTDegree;
+    pge2dinfo->ge2d_op = AML_GE2D_STRETCHBLIT;
+
+    if (pge2dinfo->src_info[0].format == -1 || pge2dinfo->dst_info.format == -1) {
+        GST_ERROR("ge2d not support format src_info[0].format %d, dst_info.format %d",pge2dinfo->src_info[0].format, pge2dinfo->dst_info.format);
+        goto beach;
+    }
+
+#if DUMP_ROTATION_DATA
+    // DEBUG: dump src data
+    aml_write_file(pge2dinfo->src_info[0].shared_fd[0], "/tmp/input.yuv", src_width*src_height);
+    aml_write_file(pge2dinfo->src_info[0].shared_fd[1], "/tmp/input.yuv", src_width*src_height/2);
+#endif
+
+    GST_DEBUG("pge2dinfo: in_rect_w %d in_rect_h %d canvas_w %d canvas_h %d" , \
+                            pge2dinfo->src_info[0].rect.w, pge2dinfo->src_info[0].rect.h, \
+                            pge2dinfo->src_info[0].canvas_w,pge2dinfo->src_info[0].canvas_h);
+    ret = aml_ge2d_process(pge2dinfo);
+    if (ret < 0) {
+        GST_ERROR("ge2d process failed");
+        goto beach;
+    }
+    GST_DEBUG("pge2dinfo: out_rect_w %d out_rect_h %d canvas_w %d canvas_h %d" , \
+                                pge2dinfo->dst_info.rect.w, pge2dinfo->dst_info.rect.h,\
+                                pge2dinfo->dst_info.canvas_w,pge2dinfo->dst_info.canvas_h);
+#if DUMP_ROTATION_DATA
+    // DEBUG: dump dst data
+    aml_write_file(pge2dinfo->dst_info.shared_fd[0], "/tmp/output.yuv", dst_width*dst_height);
+    aml_write_file(pge2dinfo->dst_info.shared_fd[1], "/tmp/output.yuv", dst_width*dst_height/2);
+#endif
+    ret = GST_FLOW_OK;
+beach:
+    return ret;
+}
diff --git a/src/ge2drotation.h b/src/ge2drotation.h
new file mode 100644
index 0000000..3a18b09
--- /dev/null
+++ b/src/ge2drotation.h
@@ -0,0 +1,57 @@
+/* GStreamer
+ * Copyright (C) 2022 amlogic
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
+ * Boston, MA 02110-1335, USA.
+ */
+
+#include <gst/gst.h>
+#include <aml_ge2d.h>
+#include <gst/video/video.h>
+
+#define MAX_PLANES (3)
+typedef struct _GemBuffer
+{
+   gint width;
+   gint height;
+   gint planeCount;
+   guint handle[MAX_PLANES];
+   guint stride[MAX_PLANES];
+   guint offset[MAX_PLANES];
+   guint size[MAX_PLANES];
+   gint fd[MAX_PLANES];
+} GemBuffer;
+
+typedef struct _ROTBuffer{
+   gint index;
+   gboolean used;
+   GemBuffer gemBuf;
+}ROTBuffer;
+
+typedef enum
+{
+  WST_ROTATION_0 = 0,
+  WST_ROTATION_90 = 1,
+  WST_ROTATION_180 = 2,
+  WST_ROTATION_270 = 3,
+}ROTATION_DEGREE;
+
+
+ROTBuffer* rotation_buffer_setup(int numPlanes, int width, int height, int numBuffer);
+void rotation_buffer_teardown(ROTBuffer *pROTbuf, int numBuffer);
+GstFlowReturn rotation_transform(aml_ge2d_info_t *pge2dinfo, GstBuffer *inbuf, ROTBuffer *pROTbuf, ROTATION_DEGREE ROTDegree);
+gboolean rotation_init(aml_ge2d_t* pAmlge2d, aml_ge2d_info_t **pge2dinfo);
+gboolean rotation_exit(aml_ge2d_t* pAmlge2d);
+gboolean get_rotation_size(int src_w, int src_h, int* dst_w, int* dst_h);
diff --git a/src/gstamlvideosink.c b/src/gstamlvideosink.c
index 471a0da..77d440c 100644
--- a/src/gstamlvideosink.c
+++ b/src/gstamlvideosink.c
@@ -39,6 +39,7 @@
 #include <unistd.h>
 // #endif
 // #endif
+#include "ge2drotation.h"
 
 #ifdef GST_OBJECT_LOCK
 #undef GST_OBJECT_LOCK
@@ -124,6 +125,7 @@
     PROP_LGE_DISH_TRICK,
     PROP_LGE_DISH_TRICK_IGNORE_RATE,
 #endif
+    PROP_VIDEO_ROTATION,
 };
 
 typedef enum {
@@ -150,6 +152,9 @@
 
 #define PTS_90K (90000)
 
+#define ROT_BUFFER_MAX (5)
+#define ROT_QUEUE_MAX (20)
+
 typedef struct _GstAmlVideoSinkWindowSet
 {
     gboolean window_change;
@@ -224,6 +229,14 @@
     gint              video_latency;
     gboolean          set_video_latency;
 
+    /* rotation and ge2d */
+    aml_ge2d_info_t *pge2dinfo;
+    aml_ge2d_t amlge2d;
+    ROTBuffer* rot_buffer;
+    ROTATION_DEGREE rot_degree;
+    gboolean rot_changed;
+    gboolean rot_enable_property;
+
 #if GST_IMPORT_LGE_PROP
     GstAmlVideoSinkLgeCtxt lge_ctxt;
 #endif
@@ -264,6 +277,7 @@
                                             GstCaps *filter);
 static gboolean gst_aml_video_sink_set_caps(GstBaseSink *bsink, GstCaps *caps);
 static gboolean gst_aml_video_sink_show_frame(GstVideoSink *bsink, GstBuffer *buffer);
+static GstFlowReturn gst_aml_video_sink_show_frame_ex(GstVideoSink *vsink, GstBuffer *buffer);
 static gboolean gst_aml_video_sink_pad_event (GstBaseSink *basesink, GstEvent *event);
 static gboolean gst_aml_video_sink_send_event(GstElement *element, GstEvent *event);
 
@@ -275,6 +289,7 @@
 static gboolean gst_aml_video_sink_tunnel_buf(GstAmlVideoSink *vsink, GstBuffer *gst_buf, RenderBuffer *tunnel_lib_buf_wrap);
 static gboolean gst_get_mediasync_instanceid(GstAmlVideoSink *vsink);
 static void gst_set_report_info(void *userData, RenderMsgType type, int64_t pts, int64_t duration);
+static int get_rotation_buffer_idx(GstAmlVideoSink *vsink);
 
 #if GST_USE_PLAYBIN
 static GstElement *gst_aml_video_sink_find_audio_sink(GstAmlVideoSink *sink);
@@ -316,7 +331,7 @@
     gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR(gst_aml_video_sink_set_caps);
     gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_aml_video_sink_pad_event);
 
-    gstvideosink_class->show_frame = GST_DEBUG_FUNCPTR(gst_aml_video_sink_show_frame);
+    gstvideosink_class->show_frame = GST_DEBUG_FUNCPTR(gst_aml_video_sink_show_frame_ex);
 
     g_object_class_install_property(
         gobject_class, PROP_FULLSCREEN,
@@ -540,6 +555,12 @@
                              "H15, M16",
                              FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 #endif
+
+    g_object_class_install_property (
+        G_OBJECT_CLASS (klass), PROP_VIDEO_ROTATION,
+        g_param_spec_int ("video-rotation", "video-rotation",
+                             "video rotation",
+                             0, 3, 0, G_PARAM_WRITABLE));
 }
 
 static void gst_aml_video_sink_init(GstAmlVideoSink *sink)
@@ -561,6 +582,9 @@
     sink->secure_mode = FALSE;
     sink->detect_thread_handle = NULL;
     sink->quit_eos_detect_thread = FALSE;
+    sink->rotation_thread_handle = NULL;
+    sink->quit_rotation_thread = FALSE;
+    sink->buffer_queue = gst_queue_array_new (ROT_QUEUE_MAX);
     sink->frame_rate_num = 0;
     sink->frame_rate_denom = 0;
     sink->frame_rate_changed = FALSE;
@@ -574,6 +598,7 @@
     g_mutex_init(&sink->eos_lock);
     g_cond_init(&sink->eos_cond);
     g_mutex_init(&sink->report_info_lock);
+    g_mutex_init(&sink->rotation_buffer_lock);
 
     GST_AML_VIDEO_SINK_GET_PRIVATE(sink) = malloc(sizeof(GstAmlVideoSinkPrivate));
     gst_aml_video_sink_reset_private(sink);
@@ -957,6 +982,18 @@
         break;
     }
 #endif
+    case PROP_VIDEO_ROTATION:
+    {
+        int rotationDegree = g_value_get_int(value);
+        if (rotationDegree >= WST_ROTATION_0 && rotationDegree <= WST_ROTATION_270)
+        {
+          GST_DEBUG_OBJECT(sink, "set rotation degree %d", rotationDegree);
+          sink_priv->rot_degree = (ROTATION_DEGREE)rotationDegree;
+          sink_priv->rot_changed = TRUE;
+          sink_priv->rot_enable_property = TRUE;
+        }
+        break;
+    }
     default:
         // G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
         break;
@@ -966,12 +1003,18 @@
 static void gst_aml_video_sink_finalize(GObject *object)
 {
     GstAmlVideoSink *sink = GST_AML_VIDEO_SINK(object);
-
     GST_DEBUG_OBJECT(sink, "Finalizing aml video sink..");
 
+    if (sink->buffer_queue)
+    {
+      gst_queue_array_free (sink->buffer_queue);
+      sink->buffer_queue = NULL;
+    }
+
     g_mutex_clear(&sink->eos_lock);
     g_cond_clear(&sink->eos_cond);
     g_mutex_clear(&sink->report_info_lock);
+    g_mutex_clear(&sink->rotation_buffer_lock);
 
     gst_aml_video_sink_reset_private(sink);
     if (GST_AML_VIDEO_SINK_GET_PRIVATE(sink))
@@ -1057,6 +1100,7 @@
             GST_ERROR_OBJECT(sink, "render lib pause device fail when first into paused state");
             goto error;
         }
+        rotation_init(&sink_priv->amlge2d, &sink_priv->pge2dinfo);
         break;
     }
     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
@@ -1122,11 +1166,26 @@
             g_thread_join(sink->detect_thread_handle);
             sink->detect_thread_handle = NULL;
         }
+        if (sink->rotation_thread_handle)
+        {
+            sink->quit_rotation_thread = TRUE;
+            GST_OBJECT_UNLOCK(sink);
+            g_thread_join(sink->rotation_thread_handle);
+            GST_OBJECT_LOCK(sink);
+            sink->rotation_thread_handle = NULL;
+        }
         GST_DEBUG_OBJECT(sink, "before disconnect rlib");
         render_disconnect(sink_priv->render_device_handle);
         GST_DEBUG_OBJECT(sink, "after disconnect rlib");
         GST_DEBUG_OBJECT(sink, "buf stat | queued:%d, dequeued:%d, dropped:%d, rendered:%d",
                          sink->queued, sink->dequeued, sink->dropped, sink->rendered);
+
+        if (sink_priv->rot_buffer)
+        {
+          rotation_buffer_teardown(sink_priv->rot_buffer, ROT_BUFFER_MAX);
+          sink_priv->rot_buffer = NULL;
+        }
+        rotation_exit(&sink_priv->amlge2d);
         break;
     }
     case GST_STATE_CHANGE_READY_TO_NULL:
@@ -1431,6 +1490,100 @@
     return TRUE;
 }
 
+static void rotation_flush_buffer(GstAmlVideoSink *vsink)
+{
+  GstAmlVideoSink *sink = GST_AML_VIDEO_SINK(vsink);
+
+  g_mutex_lock(&sink->rotation_buffer_lock);
+  if (sink->buffer_queue)
+  {
+    while (!gst_queue_array_is_empty(sink->buffer_queue))
+    {
+      GstBuffer* buffer = (GstBuffer *)gst_queue_array_pop_head(sink->buffer_queue);
+      gst_buffer_unref(buffer);
+      GST_DEBUG_OBJECT(sink, "reset pop head buffer_queue len:%d", gst_queue_array_get_length(sink->buffer_queue));
+    }
+  }
+  g_mutex_unlock(&sink->rotation_buffer_lock);
+  GST_DEBUG_OBJECT(sink, "flush over buffer_queue len:%d", gst_queue_array_get_length(sink->buffer_queue));
+}
+
+static gpointer rotation_thread(gpointer data)
+{
+  GstAmlVideoSink *sink = (GstAmlVideoSink *)data;
+  GstAmlVideoSinkPrivate *sink_priv = GST_AML_VIDEO_SINK_GET_PRIVATE(sink);
+
+  while (!sink->quit_rotation_thread)
+  {
+    if (sink_priv->rot_buffer && -1 == get_rotation_buffer_idx(sink))
+    {
+      usleep(5000);
+      continue;
+    }
+
+    if (sink->buffer_queue && !gst_queue_array_is_empty(sink->buffer_queue))
+    {
+      // pop queue
+      g_mutex_lock(&sink->rotation_buffer_lock);
+      GstBuffer *buffer = (GstBuffer *)gst_queue_array_pop_head(sink->buffer_queue);
+      g_mutex_unlock(&sink->rotation_buffer_lock);
+
+      // show frame
+      gst_aml_video_sink_show_frame((GstVideoSink*)sink, buffer);
+      gst_buffer_unref(buffer);
+    }
+    else
+    {
+      usleep(10000);
+    }
+  }
+
+  // thread join, unref all gst buffer
+  rotation_flush_buffer(sink);
+
+  if (!sink->quit_rotation_thread)
+  {
+      g_thread_unref( sink->rotation_thread_handle );
+      sink->rotation_thread_handle= NULL;
+  }
+  GST_DEBUG("rotation_thread: exit");
+  return NULL;
+}
+
+static GstFlowReturn gst_aml_video_sink_push_queue(GstVideoSink *vsink, GstBuffer *buffer)
+{
+  GstAmlVideoSink *sink = GST_AML_VIDEO_SINK(vsink);
+  if (sink->rotation_thread_handle == NULL )
+  {
+    sink->quit_rotation_thread = FALSE;
+    GST_DEBUG_OBJECT(sink, "start rotation thread");
+    sink->rotation_thread_handle = g_thread_new("video_sink_rotation", rotation_thread, sink);
+  }
+
+  g_mutex_lock(&sink->rotation_buffer_lock);
+  // push queue
+  if (sink->buffer_queue)
+  {
+    gst_queue_array_push_tail (sink->buffer_queue, buffer);
+    gst_buffer_ref(buffer);
+  }
+  g_mutex_unlock(&sink->rotation_buffer_lock);
+  return GST_FLOW_OK;
+}
+
+
+static GstFlowReturn gst_aml_video_sink_show_frame_ex(GstVideoSink *vsink, GstBuffer *buffer)
+{
+  GstAmlVideoSink *sink = GST_AML_VIDEO_SINK(vsink);
+  GstAmlVideoSinkPrivate *sink_priv = GST_AML_VIDEO_SINK_GET_PRIVATE(sink);
+
+  if (sink_priv->rot_degree == WST_ROTATION_90 || sink_priv->rot_degree == WST_ROTATION_270)
+  {
+    return gst_aml_video_sink_push_queue(vsink, buffer);
+  }
+  return gst_aml_video_sink_show_frame(vsink, buffer);
+}
+
 static GstFlowReturn gst_aml_video_sink_show_frame(GstVideoSink *vsink, GstBuffer *buffer)
 {
     GstAmlVideoSink *sink = GST_AML_VIDEO_SINK(vsink);
@@ -1603,6 +1756,39 @@
     return ret;
 }
 
+void gst_aml_video_sink_tag_event(GstAmlVideoSink *vsink, GstTagList *list )
+{
+   if (NULL == vsink || NULL == list)
+   {
+      return;
+   }
+
+   GstAmlVideoSinkPrivate *sink_priv = GST_AML_VIDEO_SINK_GET_PRIVATE(vsink);
+   gchar *rotation_tag = NULL;
+   gint tagDegree = 0;
+   ROTATION_DEGREE rotationDegree = WST_ROTATION_0;
+
+   if ((!sink_priv->rot_enable_property) &&
+       (gst_tag_list_get_string(list, GST_TAG_IMAGE_ORIENTATION, &rotation_tag)))
+   {
+      if (sscanf(rotation_tag, "rotate-%d", &tagDegree))
+      {
+         rotationDegree = (ROTATION_DEGREE)(tagDegree/90);
+
+         if (rotationDegree >= WST_ROTATION_0 && rotationDegree <= WST_ROTATION_270)
+         {
+            sink_priv->rot_changed = TRUE;
+            sink_priv->rot_degree = rotationDegree;
+            GST_DEBUG_OBJECT(vsink, "set rotation degree %d", rotationDegree);
+         }
+      }
+      else if (sscanf(rotation_tag, "flip-rotate-%d", &tagDegree))
+      {
+         // not support flip right now, need support later
+      }
+   }
+}
+
 static gboolean gst_aml_video_sink_pad_event (GstBaseSink *basesink, GstEvent *event)
 {
     gboolean result = TRUE;
@@ -1620,6 +1806,8 @@
         sink_priv->is_flushing = TRUE;
         render_set_value(sink_priv->render_device_handle, KEY_KEEP_LAST_FRAME_ON_FLUSH, &sink->keep_last_frame_on_flush);
 
+        rotation_flush_buffer(sink);
+
         if (render_flush(sink_priv->render_device_handle) == 0)
         {
             GST_INFO_OBJECT(sink, "recv flush start and set render lib flushing succ");
@@ -1690,6 +1878,16 @@
         }
         break;
     }
+    case GST_EVENT_TAG:
+    {
+        GstTagList *list = NULL;
+        gst_event_parse_tag (event, &list);
+        if (list)
+        {
+           gst_aml_video_sink_tag_event(sink, list);
+        }
+        break;
+    }
     case GST_EVENT_CAPS:
     {
         GstCaps *caps;
@@ -1916,6 +2114,10 @@
     sink_priv->fps_d = 0;
     sink_priv->video_latency = 0;
     sink_priv->set_video_latency = FALSE;
+    sink_priv->rot_buffer = NULL;
+    sink_priv->rot_degree = WST_ROTATION_0;
+    sink_priv->rot_changed = FALSE;
+    sink_priv->rot_enable_property = FALSE;
 }
 
 static void gst_set_report_info(void *userData, RenderMsgType type, int64_t pts, int64_t duration)
@@ -1932,6 +2134,28 @@
     g_mutex_unlock(&sink->report_info_lock);
 }
 
+static gboolean is_rotation_buffer(void *userData, void* pbuf)
+{
+  GstAmlVideoSink *sink = (GstAmlVideoSink *)userData;
+  GstAmlVideoSinkPrivate *sink_priv = GST_AML_VIDEO_SINK_GET_PRIVATE(sink);
+
+  // no rotation buffer
+  if (!sink_priv->rot_buffer)
+  {
+    return FALSE;
+  }
+
+  ROTBuffer* rotation_buffer = (ROTBuffer*)pbuf;
+  for (int i = 0; i < ROT_BUFFER_MAX; i++)
+  {
+    if (rotation_buffer == &sink_priv->rot_buffer[i])
+    {
+      return TRUE;
+    }
+  }
+  return FALSE;
+}
+
 static void gst_render_msg_callback(void *userData, RenderMsgType type, void *msg)
 {
     GstAmlVideoSink *sink = (GstAmlVideoSink *)userData;
@@ -1943,30 +2167,29 @@
     {
         RenderBuffer *tunnel_lib_buf_wrap = (RenderBuffer *)msg;
         RenderDmaBuffer *dmabuf = &tunnel_lib_buf_wrap->dma;
-        GstBuffer *buffer = (GstBuffer *)tunnel_lib_buf_wrap->priv;
-        if (buffer)
-        {
-            sink->last_displayed_buf_pts = GST_BUFFER_PTS(buffer);
-            if (type == MSG_DROPED_BUFFER)
-            {
-                GST_LOG_OBJECT(sink, "get message: MSG_DROPED_BUFFER from tunnel lib");
-                sink->dropped++;
-                gst_set_report_info(userData, type, GST_BUFFER_PTS(buffer), 0);
-            }
-            else if (type == MSG_DISPLAYED_BUFFER)
-            {
-                GST_LOG_OBJECT(sink, "get message: MSG_DISPLAYED_BUFFER from tunnel lib");
-                sink->rendered++;
-                if (sink_priv->frame_step_on_preroll && sink->video_playing == FALSE) {
-                    render_pause(sink_priv->render_device_handle);
-                }
-            }
 
-            GST_DEBUG_OBJECT(sink, "buf:%p planeCnt:%d, plane[0].fd:%d, plane[1].fd:%d pts:%lld, buf stat | queued:%d, dequeued:%d, dropped:%d, rendered:%d",
-                             buffer,
-                             dmabuf->planeCnt, dmabuf->fd[0], dmabuf->fd[1],
-                             buffer ? GST_BUFFER_PTS(buffer) : -1, sink->queued, sink->dequeued, sink->dropped, sink->rendered);
-            //gst_aml_video_sink_dump_stat(sink, GST_DUMP_STAT_FILENAME);
+        if (tunnel_lib_buf_wrap->priv)
+        {
+          sink->last_displayed_buf_pts = tunnel_lib_buf_wrap->pts;
+          if (type == MSG_DROPED_BUFFER)
+          {
+              GST_LOG_OBJECT(sink, "get message: MSG_DROPED_BUFFER from tunnel lib");
+              sink->dropped++;
+              gst_set_report_info(userData, type, tunnel_lib_buf_wrap->pts, 0);
+          }
+          else if (type == MSG_DISPLAYED_BUFFER)
+          {
+              GST_LOG_OBJECT(sink, "get message: MSG_DISPLAYED_BUFFER from tunnel lib");
+              sink->rendered++;
+              if (sink_priv->frame_step_on_preroll && sink->video_playing == FALSE) {
+                render_pause(sink_priv->render_device_handle);
+              }
+          }
+          GST_DEBUG_OBJECT(sink, "buf:%p planeCnt:%d, plane[0].fd:%d, plane[1].fd:%d pts:%lld, buf stat | queued:%d, dequeued:%d, dropped:%d, rendered:%d",
+                               tunnel_lib_buf_wrap->priv,
+                               dmabuf->planeCnt, dmabuf->fd[0], dmabuf->fd[1],
+                               tunnel_lib_buf_wrap->priv ? tunnel_lib_buf_wrap->pts : -1, sink->queued, sink->dequeued, sink->dropped, sink->rendered);
+          //gst_aml_video_sink_dump_stat(sink, GST_DUMP_STAT_FILENAME);
         }
         else
         {
@@ -1978,20 +2201,30 @@
     {
         GstAmlVideoSinkPrivate *sink_priv = GST_AML_VIDEO_SINK_GET_PRIVATE(sink);
         RenderBuffer *tunnel_lib_buf_wrap = (RenderBuffer *)msg;
-        GstBuffer *buffer = (GstBuffer *)tunnel_lib_buf_wrap->priv;
-        GST_LOG_OBJECT(sink, "get message: MSG_RELEASE_BUFFER from tunnel lib,%p, pts:%lld ns",buffer, tunnel_lib_buf_wrap->pts);
-
-        if (buffer)
+        GST_LOG_OBJECT(sink, "get message: MSG_RELEASE_BUFFER from tunnel lib,%p, pts:%lld ns",tunnel_lib_buf_wrap->priv, tunnel_lib_buf_wrap->pts);
+        if (tunnel_lib_buf_wrap->priv)
         {
+          if (is_rotation_buffer(userData, tunnel_lib_buf_wrap->priv))
+          {
+            ROTBuffer* rotation_buffer = (ROTBuffer*)tunnel_lib_buf_wrap->priv;
+            rotation_buffer->used = FALSE;
+            /*printf("release [%d][%d][%d][%d][%d]\n", sink_priv->rot_buffer[0].used, sink_priv->rot_buffer[1].used,
+            sink_priv->rot_buffer[2].used, sink_priv->rot_buffer[3].used, sink_priv->rot_buffer[4].used);*/
+          }
+          else
+          {
+            GstBuffer *buffer = (GstBuffer *)tunnel_lib_buf_wrap->priv;
             GST_DEBUG_OBJECT(sink, "get message: MSG_RELEASE_BUFFER from tunnel lib, buffer:%p, from pool:%p", buffer, buffer->pool);
             gst_buffer_unref(buffer);
-            sink->dequeued++;
-            //gst_aml_video_sink_dump_stat(sink, GST_DUMP_STAT_FILENAME);
+          }
+          sink->dequeued++;
+          //gst_aml_video_sink_dump_stat(sink, GST_DUMP_STAT_FILENAME);
         }
         else
         {
-            GST_ERROR_OBJECT(sink, "tunnel lib: return void GstBuffer when MSG_RELEASE_BUFFER");
+          GST_ERROR_OBJECT(sink, "tunnel lib: return void GstBuffer when MSG_RELEASE_BUFFER");
         }
+
         render_free_render_buffer_wrap(sink_priv->render_device_handle, tunnel_lib_buf_wrap);
         break;
     }
@@ -2066,6 +2299,44 @@
     return ret;
 }
 
+static int get_rotation_buffer_idx(GstAmlVideoSink *vsink)
+{
+  GstAmlVideoSinkPrivate *sink_priv = GST_AML_VIDEO_SINK_GET_PRIVATE(vsink);
+
+  if (sink_priv->rot_buffer)
+  {
+    for (int i = 0; i < ROT_BUFFER_MAX; i++)
+    {
+      if (!sink_priv->rot_buffer[i].used)
+      {
+        /*printf("ok [%d][%d][%d][%d][%d]\n", sink_priv->rot_buffer[0].used, sink_priv->rot_buffer[1].used,
+            sink_priv->rot_buffer[2].used, sink_priv->rot_buffer[3].used, sink_priv->rot_buffer[4].used);*/
+        return i;
+      }
+    }
+    /*printf("fail [%d][%d][%d][%d][%d]\n", sink_priv->rot_buffer[0].used, sink_priv->rot_buffer[1].used,
+            sink_priv->rot_buffer[2].used, sink_priv->rot_buffer[3].used, sink_priv->rot_buffer[4].used);*/
+  }
+
+  return -1;
+}
+
+static void set_video_reversal(GstAmlVideoSink *vsink)
+{
+  GstAmlVideoSinkPrivate *sink_priv = GST_AML_VIDEO_SINK_GET_PRIVATE(vsink);
+  sink_priv->video_info_changed = TRUE;
+  int degree = (sink_priv->rot_degree == WST_ROTATION_180 ? 180 : 0);
+  if (render_set_value(sink_priv->render_device_handle, KEY_ROTATE_VIDEO, &degree) != -1)
+  {
+    GST_ERROR_OBJECT(vsink, "tunnel lib: set render video rotation success:%d", degree);
+    sink_priv->rot_changed = FALSE;
+  }
+  else
+  {
+    GST_ERROR_OBJECT(vsink, "tunnel lib: set render video rotation failed:%d", degree);
+  }
+}
+
 static gboolean gst_aml_video_sink_tunnel_buf(GstAmlVideoSink *vsink, GstBuffer *gst_buf, RenderBuffer *tunnel_lib_buf_wrap)
 {
     GstAmlVideoSinkPrivate *sink_priv = GST_AML_VIDEO_SINK_GET_PRIVATE(vsink);
@@ -2096,68 +2367,113 @@
         GST_ERROR_OBJECT(vsink, "too many memorys in gst buffer");
         goto error;
     }
-    //GST_DEBUG_OBJECT(vsink, "dbg3-0, dmabuf:%p", dmabuf);
 
-    dmabuf->planeCnt = n_mem;
-    dmabuf->width = vmeta->width;
-    dmabuf->height = vmeta->height;
-    if (sink_priv->dw_mode == 0) {
-        dmabuf->width = sink_priv->src_width;
-        dmabuf->height = sink_priv->src_height;
-    }
-
-    //GST_DEBUG_OBJECT(vsink, "dbgjxs, vmeta->width:%d, dmabuf->width:%d", vmeta->width, dmabuf->width);
-
-    for (guint i = 0; i < n_mem; i++)
+    // weston do rotation, set rotation 180 or 0
+    if (sink_priv->rot_changed)
     {
-        gint dmafd;
-        gsize size, offset, maxsize;
-        dma_mem = gst_buffer_peek_memory(gst_buf, i);
-        guint mem_idx = 0;
-        guint length = 0;
-        gsize skip = 0;
+      set_video_reversal(vsink);
+    }
+    dmabuf->planeCnt = n_mem;
+    if (sink_priv->rot_degree == WST_ROTATION_90 || sink_priv->rot_degree == WST_ROTATION_270)
+    {
+      // get output size, and align 32byte
+      get_rotation_size(vmeta->height, vmeta->width, &dmabuf->width, &dmabuf->height);
+      // create ROT buf
+      if (!sink_priv->rot_buffer)
+      {
+        sink_priv->video_info_changed = TRUE;
+        GST_ERROR_OBJECT(vsink, "planeCnt:%d, width:%d, height:%d", dmabuf->planeCnt, dmabuf->width, dmabuf->height);
+        sink_priv->rot_buffer = rotation_buffer_setup(dmabuf->planeCnt, dmabuf->width, dmabuf->height, ROT_BUFFER_MAX);
+      }
 
-        if (!gst_is_dmabuf_memory(dma_mem))
-        {
-            GST_ERROR_OBJECT(vsink, "not support non-dma buf");
-            ret = FALSE;
-            goto error;
-        }
-        size = gst_memory_get_sizes(dma_mem, &offset, &maxsize);
-        GST_LOG_OBJECT(vsink, "get memory size:%d, offeset:%d, maxsize:%d", size, offset, maxsize);
+      // get ROT buf
+      int index = get_rotation_buffer_idx(vsink);
+      if (-1 == index)
+      {
+        GST_ERROR_OBJECT(vsink, "cant get rotation buffer");
+        vsink->dropped++;
+        goto error;
+      }
+      // do transform
+      if (GST_FLOW_OK == rotation_transform(sink_priv->pge2dinfo, gst_buf, &sink_priv->rot_buffer[index], sink_priv->rot_degree))
+      {
+        sink_priv->rot_buffer[index].used = TRUE;
+      }
 
-        dmafd = gst_dmabuf_memory_get_fd(dma_mem);
+      // ROT buffer trans to RenderBuffer
+      for (guint i = 0; i < dmabuf->planeCnt; i++)
+      {
         dmabuf->handle[i] = 0;
-        dmabuf->fd[i] = dmafd;
-        dmabuf->size[i] = dma_mem->size;
-        dmabuf->stride[i] = vmeta->stride[i];
-        if (sink_priv->dw_mode == 0) {
-            dmabuf->stride[i]  = sink_priv->stride;
-        }
-        if (gst_buffer_find_memory(gst_buf, vmeta->offset[i], 1, &mem_idx, &length, &skip) && mem_idx == i)
+        dmabuf->fd[i] = sink_priv->rot_buffer[index].gemBuf.fd[i];
+        if ( i == 0 )
         {
-            dmabuf->offset[i] = dma_mem->offset + skip;
-            //GST_DEBUG_OBJECT(vsink, "get skip from buffer:%d, offset[%d]:%d", skip, i, dmabuf->offset[i]);
+          dmabuf->size[i] = sink_priv->rot_buffer[index].gemBuf.width * sink_priv->rot_buffer[index].gemBuf.height;
         }
         else
         {
-            GST_ERROR_OBJECT(vsink, "get skip from buffer error");
+          dmabuf->size[i] = sink_priv->rot_buffer[index].gemBuf.width * sink_priv->rot_buffer[index].gemBuf.height/2;
         }
-
-        GST_LOG_OBJECT(vsink, "dma buffer layer:%d, handle:%d, fd:%d, size:%d, offset:%d, stride:%d",
-                         i, dmabuf->handle[i], dmabuf->fd[i], dmabuf->size[i], dmabuf->offset[i], dmabuf->stride[i]);
+        dmabuf->stride[i] = sink_priv->rot_buffer[index].gemBuf.stride[i];
+        dmabuf->offset[i] = sink_priv->rot_buffer[index].gemBuf.offset[i];
+      }
+      tunnel_lib_buf_wrap->flag = BUFFER_FLAG_DMA_BUFFER;
+      tunnel_lib_buf_wrap->priv = (void *)&sink_priv->rot_buffer[index];
+      tunnel_lib_buf_wrap->pts = GST_BUFFER_PTS(gst_buf);
+      // unref gstbuf
+      gst_buffer_unref(gst_buf);
     }
-    tunnel_lib_buf_wrap->flag = BUFFER_FLAG_DMA_BUFFER;
-    tunnel_lib_buf_wrap->pts = GST_BUFFER_PTS(gst_buf);
-    tunnel_lib_buf_wrap->priv = (void *)gst_buf;
-    GST_LOG_OBJECT(vsink, "set tunnel lib buf priv:%p from pool:%p, pts:%lld", tunnel_lib_buf_wrap->priv, gst_buf->pool, tunnel_lib_buf_wrap->pts);
-    // GST_LOG_OBJECT(vsink, "dbg: buf in:%p, planeCnt:%d, plane[0].fd:%d, plane[1].fd:%d",
-    //                  tunnel_lib_buf_wrap->priv,
-    //                  dmabuf->planeCnt,
-    //                  dmabuf->fd[0],
-    //                  dmabuf->fd[1]);
+    else
+    {
+      dmabuf->width = vmeta->width;
+      dmabuf->height = vmeta->height;
+      if (sink_priv->dw_mode == 0) {
+        dmabuf->width = sink_priv->src_width;
+        dmabuf->height = sink_priv->src_height;
+      }
+      // gstbuffer trans to RenderBuffer
+      for (guint i = 0; i < n_mem; i++)
+      {
+          gint dmafd;
+          gsize size, offset, maxsize;
+          dma_mem = gst_buffer_peek_memory(gst_buf, i);
+          guint mem_idx = 0;
+          guint length = 0;
+          gsize skip = 0;
 
-    return ret;
+          if (!gst_is_dmabuf_memory(dma_mem))
+          {
+              GST_ERROR_OBJECT(vsink, "not support non-dma buf");
+              ret = FALSE;
+              goto error;
+          }
+          size = gst_memory_get_sizes(dma_mem, &offset, &maxsize);
+          GST_LOG_OBJECT(vsink, "get memory size:%d, offset:%d, maxsize:%d", size, offset, maxsize);
+
+          dmafd = gst_dmabuf_memory_get_fd(dma_mem);
+          dmabuf->handle[i] = 0;
+          dmabuf->fd[i] = dmafd;
+          dmabuf->size[i] = dma_mem->size;
+          dmabuf->stride[i] = vmeta->stride[i];
+          if (sink_priv->dw_mode == 0) {
+            dmabuf->stride[i]  = sink_priv->stride;
+          }
+          if (gst_buffer_find_memory(gst_buf, vmeta->offset[i], 1, &mem_idx, &length, &skip) && mem_idx == i)
+          {
+              dmabuf->offset[i] = dma_mem->offset + skip;
+              //GST_DEBUG_OBJECT(vsink, "get skip from buffer:%d, offset[%d]:%d", skip, i, dmabuf->offset[i]);
+          }
+          else
+          {
+              GST_ERROR_OBJECT(vsink, "get skip from buffer error");
+          }
+          GST_LOG_OBJECT(vsink, "dma buffer layer:%d, handle:%d, fd:%d, size:%d, offset:%d, stride:%d",
+                           i, dmabuf->handle[i], dmabuf->fd[i], dmabuf->size[i], dmabuf->offset[i], dmabuf->stride[i]);
+      }
+      tunnel_lib_buf_wrap->flag = BUFFER_FLAG_DMA_BUFFER;
+      tunnel_lib_buf_wrap->priv = (void *)gst_buf;
+      tunnel_lib_buf_wrap->pts = GST_BUFFER_PTS(gst_buf);
+      GST_LOG_OBJECT(vsink, "set tunnel lib buf priv:%p from pool:%p, pts:%lld", tunnel_lib_buf_wrap->priv, gst_buf->pool, tunnel_lib_buf_wrap->pts);
+    }
 
 error:
     return ret;
@@ -2282,6 +2598,12 @@
     int tunnelmode = 0; // 1 for tunnel mode; 0 for non-tunnel mode
 
     RenderFrameSize frame_size = {sink_priv->src_width, sink_priv->src_height};
+    if ((sink_priv->rot_degree == WST_ROTATION_90 || sink_priv->rot_degree == WST_ROTATION_270) && sink_priv->rot_buffer)
+    {
+      frame_size.width = sink_priv->rot_buffer[0].gemBuf.width;
+      frame_size.height = sink_priv->rot_buffer[0].gemBuf.height;
+    }
+
     GstVideoFormat format = sink_priv->format;
 
     if (render_set_value(sink_priv->render_device_handle, KEY_MEDIASYNC_TUNNEL_MODE, (void *)&tunnelmode) == -1)
diff --git a/src/gstamlvideosink.h b/src/gstamlvideosink.h
index 9fec5c2..e8ffff4 100644
--- a/src/gstamlvideosink.h
+++ b/src/gstamlvideosink.h
@@ -23,6 +23,7 @@
 #include <gst/gst.h>
 #include <gst/video/video.h>
 #include "aml_version.h"
+#include <gst/base/gstqueuearray.h>
 
 G_BEGIN_DECLS
 
@@ -66,6 +67,10 @@
   GMutex report_info_lock;
   gboolean quit_eos_detect_thread;
   GThread *detect_thread_handle;
+  gboolean quit_rotation_thread;
+  GThread *rotation_thread_handle;
+  GstQueueArray* buffer_queue;
+  GMutex rotation_buffer_lock;
   gint frame_rate_num;
   gint frame_rate_denom;
   gboolean frame_rate_changed;