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. 编码流程
调用
mcInitAPI进行设备初始化。调用
mcSetDeviceAPI选择合适的设备。执行以下命令,声明一个编码器资源对象并调用
mcVpueOpenAPI初始化编码器资源。mcVPUInst encInst; mcVpueOpen(&encInst);
使用
mcVpueCtrlAPI配置编码参数。mcVpueCtrlAPI提供了丰富的控制编码器功能,使用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);
调用
mcVpuePutFrameAPI将原始数据发送到编码器开始编码。在MCRUNTIME中,原始数据和编码后数据都使用
mcVpuFrameInfo结构体表示。 调用时需要确保mcVpuFrameInfo结构体中的addr指针指向有效的原始数据,len等于有效的一帧数据长度。调用时,
mcVpuFrameInfo结构体中的addr指针由用户负责申请和释放。调用
mcVpueGetFrameAPI来获取编码后数据。编码后数据保存在
mcVpuFrameInfo结构体中的addr指向的内存中,长度信息在len成员中。调用mcVpueGetFrame时,mcVpuFrameInfo中的内存由编码器申请,需要用户在使用后调用mcVpueCtrlAPI携带mcFreeOutputFrame参数释放该帧内存。调用
mcVpuePutFrameAPI携带0长度的一帧输入数据,表示输入结束。调用
mcVpueGetFrameAPI,当函数返回mcSuccess且获取到0长度的输出帧时,表示编码已经结束。调用
mcVpueCloseAPI关闭编码器内部资源。
4.1.3. 编码输入内存类型
编码数据结构中,用 mcVpuMemType 枚举类型表示视频数据的存储位置和类型,具体的定义如下:
typedef enum {
mcMemHost, //存储在主机内存的数据
mcMemDevice, //存储在设备内存的数据
mcHost3Plane, //存储在主机内存且用3个地址分开存储yuv通道数据
mcDevice3Plane, //存储在设备内存且用3个地址分开存储yuv通道数据
mcMemMappedHost,//存储在主机和设备都能访问的地址的数据
} mcVpuMemType;
帧数据结构体 mcVpuFrameInfo 含有 mcVpuMemType mem_type 成员,表示当前帧数据存储和布局类型。编程时需选择正确的内存类型且使用对应的地址和长度成员变量。
当帧内存类型为以下3种时,
mcVpuFrameInfo中的addr和len分别为数据的有效地址和长度:mcMemHostmcMemDevicemcMemMappedHost
当帧内存类型为以下2种时,
mcVpuFrameInfo中的data[3]和line_size[3]分别为y/u/v通道的数据地址和数据长度:mcHost3PlanemcDevice3Plane
4.1.4. 编码高级配置
在初始化编码器后,通过调用 mcVpueCtrl API携带 mcSetEncAll 、 mcVPUEncParamterType 类型参数来配置编码器。编码器主要有以下配置项:
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数组内,随mcVpuePutFrameAPI的调用告知编码器。 设置ROI时需要设置mcVpuFrameInfo结构体中的roi_changed为mcTrue,并且填充ROI数组内的坐标信息和QP值。设置特定帧编码为关键帧
如果需要把某一帧原始数据编码为关键帧,只需在调用
mcVpuePutFrameAPI前,配置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. 解码流程
调用
mcInitAPI初始化设备。调用
mcSetDeviceAPI设置device ID。使用解码具体功能相关API,如
mcVpudOpen和mcVpudClose等。销毁设备任务记录。
全部任务完成后,调用
mcDeviceDestroy()API反初始化。
4.2.3. 解码输入内存类型
解码数据结构中,用 mcVpuMemType 枚举类型表示视频数据的存储位置和类型,具体的定义如下:
typedef enum {
mcMemHost, //存储在主机内存的数据
mcMemDevice, //存储在设备内存的数据
mcHost3Plane, //存储在主机内存且用3个地址分开存储yuv通道数据
mcDevice3Plane, //存储在设备内存且用3个地址分开存储yuv通道数据
mcMemMappedHost,//存储在主机和设备都能访问的地址的数据
} mcVpuMemType;
帧数据结构体 mcVpuFrameInfo 含有 mcVpuMemType mem_type 成员,表示当前帧数据存储和布局类型。编程时需选择正确的内存类型且使用对应的地址和长度成员变量。
当帧内存类型为以下3种时,
mcVpuFrameInfo中的addr和len分别为数据的有效地址和长度:mcMemHostmcMemDevicemcMemMappedHost
目前解码暂不支持
mcHost3Plane和mcDevice3Plane。
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