基于海思平台与QT框架的高效视频显示

    首先,不涉及视频解码,仅涉及解码之后的视频缩放、颜色空间转换(如YUV转RGB)、贴图。本文主要说明的是在QT框架没有使用OpenGl的情况下,如何让解码后的视频更高效的展示出来。

    海思底层解码出来的视频都是YUV格式的,而QT的贴图格式是RGB,并且QT的视频展示框的宽高往往和解码出来的YUV不一样。所以要把解码出来的视频展示出来需要对源YUV做缩放、然后转RGB,QT的QImage类自带缩放功能,但是使用软缩放效率太低,相关的YUV转RGB的代码做转换也是一样效率很低,特别对于一些CPU能力比较低的芯片视频完全没法看。本文中的方案主要通过以下流程来实现:

                                           基于海思平台与QT框架的高效视频显示_第1张图片

                                                                  图 1.视频播放流程 

    从解码器拿出来YUV图片,使用VGS缩放到与展示框大小差不多(由于对齐的原因一般不会完全相同),缩放之后的YUV经过IVE模块转成BGR,最后用TDE将BGR转成RGB。整个流程都使用硬件模块实现,大大提高效率。

一. 缩放

    利用VGS做YUV的缩放,代码片段如下:

#include 
#include 

// 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 
#include 
#include 
// 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 
// 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有个间隔拷贝的方法间接实现,不然软件实现效率很低。

    

你可能感兴趣的:(视频处理)