JPEGE(JPEG Encoder)负责完成图像编码功能,将YUV格式图片编码成.jpg图片。关于JPEGE功能的详细介绍及约束请参见JPEGE功能及约束说明。
本节介绍JPEGE图片编码的接口调用流程,同时配合示例代码辅助理解该接口调用流程。
Atlas 200/300/500 推理产品上,当前版本不支持该功能。
Atlas 训练系列产品上,当前版本不支持该功能。
开发应用时,如果涉及将YUV格式图片编码成JPEG压缩格式的图片文件,则应用程序中必须包含编码的代码逻辑,关于编码的接口调用流程,请先参见AscendCL接口调用流程了解整体流程,再查看本节中的流程说明。
成功创建通道之后,您可以根据实际需求设置编码的高级参数,例如场景模式、码流控制器的高级参数等,请参见hi_mpi_venc_set_jpeg_param~hi_mpi_venc_compact_jpeg_tables章节中的接口说明。
select或者poll方式,不需要执行该步骤。
在调用hi_mpi_venc_create_chn接口创建通道时,必须正确设置hi_venc_chn_attr.venc_attr.buf_size参数值(参数描述请参见hi_venc_attr)。
该方式下,相比由用户管理内存,输出结果数据的JPEG头中不存在COM注释字段,数据长度会短一点,但需要用户从DVPP返回的内存中拷贝输出结果数据到指定内存。
在调用hi_mpi_venc_create_chn接口创建通道时,需将hi_venc_chn_attr.venc_attr.buf_size参数值设置为0(参数描述请参见hi_venc_attr),然后调用hi_mpi_venc_get_jpege_predicted_size接口预估输出内存大小,调用hi_mpi_dvpp_malloc/hi_mpi_dvpp_free接口申请/释放输出内存。
该方式下,直接在调用hi_mpi_venc_send_jpege_frame接口时,设置输出内存地址,输出结果数据直接存放到用户设置的内存中,相比由系统管理内存的方式,用户可减少一次“从DVPP返回的内存中拷贝输出结果数据到指定内存”的操作,但输出结果数据的JPEG头中可能会存在COM注释字段(字段长度范围4~19Byte),数据长度会长一点。
您可以从样例介绍中获取完整样例代码。
调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
// 1.AscendCL初始化 aclRet = aclInit(nullptr); // 2.运行管理资源申请(依次申请Device、Context) aclrtContext g_context; aclRet = aclrtSetDevice(0); aclRet = aclrtCreateContext(&g_context, 0); // 获取软件栈的运行模式,不同运行模式影响后续的接口调用流程(例如是否进行数据传输等) aclrtRunMode runMode; aclError aclRet = aclrtGetRunMode(&runMode); // 3.初始化媒体数据处理系统 int32_t ret = hi_mpi_sys_init(); // 4.创建通道 hi_venc_chn chn = 0; hi_venc_chn_attr attr{}; attr.venc_attr.type = HI_PT_JPEG; 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.venc_attr.jpeg_attr.dcf_en = HI_FALSE; attr.venc_attr.jpeg_attr.recv_mode = HI_VENC_PIC_RECV_SINGLE; attr.venc_attr.jpeg_attr.mpf_cfg.large_thumbnail_num = 0; ret = hi_mpi_venc_create_chn(chn, &attr); // 5.通知编码器开始接收输入数据 hi_venc_start_param recv_param{}; recv_param.recv_pic_num = -1; ret = hi_mpi_venc_start_chn(chn, &recv_param); // 6.发送输入数据 // 6.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); // 将输入数据读入内存中,该自定义函数JpegeReadYuvFile由用户实现 JpegeReadYuvFile(streamName, hostInputAddr, inputSize); // 数据传输 aclRet = aclrtMemcpy(inputAddr, inputSize, hostInputAddr, inputSize, ACL_MEMCPY_HOST_TO_DEVICE); // 完成数据传输后,需及时释放不使用的内存 aclrtFreeHost(hostInputAddr ); hostInputAddr = nullptr; } else { // 将输入数据读入内存中,该自定义函数JpegeReadYuvFile由用户实现 JpegeReadYuvFile(streamName, inputAddr, inputSize); } // 6.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); // 7.获取编码结果 // 7.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); // 7.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); // 7.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指向的内存中 // ...... } // 8.释放输入内存和输出码流 ret = hi_mpi_dvpp_free(inputAddr); ret = hi_mpi_venc_release_stream(chn, &stream); delete[] stream.pack; // 9.通知编码器停止接收输入数据 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); // 10.销毁通道 ret = hi_mpi_venc_destroy_chn(chn); // 11.媒体数据处理系统去初始化 ret = hi_mpi_sys_exit(); // 12.释放运行管理资源(依次释放Context、Device) aclRet = aclrtDestroyContext(g_context); aclRet = aclrtResetDevice(0); // 13.AscendCL去初始化 aclRet = aclFinalize(); // ....