4. 视频编解码

4.1. 视频编码

4.1.1. 支持编码标准

支持以下格式视频编码:

  • HEVC (H.265)

    Main Profile, Level 5.1, High Tier

    Main 10 Profile, Level 5.1, High Tier

    Main Still Picture Profile, Level 5.1, High Tier

  • AVC (H.264)

    Baseline Profile, Levels 6.2

    Main Profile, Levels 6.2

    High Profile, Levels 6.2

    High 10 Profile, Levels 6.2

  • JPEG

4.1.2. 编码流程

  1. 调用 mcInit API进行设备初始化。

  2. 调用 mcSetDevice API选择合适的设备。

  3. 执行以下命令,声明一个编码器资源对象并调用 mcVpueOpen API初始化编码器资源。

    mcVPUInst encInst;
    mcVpueOpen(&encInst);
    
  4. 使用 mcVpueCtrl API配置编码参数。

    mcVpueCtrl API提供了丰富的控制编码器功能,使用 mcSetEncAll 参数携带 mcVPUEncParamterType 结构体来配置编码器。代码示例如下:

    mcVPUEncParamterType cfg;
    
    memset(&cfg, 0, sizeof(mcVPUEncParamterType )); //配置清零
    
    cfg.code_type = mcVpuH264; //配置编码标准为h264
    
    cfg.width = 1920; //配置原始图片宽
    
    cfg.height = 1080; //配置原始图片高
    
    cfg.pixfmt = mcEncYuv420Planar; //配置原始图片yuv格式或rgb格式
    
    mcVpueCtrl(encInst, mcSetEncAll, (void*)&cfg);
    
  5. 调用 mcVpuePutFrame API将原始数据发送到编码器开始编码。

    在MCRUNTIME中,原始数据和编码后数据都使用 mcVpuFrameInfo 结构体表示。 调用时需要确保 mcVpuFrameInfo 结构体中的 addr 指针指向有效的原始数据, len 等于有效的一帧数据长度。

    调用时, mcVpuFrameInfo 结构体中的 addr 指针由用户负责申请和释放。

  6. 调用 mcVpueGetFrame API来获取编码后数据。

    编码后数据保存在 mcVpuFrameInfo 结构体中的 addr 指向的内存中,长度信息在 len 成员中。调用 mcVpueGetFrame 时, mcVpuFrameInfo 中的内存由编码器申请,需要用户在使用后调用 mcVpueCtrl API携带 mcFreeOutputFrame 参数释放该帧内存。

  7. 调用 mcVpuePutFrame API携带0长度的一帧输入数据,表示输入结束。

  8. 调用 mcVpueGetFrame API,当函数返回 mcSuccess 且获取到0长度的输出帧时,表示编码已经结束。

  9. 调用 mcVpueClose API关闭编码器内部资源。

4.1.3. 编码输入内存类型

编码数据结构中,用 mcVpuMemType 枚举类型表示视频数据的存储位置和类型,具体的定义如下:

typedef enum {

   mcMemHost, //存储在主机内存的数据

   mcMemDevice, //存储在设备内存的数据

   mcHost3Plane, //存储在主机内存且用3个地址分开存储yuv通道数据

   mcDevice3Plane, //存储在设备内存且用3个地址分开存储yuv通道数据

   mcMemMappedHost,//存储在主机和设备都能访问的地址的数据

} mcVpuMemType;

帧数据结构体 mcVpuFrameInfo 含有 mcVpuMemType mem_type 成员,表示当前帧数据存储和布局类型。编程时需选择正确的内存类型且使用对应的地址和长度成员变量。

  • 当帧内存类型为以下3种时, mcVpuFrameInfo 中的 addrlen 分别为数据的有效地址和长度:

    • mcMemHost

    • mcMemDevice

    • mcMemMappedHost

  • 当帧内存类型为以下2种时, mcVpuFrameInfo 中的 data[3]line_size[3] 分别为y/u/v通道的数据地址和数据长度:

    • mcHost3Plane

    • mcDevice3Plane

4.1.4. 编码高级配置

在初始化编码器后,通过调用 mcVpueCtrl API携带 mcSetEncAllmcVPUEncParamterType 类型参数来配置编码器。编码器主要有以下配置项:

  • GoPSize:配置Group of Pictures大小

    通常,一个GoPSize的第一帧编码为关键帧。

  • rc_mode:配置码率控制模式

    SDK编码器支持4种码率控制模式:CBR,VBR,CRF和CQP模式。

    配置CBR或VBR码率控制模式时,需要配置合适的目标码率(Bps); 配置CRF模式时即开启2 pass编码,可以同时配置 lookaheadDepth 选择合适的Lookahead Frames; 配置CQP模式时,需要配置 rc_val,固定QP的值即等于 rc_val

  • qpmin qpmax:影响编码后的图片清晰度和码率大小

    正常编码情况下, qpmin qpmax 需要配置成0。如果需要调整QP范围,可以配置为合适的值。

  • preset:预设编码

    编码器支持预设 preset 为6种模式:mcEncVeryFaster,mcEncFaster,mcEncFast,mcEncMedium,mcEncSlow和mcEncSlower。

  • ROI:每帧ROI配置,每帧图片都可以最多设置8个感兴趣区域。

    ROI信息保存在 mcVpuFrameInfo 结构体的ROI数组内,随 mcVpuePutFrame API的调用告知编码器。 设置ROI时需要设置 mcVpuFrameInfo 结构体中的 roi_changedmcTrue ,并且填充ROI数组内的坐标信息和QP值。

  • 设置特定帧编码为关键帧

    如果需要把某一帧原始数据编码为关键帧,只需在调用 mcVpuePutFrame API前,配置 mcVpuFrameInfo 结构体参数内的 IsIDR 成员为1。

  • Bps:配置目标码率

    rc_mode 配置为CBR或VBR模式时,需要设置合理的目标码率, 0 表示自适应。

  • frame_order_type:配置帧顺序类型

    例如,配置IPB帧编码顺序类型:

    • 0 表示内部自适应;

    • 1 表示第一帧为I帧,后续帧为P帧;

    • 1pass时, 2 - 8 表示第一帧为I帧,第二帧为P帧,后续为 frame_order_type-1 个B帧,如此循环。

  • lookaheadDepth:配置前向预测深度

    2pass编码时,可以配置 lookaheadDepth 前向预测深度。在CBR或VBR模式下, preset 大于mcEncFaster,内部开启2pass编码;CRF模式下默认开启2pass编码。

  • svc_layers:配置svc多层编码

    编码器支持svc多层编码,目前只开放编码层数配置,通过 svc_layers 来配置编码层数。

  • ten_bit_depth_output:控制输出帧像素深度

    0 表示输出8-bit, 1 表示输出10-bit。

  • disable_sps_pps:控制编码器是否输出SPS、PPS帧

    0 表示输出, 1 表示不输出。

  • disable_sei:控制编码器是否输出SEI帧

    0 表示输出, 1 表示不输出。

4.1.5. 代码示例

示例1

基础编码,代码示例如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <malloc.h>
#include "mcr/mc_type.h"
#include "mcr/mc_vpu_api.h"
#include "mcr/mc_runtime_api.h"

int main(int argc, char** argv)
{
   mcInit(0);
   mcSetDevice(0);
   mcVPUInst inst;
   mcVPUEncParamterType cfg;
   mcVpuFrameInfo input_frame, output_frame;
   char* input_file = "demo.yuv";
   char* output_file = "out.h264";
   mcVpueOpen(&inst);
   memset(&cfg, 0, sizeof(mcVPUEncParamterType));
   memset(&input_frame, 0, sizeof(mcVpuFrameInfo));
   cfg.width = 320;
   cfg.height = 480;
   cfg.code_type = mcVpuH264;
   mcVpueCtrl(inst, mcSetEncAll, (void*)&cfg);
   int frame_size = cfg.width * cfg.height * 3 / 2;
   int infd = open(input_file, O_RDWR);
   int outfd = open(output_file, O_RDWR|O_CREAT|O_TRUNC, 0644);
   input_frame.addr = (uint8_t*)malloc(frame_size);
   int readed, get_frame;
   while((readed = read(infd, input_frame.addr, frame_size)) > 0) {
      input_frame.len = readed;
      mcVpuePutFrame(inst, &input_frame, NULL);
      get_frame = !mcVpueGetFrame(inst, &output_frame, NULL);
      if (get_frame && (output_frame.len > 0)) {
         write(outfd, output_frame.addr, output_frame.len);
         mcVpueCtrl(inst, mcFreeOutputFrame, (void*)&output_frame);
      }
   }
   input_frame.len = 0;
   mcVpuePutFrame(inst, &input_frame, NULL);
   do {
      get_frame = !mcVpueGetFrame(inst, &output_frame, NULL);
      if (get_frame && (output_frame.len > 0)) {
         write(outfd, output_frame.addr, output_frame.len);
         mcVpueCtrl(inst, mcFreeOutputFrame, (void*)&output_frame);
      } else if (get_frame && !output_frame.len) {
         break;
      } else {
         usleep(100);
      }
   } while (1);
   free(input_frame.addr);
   mcVpueClose(inst);
   mcDeviceDestroy();
   close(infd);
   close(outfd);
}

示例2

ROI编码,代码示例如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <malloc.h>
#include "mcr/mc_type.h"
#include "mcr/mc_vpu_api.h"
#include "mcr/mc_runtime_api.h"

void set_default_roi(mcVpuFrameInfo* frame)
{
   frame->roi_changed = mcFalse;
   for(int i = 0; i < 8; i++) {
      frame->roi[i].roiAreaBottom = -255;
      frame->roi[i].roiAreaRight = -255;
      frame->roi[i].roiAreaTop = -255;
      frame->roi[i].roiAreaLeft = -255;
      frame->roi[i].roiQp = -1;
      frame->roi[i].roiDeltaQp = 0;
   }
}

int main(int argc, char** argv)
{

   mcInit(0);
   mcSetDevice(0);
   mcVPUInst inst;
   mcVPUEncParamterType cfg;
   mcVpuFrameInfo input_frame, output_frame;
   char* input_file = "demo.yuv";
   char* output_file = "out.h264";

   mcVpueOpen(&inst);
   memset(&cfg, 0, sizeof(mcVPUEncParamterType));
   memset(&input_frame, 0, sizeof(mcVpuFrameInfo));

   cfg.width = 320;
   cfg.height = 480;
   cfg.code_type = mcVpuH264;
   mcVpueCtrl(inst, mcSetEncAll, (void*)&cfg);

   int frame_size = cfg.width * cfg.height * 3 / 2;
   int infd = open(input_file, O_RDWR);
   int outfd = open(output_file, O_RDWR|O_CREAT|O_TRUNC, 0644);
   input_frame.addr = (uint8_t*)malloc(frame_size);
   set_default_roi(&input_frame);

   int readed, get_frame;

   while((readed = read(infd, input_frame.addr, frame_size)) > 0) {

      input_frame.len = readed;
      input_frame.roi_changed = mcTrue;
      input_frame.roi[1].roiAreaBottom = 60; // here you can change every frame’s roi area
      input_frame.roi[1].roiAreaRight = 60;
      input_frame.roi[1].roiAreaTop = 0;
      input_frame.roi[1].roiAreaLeft = 0;
      input_frame.roi[1].roiQp = 47;
      input_frame.roi[1].roiDeltaQp = 0;

      mcVpuePutFrame(inst, &input_frame, NULL);

      get_frame = !mcVpueGetFrame(inst, &output_frame, NULL);

      if (get_frame && (output_frame.len > 0)) {
         write(outfd, output_frame.addr, output_frame.len);
         mcVpueCtrl(inst, mcFreeOutputFrame, (void*)&output_frame);
      }

   }

   input_frame.len = 0;
   mcVpuePutFrame(inst, &input_frame, NULL);

   do {
      get_frame = !mcVpueGetFrame(inst, &output_frame, NULL);

      if (get_frame && (output_frame.len > 0)) {
         write(outfd, output_frame.addr, output_frame.len);
         mcVpueCtrl(inst, mcFreeOutputFrame, (void*)&output_frame);
      } else if (get_frame && !output_frame.len) {
         break;
      } else {
         usleep(100);
      }
   } while (1);
   free(input_frame.addr);
   mcVpueClose(inst);
   mcDeviceDestroy();
   close(infd);
   close(outfd);
}

示例3

Lookahead编码,代码示例如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <malloc.h>
#include "mcr/mc_type.h"
#include "mcr/mc_vpu_api.h"
#include "mcr/mc_runtime_api.h"

int main(int argc, char** argv)
{
   mcInit(0);
   mcSetDevice(0);
   mcVPUInst inst;
   mcVPUEncParamterType cfg;
   mcVpuFrameInfo input_frame, output_frame;
   char* input_file = "demo.yuv";
   char* output_file = "out.h264";

   mcVpueOpen(&inst);
   memset(&cfg, 0, sizeof(mcVPUEncParamterType));
   memset(&input_frame, 0, sizeof(mcVpuFrameInfo));

   cfg.width = 320;
   cfg.height = 480;
   cfg.code_type = mcVpuH264;
   cfg.preset = mcEncFast;
   cfg.lookaheadDepth=10;
   //mcEncFaster、mcEncFast for h264.
   //mcEncFaster、mcEncFast 、mcEncMedium、mcEncSlow
   //mcEncSlower for hevc.
   mcVpueCtrl(inst, mcSetEncAll, (void*)&cfg);
   int frame_size = cfg.width * cfg.height * 3 / 2;
   int infd = open(input_file, O_RDWR);
   int outfd = open(output_file, O_RDWR|O_CREAT|O_TRUNC, 0644);
   input_frame.addr = (uint8_t*)malloc(frame_size);
   input_frame.roi_changed = mcFalse;
   int readed, get_frame;

   while((readed = read(infd, input_frame.addr, frame_size)) > 0) {
      input_frame.len = readed;
      mcVpuePutFrame(inst, &input_frame, NULL);
      get_frame = !mcVpueGetFrame(inst, &output_frame, NULL);
      if (get_frame && (output_frame.len > 0)) {
      write(outfd, output_frame.addr, output_frame.len);
      mcVpueCtrl(inst, mcFreeOutputFrame, (void*)&output_frame);
      }
   }
   input_frame.len = 0;
   mcVpuePutFrame(inst, &input_frame, NULL);

   do {
         get_frame = !mcVpueGetFrame(inst, &output_frame, NULL);
         if (get_frame && (output_frame.len > 0)) {
         write(outfd, output_frame.addr, output_frame.len);
         mcVpueCtrl(inst, mcFreeOutputFrame, (void*)&output_frame);
      } else if (get_frame && !output_frame.len) {
         break;
      } else {
         usleep(100);
      }
   } while (1);

   free(input_frame.addr);
   mcVpueClose(inst);
   mcDeviceDestroy();
   close(infd);
   close(outfd);
}

4.2. 视频解码

4.2.1. 支持解码标准

支持以下格式视频解码:

  • HEVC (H.265)

  • AVC (H.264)

  • AV1

  • AVS2

  • JPEG

4.2.2. 解码流程

  1. 调用 mcInit API初始化设备。

  2. 调用 mcSetDevice API设置device ID。

  3. 使用解码具体功能相关API,如 mcVpudOpenmcVpudClose 等。

  4. 销毁设备任务记录。

    全部任务完成后,调用 mcDeviceDestroy() API反初始化。

4.2.3. 解码输入内存类型

解码数据结构中,用 mcVpuMemType 枚举类型表示视频数据的存储位置和类型,具体的定义如下:

typedef enum {

   mcMemHost, //存储在主机内存的数据

   mcMemDevice, //存储在设备内存的数据

   mcHost3Plane, //存储在主机内存且用3个地址分开存储yuv通道数据

   mcDevice3Plane, //存储在设备内存且用3个地址分开存储yuv通道数据

   mcMemMappedHost,//存储在主机和设备都能访问的地址的数据

} mcVpuMemType;

帧数据结构体 mcVpuFrameInfo 含有 mcVpuMemType mem_type 成员,表示当前帧数据存储和布局类型。编程时需选择正确的内存类型且使用对应的地址和长度成员变量。

  • 当帧内存类型为以下3种时, mcVpuFrameInfo 中的 addrlen 分别为数据的有效地址和长度:

    • mcMemHost

    • mcMemDevice

    • mcMemMappedHost

  • 目前解码暂不支持 mcHost3PlanemcDevice3Plane

4.2.4. 代码示例

视频解码代码示例如下:

#include "mcr/mc_type.h"
#include "mcr/mc_vpu_api.h"
#include "mcr/mc_runtime_api.h"

mcError_t decTestCase(void *arg)
{
   char file_output[256] = {0};
   time_t timep;
   tm *p;

   int fp, fp_w;
   uint8_t *packet_buffer;
   uint32_t packet_size = BUFFER_SIZE;

   mcVPUInst inst;
   mcVpuFrameInfo frame;
   mcVPUDecParameterType config;

   uint32_t strm_read_len,hbm_size;
   uint32_t closed_flag = 0, send_buffer_count = 0, rece_buffer_count = 0, read_flag = 0;

   CaseParam *param = (CaseParam *)arg;
   char *file_name = (char *)(param->file);

   fp = open(file_name, O_RDONLY);

   if (fp == -1) {
      printf("Unable to open input file: %s\n", file_name);
      return mcError;
   }

   if (flock(fp, LOCK_SH) < 0) {
      printf("flock failed file: %s\n", file_name);
      return mcError;
   }

   time(&timep);
   p = gmtime(&timep);

   sprintf(file_output, "%s.%d_%02d%02d_%02d_%02d_case%d.yuv", file_name, 1900 + p->tm_year, 1 + p->tm_mon, p->tm_mday,
   8 + p->tm_hour, p->tm_min, param->case_id);

   fp_w = open(file_output, O_RDWR|O_CREAT, 0666);

   if (fp_w == -1) {
      printf("Unable to open fp_w\n");
      return mcError;
   }

   // step1: open vpu
   mcVpudOpen(&inst);

   // step2: config
   if (NULL != strstr(file_name, "264")) {
      config.code_type = mcVpuH264;
   } else if (NULL != strstr(file_name, "hevc")) {
      config.code_type = mcVpuHevc;
   } else if (NULL != strstr(file_name, "ivf")) {
      config.code_type = mcVpuAv1;
   } else if (NULL != strstr(file_name, "avs")) {
      config.code_type = mcVpuAvs2;
   } else {
      config.code_type = mcVpuJpeg;
   }

   printf("DEBUG PRINT ==============> code type:%d\n", config.code_type);

   if (mcVpuJpeg == config.code_type) {
      struct stat statbuff;
      if (fstat(fp, &statbuff) < 0) {
         printf("Unable to get file stat\n");
         return mcError;
      }
      packet_size = statbuff.st_size;
      packet_buffer = (uint8_t *)malloc(packet_size);
   } else {
      packet_buffer = (uint8_t *)malloc(packet_size);
   }

   config.pp_planar_flag = mcDecPpPlanarYuv420;
   config.pic_num = 0;
   config.output_dma_copy = mcTrue;
   mcVpudCtrl(inst, mcSetDecAll, (void *)&config);

   // step3: send & receive frames

   while (1) {
      if(!closed_flag) {
         strm_read_len = read(fp, packet_buffer, packet_size);
         if (strm_read_len) {
            frame.addr = packet_buffer;
            frame.len = strm_read_len;
            frame.type = mcPacketType;
            frame.mem_type = mcMemHost;
            mcVpudPutFrame(inst, &frame, NULL);
         } else {
            // step4: ask codec to stop after finishing decoding all frames
            mcVpudCtrl(inst, mcStopCodecWhenFinish, NULL);
            closed_flag = 1;
         }
      }

      do {
         mcVpudGetFrame(inst, &frame, NULL);

         if (frame.len > 0) {
            if(read_flag == 0)
            {
               read_flag = 1;
               mcVpudCtrl(inst, mcGetHbmSizeKbyte, &hbm_size);
            }

            if (config.output_dma_copy) {
               if (write(fp_w, frame.addr, frame.len) == -1) {
                  perror("write error\n");
                  return mcError;
               }
            } else {
               if (write(fp_w, frame.lu_addr, frame.lu_len) == -1 || write(fp_w, frame.ch_addr, frame.ch_len) == -1) {
                  perror("write error\n");
                  return mcError;
               }
            }

            //need to free frame
            mcVpudCtrl(inst, mcFreeOutputFrame, (void *)&frame);
            printf("**************************************receive buffer_count = %d[%d]\n", rece_buffer_count++,
            frame.len);

            if ((0 == closed_flag) && ((rece_buffer_count >= config.pic_num) && (config.pic_num > 0))) {
               printf("stop \n");
               // step4: ask codec to stop after finishing decoding all frames
               mcVpudCtrl(inst, mcStopCodecWhenFinish, NULL);
            }
            usleep(10 * 1000);

         } else {
            if (mcVpuTaskEnd == frame.task_status) {
               goto TASK_STOP;
            }
         }

      } while (frame.len > 0);

   }

TASK_STOP:

   // step5: close vpu
   mcVpudClose(inst);
   close(fp);
   close(fp_w);
   printf("decTestCase_H264 [%s] end\n", file_output);
   return mcSuccess;

}

4.3. 多进程编解码

VPU编解码最高可支持128进程同时工作,使用多进程时需要设置以下环境变量:

export DYNAMIC_QUEUE_SCHEDULE=1
export QUEUE_MONITOR_TIME=100
export DYNAMIC_QUEUE_SCHEDULE_MONITOR_CYCLE=100
export DYNAMIC_QUEUE_SCHEDULE_FORCE_DESTROY=400
export DYNAMIC_QUEUE_SCHEDULE_FORCE_HOLD=0