跳到主要内容

JPEGE图片编码

VPC图片处理典型功能

VPC(Vision Preprocessing Core)负责图像处理功能,支持对图片做抠图、缩放、格式转换等操作。关于VPC功能的详细介绍请参见VPC功能,关于VPC功能对输入、输出的约束要求,请参见约束说明

本节以抠图、缩放为例说明VPC图像处理时的接口调用流程,同时配合以下典型功能的示例代码辅助理解该接口调用流程:

典型功能接口调用流程(以缩放为例)

开发应用时,如果涉及抠图、缩放等图片处理,则应用程序中必须包含图片处理的代码逻辑,关于图片处理的接口调用流程,请先参见pyACL接口调用流程了解整体流程,再查看本节中的流程说明。

图1 接口调用流程(以缩放为例)

当前系统支持对输入图片做抠图、缩放等处理,关键接口的说明如下:

  1. 调用acl.himpi.sys_init接口进行媒体数据处理系统初始化。

  2. 调用acl.himpi.vpc_create_chn接口创建通道。

  3. 调用acl.himpi.dvpp_malloc接口申请Device上的内存,存放输入或输出数据。

  4. 执行抠图、缩放等,此步骤以缩放为例说明,其它功能(例如格式转换、金字塔等)请参见VPC功能下的接口说明。

    调用acl.himpi.vpc_resize接口,按指定区域从输入图片中抠图,抠出的图作为输出图片。acl.himpi.vpc_resize接口是异步接口,调用该接口成功仅表示任务下发成功,还需要调用acl.himpi.vpc_get_process_result接口等待任务完成。

  5. 调用acl.himpi.dvpp_free接口释放输入、输出内存。

  6. 调用acl.himpi.vpc_destroy_chn接口销毁通道。

  7. 调用acl.himpi.sys_exit接口进行媒体数据处理系统去初始化。

图片缩放示例代码

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝运行,仅供参考。

## 1.获取软件栈的运行模式,不同运行模式影响后续的接口调用流程(例如是否进行数据传输等)。
run_mode, ret = acl.rt.get_run_mode()

## 2.pyACL 初始化。
ret = acl.init()

## 3.运行管理资源申请(依次申请Device、Context)。
ret = acl.rt.set_device(0)
context, ret = acl.rt.create_context(0)

## 4.初始化媒体数据处理系统。
ret = acl.himpi.sys_init()

## 5.创建通道。
channel_id = 0
chn_attr = {'attr': 0, 'pic_width': 0, 'pic_height': 0}
ret = acl.himpi.vpc_create_chn(channel_id, chn_attr)

## 6.执行缩放。
## 6.1 构造存放输入图片信息的字典。
input_pic = {'picture_width': 1920,
'picture_height': 1080,
'picture_width_stride': 1920,
'picture_height_stride': 1080,
'picture_format': HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420}
input_pic["picture_buffer_size"] = input_pic["picture_width_stride"] * input_pic["picture_height_stride"] * 3 // 2
picture_address, ret = acl.himpi.dvpp_malloc(0, input_pic["picture_buffer_size"])
input_pic["picture_address"] = picture_address

## 如果运行模式为ACL_HOST,则需要申请Host内存,将输入图片数据读入Host内存,再通过acl.rt.memcpy接口将Host的图片数据传输到Device,数据传输完成后,需及时释放Host内存;否则直接将输入图片数据读入Device内存。
## 直接将输入图片数据读入Device内存。
if run_mode == ACL_HOST:
# 将输入图片读入内存中。
vpc_file = np.fromfile(vpc_file_path, dtype=np.byte)
vpc_file_size = vpc_file.itemsize * vpc_file.size

bytes_data = vpc_file.tobytes()
vpc_file_ptr = acl.util.bytes_to_ptr(bytes_data)
# 数据传输。
ret = acl.rt.memcpy(input_pic["picture_address"], input_pic["picture_buffer_size"],
vpc_file_ptr, vpc_file_size, ACL_MEMCPY_HOST_TO_DEVICE)
else:
# 将输入图片读入内存中。
vpc_file = np.fromfile(vpc_file_path, dtype=np.byte)
vpc_file_size = vpc_file.itemsize * vpc_file.size

bytes_data = vpc_file.tobytes()
vpc_file_ptr = acl.util.bytes_to_ptr(bytes_data)
# 数据传输。
ret = acl.rt.memcpy(input_pic["picture_address"], input_pic["picture_buffer_size"],
vpc_file_ptr, vpc_file_size, ACL_MEMCPY_DEVICE_TO_DEVICE)

## 6.3 构造存放输出图片信息的字典。
output_pic = {'picture_width': 960,
'picture_height': 540,
'picture_width_stride': 960,
'picture_height_stride': 540,
'picture_format': HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420}
output_pic["picture_buffer_size"] = output_pic["picture_width_stride"] * output_pic["picture_height_stride"] * 3 // 2
picture_address, ret = acl.himpi.dvpp_malloc(0, output_pic["picture_buffer_size"])
output_pic["picture_address"] = picture_address
## 初始化内存。
ret = acl.rt.memset(output_pic["picture_address"], output_pic["picture_buffer_size"], 0, output_pic["picture_buffer_size"])

## 6.4 调用缩放接口。
testid, ret = acl.himpi.vpc_resize(channel_id, input_pic, output_pic, 0, 0, 0, -1)

## 6.5 等待任务处理结束,任务处理结束后,输出图片数据在outputPic.picture_address指向的内存中。
ret = acl.himpi.vpc_get_process_result(channel_id, task_id, -1)

## 6.6 如果运行模式为ACL_HOST,且Host上需要展示VPC输出的图片数据,则需要申请Host内存,通过acl.rt.memcpy接口将Device的输出图片数据传输到Host;如果Host上不需要展示VPC输出的图片数据,则VPC的输出图片数据可以直接作为模型推理的输入。
## 6.6 VPC的输出图片数据可以直接作为模型推理的输入。
if run_mode == ACL_HOST:
np_output = np.zeros(output_pic.get('picture_buffer_size'), dtype=np.byte)

bytes_data = np_output.tobytes()
np_output_ptr = acl.util.bytes_to_ptr(bytes_data)
ret = acl.rt.memcpy(np_output_ptr, output_pic.get('picture_buffer_size'), output_pic.get("picture_address"),
output_pic.get('picture_buffer_size'), ACL_MEMCPY_DEVICE_TO_HOST)
# ......
else
# 可以直接使用VPC的输出图片数据,在outputPic.picture_address指向的内存中。
# ......
}

## 6.7 释放输入、输出内存。
ret = acl.himpi.dvpp_free(input_pic['picture_address'])
ret = acl.himpi.dvpp_free(output_pic['picture_address'])

## 7.销毁通道。
ret = acl.himpi.vpc_destroy_chn(channel_id)

## 8. 媒体数据处理系统去初始化。
ret = acl.himpi.sys_exit()

## 9. 释放运行管理资源(依次释放Context、Device)。
ret = acl.rt.destroy_context(context)
ret = acl.rt.reset_device(0)

## 10.pyACL去初始化。
ret = acl.finalize()

抠图示例代码

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝运行,仅供参考。

## 1.获取软件栈的运行模式,不同运行模式影响后续的接口调用流程(例如是否进行数据传输等)。
run_mode, ret = acl.rt.get_run_mode()

## 2.pyACL 初始化。
ret = acl.init()

## 3.运行管理资源申请(依次申请Device、Context)。
ret = acl.rt.set_device(0)
context, ret = acl.rt.create_context(0)

## 4.初始化媒体数据处理系统。
ret = acl.himpi.sys_init()

## 5.创建通道。
channel_id = 0
chn_attr = {'attr': 0, 'pic_width': 0, 'pic_height': 0}
ret = acl.himpi.vpc_create_chn(channel_id, chn_attr)

## 6.执行抠图。
## 6.1 构造存放输入图片信息的字典。
input_pic = {'picture_width': 1920,
'picture_height': 1080,
'picture_width_stride': 1920,
'picture_height_stride': 1080,
'picture_format': HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420}
input_pic["picture_buffer_size"] = input_pic["picture_width_stride"] * input_pic["picture_height_stride"] * 3 // 2
picture_address, ret = acl.himpi.dvpp_malloc(0, input_pic["picture_buffer_size"])
input_pic["picture_address"] = picture_address

## 如果运行模式为ACL_HOST,则需要申请Host内存,将输入图片数据读入Host内存,再通过acl.rt.memcpy接口将Host的图片数据传输到Device,数据传输完成后,需及时释放Host内存;否则直接将输入图片数据读入Device内存。
## 直接将输入图片数据读入Device内存。
if run_mode == ACL_HOST:
# 将输入图片读入内存中。
vpc_file = np.fromfile(vpc_file_path, dtype=np.byte)
vpc_file_size = vpc_file.itemsize * vpc_file.size

bytes_data = vpc_file.tobytes()
vpc_file_ptr = acl.util.bytes_to_ptr(bytes_data)
# 数据传输。
ret = acl.rt.memcpy(input_pic["picture_address"], input_pic["picture_buffer_size"],
vpc_file_ptr, vpc_file_size, ACL_MEMCPY_HOST_TO_DEVICE)
else:
# 将输入图片读入内存中。
vpc_file = np.fromfile(vpc_file_path, dtype=np.byte)
vpc_file_size = vpc_file.itemsize * vpc_file.size

bytes_data = vpc_file.tobytes()
vpc_file_ptr = acl.util.bytes_to_ptr(bytes_data)
# 数据传输。
ret = acl.rt.memcpy(input_addr, input_size, vpc_file_ptr, vpc_file_size, ACL_MEMCPY_DEVICE_TO_DEVICE)

## 6.3 构造存放输出图片信息的字典。
multithreading_count = 1
crop_region_infos = []
for i in range(multithreading_count):
output_pic = {'picture_width': 960,
'picture_height': 540,
'picture_width_stride': 960,
'picture_height_stride': 540,
'picture_format': HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420}
output_pic["picture_buffer_size"] = output_pic["picture_width_stride"] * output_pic["picture_height_stride"] * 3 // 2
picture_address, ret = acl.himpi.dvpp_malloc(0, output_pic["picture_buffer_size"])
output_pic["picture_address"] = picture_address

ret = acl.rt.memset(output_pic["picture_address"], output_pic["picture_buffer_size"], 0, output_pic["picture_buffer_size"])
crop_region = {'top_offset': 0, 'left_offset': 0, 'crop_width': 960, 'crop_height': 540}
crop_region_info = {'dest_pic_info': output_pic, 'crop_region': crop_region}
crop_region_infos.append(crop_region_info)

## 6.4 调用抠图接口。
task_id, ret = acl.himpi.vpc_crop(channel_id, input_pic, crop_region_infos, 1, -1)

## 6.5 等待任务处理结束,任务处理结束后,输出图片数据在outputPic.picture_address指向的内存中。
ret = acl.himpi.vpc_get_process_result(channel_id, task_id, -1)

## 6.6 如果运行模式为ACL_HOST,且Host上需要展示VPC输出的图片数据,则需要申请Host内存,通过acl.rt.memcpy接口将Device的输出图片数据传输到Host;如果Host上不需要展示VPC输出的图片数据,则VPC的输出图片数据可以直接作为模型推理的输入。
## 6.6 VPC的输出图片数据可以直接作为模型推理的输入。
if run_mode == ACL_HOST:
np_output = np.zeros(output_pic.get('picture_buffer_size'), dtype=np.byte)

bytes_data = np_output.tobytes()
np_output_ptr = acl.util.bytes_to_ptr(bytes_data)
ret = acl.rt.memcpy(np_output_ptr, output_pic.get('picture_buffer_size'), output_pic.get("picture_address"),
output_pic.get('picture_buffer_size'), ACL_MEMCPY_DEVICE_TO_HOST)
# ......
else
# 可以直接使用VPC的输出图片数据,在outputPic.picture_address指向的内存中。
# ......
}

## 6.7 释放输入、输出内存。
ret = acl.himpi.dvpp_free(input_pic['picture_address'])
ret = acl.himpi.dvpp_free(output_pic['picture_address']))

## 7.销毁通道。
ret = acl.himpi.vpc_destroy_chn(channel_id)

## 8. 媒体数据处理系统去初始化。
ret = acl.himpi.sys_exit()

## 9. 释放运行管理资源(依次释放Context、Device)。
ret = acl.rt.destroy_context(context)
ret = acl.rt.reset_device(0)

## 10.pyACL去初始化。
ret = acl.finalize()

父主题: 媒体数据处理V2

JPEGD图片解码

JPEGD(JPEG Decoder)负责完成图像解码功能,将.jpg、.jpeg、.JPG、.JPEG图片解码成YUV格式图片。关于JPEGD功能的详细介绍及约束请参见JPEGD功能及约束说明

本节介绍JPEGD图片解码的接口调用流程,同时配合示例代码辅助理解该接口调用流程。

接口调用流程

开发应用时,如果涉及对JPEG图片的解码,则应用程序中必须包含解码的代码逻辑,关于图片解码的接口调用流程,请先参见pyACL接口调用流程了解整体流程,再查看本节中的流程说明。

图1 接口调用的流程

当前系统支持解码JPEG图片,关键接口的说明如下:

  1. 调用acl.himpi.sys_init接口进行媒体数据处理系统初始化。

  2. 调用acl.himpi.vdec_create_chn接口创建通道。

  3. 调用acl.himpi.dvpp_malloc接口申请Device上的内存,存放输入或输出数据。

  4. 解码前,需调用acl.himpi.vdec_start_recv_stream接口通知解码器启动接收码流,再调用acl.himpi.vdec_send_stream接口发送解码码流,acl.himpi.vdec_send_stream接口是异步接口,调用该接口仅表示任务下发成功,还需要调acl.himpi.vdec_get_frame接口获取解码结果数据,成功获取解码数据后,可以调用acl.himpi.vdec_release_frame接口释放帧相关的资源。

    解码结束后,需调用acl.himpi.vdec_stop_recv_stream接口通知解码器停止接收码流。

  5. 调用acl.himpi.dvpp_free接口释放输入、输出内存。

  6. 调用acl.himpi.vdec_destroy_chn接口销毁通道。

  7. 调用acl.himpi.sys_exit接口进行媒体数据处理系统去初始化。

示例代码

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝运行,仅供参考。

## 1.获取软件栈的运行模式,不同运行模式影响后续的接口调用流程(例如是否进行数据传输等)。
run_mode, ret = acl.rt.get_run_mode()

## 2.pyACL 初始化。
ret = acl.init()

## 3.运行管理资源申请(依次申请Device、Context)。
ret = acl.rt.set_device(0)
context, ret = acl.rt.create_context(0)

## 4.初始化媒体数据处理系统。
ret = acl.himpi.sys_init()

## 5.创建通道。
channel_id = 0
attr = {'type': HI_PT_JPEG, 'mode': HI_VDEC_SEND_MODE_FRAME,
'pic_width': 1920, 'pic_height': 1080,
'stream_buf_size': 1920 * 1080, 'frame_buf_size': 0,
'frame_buf_cnt': 9}

ret = acl.himpi.vdec_create_chn(channel_id, attr)
## 6.设置通道属性。
jpegd_param_dict, ret = acl.himpi.vdec_get_chn_param(channel_id)
jpegd_param_dict["pic_param"]["pixel_format"] = HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420
jpegd_param_dict["pic_param"]["alpha"] = 255
jpegd_param_dict["display_frame_num"] = 0
ret = acl.himpi.vdec_set_chn_param(i, jpegd_param_dict)

## 7.解码器启动接收码流。
ret = acl.himpi.vdec_start_recv_stream(channel_id)

## 8.发送码流。
## 8.1 申请输入内存。
## input_size表示输入图片占用的内存大小,此处以1024 Byte为例,用户需根据实际情况计算内存大小。
input_size = 1024;
input_addr, ret = acl.himpi.dvpp_malloc(0, input_size);

#如果运行模式为ACL_HOST,则需要申请Host内存,将输入图片数据读入Host内存,再通过acl.rt.memcpy接口将Host的图片数据传输到Device,数据传输完成后,需及时释放Host内存;否则直接将输入图片数据读入Device内存。
#直接将输入图片数据读入Device内存。
if run_mode == ACL_HOST:
# 申请Host内存。
input_buffer, ret= acl.rt.malloc_host(input_size)
# 将输入图片读入内存中。
vdec_file = np.fromfile(vdec_file_path, dtype=np.byte)
vdec_file_size = vdec_file.itemsize * vdec_file.size

bytes_data = vdec_file.tobytes()
vdec_file_ptr = acl.util.bytes_to_ptr(bytes_data)
# 数据传输。
ret = acl.rt.memcpy(input_addr, input_size, vdec_file_ptr, vdec_file_size, ACL_MEMCPY_HOST_TO_DEVICE)
else:
# 将输入图片读入内存中。
vdec_file = np.fromfile(vdec_file_path, dtype=np.byte)
vdec_file_size = vdec_file.itemsize * vdec_file.size

bytes_data = vdec_file.tobytes()
vdec_file_ptr = acl.util.bytes_to_ptr(bytes_data)
# 数据传输。
ret = acl.rt.memcpy(input_addr, input_size, vdec_file_ptr, vdec_file_size, ACL_MEMCPY_DEVICE_TO_DEVICE)

## 8.2 构造存放输入图片信息的字典。
stream = {'end_of_frame': HI_TRUE, 'end_of_stream': HI_FALSE,
'need_display': HI_TRUE, 'pts': 0,
'len': input_size, 'addr': vdec_file_ptr}
img_info, ret = acl.himpi.dvpp_get_image_info(HI_PT_JPEG, stream)
if run_mode == ACL_HOST:
# 如果不使用Host上的数据,需及时释放。
ret = acl.rt.free_host(input_buffer)

stream['addr'] = input_addr

## 8.3 构造存放输出图片信息的字典,并申请输出内存。
out_pic_info = {"width": img_info['width'],
"height": img_info['height'],
"width_stride": img_info['width_stride'],
"height_stride": img_info['height_stride'],
"pixel_format": HI_PIXEL_FORMAT_UNKNOWN,
"buffer_size": img_info['img_buf_size']}
out_buffer, ret = acl.himpi.dvpp_malloc(0, out_pic_info['buffer_size'])
out_pic_info['vir_addr '] = out_buffer

## 8.4 发送需解码的输入图片。
ret = acl.himpi.vdec_send_stream(channel_id, stream, out_pic_info, 0)

## 9.接收解码结果。
## 9.1 获取解码结果。
frame_info, supplement, stream, ret = acl.himpi.vdec_get_frame(channel_id, 0)
if ret == 0:
dec_result = frame_info['v_frame']['frame_flag']
if dec_result == 0: # 0: Decode success
print("Chn %u GetFrame Success, Decode Success \n"%channel_id)
else: # Decode fail
print("Chn %u GetFrame Success, Decode Fail \n"%channel_id)

## 9.2 如果运行模式为ACL_HOST,且Host上需要展示JPEGD输出的图片数据,则需要申请Host内存,通过aclrtMemcpy接口将Device的输出图片数据传输到Host。
## 9.2 获取JPEGD的输出图片数据。

if run_mode == ACL_HOST:
# 申请Host内存。
output_buffer, ret= acl.rt.malloc_host(out_pic_info['buffer_size'])
# 数据传输。
ret = acl.rt.memcpy(output_buffer, out_pic_info['buffer_size'], frame_info['v_frame']['virt_addr'][0], out_pic_info['buffer_size'], ACL_MEMCPY_DEVICE_TO_HOST)

# ......
# 数据使用完成后,及时释放不使用的内存。
ret = acl.rt.free_host(output_buffer)
else:
# 可以直接使用JPEGD的输出图片数据,在outputPic.picture_address指向的内存中。
# ......

## 9.3 释放输入、输出内存。
ret = acl.himpi.dvpp_free(frame_info['v_frame']['virt_addr'][0])
ret = acl.himpi.dvpp_free(stream['addr'])

## 9.4 释放资源。
ret = acl.himpi.vdec_release_frame(channel_id, frame_info)

## 10.解码器停止接收码流。
ret = acl.himpi.vdec_stop_recv_stream(channel_id)

## 7.销毁通道。
ret = acl.himpi.vdec_destroy_chn(channel_id)

## 8. 媒体数据处理系统去初始化。
ret = acl.himpi.sys_exit()

## 9. 释放运行管理资源(依次释放Context、Device)。
ret = acl.rt.destroy_context(context)
ret = acl.rt.reset_device(0)

## 10.pyACL去初始化。
ret = acl.finalize()

父主题: 媒体数据处理V2

JPEGE图片编码

JPEGE(JPEG Encoder)负责完成图像编码功能,将YUV格式图片编码成.jpg图片。关于JPEGE功能的详细介绍请参见JPEGE功能及约束说明

本节介绍JPEGE图片编码的接口调用流程,同时配合示例代码辅助理解该接口调用流程。

接口调用流程

开发应用时,如果涉及将YUV格式图片编码成JPEG压缩格式的图片文件,则应用程序中必须包含编码的代码逻辑,关于编码的接口调用流程,请先参见pyACL接口调用流程了解整体流程,再查看本节中的流程说明。

图1 接口调用流程

当前系统支持将YUV格式图片编码成JPEG压缩格式的图片文件,关键接口的说明如下:

  1. 调用acl.himpi.sys_init接口进行媒体数据处理系统初始化。

  2. 调用acl.himpi.venc_create_chn函数创建完通道。

    成功创建通道之后,您可以根据实际需求设置编码的高级参数,例如场景模式、码流控制器的高级参数等,请参见acl.himpi.venc_set_jpeg_param~acl.himpi.venc_compact_jpeg_tables章节中的接口说明。

  3. 调用acl.himpi.venc_get_fd将通道ID转换为一个文件句柄。

  4. 调用acl.himpi.sys_create_epoll函数创建DVPP epoll实例。

  5. 调用acl.himpi.sys_ctl_epoll函数将编码通道的文件句柄添加到epoll实例中,由epoll实例处理。

    select或者poll方式,不需要执行该步骤。

  6. 调用acl.himpi.venc_start_chn函数通知通道准备开始编码。

  7. 调用acl.himpi.dvpp_malloc接口申请存放Device上输入数据的内存。

  8. 启动一个用户态线程,调用acl.himpi.sys_wait_epoll函数等待编码完成。

  9. 之后用户就可以调用acl.himpi.venc_send_frame函数发送待编码的码流。

  10. 一旦编码完成,acl.himpi.sys_wait_epoll函数或select函数或poll函数就会返回,用户就可以调用acl.himpi.venc_query_status接口查询编码状态,再调用acl.himpi.venc_get_stream函数获取编码结果。

  11. 用户需要注意的是,编码结果数据使用完成之后,需要及时调用acl.himpi.venc_release_stream函数释放buffer。否则会因编码buffer用完导致后续编码无法进行。

  12. 调用acl.himpi.dvpp_free接口释放输入内存。

  13. 当用户不需发送图像到目的通道继续编码时,需要调用acl.himpi.venc_stop_chn函数通知该通道不再接收新的输入图片。

  14. 调用acl.himpi.sys_ctl_epoll函数从epoll实例中删除编码通道的文件句柄。

  15. 当用户完成所有编码之后,需要调用acl.himpi.venc_destroy_chn释放编码通道以及内部内存资源。

  16. 调用acl.himpi.sys_close_epoll函数销毁DVPP epoll实例。

  17. 调用acl.himpi.sys_exit接口进行媒体数据处理系统去初始化。

示例代码

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝运行,仅供参考。

## 1.获取软件栈的运行模式,不同运行模式影响后续的接口调用流程(例如是否进行数据传输等)。
run_mode, ret = acl.rt.get_run_mode()

## 2.pyACL 初始化。
ret = acl.init()

## 3.运行管理资源申请(依次申请Device、Context)。
ret = acl.rt.set_device(0)
context, ret = acl.rt.create_context(0)

## 4.初始化媒体数据处理系统。
ret = acl.himpi.sys_init()

## 5.创建通道。
channel_id = 0
venc_attr = {'type': HI_PT_JPEG, 'profile': 0,
'max_pic_width': 128, 'pic_width': 128,
'max_pic_height': 128, 'pic_height': 128,
'buf_size': 1024 * 1024 * 2, 'is_by_frame': 1}
jpeg_attr = {'recv_mode':HI_VENC_PIC_RECV_SINGLE,
'mpf_cfg':{'large_thumbnail_num ': 0}}
attr = {'venc_attr':venc_attr, 'jpeg_attr':jpeg_attr}
ret = acl.himpi.venc_create_chn(channel_id, attr)

## 6.设置JPEGE参数。
param, ret = acl.himpi.venc_get_jpeg_param(channel_id)
param['qfactor'] = 100
ret = acl.himpi.venc_set_jpeg_param(channel_id, param)

## 7.通知编码器开始接收输入数据。
recv_param = {'recv_pic_num': -1}
ret = acl.himpi.venc_start_chn(channel_id, recv_param)

## 8.发送输入数据。
## 8.1 申请输入内存。
input_size = 128 * 128 * 3 // 2
input_addr, ret = acl.himpi.dvpp_malloc(0, input_size);

## 如果运行模式为ACL_HOST,则需要申请Host内存,将输入图片数据读入Host内存,再通过acl.rt.memcpy接口将Host的图片数据传输到Device,数据传输完成后,需及时释放Host内存;否则直接将输入图片数据读入Device内存。
## 直接将输入图片数据读入Device内存。
if run_mode == ACL_HOST:
# 将输入图片读入内存中。
jpege_file = np.fromfile(jpege_filee_path, dtype=np.byte)
jpege_file_size = jpege_file.itemsize * jpege_file.size

bytes_data = jpege_file.tobytes()
jpege_file_ptr = acl.util.bytes_to_ptr(bytes_data)
# 数据传输。
ret = acl.rt.memcpy(input_addr, input_size, jpege_file_ptr, jpege_file_size, ACL_MEMCPY_HOST_TO_DEVICE)
else:
# 将输入图片读入内存中。
jpege_file = np.fromfile(jpege_file_path, dtype=np.byte)
jpege_file = jpege_file.itemsize * jpege_file.size

bytes_data = jpege_file.tobytes()
jpege_file_ptr = acl.util.bytes_to_ptr(bytes_data)
# 数据传输。
ret = acl.rt.memcpy(input_addr, input_size, jpege_file_ptr, jpege_file_size, ACL_MEMCPY_DEVICE_TO_DEVICE)

## 8.2 发送输入数据,开始编码。
v_frame = {'width': 128,
'height': 128,
'field': HI_VIDEO_FIELD_FRAME,
'pixel_format': HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420,
'video_format': HI_VIDEO_FORMAT_LINEAR,
'compress_mode': HI_COMPRESS_MODE_NONE,
'dynamic_range': HI_DYNAMIC_RANGE_SDR8,
'color_gamut': HI_COLOR_GAMUT_BT709,
'header_stride': [0, 0, 0],
'width_stride': [128, 0, 0],
'height_stride': [0, 0, 0],
'header_phys_addr': [0, 0, 0],
'phys_addr': [0, 0, 0],
'header_virt_addr': [0, 0, 0],
'virt_addr': [input_addr, 0, 0],
'time_ref': 0,'pts': cur_time}
frame = {'v_frame':v_frame, 'pool_id':0, 'mod_id':HI_ID_VGS}
ret = acl.himpi.venc_send_frame(channel_id, frame, 0)

## 9.获取编码结果。
## 9.1 通过EPOLL处理编码完成事件。
fd = acl.himpi.venc_get_fd(channel_id)
epoll_fd, ret = acl.himpi.sys_create_epoll(10)

event['data'] = fd
event['events'] = HI_DVPP_EPOLL_IN
ret = acl.himpi.sys_ctl_epoll(epoll_fd, HI_DVPP_EPOLL_CTL_ADD, fd, event)

## 编码完成前,会超时阻塞在这里,一旦完成,才会往下执行。
events, eventCount, ret = acl.himpi.sys_wait_epoll(epoll_fd, 3, 1000);

## 9.2 获取编码结果。
status, ret = acl.himpi.venc_query_status(channel_id)
stream = {'pack_cnt': status['cur_packs']}
stream, ret = acl.himpi.venc_get_stream(self.channel_id, stream, 1000)
## 9.3 如果运行模式为ACL_HOST,且Host上需要使用编码输出的码流,则需要申请Host内存,通过acl.rt.memcpy接口将Device的输出码流传输到Host;否则直接使用编码输出码流数据。
## 9.3 获取编码输出码流数据。
if run_mode == ACL_HOST:
# 申请Host内存。
output_buffer, ret= acl.rt.malloc_host(output_ize)
# 数据传输。
ret = acl.rt.memcpy(output_buffer, output_ize, stream['pack'][0]['addr'], output_ize, ACL_MEMCPY_DEVICE_TO_HOST)
# ......
# 数据使用完成后,及时释放不使用的内存。
ret = acl.rt.free_host(output_buffer)
else:
# 可以直接使用编码输出码流数据,在stream['pack'][0]['addr']指向的内存中。
# ......

## 10.释放输入内存和输出码流。
ret = acl.himpi.dvpp_free(input_addr)
ret = acl.himpi.venc_release_stream(channel_id, stream)

## 11.通知编码器停止接收输入数据。
ret = acl.himpi.venc_stop_chn(channel_id)
ret = acl.himpi.sys_ctl_epoll(epoll_fd, HI_DVPP_EPOLL_CTL_DEL, fd, event)
ret = acl.himpi.sys_close_epoll(epoll_fd)

## 12.销毁通道。
ret = acl.himpi.venc_destroy_chn(channel_id)

## 13. 媒体数据处理系统去初始化。
ret = acl.himpi.sys_exit()

## 14. 释放运行管理资源(依次释放Context、Device)。
ret = acl.rt.destroy_context(context)
ret = acl.rt.reset_device(0)

## 15.pyACL去初始化。
ret = acl.finalize()

父主题: 媒体数据处理V2

PNGD图片解码

PNGD(PNG decoder)负责PNG格式图片的解码。关于PNGD功能的详细介绍请参见PNGD图像处理接口

本节介绍PNGD图片编码的接口调用流程,同时配合示例代码辅助理解该接口调用流程。

接口调用流程

开发应用时,如果涉及对PNG图片的解码,则应用程序中必须包含解码的代码逻辑,关于图片解码的接口调用流程,请先参见pyACL接口调用流程了解整体流程,再查看本节中的流程说明。

图1 接口调用流程

当前系统支持解码PNG图片,关键接口的说明如下:

  1. 调用acl.himpi.sys_init接口进行媒体数据处理系统初始化。
  2. 调用acl.himpi.pngd_create_chn接口创建通道。
  3. 调用acl.himpi.dvpp_malloc接口申请Device上的内存,存放输入或输出数据。
  4. 调用acl.himpi.pngd_send_stream接口发送解码码流,acl.himpi.pngd_send_stream接口是异步接口,调用该接口仅表示任务下发成功,还需要调acl.himpi.pngd_get_image_data接口获取解码结果数据。
  5. 调用acl.himpi.dvpp_free接口释放输入、输出内存。
  6. 调用acl.himpi.pngd_destroy_chn接口销毁通道。
  7. 调用acl.himpi.sys_exit接口进行媒体数据处理系统去初始化。

示例代码

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝运行,仅供参考。

## 1.获取软件栈的运行模式,不同运行模式影响后续的接口调用流程(例如是否进行数据传输等)。
run_mode, ret = acl.rt.get_run_mode()

## 2.pyACL 初始化。
ret = acl.init()

## 3.运行管理资源申请(依次申请Device、Context)。
ret = acl.rt.set_device(0)
context, ret = acl.rt.create_context(0)

## 4.初始化媒体数据处理系统。
ret = acl.himpi.sys_init()

## 5.创建通道。
attr = {"stream_que_cnt": STREAM_QUE_CNT}
channel_id = 0
ret = acl.himpi.pngd_create_chn(channel_id, attr)

## 6.发送码流。
## 6.1 申请输入内存。
input_addr, ret = acl.himpi.dvpp_malloc(0, input_size);

## 如果运行模式为ACL_HOST,则需要申请Host内存,将输入图片数据读入Host内存,再通过acl.rt.memcpy接口将Host的图片数据传输到Device,数据传输完成后,需及时释放Host内存;否则直接将输入图片数据读入Device内存。
## 直接将输入图片数据读入Device内存。
if run_mode == ACL_HOST:
# 将输入图片读入内存中。
jpege_file = np.fromfile(jpege_filee_path, dtype=np.byte)
jpege_file_size = jpege_file.itemsize * jpege_file.size

bytes_data = jpege_file.tobytes()
jpege_file_ptr = acl.util.bytes_to_ptr(bytes_data)
# 数据传输。
ret = acl.rt.memcpy(input_addr, input_size, jpege_file_ptr, jpege_file_size, ACL_MEMCPY_HOST_TO_DEVICE)
else:
# 将输入图片读入内存中。
jpege_file = np.fromfile(jpege_file_path, dtype=np.byte)
jpege_file = jpege_file.itemsize * jpege_file.size

bytes_data = jpege_file.tobytes()
jpege_file_ptr = acl.util.bytes_to_ptr(bytes_data)
# 数据传输。
ret = acl.rt.memcpy(input_addr, input_size, jpege_file_ptr, jpege_file_size, ACL_MEMCPY_DEVICE_TO_DEVICE)

## 6.2 构造存放输入图片信息的字典。
stream = {"type": HI_PT_PNG, "addr": input_addr, "len": input_size, "pts": 0}
img_info, ret = acl.himpi.png_get_image_info(stream)

## 6.3 构造存放输出图片信息的字典,并申请输出内存。
output_addr, ret = acl.himpi.dvpp_malloc(0, img_info['img_buf_size']);
out_pic_info = {"picture_address": output_addr,
"picture_buffer_size": img_info['img_buf_size'],
"picture_width": img_info['width'],
"picture_height": img_info['height'],
"picture_width_stride": img_info['width_stride'],
"picture_height_stride": img_info['height_stride'],
"picture_format": HI_PIXEL_FORMAT_UNKNOWN}
ret = acl.himpi.pngd_send_stream(channel_id, stream, out_pic_info, 0)

## 7.接收解码结果。
## 7.1 获取解码结果。
pic_info, get_stream, ret = acl.himpi.pngd_get_image_data(channel_id, -1)
if ret == 0: # Decode success
print("Chn %u GetFrame Success, Decode Success \n"%channel_id)
elif ret == HI_ERR_PNGD_BUF_EMPTY: # Decoding
print("Chn %u Decoding, try again \n"%channel_id)
else: # Decode Fail
print("Chn %u GetFrame Success, Decode Fail \n"%channel_id)

## 7.2 如果运行模式为ACL_HOST,且Host上需要展示PNGD输出的图片数据,则需要申请Host内存,通过acl.rt.memcpy接口将Device的输出图片数据传输到Host。
## 7.2 获取PNGD的输出图片数据。
if run_mode == ACL_HOST:
# 申请Host内存。
output_buffer, ret= acl.rt.malloc_host(output_ize)
# 数据传输。
ret = acl.rt.memcpy(output_buffer, output_ize, stream['pack'][0]['addr'], output_ize, ACL_MEMCPY_DEVICE_TO_HOST)
# ......
# 数据使用完成后,及时释放不使用的内存。
ret = acl.rt.free_host(output_buffer)
else:
# 可以直接使用PNGD的输出图片数据,在outputPic.picture_address指向的内存中。
# ......

## 7.3 释放输入内存和输出码流。
ret = acl.himpi.dvpp_free(input_addr)
ret = acl.himpi.dvpp_free(output_addr)

## 8.销毁通道。
ret = acl.himpi.pngd_destroy_chn(channel_id)

## 9. 媒体数据处理系统去初始化。
ret = acl.himpi.sys_exit()

## 10. 释放运行管理资源(依次释放Context、Device)。
ret = acl.rt.destroy_context(context)
ret = acl.rt.reset_device(0)

## 11.pyACL去初始化。
ret = acl.finalize()

父主题: 媒体数据处理V2

VDEC视频解码

VDEC(Video Decoder)负责将H264/H265格式的视频码流解码为YUV/RGB格式的图片。关于VDEC功能的详细介绍及约束请参见VDEC功能及约束说明

本节介绍VDEC视频编码的接口调用流程,同时配合示例代码辅助理解该接口调用流程。

接口调用流程

开发应用时,如果涉及视频解码,则应用程序中必须包含解码的代码逻辑,关于视频解码的接口调用流程,请先参见pyACL接口调用流程了解整体流程,再查看本节中的流程说明。

图1 接口调用的流程

当前系统支持解码H264/H265的视频码流,关键接口的说明如下:

  1. 调用acl.himpi.sys_init接口进行媒体数据处理系统初始化。

  2. 调用acl.himpi.vdec_get_pic_buf_size接口获取解码所需的帧存大小、调用acl.himpi.vdec_get_tmv_buf_size接口获取矢量预测缓冲区的大小,在创建通道时需要使用这些数据。

  3. 调用acl.himpi.vdec_create_chn接口创建通道。

  4. 调用acl.himpi.dvpp_malloc接口申请Device上的内存,存放输入或输出数据。

  5. 解码前,需调用acl.himpi.vdec_start_recv_stream接口通知解码器启动接收码流,再调用acl.himpi.vdec_send_stream接口发送解码码流,acl.himpi.vdec_send_stream接口是异步接口,调用该接口仅表示任务下发成功,还需要调acl.himpi.vdec_get_frame接口获取解码结果数据,成功获取解码数据后,可以调用acl.himpi.vdec_release_frame接口释放帧相关的资源。

    解码结束后,需调用acl.himpi.vdec_stop_recv_stream接口通知解码器停止接收码流。

  6. 调用acl.himpi.dvpp_free接口释放输入、输出内存。

  7. 调用acl.himpi.vdec_destroy_chn接口销毁通道。

  8. 调用acl.himpi.sys_exit接口进行媒体数据处理系统去初始化。

示例代码

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝运行,仅供参考。

## 1.获取软件栈的运行模式,不同运行模式影响后续的接口调用流程(例如是否进行数据传输等)。
run_mode, ret = acl.rt.get_run_mode()

## 2.pyACL 初始化。
ret = acl.init()

## 3.运行管理资源申请(依次申请Device、Context)。
ret = acl.rt.set_device(0)
context, ret = acl.rt.create_context(0)

## 4.初始化媒体数据处理系统。
ret = acl.himpi.sys_init()

## 5.创建通道。
channel_id = 0
buf_attr = [1920, 1080, 0, 8, HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420, HI_COMPRESS_MODE_NONE]
tmv_buf_size = acl.himpi.vdec_get_tmv_buf_size(HI_PT_H264, 1920, 1080)
attr = {'type': HI_PT_H264, 'mode': HI_VDEC_SEND_MODE_FRAME,
'pic_width': 1920, 'pic_height': 1080,
'stream_buf_size': 1920 * 1080 * 3 // 2, 'frame_buf_size': 0,
'frame_buf_cnt': 16, 'video_attr': {'ref_frame_num': 12, 'temporal_mvp_en': HI_TRUE,
'tmv_buf_size': tmv_buf_size}}
ret = acl.himpi.vdec_create_chn(channel_id, attr)

## 6.设置通道属性。
video_param_dict, ret = acl.himpi.vdec_get_chn_param(channel_id)
video_param_dict["video_param"]["dec_mode "] = HI_VIDEO_DEC_MODE_IPB
video_param_dict["video_param"]["compress_mode "] = HI_COMPRESS_MODE_HFBC
video_param_dict["video_param"]["video_format "] = HI_VIDEO_FORMAT_TILE_64x16
video_param_dict["video_param"]["out_order"] = HI_VIDEO_OUT_ORDER_DISPLAY
video_param_dict["display_frame_num"] = 3
ret = acl.himpi.vdec_set_chn_param(i, video_param_dict)

## 7.解码器启动接收码流。
ret = acl.himpi.vdec_start_recv_stream(channel_id)

## 8.发送码流。
## 8.1 申请输入内存。
## input_size表示输入图片占用的内存大小,此处以1024 Byte为例,用户需根据实际情况计算内存大小。
input_size = 1024;
input_addr, ret = acl.himpi.dvpp_malloc(0, input_size);

#如果运行模式为ACL_HOST,则需要申请Host内存,将输入图片数据读入Host内存,再通过acl.rt.memcpy接口将Host的图片数据传输到Device,数据传输完成后,需及时释放Host内存;否则直接将输入图片数据读入Device内存。
#直接将输入图片数据读入Device内存。
if run_mode == ACL_HOST:
# 申请Host内存。
input_buffer, ret= acl.rt.malloc_host(input_size)
# 将输入图片读入内存中。
vdec_file = np.fromfile(vdec_file_path, dtype=np.byte)
vdec_file_size = vdec_file.itemsize * vdec_file.size

bytes_data = vdec_file.tobytes()
vdec_file_ptr = acl.util.bytes_to_ptr(bytes_data)
# 数据传输。
ret = acl.rt.memcpy(input_addr, input_size, vdec_file_ptr, vdec_file_size, ACL_MEMCPY_HOST_TO_DEVICE)
else:
# 将输入图片读入内存中。
vdec_file = np.fromfile(vdec_file_path, dtype=np.byte)
vdec_file_size = vdec_file.itemsize * vdec_file.size

bytes_data = vdec_file.tobytes()
vdec_file_ptr = acl.util.bytes_to_ptr(bytes_data)
# 数据传输。
ret = acl.rt.memcpy(input_addr, input_size, vdec_file_ptr, vdec_file_size, ACL_MEMCPY_DEVICE_TO_DEVICE)

## 8.2 申请输出内存。
output_size = 1920 * 1080 * 3 // 2
output_addr, ret = acl.himpi.dvpp_malloc(0, output_size);

## 8.3 构造存放一帧输入码流信息的字典。
stream = {'end_of_frame': HI_TRUE, 'end_of_stream': HI_FALSE,
'need_display': HI_TRUE, 'pts': 0,
'len': input_size, 'addr': input_addr}
## 8.4 构造存放一帧输出结果信息的字典。
out_pic_info = {"width": 1920,
"height": 1080,
"width_stride": 1920,
"height_stride": 1080,
"pixel_format": HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420,
'vir_addr': output_addr,
"buffer_size": output_size}

## 8.4 发送一帧码流。
ret = acl.himpi.vdec_send_stream(channel_id, stream, out_pic_info, 0)

## 9.接收解码结果。
## 9.1 获取解码结果。
frame_info, supplement, stream, ret = acl.himpi.vdec_get_frame(channel_id, 0)
if ret == 0:
dec_result = frame_info['v_frame']['frame_flag']
if dec_result == 0: # 0: Decode success
print("Chn %u GetFrame Success, Decode Success \n"%channel_id)
elif dec_result == 1: # 1:Decode fail
print("Chn %u GetFrame Success, Decode Fail \n"%channel_id)
elif dec_result == 2: # 2:This result is returned for the second field of
print("Chn %u GetFrame Success, No Picture \n"%channel_id)
elif dec_result == 3: # 3: Reference frame number set error
print("Chn %u GetFrame Success, RefFrame Num Error \n"%channel_id)
elif dec_result == 4: # 4: Reference frame size set error
print("Chn %u GetFrame Success, RefFrame Size Error \n"% channel_id)
## 9.2 如果运行模式为ACL_HOST,且Host上需要展示VDEC输出的图片数据,则需要申请Host内存,通过acl.rt.memcpy接口将Device的输出图片数据传输到Host。
## 9.2 获取解码结果数据。
if run_mode == ACL_HOST:
# 申请Host内存。
output_buffer, ret= acl.rt.malloc_host(out_pic_info['buffer_size'])
# 数据传输。
ret = acl.rt.memcpy(output_buffer, out_pic_info['buffer_size'], frame_info['v_frame']['virt_addr'][0], out_pic_info['buffer_size'], ACL_MEMCPY_DEVICE_TO_HOST)
# ......
# 数据使用完成后,及时释放不使用的内存。
ret = acl.rt.free_host(output_buffer)
else:
# 可以直接使用JPEGD的输出图片数据,在outputPic.picture_address指向的内存中。
# ......

## 9.3 释放输入、输出内存。
ret = acl.himpi.dvpp_free(frame_info['v_frame']['virt_addr'][0])
ret = acl.himpi.dvpp_free(stream['addr'])

## 9.4 释放资源。
ret = acl.himpi.vdec_release_frame(channel_id, frame_info)

## 10.解码器停止接收码流。
ret = acl.himpi.vdec_stop_recv_stream(channel_id)

## 7.销毁通道。
ret = acl.himpi.vdec_destroy_chn(channel_id)

## 8. 媒体数据处理系统去初始化。
ret = acl.himpi.sys_exit()

## 9. 释放运行管理资源(依次释放Context、Device)。
ret = acl.rt.destroy_context(context)
ret = acl.rt.reset_device(0)

## 10.pyACL去初始化。
ret = acl.finalize()

父主题: 媒体数据处理V2

VENC视频编码

VENC(Video Encoder)将YUV420SP格式的图片编码成H264/H265格式的视频码流。关于VENC功能的详细介绍请参见VENC功能及约束说明

本节介绍VENC视频编码的接口调用流程,同时配合示例代码辅助理解该接口调用流程。

接口调用流程

开发应用时,如果涉及视频编码,则应用程序中必须包含编码的代码逻辑,关于编码的接口调用流程,请先参见pyACL接口调用流程了解整体流程,再查看本节中的流程说明。

图1 接口调用流程

当前系统支持H264/H256格式的视频码流,关键接口的说明如下:

  1. 调用acl.himpi.sys_init接口进行媒体数据处理系统初始化。

  2. 调用acl.himpi.venc_create_chn函数创建完通道。

    成功创建通道之后,您可以根据实际需求设置编码的高级参数,例如场景模式、码流控制器的高级参数等,请参见acl.himpi.venc_set_jpeg_param~acl.himpi.venc_compact_jpeg_tables章节中的接口说明。

  3. 调用acl.himpi.venc_get_fd将通道ID转换为一个文件句柄。

  4. 调用acl.himpi.sys_create_epoll函数创建DVPP epoll实例。

  5. 调用acl.himpi.sys_ctl_epoll函数将编码通道的文件句柄添加到epoll实例中,由epoll实例处理。

    select或者poll方式,不需要执行该步骤。

  6. 调用acl.himpi.venc_start_chn函数通知通道准备开始编码。

  7. 调用acl.himpi.dvpp_malloc接口申请存放Device上输入数据的内存。

  8. 启动一个用户态线程,调用acl.himpi.sys_wait_epoll函数等待编码完成。

  9. 之后用户就可以调用acl.himpi.venc_send_frame函数发送待编码的码流。

  10. 一旦编码完成,acl.himpi.sys_wait_epoll函数或select函数或poll函数就会返回,用户就可以调用acl.himpi.venc_query_status接口查询编码状态,再调用acl.himpi.venc_get_stream函数获取编码结果。

  11. 用户需要注意的是,编码结果数据使用完成之后,需要及时调用acl.himpi.venc_release_stream函数释放buffer。否则会因编码buffer用完导致后续编码无法进行。

  12. 调用acl.himpi.dvpp_free接口释放输入内存。

  13. 当用户不需发送图像到目的通道继续编码时,需要调用acl.himpi.venc_stop_chn函数通知该通道不再接收新的输入图片。

  14. 调用acl.himpi.sys_ctl_epoll函数从epoll实例中删除编码通道的文件句柄。

  15. 当用户完成所有编码之后,需要调用acl.himpi.venc_destroy_chn释放编码通道以及内部内存资源。

  16. 调用acl.himpi.sys_close_epoll函数销毁DVPP epoll实例。

  17. 调用acl.himpi.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)、码率有关,在调用acl.himpi.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*216025或3050或60- 视频数据获取场景 H264/H265码流,码率取值812。
    - 秀场/主播/短视频场景 H265码流,码率取值6
    12。 H264码流,不涉及。
    - 游戏视频场景 H264/H265码流,码率取值10~16。
    2K 2560*144025或3050或60- 视频数据获取场景 H264/H265码流,码率取值610。
    - 秀场/主播/短视频场景 H265码流,码率取值4.8
    8。 H264码流,不涉及。
    - 游戏视频场景 H264/H265码流,码率取值6~10。
    1080P(蓝光) 1920*108025或3050或60- 视频数据获取场景 H265码流,码率取值14。 H264码流,码率取值26。
    - 秀场/主播/短视频场景 H265码流,码率取值1.43.6。 H264码流,码率取值24.8。
    - 游戏视频场景 H264/H265码流,码率取值3~6。
    720P(高清) 1280*72025或3050或60- 视频数据获取场景 H265码流,码率取值0.82。 H264码流,码率取值13。
    - 秀场/主播/短视频场景 H265码流,码率取值12。 H264码流,码率取值13。
    - 游戏视频场景 H264/H265码流,码率取值2~4。
    480P/D1_N(标清) 854*480/720*48025或3050或60- 视频数据获取场景 H265码流,码率取值0.30.7。 H264码流,码率取值0.61.4。
    - 秀场/主播/短视频场景 H265码流,码率取值0.250.6。 H264码流,码率取值0.30.7。
    - 游戏视频场景 不涉及。
    576P/D1(标清) 720*57625或3050或60- 视频数据获取场景 H265码流,码率取值0.30.7。 H264码流,码率取值0.61.4。
    - 秀场/主播/短视频场景 H265码流,码率取值0.250.6。 H264码流,码率取值0.30.7。
    - 游戏视频场景 不涉及。
    270P(流畅) 480*27025或3050或60- 视频数据获取场景 不涉及。
    - 秀场/主播/短视频场景 H265码流,码率取值0.2。 H264码流,码率取值0.3。
    - 游戏视频场景 不涉及。
    CIF P/N 352*288/320*24025或3050或60- 视频数据获取场景 H264/H265码流,码率取值0.25。
    - 秀场/主播/短视频场景 不涉及。
    - 游戏视频场景 不涉及。
  • 设置高级参数,调整视频编码细节

    您可以调用接口设置码控模式、宏块级码率控制参数、编码场景模式等,来调整视频编码的细节,进一步改善编码质量。

    表2 高级配置项列表

    配置项接口参数名说明
    码控模式acl.himpi.venc_create_chnhi_venc_chn_attr["rc_attr"]字典内的“rc_mode”属性追求码率平稳或追求PSNR大且码率符合目标值,配置为CBR。 追求节省码率,对主观编码质量有一定要求,配置为VBR。 追求节省码率,对主观编码质量有一定要求,且场景中有较多静止画面,配置为AVBR。 追求PSNR且对码率上浮没有严格要求,配置为QVBR。 追求节省码率,对主观编码质量有一定要求,且可以根据带宽、存储空间要求进行更多调整,配置为CVBR。
    码率控制模型统计时间acl.himpi.venc_create_chnhi_venc_chn_attr["rc_attr"]字典内各模式属性值字典内的“stats_time”属性关注长期码率稳定,短期波动不在意的可以设置大一些,例:DVR存盘。设大可以提高重编码判决的门槛,重编码次数会减少,但是码率波动会加大。
    宏块级码率控制参数acl.himpi.venc_set_rc_paramhi_venc_rc_param字典内的“threshold_i”、“threshold_p”、“threshold_b”、“direction”、“row_qp_delta”属性。如果图像内容复杂、细节较多或用户关注PSNR等客观指标时,需关闭宏块级码率控制。
    第一帧的起始Qp值acl.himpi.venc_create_chnhi_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”,会获得较好的编码质量。
    编码场景模式acl.himpi.venc_set_scene_modehi_venc_scene_mode安防场景配置为HI_VENC_SCENE_0;自动驾驶、直播、游戏、动画、电影配置为HI_VENC_SCENE_1。

示例代码

调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝运行,仅供参考。

## 1.获取软件栈的运行模式,不同运行模式影响后续的接口调用流程(例如是否进行数据传输等)。
run_mode, ret = acl.rt.get_run_mode()

## 2.pyACL 初始化。
ret = acl.init()

## 3.运行管理资源申请(依次申请Device、Context)。
ret = acl.rt.set_device(0)
context, ret = acl.rt.create_context(0)

## 4.初始化媒体数据处理系统。
ret = acl.himpi.sys_init()

## 5.设置VENC模块参数。
param = {'mod_type':HI_VENC_MOD_H265}
param, ret = acl.himpi.venc_get_mod_param(param)
param['jpeg_mod_param']['one_stream_buf'] = 1
ret = acl.himpi.venc_set_mod_param(param)

## 6.创建通道。
channel_id = 0
venc_attr = {'type': HI_VENC_MOD_H265, 'profile': 0,
'max_pic_width': 128, 'pic_width': 128,
'max_pic_height': 128, 'pic_height': 128,
'buf_size': 1024 * 1024 * 2, 'is_by_frame': 1}
rc_attr = {'rc_mode':HI_VENC_RC_MODE_H265_VBR,
'h265_vbr':{'gop': 30, 'stats_time': 1,
'src_frame_rate': 30, 'dst_frame_rate': 30,
'max_bit_rate': 4000}}
gop_attr = {'gop_mode':0, 'normal_p':{'ip_qp_delta':3}}
attr = {'venc_attr':venc_attr, 'rc_attr':rc_attr, 'gop_attr':gop_attr}
ret = acl.himpi.venc_create_chn(channel_id, attr)

## 7.通知编码器开始接收输入数据。
recv_param = {'recv_pic_num':-1}
ret = acl.himpi.venc_start_chn(channel_id, recv_param)

## 8.发送输入数据。
## 8.1 申请输入内存。
input_size = 128 * 128 * 3 // 2
input_addr, ret = acl.himpi.dvpp_malloc(0, input_size);

## 如果运行模式为ACL_HOST,则需要申请Host内存,将输入数据读入Host内存,再通过acl.rt.memcpy接口将Host的数据传输到Device,数据传输完成后,需及时释放Host内存;否则直接将输入数据读入Device内存。
## 直接将输入数据读入Device内存。
if run_mode == ACL_HOST:
# 将输入图片读入内存中。
jpege_file = np.fromfile(jpege_filee_path, dtype=np.byte)
jpege_file_size = jpege_file.itemsize * jpege_file.size

bytes_data = jpege_file.tobytes()
jpege_file_ptr = acl.util.bytes_to_ptr(bytes_data)
# 数据传输。
ret = acl.rt.memcpy(input_addr, input_size, jpege_file_ptr, jpege_file_size, ACL_MEMCPY_HOST_TO_DEVICE)
else:
# 将输入图片读入内存中。
jpege_file = np.fromfile(jpege_file_path, dtype=np.byte)
jpege_file = jpege_file.itemsize * jpege_file.size

bytes_data = jpege_file.tobytes()
jpege_file_ptr = acl.util.bytes_to_ptr(bytes_data)
# 数据传输。
ret = acl.rt.memcpy(input_addr, input_size, jpege_file_ptr, jpege_file_size, ACL_MEMCPY_DEVICE_TO_DEVICE)

## 8.2 发送输入数据,开始编码。
v_frame = {'width': 128,
'height': 128,
'field': HI_VIDEO_FIELD_FRAME,
'pixel_format': HI_PIXEL_FORMAT_YUV_SEMIPLANAR_420,
'video_format': HI_VIDEO_FORMAT_LINEAR,
'compress_mode': HI_COMPRESS_MODE_NONE,
'dynamic_range': HI_DYNAMIC_RANGE_SDR8,
'color_gamut': HI_COLOR_GAMUT_BT709,
'header_stride': [0, 0, 0],
'width_stride': [128, 0, 0],
'height_stride': [0, 0, 0],
'header_phys_addr': [0, 0, 0],
'phys_addr': [0, 0, 0],
'header_virt_addr': [0, 0, 0],
'virt_addr': [input_addr, 0, 0],
'time_ref': 0,'pts': cur_time}
frame = {'v_frame':v_frame, 'pool_id':0, 'mod_id':HI_ID_VENC}
ret = acl.himpi.venc_send_frame(channel_id, frame, 0)

## 9.获取编码结果。
## 9.1 通过EPOLL处理编码完成事件。
fd = acl.himpi.venc_get_fd(channel_id)
epoll_fd, ret = acl.himpi.sys_create_epoll(10)

event['data'] = fd
event['events'] = HI_DVPP_EPOLL_IN
ret = acl.himpi.sys_ctl_epoll(epoll_fd, HI_DVPP_EPOLL_CTL_ADD, fd, event)

## 编码完成前,会超时阻塞在这里,一旦完成,才会往下执行。
events, eventCount, ret = acl.himpi.sys_wait_epoll(epoll_fd, 3, 1000);

## 9.2 获取编码结果。
status, ret = acl.himpi.venc_query_status(channel_id)
stream = {'pack_cnt': status['cur_packs']}
stream, ret = acl.himpi.venc_get_stream(self.channel_id, stream, 1000)
## 9.3 如果运行模式为ACL_HOST,且Host上需要使用编码输出的码流,则需要申请Host内存,通过acl.rt.memcpy接口将Device的输出码流传输到Host。
## 9.3 获取编码输出码流数据。
if run_mode == ACL_HOST:
# 申请Host内存。
output_buffer, ret= acl.rt.malloc_host(output_ize)
# 数据传输。
ret = acl.rt.memcpy(output_buffer, output_ize, stream['pack'][0]['addr'], output_ize, ACL_MEMCPY_DEVICE_TO_HOST)
# ......
# 数据使用完成后,及时释放不使用的内存。
ret = acl.rt.free_host(output_buffer)
else:
# 可以直接使用编码输出码流数据,在stream['pack'][0]['addr']指向的内存中。
# ......

## 10.释放输入内存和输出码流。
ret = acl.himpi.dvpp_free(input_addr)
ret = acl.himpi.venc_release_stream(channel_id, stream)

## 11.通知编码器停止接收输入数据。
ret = acl.himpi.venc_stop_chn(channel_id)
ret = acl.himpi.sys_ctl_epoll(epoll_fd, HI_DVPP_EPOLL_CTL_DEL, fd, event)
ret = acl.himpi.sys_close_epoll(epoll_fd)

## 12.销毁通道。
ret = acl.himpi.venc_destroy_chn(channel_id)

## 13. 媒体数据处理系统去初始化。
ret = acl.himpi.sys_exit()

## 14. 释放运行管理资源(依次释放Context、Device)。
ret = acl.rt.destroy_context(context)
ret = acl.rt.reset_device(0)

## 15.pyACL去初始化。
ret = acl.finalize()

父主题: 媒体数据处理V2

在线提单