# JPEGE图片编码

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

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

#### 接口调用流程

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

图1 接口调用流程

当前系统支持将YUV格式图片编码成JPEG压缩格式的图片文件，关键接口的说明如下：1. 资源初始化：

  a. 调用    hi_mpi_sys_init
接口进行媒体数据处理系统初始化；
  b. 调用    hi_mpi_venc_create_chn
函数创建完通道;
    成功创建通道之后，您可以根据实际需求设置编码的高级参数，例如场景模式、码流控制器的高级参数等，请参见hi_mpi_venc_set_jpeg_param~hi_mpi_venc_compact_jpeg_tables章节中的接口说明。

  c. 调用    hi_mpi_venc_get_fd
将通道ID转换为一个文件句柄;
  d. 调用    hi_mpi_sys_create_epoll
函数创建DVPP epoll实例；
  e. 调用    hi_mpi_sys_ctl_epoll
函数将编码通道的文件句柄添加到epoll实例中，由epoll实例处理；
    select或者poll方式，不需要执行该步骤。

2. 图片编码：

  a. 调用    hi_mpi_venc_start_chn
函数通知通道准备开始编码；
  b. 调用    hi_mpi_dvpp_malloc
接口申请存放Device上输入数据的内存。
    Atlas 200/500 A2推理产品上，还支持使用aclrtMalloc接口申请内存。

    调用hi_mpi_dvpp_malloc接口申请的内存为媒体数据处理的专用内存，但专用内存的地址空间有限，若关注内存规划或内存资源有限时，建议调用aclrtMalloc接口申请内存。

  c. 启动一个用户态线程，调用    hi_mpi_sys_wait_epoll
函数等待编码完成；
  d. 之后用户就可以调用    hi_mpi_venc_send_frame
函数发送待编码的码流；
  e. 一旦编码完成，    hi_mpi_sys_wait_epoll
函数或select函数或poll函数就会返回，用户就可以调用    hi_mpi_venc_query_status
接口查询编码状态，再调用    hi_mpi_venc_get_stream
函数获取编码结果;
  f. 用户需要注意的是，编码结果数据使用完成之后，需要及时调用    hi_mpi_venc_release_stream
函数释放buffer。否则会因编码buffer用完导致后续编码无法进行；
  g. 调用    hi_mpi_dvpp_free
接口释放输入内存；
    Atlas 200/500 A2推理产品上，若使用aclrtMalloc接口申请内存，则需使用aclrtFree接口释放内存。

  h. 当用户不需发送图像到目的通道继续编码时，需要调用    hi_mpi_venc_stop_chn
函数通知该通道不再接收新的输入图片；
3. 资源释放：

  a. 调用    hi_mpi_sys_ctl_epoll
函数从epoll实例中删除编码通道的文件句柄；
  b. 当用户完成所有编码之后，需要调用    hi_mpi_venc_destroy_chn
释放编码通道以及内部内存资源；
  c. 调用    hi_mpi_sys_close_epoll
函数销毁DVPP epoll实例；
  d. 调用    hi_mpi_sys_exit
接口进行媒体数据处理系统去初始化。


支持DVPP内部管理输出内存，或用户自行管理输出内存两种方式：- 不需要用户管理、由DVPP内部管理输出内存时，调用  hi_mpi_venc_send_frame
接口发送原始图像进行图像编码。
  在调用hi_mpi_venc_create_chn接口创建通道时，必须正确设置hi_venc_chn_attr.venc_attr.buf_size参数值（参数描述请参见hi_venc_attr）。

  该方式下，相比由用户管理内存，输出结果数据的JPEG头中不存在COM注释字段，数据长度会短一点，但需要用户从DVPP返回的内存中拷贝输出结果数据到指定内存。

- 由用户自行管理输出内存、管理内存的生命周期，调用  hi_mpi_venc_send_jpege_frame
接口发送原始图像进行图像编码。
  在调用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);

if (runMode == ACL_HOST) {
    void* hostInputAddr = nullptr;
    
    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);

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();

// ....
```
