摘要:本期就分享几个关于DVPP视频解码问题的典型案例,并给出原因分析及解决方法

本文分享自华为云社区《DVPP媒体数据处理视频解码问题案例》,作者:昇腾CANN 。

DVPP(Digital Vision Pre-Processing)是昇腾AI处理器内置的图像处理单元,通过AscendCL媒体数据处理接口提供强大的媒体处理硬加速能力,主要功能包括图像编解码、视频编解码、图像抠图缩放等。

本期就分享几个关于DVPP视频解码问题的典型案例,并给出原因分析及解决方法:

  1. 视频解码进程卡死,无法退出
  2. retCode返回值设置错误,导致视频解码异常
  3. 视频解码无报错,但无解码结果数据,且CPU占用率高

01 视频解码进程卡死,无法退出现象描述

用户进程卡死,无法退出。查看应用类日志,一直重复提示信息“fault kernel_name=DvppSendVdecFrame”、“Kernel task happen error, retCode=0x28, [aicpu timeout]”,表示AI CPU异常,无法处理视频解码任务,导致任务超时。

日志片段举例如下:

[ERROR] RUNTIME(pid,pName):DateTimeMS [task.cc:878]1827 PreCheckTaskErr:[DVPP][DEFAULT]Kernel task happen error, retCode=0x28, [aicpu timeout].[ERROR] RUNTIME(pid,pName):DateTimeMS [task.cc:676]1827 PrintAicpuErrorInfo:[DVPP][DEFAULT]Aicpu kernel execute failed, device_id=0, stream_id=177, task_id=4, fault so_name=libdvpp_kernels.so, fault kernel_name=DvppSendVdecFrame, fault op_name=, extend_info=.[ERROR] RUNTIME(pid,pName):DateTimeMS [task.cc:878]1831 PreCheckTaskErr:[DVPP][DEFAULT]Kernel task happen error, retCode=0x28, [aicpu timeout].[ERROR] RUNTIME(pid,pName):DateTimeMS [task.cc:676]1831 PrintAicpuErrorInfo:[DVPP][DEFAULT]Aicpu kernel execute failed, device_id=0, stream_id=170, task_id=8, fault so_name=libdvpp_kernels.so, fault kernel_name=DvppSendVdecFrame, fault op_name=, extend_info=.[ERROR] RUNTIME(pid,pName):DateTimeMS [engine.cc:960]1766 ReportExceptProc:[DVPP][DEFAULT]Task exception! device_id=0, stream_id=107, task_id=8, type=1, retCode=0x28.[ERROR] RUNTIME(pid,pName):DateTimeMS [engine.cc:960]1773 ReportExceptProc:[DVPP][DEFAULT]Task exception! device_id=0, stream_id=130, task_id=4, type=1, retCode=0x28.

可能原因

Device内存不足,AI CPU无法处理视频解码任务,导致任务超时。

处理步骤

1.在使用媒体数据处理V1版本的视频解码功能前,可参考性能指标说明页面中的“每路VDEC解码的内存消耗计算公式”,预估需使用的Device内存,并合理规划Device上的内存。
您可以在页面左上侧切换版本,查看对应版本的性能指标说明。

2.优化应用程序的代码逻辑,增加异常处理机制,获取视频解码异常信息,强制退出进程。

在调用aclinit接口初始化之后、调用aclvdecSendFrame接口解码之前,定义异常回调函数,并调用aclrtSetExceptionInfoCallback接口设置异常回调函数,用于获取任务异常信息,以便在异常分支中根据任务异常信息来判断是否退出应用进程。

  • 定义异常回调函数,回调函数原型为:typedef void (*aclrtExceptionInfoCallback)(aclrtExceptionInfo *exceptionInfo)
  • 实现异常回调函数,在异常回调函数fn内调用aclrtGetDeviceIdFromExceptionInfo、aclrtGetStreamIdFromExceptionInfo、aclrtGetTaskIdFromExceptionInfo接口分别获取Device ID、Stream ID、Task ID。

根据Stream ID、Task ID判断Device是否异常,若异常,则强制退出进程。

异常回调函数实现示例如下:

void dvpp_callback(aclrtExceptionInfo * exception_info){    uint32_t taskId = aclrtGetTaskIdFromExceptionInfo(exception_info);    uint32_t streamId = aclrtGetStreamIdFromExceptionInfo(exception_info);    uint32_t deviceId = aclrtGetDeviceIdFromExceptionInfo(exception_info); if(taskId == 0xffffffff) || (streamId == 0xffffffff) { //Device异常,强制退出进程 } else { //任务异常,如果频繁出现(例如,统计1秒内触发异常回调函数的次数),进程退出 } return;}

3.调用aclrtSetExceptionInfoCallback接口设置异常回调函数。

02 retCode返回值设置错误,导致视频解码异常现象描述

调用aclvdecSendFrame接口发送一帧码流后,继续复用输出图片描述信息,进行后续帧码流的解码操作,结果反复出现解码不成功、解码异常的情况。

日志片段举例如下:

Channel[0]: success to aclvdecSendFrame, loop=1, count=7get frame success, totalCount=7packet.size is 26084.Channel[0]: begin to send frame, loop=1, count=8acldvppGetPicDescRetCode, retCode: 2.Vdec ERROR!!!!!!!!!!!!!!!!errCount is 3. total count is 3.!!!!!!!!!!!!!!!!!!acldvppGetPicDescRetCode, retCode: 2.right_count:0,fail_count:3,total_count:3Channel[0]: success to aclvdecSendFrame, loop=1, count=8get frame success, totalCount=8packet.size is 27927.Channel[0]: begin to send frame, loop=1, count=9acldvppGetPicDescRetCode, retCode: 2.Vdec ERROR!!!!!!!!!!!!!!!!errCount is 4. total count is 4.!!!!!!!!!!!!!!!!!!acldvppGetPicDescRetCode, retCode: 2.right_count:0,fail_count:4,total_count:4

可能原因

根据日志中的提示,通过acldvppGetPicDescRetCode接口获取到的retCode为2,retCode为非0值时,表示解码异常。

再查看代码逻辑时,发现由于前一帧码流解码失败,retCode被置为2,在复用输出图片描述信息时,retCode也继承了前一帧解码失败的状态值2,导致AscendCL在解码后续帧时,获取到retCode值为2,就一直判断解码是失败。

处理步骤

如果存在复用输出图片描述信息的场景,需先调用acldvppSetPicDescRetCode设置为0,防止前一帧解码异常的状态影响后续解码。

03 视频解码无报错,但无解码结果数据、CPU占用率高现象描述

查看应用类日志,无ERROR报错、无解码结果数据输出,另外,在运行应用程序的Linux服务器上执行top命令,该应用进程的CPU占用率持续升高。

可能原因

1. 无ERROR、无解码结果数据输出,初步判断可能是因为解码发帧接口aclvdecSendFrame调用正常,但未触发回调函数,无法获取解码结果数据。

2. 检查触发回调函数的代码逻辑。

按照视频解码的接口调用逻辑:由用户提前创建一个单独的线程,并自定义线程函数,在线程函数内调用aclrtProcessReport接口,通过该接口配置超时时间,等待指定的超时时间后,触发回调函数,获取解码结果数据。

Channel[0]: success to aclvdecSendFrame, loop=1, count=7get frame success, totalCount=7void *ThreadFunc(aclrtContext sharedContext){ if (sharedContext == nullptr) { ERROR_LOG("sharedContext can not be nullptr"); return ((void*)(-1)); } INFO_LOG("use shared context for this thread"); aclError ret = aclrtSetCurrentContext(sharedContext); if (ret != ACL_SUCCESS) { ERROR_LOG("aclrtSetCurrentContext failed, errorCode = %d", static_cast(ret)); return ((void*)(-1)); } INFO_LOG("thread start "); while (runFlag) { // Notice: timeout 1000ms (void)aclrtProcessReport(1000); } return (void*)0;}

3. 如果触发回调函数的接口调用逻辑正确,则在aclrtProcessReport接口处增加日志打印,判断应用运行过程中线程是否成功调用了aclrtProcessReport接口,只有成功调用aclrtProcessReport接口,才会触发回调函数。

示例代码如下:

while (runFlag) { // Notice: timeout 1000ms aclError ret = aclrtProcessReport(1000); printf("aclrtProcessReport failed, ret=%d.\n", ret);}

4. 修改代码后,重新编译运行应用。

在终端屏幕重复出现以下打印信息,表示调用aclrtProcessReport接口失败:

aclrtProcessReport failed, ret = 107012

查阅该接口的返回值说明,107012表示线程未订阅或重复订阅。

5. 检查代码逻辑,检查是否调用aclvdecSetChannelDescThreadId接口绑定用户新建的线程,按照VDEC视频解码的接口调用逻辑,只有调用该接口绑定用户线程,才可以触发调用aclrtProcessReport接口,进而触发回调函数。

6. 修改代码后,重新编译运行应用,视频解码正常,正常输出解码结果数据,同时CPU占用率下降。

处理步骤

参见视频解码的接口调用流程页面或者参考VDEC功能样例开发视频解码功能。您可以在页面左上侧切换版本,查看对应版本的接口调用流程。

其中,需关注以下注意点:

  • 创建新线程,并自定义线程函数,在线程函数内调用aclrtProcessReport接口,等待指定时间后,触发回调函数中的回调函数。
  • 需调用aclvdecSetChannelDescThreadId接口绑定用户创建的新线程。
  • 释放资源时,依次销毁通道、销毁通道描述信息后,才可以销毁中用户创建的新线程。

04 更多介绍

[1]昇腾文档中心

[2]昇腾社区在线课程

[3]昇腾论坛

点击关注,第一时间了解华为云新鲜技术~