简介

在Enflame产品S60上,含有一个视频解码引擎(文档中称为EFDEC),提供完全加速的Hardware视频解码能力。EFDEC可以用于解码不同格式的视频流:AV1、H.264、HEVC(H.265)、VP8、VP9、AVS、AVS2、MPEG-2、MPEG-4和VC-1。EFDEC的运行完全独立于计算引擎。

TopsCodec提供了一个软件API和库,允许开发者通过TopsCodec API访问EFDEC硬件的视频解码功能。

EFDEC解码视频流并将结果的YUV帧复制到GCU内存中。YUV帧可以通过CPU或者GCU进行视频后处理。TopsCodec API还提供了视频优化的普遍使用的一些后处理操作,如缩放(只支持缩小Down Scale Resize)、截取(Crop)、旋转(Rotation)、抽帧等,以支持多种流行的输出视频格式。客户可以选择使用TopsCodec API提供的视频优化后处理步骤,或者根据需要实现自己的后处理操作。

支持的解码器

EFDEC硬件设计上,在S60上,都有四个DECODER Core和四个COMBO Core, 他们支持的解码器如下图所示:

| Codec      | DECODER_CORE | COMBO_CORE |  CASE   |
| ---------- | ------------ | ---------- | ------- |
| JPEG       | Y            | Y          | PASS    |
| MJPEG      | Y            | Y          | PASS    |
| H.264      | Y            | Y          | PASS    |
| H.265      | Y            | Y          | PASS    |
| AV1        | N            | N          | PASS    |
| VP8        | Y            | Y          | PASS    |
| VP9        | Y            | Y          | PASS    |
| MPEG-2     | Y            | Y          | PASS    |
| MPEG-4     | Y            | Y          | PASS    |
| AVS        | Y            | Y          | PASS    |
| AVS2       | Y            | Y          | PASS    |
| VC-1       | Y            | Y          | PASS    |

TopsCodec安装

一键式安装(推荐)

一键式安装适合习惯在docker环境中使用 TopsCodec 的用户。用户需要在 Host 环境和 Docker 环境中分别运行一次安装包,安装时需要增加 -y 选项。

1.在主机Host安装TopsPlatform 的Enflame Basic Driver Package,执行:

$ sudo ./TopsPlatform_1.0.7.1-b6b519_deb_amd64.run -y

Enflame Basic Driver Package 和 Dev Tools 将被一键安装。

2.进入docker, 再次执行:

$ docker enter xxx
$ sudo ./TopsPlatform_1.0.7.1-b6b519_deb_amd64.run -y

topscodec SDK (Codec runtime for Enflame devices)和topsruntime(User Mode Driver and Runtime for Enflame Devices) , topscc(Enflame TopsCC Compiler Toolkit) ,topsgdb(Enflame TopsCC Debugger), topsdbgapi(Enflame Tops Debugger API),topsprof(Enflame TOPS Profiler Post Process),topspti(Enflame Tops Profiling Tools Interface), topstx(Enflame Tops Tools Extension) 和 Samples 将在Docker内被一键安装。

交互式安装

如果希望在Host环境下安装全部内容,可以使用交互式安装方法。

1.运行安装包:

$ sudo ./TopsPlatform_1.0.7.1-b6b519_deb_amd64.run

2.弹出《TopsRider软件栈许可协议条款与条件》,选择 accept 表示接受。

3.弹出 TopsPlatform Installer,勾选需要安装的软件。选择 Install 项开始安装。

Video解码能力

表 2 Video解码能力预览

解码格式

Profile&Level

Maximum Resolution:

MPEG-1 & MPEG-2

MP

4096x4096

VC-1

SP/MP ANNEX L/AP ANNEX G

2048x4096

MPEG-4

SP/ASP

2048x2048

H.264/AVCHD

baseline/main/high/high10(10bit)

8192x8192

H.265/HEVC

main/main10(10bit)

8192x8192

VP8

N/A

2048x2048

VP9

profile 0/ profile 2(10bit)

8192x8192

AV1

main

8192x8192

H.263

profile 0

2048x2048

AVS

Part 2 (AVS1-P2 Jizhun)

1920x1080

AVS2

Part 2 (GY/T 299.1)AVS2-P2/IEEE1857.4 Main/Main Picture/Main10

8192x8192

MJPEG

baseline sequential

32768x32768

Video解码器简介

在使用TopsCodec API解码任何视频内容时,需要遵循以下步骤:

  • 创建Codec上下文。

  • 查询硬件解码器的解码能力。

  • 创建解码器实例。

  • 配置解码器。

  • 对内容(如.mp4)进行解复用。这可以使用第三方软件如FFMPEG来完成。

  • 使用第三方解析器(如FFmpeg)解析视频码流。

  • 使用TopsCodec API启动解码过程。

  • 获取解码后的YUV数据以供进一步处理。

  • 查询解码帧的状态。

  • 根据解码状态,使用解码输出进行进一步处理,如渲染、推理、后处理等。

  • 在解码过程完成后,销毁解码器实例。

  • 销毁Codec上下文。

上述步骤在本文档的其余部分有详细说明,并在TopsCodec SDK软件包中的Sample应用程序中进行了演示。

使用视频解码器API

Decoder API

| No. | API                         | Remark              |
| --- | --------------------------- | ------------------- |
| 1   | topscodecDecGetCaps()       | 获取设备能力        |
| 2   | topscodecDecCreate()        | 创建解码器          |
| 3   | topscodecDecSetParams()     | 设置解码参数        |
| 4   | topscodecDecDestroy()       | 销毁解码器          |
| 5   | topscodecDecodeStream()     | 启动解码            |
| 6   | topscodecDecFrameMap()      | map 输出            |
| 7   | topscodecDecFrameUnmap()    | unmap相关资源       |
| 8   | topscodecGetLoading()       | 获取设备负载        |

TopsCodec API定义在头文件tops_codec.h中。这个头文件可以在TopsCodec SDK包安装后在安装文件夹/opt/tops/include/tops/tops中找到。 安装后可以选择使用动态库/opt/tops/lib/libtopscodec.so或者静态库/opt/tops/lib/libtopscodec_static.a。

本章的以下部分解释了使用TopsCodec Decoder API加速解码应遵循的流程。

Video Decoder

查询解码能力

API topscodecDecGetCaps()允许用户查询底层硬件视频解码器的功能。

不同的Codec有不同的硬件解码器。因此,为确保应用程序可正确在硬件上运行,强烈建议应用程序查询硬件功能,并根据所需功能/功能的存在与否做出适当决策。

topscodecDecGetCaps() API允许用户查询底层硬件视频解码器的功能。调用线程应与指定有效的GCU卡和有效的codec。

在调用topscodecDecGetCaps()是,客户端需要填写输入参数:

codec:解码器类型,包含:

TOPSCODEC_MPEG1
TOPSCODEC_MPEG2
TOPSCODEC_MPEG4
TOPSCODEC_VC1
TOPSCODEC_H263
TOPSCODEC_H264
TOPSCODEC_H264_SVC
TOPSCODEC_H264_MVC
TOPSCODEC_HEVC
TOPSCODEC_VP8
TOPSCODEC_VP9
TOPSCODEC_AVS
TOPSCODEC_AVS_PLUS
TOPSCODEC_AVS2
TOPSCODEC_JPEG
TOPSCODEC_AV1

card_id: 物理卡的编号(不能大于服务器上卡的数量) device_id: 卡上video节点的编号(每张卡0~7)

当调用topscodecDecGetCaps()时,底层驱动程序将填充topscodecDecCaps_t的字段,指示对查询的功能的支持、支持的输出格式以及硬件支持的最大和最小分辨率。

以下伪代码说明了如何查询Codec的功能:

代码 1 查询Codec功能
topscodecDecCaps_t DecCaps;
memset(&DecCaps, 0, sizeof(DecCaps));

topscodecType_t codec_type = TOPSCODEC_H264;
u32_t card_id = 0;
u32_t device_id = 0;
ret = topscodecDecGetCaps(codec_type, card_id, device_id, &DecCaps);

可按如下方式解释从API返回的参数,以验证底层硬件是否可解码内容:

代码 2 解析Codec功能
INFO("\t Caps supported(%d)            \n", DecCaps->supported);
INFO("\t max_width(%d)                 \n", DecCaps->max_width);
INFO("\t max_height(%d)                \n", DecCaps->max_height);
INFO("\t min_width(%d)                 \n", DecCaps->min_width);
INFO("\t min_height(%d)                \n", DecCaps->min_height);
INFO("\t output_pixel_format_mask(%d)  \n", DecCaps->output_pixel_format_mask);
INFO("\t scale_up_supported(%d)        \n", DecCaps->scale_up_supported);
INFO("\t crop_supported(%d)            \n", DecCaps->crop_supported);
INFO("\t rotation_supported(%d)        \n", DecCaps->rotation_supported);

// 检查是否支持该解码器/
if (!DecCaps->supported){
  INFO("此GPU不支持该编解码器");
}
// 验证底层硬件是否支持内容分辨率
if ((coded_width > DecCaps->max_width) ||
(coded_height > DecCaps->max_height)){
  INFO("此GPU不支持该分辨率");
}

在大多数情况下,解码器输出使用的位深度和色度子采样与解码器输入(即内容)相同。 但在某些情况下,可能需要解码器以与输入比特流不同的位深度和色度子采样生成输出。 通常,最好先检查所需的输出位深度和色度子采样格式是否受支持,然后再创建解码器。可按如下方式进行:

代码 3 解析Codec功能(2)
// 检查支持的输出格式
if (DecCaps->output_pixel_format_mask & (1<<TOPSCODEC_PIX_FMT_NV12)){
  // 解码器支持NV12输出格式
}
if (DecCaps->output_pixel_format_mask & (1<<TOPSCODEC_PIX_FMT_P010){
  // 解码器支持P010输出格式
}

可以选择的输出参数格式如下:

TOPSCODEC_PIX_FMT_NV12
TOPSCODEC_PIX_FMT_NV21
TOPSCODEC_PIX_FMT_I420
TOPSCODEC_PIX_FMT_YV12
TOPSCODEC_PIX_FMT_YUYV
TOPSCODEC_PIX_FMT_UYVY
TOPSCODEC_PIX_FMT_YVYU
TOPSCODEC_PIX_FMT_VYUY
TOPSCODEC_PIX_FMT_P010
TOPSCODEC_PIX_FMT_P010LE
TOPSCODEC_PIX_FMT_I010
TOPSCODEC_PIX_FMT_YUV444
TOPSCODEC_PIX_FMT_YUV444_10BIT
TOPSCODEC_PIX_FMT_ARGB
TOPSCODEC_PIX_FMT_ABGR
TOPSCODEC_PIX_FMT_BGRA
TOPSCODEC_PIX_FMT_RGBA
TOPSCODEC_PIX_FMT_RGB565
TOPSCODEC_PIX_FMT_BGR565
TOPSCODEC_PIX_FMT_RGB555
TOPSCODEC_PIX_FMT_BGR555
TOPSCODEC_PIX_FMT_RGB444
TOPSCODEC_PIX_FMT_BGR444
TOPSCODEC_PIX_FMT_RGB888
TOPSCODEC_PIX_FMT_BGR888
TOPSCODEC_PIX_FMT_RGB3P
TOPSCODEC_PIX_FMT_RGB101010
TOPSCODEC_PIX_FMT_BGR101010
TOPSCODEC_PIX_FMT_MONOCHROME
TOPSCODEC_PIX_FMT_MONOCHROME_10BIT
TOPSCODEC_PIX_FMT_BGR3P
...

创建解码器

在创建解码器实例之前,用户需要有一个有效的codec句柄,表征解码的上下文,该上下文将在整个解码过程中使用。

可以在填充topscodecDecCreateInfo_t结构体后,通过调用topscodecDecCreate()创建解码器实例。topscodecDecCreateInfo_t结构体应填入有关待解码流的以下信息: device_id:卡的序号(card_id) stream_buf_size: 指定输入流缓冲区的大小;推荐值:w*h*1.25,范围[4096,设备最大内存大小] codec:CodecType,必须来自topscodecType_t枚举类型,表示内容的编解码器类型,如H.264、HEVC、VP9等。 run_mode: TOPSCODEC_RUN_MODE_ASYNC 或者 TOPSCODEC_RUN_MODE_SYNC,在枚举类型:topscodecRunMode_t中定义 session_id: 卡内codec序号 user_contex: thread index(用户多线程) backend: 指定JPEG的backend IP, 来自枚举类型:topscodecJpegBackend_t。

topscodecDecCreate()调用返回解码器句柄, 该句柄应保留直到解码会话处于活动状态。需要将该句柄与其他topsCodec API调用一起传递。

配置解码器

通过调用topscodecDecSetParams()并传递解码器句柄和指向topscodecDecParams_t的指针。以下代码演示了在缩小、裁剪或旋转的解码器设置。

代码 4 配置解码器
// 缩小。缩小到400x400
topscodecDecParams_t codec_param;
memset(codec_param, 0, sizeof(codec_param));
codec_param.pp_attr.downscale.enable = 1;
codec_param.pp_attr.downscale.width = 400;
codec_param.pp_attr.downscale.height = 400;
/*Downscale mode: 0-Bilinear, 1-Nearest*/
codec_param.pp_attr.downscale.interDslMode = 0;
ret = topscodecDecSetParams(handle, &codec_params);
...

// 裁剪
unsigned int uCropL, uCropR, uCropT, uCropB;
uCropL = 30;
uCropR = 700;
uCropT = 20;
uCropB = 500;
...
topscodecDecParams_t codec_param;
memset(codec_param, 0, sizeof(codec_param));

codec_param->pp_attr.crop.enable = 1;
codec_param->pp_attr.crop.tl_x = uCropL;
codec_param->pp_attr.crop.tl_y = uCropT;
codec_param->pp_attr.crop.br_x = uCropR;
codec_param->pp_attr.crop.br_y = uCropB;
ret = topscodecDecSetParams(handle, &codec_params);
...

// 旋转
topscodecDecParams_t codec_param;
memset(codec_param, 0, sizeof(codec_param));

codec_param->pp_attr.rotation.enable = 1;
switch (info.rotation) {
  case 90:
    codec_param->pp_attr.rotation.rotation = TOPSCODEC_ROTATION_90;
    break;
  case 180:
    codec_param->pp_attr.rotation.rotation = TOPSCODEC_ROTATION_180;
    break;
  case 270:
    codec_param->pp_attr.rotation.rotation = TOPSCODEC_ROTATION_270;
    break;
  default:
    codec_param->pp_attr.rotation.rotation = TOPSCODEC_ROTATION_NONE;
    break;
}
ret = topscodecDecSetParams(handle, &codec_params);
...

我们还支持等间隔抽帧和IDR关键帧抽帧,具体设置参看Sample Code

解码 frame/field

在(使用FFmpeg) 解复用和解析之后,客户端可以将包含一帧或一幅数据的比特流提交给硬件进行解码。为此需要执行以下步骤:

填充topscodecStream_t结构体: 客户端需要用解析过程中获得的参数和数据地址填充该结构体。

调用topscodecDecodeStream()并传递解码器句柄和指向topscodecStream_t的指针。topscodecDecodeStream()将在硬件上启动解码。

解码后帧获取

用户需要调用 topscodecDecFrameMap() 来获取存放解码并后处理的帧。 调用前需填充topscodecFrame_t结构体: 客户端需要用解析过程中获得的参数和数据地址填充该结构体。 请注意,topscodecDecodeStream() 指示硬件引擎开始解码帧/幅。然而,成功完成 topscodecDecFrameMap() 表示解码过程已完成,并且解码后的 YUV 帧已从硬件copy出。

topscodecDecFrameMap() API 以解码表面索引 (nPicIdx) 作为输入,将其映射到一个可用的输出表面,对解码后的帧进行后处理并复制到输出表面,并返回输出表面的 CUDA 设备指针和相关间距。

本文档将 topscodecDecFrameMap() 执行的上述操作称为映射。

用户完成帧处理后,必须调用 topscodecDecFrameUnmap() 使输出表面可用于存储其他解码和后处理的帧。

如果用户在调用 topscodecDecFrameMap() 后续无法调用相应的 topscodecDecFrameUnmap(),则 topscodecDecFrameMap() 最终将失败。

以下代码演示了如何使用 topscodecDecFrameMap() 和 topscodecDecFrameUnmap()。

// MapFrame: 调用 topscodecDecFrameMap 并获取解码后的帧。topscodecDecFrameMap已将设备内存中的帧copy到host内存

在多实例解码用例中,EFDEC 可能是瓶颈,因此在不同的 CPU 线程上调用 topscodecDecFrameMap() 和 topscodecDecodeStream() 不会有明显的好处。如果驱动程序内部的 NVDEC 等待队列已满,topscodecDecodeStream() 将会停止。为了简单起见,视频编解码器 SDK 中的示例应用程序在相同的 CPU 线程上使用映射和解码调用。

查看解码状态

解码启动后,您可以随时调用 topscodecDecGetStatus() 函数来查询该帧的解码状态。 底层驱动程序会将解码状态填充到 topscodecDecStatus_t *status 中。

topsCodec API 目前报告以下状态:

TOPSCODECDEC_STATUS_INVALID: 无效的状态 TOPSCODECDEC_STATUS_INPROGRESS:解码正在进行中。 TOPSCODECDEC_STATUS_SUCCESS:帧解码成功完成。 TOPSCODECDEC_STATUS_ERROR:帧解码已经完成,但是有错误。

这个 API 预计将在需要根据帧解码状态做出进一步决策的场景中提供帮助,例如是否对该帧进行推断。

销毁解码器

用户需要调用 topscodecDecDestroy() 来销毁解码器会话并释放所有已分配的解码器资源。

编写高效的解码应用程序

Enflame GCU 上的 Codec 引擎EFDEC是一个专门的硬件模块,用于解码支持格式的输入视频比特流。

一个典型的视频解码应用程序通常包含以下几个阶段:

解复用(De-Muxing): 将多路复用的媒体文件分离成各个数据流,例如视频、音频等。 视频比特流解析和解码: 解析视频比特流并使用硬件Codec引擎进行解码。 准备帧进行进一步处理: 对解码后的帧进行必要的处理,例如缩放、颜色空间转换、降噪等。

需要注意的是,解复用和解析过程并非硬件加速,因此不在本文档的讨论范围之内。 解复用可以使用第三方组件,例如 FFmpeg,它支持多种多路复用视频格式。 SDK 中包含的示例应用程序展示了使用 FFmpeg 进行解复用的方法。

同样,解码后处理(例如缩放、颜色空间转换、降噪、色彩增强等)可以使用用户定义的 GCU 代码高效地执行。

对于性能要求高且延迟低的视频编解码器应用程序,请确保 PCIE 链接宽度设置为最大可用值。 可以通过运行命令 efsmi -q 获取当前配置的 PCIE 链接宽度。 可以通过系统 BIOS 设置配置 PCIE 链接宽度。

SDK 中包含的示例应用程序旨在演示各种 API 的功能,但它们可能并非完全优化。 因此,强烈建议程序员确保他们的应用程序设计合理,解码-后处理-显示流水线中的各个阶段结构合理,以实现所需的性能和内存消耗。