基於海思平臺與QT框架的高效視頻顯示

    首先,不涉及視頻解碼,僅涉及解碼之後的視頻縮放、顏色空間轉換(如YUV轉RGB)、貼圖。本文主要說明的是在QT框架沒有使用OpenGl的情況下,如何讓解碼後的視頻更高效的展示出來。

    海思底層解碼出來的視頻都是YUV格式的,而QT的貼圖格式是RGB,並且QT的視頻展示框的寬高往往和解碼出來的YUV不一樣。所以要把解碼出來的視頻展示出來需要對源YUV做縮放、然後轉RGB,QT的QImage類自帶縮放功能,但是使用軟縮放效率太低,相關的YUV轉RGB的代碼做轉換也是一樣效率很低,特別對於一些CPU能力比較低的芯片視頻完全沒法看。本文中的方案主要通過以下流程來實現:

                                           

                                                                  圖 1.視頻播放流程 

    從解碼器拿出來YUV圖片,使用VGS縮放到與展示框大小差不多(由於對齊的原因一般不會完全相同),縮放之後的YUV經過IVE模塊轉成BGR,最後用TDE將BGR轉成RGB。整個流程都使用硬件模塊實現,大大提高效率。

一. 縮放

    利用VGS做YUV的縮放,代碼片段如下:

#include <hi_comm_vgs.h>
#include <mpi_vgs.h>

// src: 源圖像
// dst:目標圖像
// 將src指向的源圖像縮放到由dst指向的寬高的大小,並且圖像輸入到dst對應的內存中
// note: 都需要開闢好空間
int ImgScale(VIDEO_FRAME_INFO_S *src,VIDEO_FRAME_INFO_S *dst) {
  HI_S32 ret;
  VGS_HANDLE vgs_handle;
  ret = HI_MPI_VGS_BeginJob(&vgs_handle);
  if (ret != HI_SUCCESS) {
    printf("HI_MPI_VGS_BeginJob failed:%#x\n", ret);
    return ret;
  }
  VGS_TASK_ATTR_S vgs_task;
  memset(&vgs_task, 0, sizeof(vgs_task));
  memcpy(&vgs_task.stImageIn,src,sizeof(vgs_task.stImageIn));
  memcpy(&vgs_task.stImgOut,dst,sizeof(vgs_task.stImgOut));
  ret = HI_MPI_VGS_AddScaleTask(vgs_handle, &vgs_task);
  if (ret != HI_SUCCESS) {
    printf("HI_MPI_VGS_AddScaleTask failed:%#x\n", ret);
    HI_MPI_VGS_CancelJob(vgs_handle);
    return ret;
  }
  ret = HI_MPI_VGS_EndJob(vgs_handle);
  if (ret != HI_SUCCESS) {
    printf("HI_MPI_VGS_EndJob failed:%#x\n", ret);
    HI_MPI_VGS_CancelJob(vgs_handle);
    return ret;
  }
  return 0;
}

    關於VGS縮放,也可以參考海思的sample和文檔,這方面資料比較多。

二. YUV轉BGR 

    利用IVE做YUV轉BGR,要注意的是IVE支持的YUV格式不多,如果解碼器輸出的YUV格式對於IVE來說不支持,可以在縮放的時候讓VGS順便轉以下格式,VGS支持YUV之間的格式轉換。這裏轉出來的是BGR,也就是按藍綠紅排列的,按我使用的海思芯片的說明來看,IVE輸出是BGR不是RGB,YUV轉BGR代碼如下:

#include <hi_comm_ive.h>
#include <hi_ive.h>
#include <mpi_ive.h>
// src: 源圖像
// dst:目標圖像
// 將src指向的源YUV圖像轉換成BGR圖像
// note: 都需要開闢好空間,並且源圖像與目標圖像大小一致
int ImgCsc(VIDEO_FRAME_INFO_S *src, VIDEO_FRAME_INFO_S *dst) {
  int ret;
  IVE_SRC_IMAGE_S src_img, dst_img;
  memset(&src_img,0,sizeof(src_img));
  memset(&dst_img,0,sizeof(dst_img));
  src_img.enType = IVE_IMAGE_TYPE_YUV420SP;  // 源圖像格式,這裏是舉例
  src_img.pu8VirAddr[0] = src->stVFrame.pVirAddr[0];
  src_img.pu8VirAddr[1] = src->stVFrame.pVirAddr[1];  // YUV410SP 格式只有兩片內存
  src_img.u32PhyAddr[0] = src->stVFrame.u32PhyAddr[0];
  src_img.u32PhyAddr[1] = src->stVFrame.u32PhyAddr[1];
  src_img.u16Stride[0] = src->stVFrame.u32Stride[0];  
  src_img.u16Stride[1] = src->stVFrame.u32Stride[1]; 
  src_img.u16Height =  src->stVFrame.u32Height;
  src_img.u16Width = src->stVFrame.u32Width;
  dst_img.enType = IVE_IMAGE_TYPE_U8C3_PACKAGE;  // 源圖像格式BGR888
  dst_img.pu8VirAddr[0] = dst->stVFrame.pVirAddr[0]; // BGR888格式只有一片內存
  dst_img.u32PhyAddr[0] = dst->stVFrame.u32PhyAddr[0];
  dst_img.u16Stride[0] = dst->stVFrame.u32Stride[0];  
  dst_img.u16Height =  dst->stVFrame.u32Height;
  dst_img.u16Width = dst->stVFrame.u32Width;
  IVE_HANDLE handle;
  HI_BOOL instant = HI_TRUE;
  IVE_CSC_CTRL_S ctrl;
  ctrl.enMode = IVE_CSC_MODE_PIC_BT601_YUV2RGB;
  ret = HI_MPI_IVE_CSC(&handle, &src_img, &dst_img, &ctrl, instant);
  if (ret != HI_SUCCESS) {
    printf("HI_MPI_IVE_CSC failed:%#x\n", ret);
    return -1;
  }
  HI_BOOL finish = HI_FALSE;
  ret = HI_MPI_IVE_Query(handle, &finish, HI_TRUE);
  if (ret != HI_SUCCESS) {
    printf("HI_MPI_IVE_Query failed:%#x\n", ret);
    return -2;
  }
  if (finish != HI_TRUE) {
    DEBUG("not finish\n");
    return -3;
  }
  return 0;
}

三. BGR轉RGB

    QT層需要用的格式是RGB,所以上一步出來的BGR還需要用TDE轉換成RGB,代碼如下:

#include <hi_tde_api.h>
// src: 源圖像
// dst:目標圖像
// 將src指向的源BGR888圖像轉換成RGB888圖像
// note: 都需要開闢好空間,並且源圖像與目標圖像大小一致
int Bgr888ToRgb888(VIDEO_FRAME_INFO_S *src, VIDEO_FRAME_INFO_S *dst) {
  HI_S32 ret;
  TDE_HANDLE handle = HI_TDE2_BeginJob();
  if (handle < 0) {
    DEBUG("HI_TDE2_BeginJob failed:%#x\n", handle);
    return -1;
  }
  TDE2_SURFACE_S src_surface, dst_surface;
  TDE2_RECT_S src_rect, dst_rect;
  memset(&src_surface, 0, sizeof(src_surface));
  src_surface.u32PhyAddr = src.stVFrame.u32PhyAddr[0];
  src_surface.enColorFmt = TDE2_COLOR_FMT_BGR888;
  src_surface.u32Height = src.stVFrame.u32Height;
  src_surface.u32Width = src.stVFrame.u32Width;
  src_surface.u32Stride = src.stVFrame.u32Stride[0] * 3;  //這裏的跨距是以字節爲單位,所以是寬度乘以3
  src_rect.s32Xpos = 0;
  src_rect.s32Ypos = 0;
  src_rect.u32Height = src.stVFrame.u32Height;
  src_rect.u32Width = src.stVFrame.u32Width;

  memset(&dst_surface, 0, sizeof(dst_surface));
  dst_surface.u32PhyAddr = dst.stVFrame.u32PhyAddr[0];
  dst_surface.enColorFmt = TDE2_COLOR_FMT_RGB888;
  dst_surface.u32Height = dst.stVFrame.u32Height;
  dst_surface.u32Width = dst.stVFrame.u32Width;
  dst_surface.u32Stride = dst.stVFrame.u32Stride[0] * 3;
  dst_rect.s32Xpos = 0;
  dst_rect.s32Ypos = 0;
  dst_rect.u32Height = dst.stVFrame.u32Height;
  dst_rect.u32Width = dst.stVFrame.u32Width;
  TDE2_OPT_S opt;
  memset(&opt, 0, sizeof(opt));
  opt.enAluCmd = TDE2_ALUCMD_NONE;
  opt.enRopCode_Color = TDE2_ROP_BUTT;
  opt.enRopCode_Alpha = TDE2_ROP_BUTT;
  opt.enColorKeyMode = TDE2_COLORKEY_MODE_NONE;
  opt.enClipMode = TDE2_CLIPMODE_NONE;
  ret = HI_TDE2_Bitblit(handle, NULL, NULL, &src_surface, &src_rect, &dst_surface, &dst_rect, &opt);
  if (ret != HI_SUCCESS) {
    printf("HI_TDE2_Bitblit failed:%#x\n", ret);
    HI_TDE2_CancelJob(handle);
    return -2;
  }
  ret = HI_TDE2_EndJob(handle, HI_TRUE, HI_TRUE, 200);
  if (ret != HI_SUCCESS) {
    HI_TDE2_CancelJob(handle);
    printf("HI_TDE2_EndJob failed:%#x\n", ret);
    return -3;
  }
  return 0;
}

    這裏需要注意的是,對於TDE來說跨距都是以字節爲單位的,所以如果是RGB888的圖片,對於TDE的跨距就是以像素爲單位的跨距的三倍,一般跨距都是等於圖像寬度的,所以tde的跨距也就是寬度乘以3。本人之前就是這個搞錯了調了好久都沒調出來。

四. QT貼圖

    一般可以用QLabel來做,但是在沒有支持顯卡的情況下效率很低,所以最好使用QWidget來貼圖,這個可以參考這篇文章:

    https://worthsen.blog.csdn.net/article/details/80969451

 

    需要注意的是,各個處理模塊VGS、IVE、TDE都需要有物理地址的內存,我都是使用HI_MPI_SYS_MmzAlloc開闢出來的。不過這種內存在做memcpy時和Qt底層貼圖時,效率會很低,不知道爲什麼。所以儘量少的對這種內存與malloc出來的內存做拷貝,如果要拷貝的也是mmz的內存可以使用IVE中的DMA拷貝比較快。我視頻播放整個流程只在貼圖的時候做了memcpy到虛擬內存,然後在QWidget的paintEvent函數裏將虛擬內存貼到Widget上,比較好一些。

    還有一點,其實TDE是支持從YUV直接轉到RGB的,考慮到轉BGR再到RGB效率也夠就沒再弄了。據我目前嘗試的現象,TDE要將YUV轉成RGB,除了YUV需要是422格式外,似乎還得是planar格式的,也就是Y、U、V分別是一片內存,另外要注意YUV的幀結構體的跨距應該是寬度乘以2。如果確實是plannar格式的,那麼需要將YUVSP422轉到YUVP422,這個VGS無法轉換,可以考慮使用IVE的DMA有個間隔拷貝的方法間接實現,不然軟件實現效率很低。

    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章