VENC视频编码
本节介绍VENC视频编码的接口调用流程,同时配合示例代码辅助理解该接口调用流程。
VENC(Video Encoder)将YUV420SP格式的图片编码成H264/H265格式的视频码流。关于VENC功能的详细介绍及约束请参见VENC功能及约束说明。
在实现VENC视频编码功能时,可在创建通道时设置基本参数、或调用对应的set接口设置高级参数,优化视频编码质量,请参见优化视频编码质量。
Atlas 200/300/500 推理产品上,当前版本不支持该功能。
Atlas 训练系列产品上,当前版本不支持该功能。
Atlas A2训练系列产品上,不支持该功能。
接口调用流程
开发应用时,如果涉及视频编码,则应用程序中必须包含编码的代码逻辑,关于编码的接口调用流程,请先参见AscendCL接口调用流程了解整体流程,再查看本节中的流程说明。
- 资源初始化:
- 用hi_mpi_sys_init接口进行媒体数据处理系统初始化;
- 调用hi_mpi_venc_create_chn函数创建完通道;
成功创建通道之后,您可以根据实际需求设置编码的高级参数,例如场景模式、码流控制器的高级参数等,请参见hi_mpi_venc_set_jpeg_param~hi_mpi_venc_compact_jpeg_tables章节中的接口说明。
- 调用hi_mpi_venc_get_fd将通道ID转换为一个文件句柄;
- 调用hi_mpi_sys_create_epoll函数创建DVPP epoll实例;
- 调用hi_mpi_sys_ctl_epoll函数将编码通道的文件句柄添加到epoll实例中,由epoll实例处理;
select或者poll方式,不需要执行该步骤。
- 视频编码:
- 调用hi_mpi_venc_start_chn函数通知通道准备开始编码;
- 调用hi_mpi_dvpp_malloc接口申请存放Device上输入数据的内存。
Atlas 200I/500 A2推理产品上,还支持使用aclrtMalloc接口申请内存。
调用hi_mpi_dvpp_malloc接口申请的内存为媒体数据处理的专用内存,但专用内存的地址空间有限,若关注内存规划或内存资源有限时,建议调用aclrtMalloc接口申请内存。
- 启动一个用户态线程,调用hi_mpi_sys_wait_epoll函数等待编码完成;
- 之后用户就可以调用hi_mpi_venc_send_frame函数发送待编码的码流;
- 一旦编码完成,hi_mpi_sys_wait_epoll函数或select函数或poll函数就会返回,用户就可以调用hi_mpi_venc_query_status接口查询编码状态,再调用hi_mpi_venc_get_stream函数获取编码结果;
- 用户需要注意的是,编码结果数据使用完成之后,需要及时调用hi_mpi_venc_release_stream函数释放buffer。否则会因编码buffer用完导致后续编码无法进行;
- 调用hi_mpi_dvpp_free接口释放输入内存;
Atlas 200I/500 A2推理产品上,若使用aclrtMalloc接口申请内存,则需使用aclrtFree接口释放内存。
- 当用户不需发送图像到目的通道继续编码时,需要调用hi_mpi_venc_stop_chn函数通知该通道不再接收新的输入图片;
- 资源释放:
- 调用hi_mpi_sys_ctl_epoll函数从epoll实例中删除编码通道的文件句柄;
- 当用户完成所有编码之后,需要调用hi_mpi_venc_destroy_chn释放编码通道以及内部内存资源;
- 调用hi_mpi_sys_close_epoll函数销毁DVPP epoll实例;
- 调用hi_mpi_sys_exit接口进行媒体数据处理系统去初始化。
优化视频编码质量
在实现VENC视频编码功能时,可在创建通道时设置基本参数、或调用对应的set接口设置高级参数,优化视频编码质量,以下调整手段可以叠加使用,效果是叠加的,例如:
- H264视频数据获取场景,分辨率720P,gop = 60,帧率30fps,码率1M需要提升编码质量,可以使用如下优化手段组合:CBR模式、HI_VENC_SCENE_0、stats_time等于2、profile等于2、关闭宏块级码控。
- H265电影场景,分辨率1080P,gop=30,帧率25fps,码率2M需要提升编码质量,可以使用如下优化手段组合:CBR模式、HI_VENC_SCENE_1、stats_time等于1、关闭宏块级码控。
当前支持以下方式优化视频编码质量:
- 设置基本参数,优化视频编码质量
不同分辨率的视频,其编码质量与视频的帧率、GOP(Group of pictures)、码率有关,在调用hi_mpi_venc_create_chn接口创建通道时,可设置编码的等级、设置H.264/H.265协议编码场景下CBR/VBR/AVBR/CVBR/QVBR模式的帧率、GOP、码率等参数,来调整视频编码质量:
- 编码等级,通过hi_venc_chn_attr.venc_attr结构内的profile参数来设置;
- 帧率,通过hi_venc_chn_attr.rc_attr结构体内的src_frame_rate输入帧率参数、dst_frame_rate输出帧率参数来设置;
- GOP,通过hi_venc_chn_attr.rc_attr结构体内的gop参数来设置;
- 码率,通过hi_venc_chn_attr.rc_attr结构体内的bit_rate或max_bit_rate或target_bit_rate参数来设置。
表1 典型场景下帧率、GOP、码率的取值 画质/分辨率
帧率
GOP
码率(Mbps)
4K
3840*2160/4096*2160
25或30
建议GOP为帧率的整数倍,例如帧率为25时,GOP建议25或50。
2K
2560*1440
25或30
建议GOP为帧率的整数倍,例如帧率为25时,GOP建议25或50。
1080P(蓝光)
1920*1080
25或30
建议GOP为帧率的整数倍,例如帧率为25时,GOP建议25或50。
720P(高清)
1280*720
25或30
建议GOP为帧率的整数倍,例如帧率为25时,GOP建议25或50。
480P/D1_N(标清)
854*480/720*480
25或30
建议GOP为帧率的整数倍,例如帧率为25时,GOP建议25或50。
576P/D1(标清)
720*576
25或30
建议GOP为帧率的整数倍,例如帧率为25时,GOP建议25或50。
270P(流畅)
480*270
25或30
建议GOP为帧率的整数倍,例如帧率为25时,GOP建议25或50。
CIF P/N
352*288/320*240
25或30
建议GOP为帧率的整数倍,例如帧率为25时,GOP建议25或50。
- 设置高级参数,调整视频编码细节
您可以调用接口设置码控模式、宏块级码率控制参数、编码场景模式等,来调整视频编码的细节,进一步改善编码质量。
表2 高级配置项列表 配置项
接口
参数名
说明
码控模式
hi_venc_chn_attr.rc_attr结构体内的rc_mode参数
追求码率平稳或追求PSNR大且码率符合目标值,配置为CBR;
追求节省码率,对主观编码质量有一定要求,配置为VBR;
追求节省码率,对主观编码质量有一定要求,且场景中有较多静止画面,配置为AVBR;
追求PSNR且对码率上浮没有严格要求,配置为QVBR;
追求节省码率,对主观编码质量有一定要求,且可以根据带宽、存储空间要求进行更多调整,配置为CVBR;
码率控制模型统计时间
hi_venc_chn_attr.rc_attr内各模式属性值结构体内的stats_time参数
关注长期码率稳定,短期波动不在意的可以设置大一些,例:DVR存盘。设大可以提高重编码判决的门槛,重编码次数会减少,但是码率波动会加大。
宏块级码率控制参数
hi_venc_rc_param结构内的threshold_i、threshold_p、threshold_b、direction、row_qp_delta参数
如果图像内容复杂、细节较多或用户关注PSNR等客观指标时,需关闭宏块级码率控制。
第一帧的起始Qp值
hi_venc_rc_param结构内的first_frame_start_qp参数
典型场景下,用户配置的码率小于表1中给的参考值,且编码后的视频第一帧明显模糊,则建议配置first_frame_start_qp参数,参数值取[min_i_qp, max_i_qp]的中间值,例如,[min_i_qp, max_i_qp]为[30, 40],则first_frame_start_qp参数配置为35,同时将max_reencode_times参数配置为0,会获得较好的编码质量。
编码场景模式
hi_venc_scene_mode
安防场景配置为HI_VENC_SCENE_0;自动驾驶、直播、游戏、动画、电影配置为HI_VENC_SCENE_1。
示例代码
您可以从样例介绍中获取完整样例代码。
调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
// 1.获取软件栈的运行模式,不同运行模式影响后续的接口调用流程(例如是否进行数据传输等) aclrtRunMode runMode; aclError aclRet = aclrtGetRunMode(&runMode); // 2.AscendCL初始化 aclRet = aclInit(nullptr); // 3.运行管理资源申请(依次申请Device、Context) aclrtContext g_context; aclRet = aclrtSetDevice(0); aclRet = aclrtCreateContext(&g_context, 0); // 4.初始化媒体数据处理系统 int32_t ret = hi_mpi_sys_init(); // 5.创建通道 hi_venc_chn chn = 0; hi_venc_chn_attr attr{}; attr.venc_attr.type = HI_PT_H265; attr.venc_attr.profile = 0; attr.venc_attr.max_pic_width = 128; attr.venc_attr.max_pic_height = 128; attr.venc_attr.pic_width = 128; attr.venc_attr.pic_height = 128; attr.venc_attr.buf_size = 2 * 1024 * 1024; attr.venc_attr.is_by_frame = HI_TRUE; attr.rc_attr.rc_mode = HI_VENC_RC_MODE_H265_VBR; attr.rc_attr.h265_vbr.gop = 30; attr.rc_attr.h265_vbr.stats_time = 1; attr.rc_attr.h265_vbr.src_frame_rate = 30; attr.rc_attr.h265_vbr.dst_frame_rate = 30; attr.rc_attr.h265_vbr.max_bit_rate = 4000; attr.gop_attr.gop_mode = HI_VENC_GOP_MODE_NORMAL_P; attr.gop_attr.normal_p.ip_qp_delta = 3; ret = hi_mpi_venc_create_chn(chn, &attr); // 6.通知编码器启动接收输入数据 hi_venc_start_param recv_param{}; recv_param.recv_pic_num = -1; ret = hi_mpi_venc_start_chn(chn, &recv_param); // 7.发送输入数据 // 7.1 申请输入内存 uint8_t* inputAddr = nullptr; int32_t inputSize = 128 * 128 * 3 / 2; ret = hi_mpi_dvpp_malloc(0, &inputAddr, inputSize); // 如果运行模式为ACL_HOST,则需要申请Host内存,将输入数据读入Host内存,再通过aclrtMemcpy接口将Host的数据传输到Device,数据传输完成后,需及时释放Host内存;否则直接将输入数据读入Device内存 if (runMode == ACL_HOST) { void* hostInputAddr = nullptr; // 申请Host内存 aclRet = aclrtMallocHost(&hostInputAddr, inputSize); // 将输入数据读入内存中,该自定义函数VencReadYuvFile由用户实现 VencReadYuvFile(streamName, hostInputAddr, inputSize); // 数据传输 aclRet = aclrtMemcpy(inputAddr, inputSize, hostInputAddr, inputSize, ACL_MEMCPY_HOST_TO_DEVICE); // 完成数据传输后,需及时释放不使用的内存 aclrtFreeHost(hostInputAddr ); hostInputAddr = nullptr; } else { // 将输入数据读入内存中,该自定义函数VencReadYuvFile由用户实现 VencReadYuvFile(streamName, inputAddr, inputSize); } // 7.2 发送输入数据,开始编码 hi_video_frame_info frame{}; frame.mod_id = HI_ID_VENC; frame.v_frame.width = 128; frame.v_frame.height = 128; frame.v_frame.field = HI_VIDEO_FIELD_FRAME; frame.v_frame.pixel_format = HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420; frame.v_frame.video_format = HI_VIDEO_FORMAT_LINEAR; frame.v_frame.compress_mode = HI_COMPRESS_MODE_NONE; frame.v_frame.dynamic_range = HI_DYNAMIC_RANGE_SDR8; frame.v_frame.color_gamut = HI_COLOR_GAMUT_BT709; frame.v_frame.width_stride[0] = 128; frame.v_frame.width_stride[1] = 128; frame.v_frame.width_stride[2] = 128; frame.v_frame.virt_addr[0] = inputAddr; frame.v_frame.virt_addr[1] = (hi_void *)((uintptr_t)frame.v_frame.virt_addr[0] + 128 * 128); frame.v_frame.frame_flag = 0; frame.v_frame.time_ref = 0; frame.v_frame.pts = 0; ret = hi_mpi_venc_send_frame(chn, &frame, 0); // 8.获取编码结果 // 8.1 创建EPOLL实例 int32_t epollFd = 0; int32_t fd = hi_mpi_venc_get_fd(chn); ret = hi_mpi_sys_create_epoll(10, &epollFd); hi_dvpp_epoll_event event; event.events = HI_DVPP_EPOLL_IN; event.data = (void*)(unsigned long)(fd); ret = hi_mpi_sys_ctl_epoll(epollFd, HI_DVPP_EPOLL_CTL_ADD, fd, &event); int32_t eventCount = 0; // 编码完成前,会超时阻塞在这里,一旦完成,才会往下执行 ret = hi_mpi_sys_wait_epoll(epollFd, event, 3, 1000, &eventCount); // 8.2 获取编码结果 hi_venc_chn_status stat; ret = hi_mpi_venc_query_status(chn, &stat); hi_venc_stream stream; stream.pack_cnt = stat.cur_packs; stream.pack = new hi_venc_pack[stream.pack_cnt]; ret = hi_mpi_venc_get_stream(chn, &stream, 1000); // 8.3 如果运行模式为ACL_HOST,且Host上需要使用编码输出的码流,则需要申请Host内存,通过aclrtMemcpy接口将Device的输出码流传输到Host if (g_runMode == ACL_HOST) { void* hostOutputAddr = nullptr; aclRet = aclrtMallocHost(&hostOutputAddr, outputSize); aclRet = aclrtMemcpy(hostOutputAddr, outputSize, stream.pack[0].addr, outputSize, ACL_MEMCPY_DEVICE_TO_HOST); // ...... // 数据使用完成后,需及时释放不使用的内存 aclrtFreeHost(hostOutputAddr); hostOutputAddr = nullptr; } else { // 可以直接使用编码输出码流数据,在stream.pack[0].addr指向的内存中 // TODO: 推理相关的代码逻辑 } // 9.释放输入内存和输出码流 ret = hi_mpi_dvpp_free(inputAddr); ret = hi_mpi_venc_release_stream(chn, &stream); delete[] stream.pack; // 10.通知编码器停止接收输入数据 ret = hi_mpi_venc_stop_chn(chn); ret = hi_mpi_sys_ctl_epoll(epollFd, HI_DVPP_EPOLL_CTL_DEL, fd, NULL); ret = hi_mpi_sys_close_epoll(epollFd); // 11.销毁通道 ret = hi_mpi_venc_destroy_chn(chn); // 12.媒体数据处理系统去初始化 ret = hi_mpi_sys_exit(); // 13.释放运行管理资源(依次释放Context、Device) aclRet = aclrtDestroyContext(g_context); aclRet = aclrtResetDevice(0); // 14.AscendCL去初始化 aclRet = aclFinalize(); // .... |