VDEC(Video Decoder)负责将H264/H265格式的视频码流解码为YUV/RGB格式的图片。关于VDEC功能的详细介绍及约束请参见功能及约束说明。
本节介绍VDEC视频编码的接口调用流程,同时配合示例代码辅助理解该接口调用流程。
开发应用时,如果涉及视频解码,则应用程序中必须包含视频解码的代码逻辑,关于视频解码的接口调用流程,请先参见AscendCL接口调用流程了解整体流程,再查看本节中的流程说明。
实现视频的解码,关键接口的说明如下:
在回调函数内,用户需调用acldvppGetPicDescRetCode接口获取retCode返回码判断是否解码成功,retCode为0表示解码成功,为1表示解码失败。如果解码失败,需要根据日志中的返回码判断具体的问题,返回码请参见返回码说明。
解码结束后,建议用户在回调函数内及时释放VDEC的输入码流内存、输出图片内存以及相应的视频码流描述信息、图片描述信息。
如果不调用aclvdecSetChannelDescOutPicFormat接口设置输出格式,则默认使用YUV420SP NV12。
aclvdecSendFrame接口内部封装了aclrtLaunchCallback接口,用于在Stream的任务队列中增加一个需要执行的回调函数。用户无需单独调用aclrtLaunchCallback接口。
获取解码数据前,先获取retCode的值,判断解码是否成功,0表示解码成功,1表示解码失败。如果解码失败,需要根据日志中的返回码判断具体的问题,返回码请参见返回码说明。
如果用户需要获取解码的帧序号,则可以在aclvdecSendFrame接口的userData参数处定义,然后解码的帧序号可以通过userData参数传递给VDEC的回调函数,用于确定回调函数中处理的是第几帧数据。
如果不想获取某一帧的解码结果, 可以调用aclvdecSendSkippedFrame接口, 将待解码的码流(输入内存)传到解码器进行解码,此时,解码结果最终不会输出,解码完成的回调函数中返回的output为nullptr。
您可以从样例介绍中获取完整样例代码。
调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
// 1.AscendCL初始化 aclRet = aclInit(nullptr); // 2.运行管理资源申请(依次申请Device、Context、Stream) aclrtContext context_; aclrtStream stream_; aclrtSetDevice(0); aclrtCreateContext(&context_, 0); aclrtCreateStream(&stream_); // 3.创建回调函数 void callback(acldvppStreamDesc *input, acldvppPicDesc *output, void *userdata) { static int count = 1; if (output != nullptr) { // 获取VDEC解码的输出内存,调用自定义函数WriteToFile将输出内存中的数据写入文件后,再调用acldvppFree接口释放输出内存 void *vdecOutBufferDev = acldvppGetPicDescData(output); if (vdecOutBufferDev != nullptr) { // 0: vdec success; others, vdec failed // retCode为0表示解码成功,为1表示解码失败 int retCode = acldvppGetPicDescRetCode(output); if (retCode == 0) { // process task: write file uint32_t size = acldvppGetPicDescSize(output); std::string fileNameSave = "outdir/image" + std::to_string(count); // vdec输出结果在device侧,在WriteToFile方法中进行下述处理 if (!Utils::WriteToFile(fileNameSave.c_str(), vdecOutBufferDev, size)) { ERROR_LOG("write file failed."); } } else { ERROR_LOG("vdec decode frame failed."); } // free output vdecOutBufferDev aclError ret = acldvppFree(vdecOutBufferDev); } // 释放acldvppPicDesc类型的数据,表示解码后输出图片描述数据 aclError ret = acldvppDestroyPicDesc(output); } // free input vdecInBufferDev and destroy stream desc if (input != nullptr) { void *vdecInBufferDev = acldvppGetStreamDescData(input); if (vdecInBufferDev != nullptr) { aclError ret = acldvppFree(vdecInBufferDev); } // 释放acldvppStreamDesc类型的数据,表示解码的输入码流描述数据 aclError ret = acldvppDestroyStreamDesc(input); } INFO_LOG("success to callback %d.", count); count++; } // 4.创建视频码流处理通道时的通道描述信息,设置视频处理通道描述信息的属性,其中线程、callback回调函数需要用户提前创建。 // vdecChannelDesc_是aclvdecChannelDesc类型 vdecChannelDesc_ = aclvdecCreateChannelDesc(); ret = aclvdecSetChannelDescChannelId(vdecChannelDesc_, 10); ret = aclvdecSetChannelDescThreadId(vdecChannelDesc_, threadId_); ret = aclvdecSetChannelDescCallback(vdecChannelDesc_, callback); // 示例中使用的是H265_MAIN_LEVEL视频编码协议 ret = aclvdecSetChannelDescEnType(vdecChannelDesc_, static_cast<acldvppStreamFormat>(enType_)); // 示例中使用的是PIXEL_FORMAT_YVU_SEMIPLANAR_420 ret = aclvdecSetChannelDescOutPicFormat(vdecChannelDesc_, static_cast<acldvppPixelFormat>(format_)); // 5.创建视频码流处理的通道 ret = aclvdecCreateChannel(vdecChannelDesc_); // 6.申请输入码流内存(区分运行状态) // 调用aclrtGetRunMode接口获取软件栈的运行模式,如果调用aclrtGetRunMode接口获取软件栈的运行模式为ACL_HOST,则需要通过aclrtMemcpy接口将输入图片数据传输到Device,数据传输完成后,需及时释放内存;否则直接申请并使用Device的内存 aclrtRunMode runMode; ret = aclrtGetRunMode(&runMode); if(runMode == ACL_HOST){ // 申请Host内存inputHostBuff void* inputHostBuff= nullptr; // inBufferSize_为输入码流大小 inputHostBuff= malloc(inBufferSize_); // 将输入图片读入内存中,该自定义函数ReadPicFile由用户实现 ReadPicFile(picName, inputHostBuff, inBufferSize_); // 申请Device内存inBufferDev_ aclRet = acldvppMalloc(&inBufferDev_, inBufferSize_); // 通过aclrtMemcpy接口将输入图片数据传输到Device aclRet = aclrtMemcpy(inBufferDev_, inBufferSize_, inputHostBuff, inBufferSize_, ACL_MEMCPY_HOST_TO_DEVICE); // 数据传输完成后,及时释放内存 free(inputHostBuff); } else { // 申请Device输入内存dataDev, StreamBufferSize为输入码流大小 ret = acldvppMalloc(&inBufferDev_, inBufferSize_); // 将输入图片读入内存中,该自定义函数ReadPicFile由用户实现 ReadPicFile(picName, inBufferDev_, inBufferSize_); } // 7.循环10次执行视频解码,输出10张YUV420SP NV12格式的图片 int rest_len = 10; int32_t count = 0; while (rest_len > 0) { // 7.1 创建输入视频码流描述信息,设置码流信息的属性 streamInputDesc_ = acldvppCreateStreamDesc(); // inBufferDev_表示Device存放输入视频数据的内存,inBufferSize_表示内存大小 ret = acldvppSetStreamDescData(streamInputDesc_, inBufferDev_); ret = acldvppSetStreamDescSize(streamInputDesc_, inBufferSize_); // 7.2 申请Device内存picOutBufferDev_,用于存放VDEC解码后的输出数据 ret = acldvppMalloc(&picOutBufferDev_, size); // 7.3 创建输出图片描述信息,设置图片描述信息的属性 // picOutputDesc_是acldvppPicDesc类型 picOutputDesc_ = acldvppCreatePicDesc(); ret = acldvppSetPicDescData(picOutputDesc_, picOutBufferDev_); ret = acldvppSetPicDescSize(picOutputDesc_, size); ret = acldvppSetPicDescFormat(picOutputDesc_, static_cast<acldvppPixelFormat>(format_)); // 7.4 执行视频码流解码,解码每帧数据后,系统自动调用callback回调函数将解码后的数据写入文件,再及时释放相关资源 ret = aclvdecSendFrame(vdecChannelDesc_, streamInputDesc_, picOutputDesc_, nullptr, nullptr); // ...... ++count; rest_len = rest_len - 1; // ...... } // 8.释放图片处理通道、图片描述信息 ret = aclvdecDestroyChannel(vdecChannelDesc_); aclvdecDestroyChannelDesc(vdecChannelDesc_); // 9. 释放运行管理资源(依次释放Stream、Context、Device) aclrtDestroyStream(stream_); aclrtDestroyContext(context_); aclrtResetDevice(0); // 10.AscendCL去初始化 aclRet = aclFinalize(); // ......
返回码 |
含义 |
可能原因及解决方法 |
---|---|---|
AICPU_DVPP_KERNEL_STATE_SUCCESS = 0 |
解码成功。 |
- |
AICPU_DVPP_KERNEL_STATE_FAILED = 1 |
其它错误。 |
- |
AICPU_DVPP_KERNEL_STATE_DVPP_ERROR = 2 |
AscendCL内部调用其它模块的接口失败。 |
- |
AICPU_DVPP_KERNEL_STATE_PARAM_INVALID = 3 |
参数校验失败。 |
请检查接口的参数是否符合接口要求。 |
AICPU_DVPP_KERNEL_STATE_OUTPUT_SIZE_INVALID = 4 |
输出内存大小校验失败。 |
请检查输出内存大小是否符合接口要求。 |
AICPU_DVPP_KERNEL_STATE_INTERNAL_ERROR = 5 |
系统内部错误。 |
- |
AICPU_DVPP_KERNEL_STATE_QUEUE_FULL = 6 |
系统内部队列满。 |
- |
AICPU_DVPP_KERNEL_STATE_QUEUE_EMPTY = 7 |
系统内部队列空。 |
- |
AICPU_DVPP_KERNEL_STATE_QUEUE_NOT_EXIST = 8 |
系统内部队列不存在。 |
- |
AICPU_DVPP_KERNEL_STATE_GET_CONTEX_FAILED = 9 |
获取系统内部上下文失败。 |
- |
AICPU_DVPP_KERNEL_STATE_SUBMIT_EVENT_FAILED = 10 |
提交系统内部事件失败。 |
- |
AICPU_DVPP_KERNEL_STATE_MEMORY_FAILED = 11 |
系统内部申请内存失败。 |
请检查系统是否有可用内存。 |
AICPU_DVPP_KERNEL_STATE_SEND_NOTIFY_FAILED = 12 |
发送系统内部通知失败。 |
- |
AICPU_DVPP_KERNEL_STATE_VPC_OPERATE_FAILED = 13 |
系统内部接口处理失败。 |
- |
AICPU_DVPP_KERNEL_STATE_CHANNEL_ABNORMAL = 14 |
当前通道异常。 |
- |
ERR_INVALID_STATE = 0x10001 |
VDEC解码器状态异常错误。 |
- |
ERR_HARDWARE = 0x10002 |
硬件错误,包含解码器启动、执行、停止等异常。 |
- |
ERR_SCD_CUT_FAIL = 0x10003 |
将视频码流分解成多帧图片异常。 |
请检查输入的视频流数据是否正确。 |
ERR_VDM_DECODE_FAIL = 0x10004 |
解码某一帧异常。 |
请检查输入的视频流数据是否正确。 |
ERR_ALLOC_MEM_FAIL = 0x10005 |
内部申请内存失败。 |
请检查系统是否有可用内存,若忽略错误,则可能导致用户持续送入码流却无任何解码结果输出。 |
ERR_ALLOC_DYNAMIC_MEM_FAIL = 0x10006 |
包括输入视频分辨率超范围、内部动态申请内存失败等异常。 |
请检查输入视频流的分辨率、系统是否有可用内存,若忽略错误,则可能导致用户持续送入码流却无任何解码结果输出。 |
ERR_ALLOC_IN_OR_OUT_PORT_MEM_FAIL = 0x10007 |
系统内部申请VDEC的输入、输出buffer异常。 |
请检查系统是否有可用内存,若忽略错误,则可能导致用户持续送入码流却无任何解码结果输出。 |
ERR_BITSTREAM = 0x10008 |
码流错误(如语法解析失败、重发eos或首帧即发送eos)。 |
请检查输入的视频流数据是否正确。 |
ERR_VIDEO_FORMAT = 0x10009 |
输入视频格式错误。 |
请检查输入视频的格式是否为h264或h265。 |
ERR_IMAGE_FORMAT = 0x1000a |
输出格式配置错误。 |
请检查输出图像的格式是否为nv12或nv21。 |
ERR_CALLBACK = 0x1000b |
回调函数为空。 |
请检查配置的回调函数是否为空。 |
ERR_INPUT_BUFFER = 0x1000c |
输入内存为空。 |
请检输入内存是否为空。 |
ERR_INBUF_SIZE = 0x1000d |
输入内存大小<=0。 |
请检查输入内存大小是否小于等于0。 |
ERR_THREAD_CREATE_FBD_FAIL = 0x1000e |
系统内部将解码结果通过回调函数返回给用户的线程异常。 |
请检查系统中资源(例如:线程、内存等)是否可用。 |
ERR_CREATE_INSTANCE_FAIL = 0x1000f |
创建解码实例失败。 |
- |
ERR_INIT_DECODER_FAIL = 0x10010 |
初始化解码器失败,例如解码实例个数超出范围(最大16)。 |
- |
ERR_GET_CHANNEL_HANDLE_FAIL = 0x10011 |
系统内部获取某路视频流的解码句柄失败。 |
- |
ERR_COMPONENT_SET_FAIL = 0x10012 |
系统内部设置解码实例异常。 |
请检查解码的入参值是否正确,例如输入视频格式video_format、输出帧格式image_format等。 |
ERR_COMPARE_NAME_FAIL = 0x10013 |
系统内部设置解码实例名称异常。 |
请检查解码的入参值是否正确,例如输入视频格式video_format、输出帧格式image_format等。 |
ERR_OTHER = 0x10014 |
其它错误。 |
- |
ERR_DECODE_NOPIC = 0x20000 |
隔行码流场景下使用,隔行码流每帧发送两场,解码时其中一块无图像输出,属于正常现象,会返回该错误码;隔行码流的解码输出数据都在奇数场对应的输出buffer中。 |
- |
0x20001 |
参考帧个数设置错误。 |
请检查码流实际参考帧个数与用户设置的参考帧个数是否一致。Atlas 推理系列产品,默认参考帧个数为8。 |
0x20002 |
VDEC解码帧存大小设置错误。 |
请检查输入码流实际宽、高与用户设置的宽、高是否一致。 |
0xA0058001 |
无效的Device ID。 |
请检查Device ID。 |
0xA0058002 |
无效的channel ID。 |
请检查传入接口的通道号是否正确,或者检查通道总数是否达到上限。 |
0xA0058003 |
参数不合法,例如不合法的枚举值。 |
请根据日志检查出错的参数。 |
0xA0058004 |
资源已存在。 |
请检查是否重复创建通道。 |
0xA0058005 |
通道资源不存在。 |
请检查是否使用了不存在的通道号或通道句柄。 |
0xA0058006 |
函数参数中有空指针。 |
请根据日志检查接口的入参。 |
0xA0058007 |
使能系统、Device或通道前未配置对应的参数。 |
请根据日志检查接口的入参。 |
0xA0058008 |
不支持的参数或者功能。 |
请根据日志检查接口的入参。 |
0xA0058009 |
该操作不允许,如试图修改静态配置参数。 |
- |
0xA005800C |
分配内存失败,如系统内存不足。 |
请检查系统是否有可用内存,若忽略错误,则可能导致用户持续送入码流却无任何解码结果输出。 |
0xA005800D |
分配缓存失败,如申请的数据缓冲区太大。暂未使用,预留。 |
- |
0xA005800E |
缓冲区中无数据。 |
系统未完成解码,缓冲区中无解码结果数据,需等待缓冲区中有数据后,再尝试获取数据。 |
0xA005800F |
缓冲区中数据满。 |
用户发送输入码流数据的速度太快,导致输入缓冲区数据满,请尝试降低发送输入码流数据的速度,或者在创建通道时将缓冲区大小设置为较大值。 |
0xA0058010 |
系统没有初始化或者相关依赖的模块没有加载。 |
请检查是否调用系统初始化接口。 |
0xA0058011 |
地址错误。 |
- |
0xA0058012 |
系统忙。 |
请检查VDEC解码总路数是否达到上限。 |
0xA0058013 |
缓存小于实际需要的大小。 |
- |
0xA0058014 |
硬件或软件处理超时。 |
- |
0xA0058015 |
内部系统错误。 |
- |
0xA005803F |
最大的返回码,该模块的错误码必须小于该值。 |
- |