NVOFA FRUC
图灵架构及更新的 NVIDIA GPU 包含基于硬件的光流加速器 (NVOFA),可提供两帧之间的光流矢量图。NVIDIA 光流 SDK 通过光流 API 提供对 NVOFA 的访问。
NVIDIA 光流 SDK 4.0 及更高版本 SDK 包含 NVOFA 辅助的帧率提升转换 (FRUC) 库。该库公开了 FRUC API,可用于游戏或视频的帧率提升转换。
本文档提供有关如何使用 FRUC API 的信息。希望开发人员应熟悉 Windows 和/或 Linux 开发环境。
帧率提升转换
帧率提升转换是一种通过在较低帧率视频中插入插帧来生成较高帧率视频的技术。这种高帧率视频显示了跨帧运动的平滑连续性,从而提高了视频的感知视觉质量。
图 1. 帧率提升转换

NVIDIA FRUC 库
NVIDIA FRUC 库公开了 FRUC API,该 API 接受两个连续帧并在它们之间生成一个插帧。这些 API 可用于游戏或视频内容的帧率提升转换。
该库内部使用 NVOFA 硬件引擎和 CUDA。因此,与纯软件方法相比,使用 FRUC 库进行帧插值要快得多。
该库支持 ARGB 和 NV12 输入表面格式。它可以直接集成到 DirectX 游戏或 CUDA 应用程序中。
FRUC 库可在 Windows 操作系统(Windows 10 及更高版本)和 Linux 操作系统(Ubuntu 18 及更高版本发行版)上运行。它在 Windows 上需要 NVIDIA 显示驱动程序版本 511.65 或更高版本,在 Linux 上需要 NVIDIA 显示驱动程序版本 510.47.03 或更高版本。
FRUC 库使用概览
这是一个框图,显示了应用程序如何使用 FRUC 库进行帧率提升转换。
图 2. FRUC 库软件堆栈

-
应用程序将连续帧传递给 FRUC 库。
-
FRUC 库使用当前调用中的帧(下一帧)和先前调用中缓存的帧(上一帧)来插值中间帧。该库首先调用 NVIDIA 光流 API 以获取两帧(上一帧和下一帧)之间的前向和后向光流矢量图。然后,它使用 CUDA 加速技术在两帧之间生成一个插帧。
-
插帧将返回给应用程序。然后,应用程序将插帧与原始帧交错,并生成帧率提高的视频。
FRUC 库内部
图 3. FRUC 库的简化框图

以下是关于 FRUC 库如何处理两个连续帧并生成一个插帧的简要说明。
FRUC 库的输入是两个连续帧(上一帧和下一帧)
- 使用 NVOFA API 生成光流矢量
连续帧对使用 NVOFA API 发送到 NVIDIA 光流引擎,以获取它们之间的前向和后向光流矢量图。
- 验证光流矢量
然后使用前向-后向一致性检查来验证光流矢量图中的所有光流矢量。未通过一致性检查的光流矢量将被拒绝,从而生成稀疏光流矢量图。
- 使用先进技术填充无效光流矢量
使用可用的光流矢量和先进技术,生成准确的光流矢量以填充被拒绝的光流矢量,从而将稀疏光流矢量图转换为完全密集的光流矢量图。
- 图像插值
使用密集光流矢量图,在两个输入帧之间生成一个插帧。
这样的图像可能包含一些空洞区域(不具有有效颜色的像素)。
- 图像域空洞填充
插帧中的空洞使用图像域空洞填充技术填充,以生成最终的插值图像。
最终的插帧将返回给应用程序。
FRUC 库组件
光流 SDK 包括以下 FRUC 库组件
NvFRUC.dll
:在 Windows 上公开 FRUC API 的 DLL。libNvFRUC.so
:在 Linux (Ubuntu) 上公开 FRUC API 的 .so 文件。NvFRUC.h
:NvFRUC API 头文件。NvFRUCSample
:应用程序源代码:显示如何使用 NVIDIA FRUC 库。ReadMe.pdf
:包含有关如何构建和运行NvFRUCSample
应用程序的说明NVOFA_FRUC.pdf
:包含有关 FRUC API 以及如何使用这些 API 的详细信息(本文档)。
基本编程流程
NVIDIA FRUC API 旨在接受 NV12 或 ARGB 格式的原始视频帧,并返回它们之间的插帧。
广义上讲,编程流程包括以下步骤
- 调用 API
PtrToFuncNvFRUCCreate
以创建 FRUC 实例。 - 创建输入资源(DirectX NV12 或 CUDA ARGB 表面)并使用 API
PtrToFuncNvFRUCRegisterResource
向 FRUC 库注册它们。 - 调用 API
PtrToFuncNvFRUCProcess
以处理输入帧,从而生成插帧。 - 调用 API
PtrToFuncNvFRUCUnregisterResource
以向 FRUC 库注销输入资源,以便可以销毁它们。 - 调用 API
PtrToFuncNvFRUCDestroy
以销毁 FRUC 实例。
头文件 NvFRUC.h
包含这些函数中使用的结构的详细信息。
背景
如FRUC 库的简化框图所示,FRUC 库使用光流 API 来获取两个连续帧之间的光流矢量图。客户端应用程序首先需要分配缓冲区以保存输入帧对数据和插帧数据。然后,客户端应用程序需要将这些缓冲区的地址传递给 FRUC 库。然后,FRUC 库利用 NVOFA API 和高级 CUDA 算法来生成插帧,并将其复制到客户端应用程序共享的输出缓冲区。
客户端应用程序可以使用 DirectX API 或 CUDA 驱动程序 API 创建输入和输出资源。
- 使用 DirectX 11 API 创建资源
在这种情况下,客户端应用程序使用 DirectX 11 API 创建共享纹理,并将创建纹理的设备指针和纹理本身共享给 FRUC 库。由于纹理在客户端应用程序和 FRUC 库之间共享,因此客户端应用程序有责任确保同步以避免竞争条件。要使用的同步机制是 Windows 操作系统内部版本号 1703 及更高版本上的 ID3D11Fence 或其余 Windows 操作系统上的 IDXGIKeyedMutex。客户端应用程序和 FRUC 库使用 CUDA-DirectX 图形互操作 API 进行缓冲区的线程安全读写。要了解更多信息,请访问 Direct3D 11 互操作性。
- 使用 CUDA 驱动程序 API 创建资源
如果客户端应用程序使用 CUDA API 创建共享资源,则只需将资源指针传递给 FRUC 库即可。
FRUC API 用法示例
NvFRUCSample 应用程序的源代码演示了如何使用 NVIDIA FRUC 库公开的 API 进行帧率提升转换。该应用程序接受 YUV 文件或 PNG 帧序列作为输入视频,并生成如下输出
- 输入为 YUV 视频序列
在这种情况下,应用程序采用 YUV(YUV420) 视频序列,在连续帧之间插值中间帧,将插帧与原始帧交错以生成输出 YUV 视频。这样生成的输出视频的帧率是输入视频的两倍。
- 输入为 PNG 帧序列
在这种情况下,应用程序采用 PNG 格式的帧序列,在连续帧之间插值中间帧,并将这些帧以 PNG 格式保存。
NvFRUCSample
具有以下帮助程序类来创建和处理共享资源。您可以在您的自定义应用程序中重用这些帮助程序类和 NVFRUCSample
应用程序的其他部分代码。
<b>FrameGeneratorD3D11</b>
此类处理
ID3D11Device
、IDXGIKeyedMutex
和ID3D11Fence
接口的创建。它还处理共享表面的读取和写入以及同步。<b>FrameGeneratorCUDA</b>
这处理
cuDevicePtr
、cuArray
接口指针的创建以及将这些指针共享给 FRUC 库。它还处理 CUDA 设备内存的读取和写入。<b>BufferManager</b>
此类处理 FRUC
NvFRUCSample
应用程序和 FRUC 库之间的设备到主机和主机到设备 CUDA 内存传输。
使用 FRUC API
创建 FRUC 实例
按如下所示加载 NvFRUC.dll
(Windows) 或 libNvFRUC.so
(Linux)。
- Windows
SecureLoadLibrary(L"NvFRUC.dll", &hDLL);
我们建议使用 SecureLoadLibrary() 加载 NvFRUC.dll,以确保应用程序加载 Nvidia 签名库。
- Linux
hDLL = dlopen("libNvFRUC.so", RTLD_LAZY);
按如下所示检索 FRUC 库导出的函数的地址。导出函数的签名在头文件 NvFRUC.h 中可用。
NvFRUCCreate = (PtrToFuncNvFRUCCreate)GETPROCEDUREADDRESS(
DLL,
CreateProcName);
NvFRUCRegisterResource =(PtrToFuncNvFRUCRegisterResource)GETPROCEDUREADDRESS(
hDLL,
RegisterResourceProcName);
NvFRUCUnregisterResource = (PtrToFuncNvFRUCUnregisterResource)GETPROCEDUREADDRESS(
hDLL,
UnregisterResourceProcName);
NvFRUCProcess = (PtrToFuncNvFRUCProcess)GETPROCEDUREADDRESS(
hDLL,
ProcessProcName);
NvFRUCDestroy = (PtrToFuncNvFRUCDestroy)GETPROCEDUREADDRESS(
hDLL,
DestroyProcName);
要创建 NvFRUC 实例,请按如下所示调用 NvFRUCCreate
函数。
NvFRUC_CREATE_PARAM createParams = { 0 };
NvFRUCHandle hFRUC;
createParams.pDevice = objFrameGenerator->GetDevice();
createParams.uiHeight = stArgs.m_Height;
createParams.uiWidth = stArgs.m_Width;
createParams.eResourceType = (NvFRUCResourceType)stArgs.m_ResourceType;
createParams.eSurfaceFormat = (NvFRUCSurfaceFormat)stArgs.m_InputSurfaceFormat;
createParams.eCUDAResourceType = (NvFRUCCUDAResourceType)stArgs.m_CudaResourceType;
//Initialize FRUC pipeline which internally initializes Optical flow engine
status = NvFRUCCreate(
&createParams,
&hFRUC);
以下是关于您需要传递给 NvFRUCCreate
函数的结构 NvFRUC_CREATE_PARAM
参数的简要说明。
<b>pDevice</b>
(输入):这是指向ID3D11Device
接口的指针。此指针与 FRUC 库共享。仅当客户端使用 DirectX API 创建资源时才使用此指针。如果您使用 CUDA API 创建资源,则应为 NULL。<b>uiHeight</b>
(输入):客户端应用程序要创建的输入表面的高度。<b>uiWidth</b>
(输入):客户端应用程序要创建的输入表面的宽度。<b>eResourceType</b>
(输入):如果您要将共享资源创建为 DirectX 11 纹理,请将其设置为 1。如果您要将共享资源创建为cuDevicePtr
或cuArray
,请将其设置为 0。<b>eSurfaceFormat</b>
(输入):对于表面格式 NV12,请将其设置为 0,对于表面格式 ARGB,请将其设置为 1。表面格式与用于创建资源的 API 无关。<b>eCUDAResourceType</b>
(输入):如果您使用 CUDA API 创建资源,则对于cuDevicePtr
,请将此参数设置为 0,对于cuArray
,请将其设置为 1。
如果此函数成功,它将返回 FRUC 实例的句柄,所有后续函数都需要该句柄。
注册资源
使用 NvFRUCRegisterResource
函数向 FRUC 库注册客户端创建的资源,如下所示。
NvFRUC_REGISTER_RESOURCE_PARAM regOutParam = { 0 };
objFrameGenerator->GetResource(
regOutParam.pArrResource,
regOutParam.uiCount);
regOutParam.pD3D11FenceObj = objFrameGenerator->GetFenceObj();
status = NvFRUCRegisterResource(
hFRUC,
®OutParam);
此处,objFrameGenerator
是 FrameGeneratorD3D11
或 FrameGeneratorCUDA
类的对象。它在初始化期间创建所需的资源。上面调用的 objFrameGenerator->GetResource()
函数将这些资源排列成 void
指针数组的形式,然后将其传递给 NvFRUCRegisterResource()
函数。
按如下所示填写 NvFRUC_REGISTER_RESOURCE_PARAM
结构。
<b>pArrResource</b>
(输入):指向输入和输出资源数组的指针。<b>uiCount</b>
(输入):输入和输出资源的总数。
如果函数调用成功,它将返回 NvFRUC_SUCCESS
。
插值中间帧
通过循环调用 NvFRUCProcess
来提供连续的输入帧并获取插帧,如下所示。
NvFRUC_PROCESS_IN_PARAMS stInParams = { 0 };
NvFRUC_PROCESS_OUT_PARAMS stOutParams = { 0 };
status = NvFRUCProcess(
hFRUC,
&stInParams,
&stOutParams);
这是一个显示如何使用 NvFRUCProcess 函数的图。
图 4. 如何使用 NvFRUCProcess 函数

假设您有一个时间戳为 1、2、3 等的连续帧序列。您希望在这些帧之间的 1.5、2.5、3.5 等时间戳处插值帧。
在循环中调用 NvFRUCProcess
,其中 stInParams
设置为时间戳为 1、2、3 的帧,并且 stOutParams
中的时间戳字段设置为 1.5、2.5、3.5 等。对于第一次调用,NvFRUCProcess
函数返回时间戳为 1 的帧本身,因为它无法仅使用一帧来插值帧。从下一次调用开始,此函数返回插帧 1.5、2.5、3.5 等。
NvFRUCProcess
API 可用于在两帧之间的任何时间戳处插值帧。例如,1.25、1.50、1.75 等。请相应地使用 stInParams.nTimeStamp
和 stOutParams.nTimeStamp
的值。
NvFRUCProcess
API 使用当前调用中的帧和先前调用中缓存的帧来插值中间帧。用户不应使用同一帧多次调用 NvFRUCProcess
。
在调用 NvFRUCProcess
API 之前,按如下所示填写 stInParams
和 stOutParams
结构
stInParams
是 NvFRUC_PROCESS_IN_PARAMS
类型的结构,具有以下成员
按如下所示填写 stFrameDataInput
结构。
<b>pFrame</b>
(输入):指向原始输入帧数据的指针。<b>nTimeStamp</b>
(输入):输入帧的时间戳。<b>bHasFrameRepetitionOccurred</b>
(忽略):FRUC 库在参数stFrameDataInput
中忽略此标志的值。<b>uSyncWait</b>
(输出):如果您在 Windows 上的 DirectX 应用程序(例如游戏)中使用 FRUC API,则此成员用于 CUDA-DirectX 互操作的同步。FRUC 库支持在 Windows 操作系统内部版本 1703 及更高版本上使用 fence 进行同步,在其他 Windows 操作系统内部版本上使用 keyed mutex 进行同步。如果您使用ID3D11Fence
,请在此处递增 fence 值,以便库可以获取输入资源,否则递增 key 值。有关更多详细信息,请参阅 NVIDIA CUDA 编程指南中的 graphics-interoperability 部分。
stOutParams
是 NvFRUC_PROCESS_OUT_PARAMS
类型的结构,具有以下成员
<b>pFrame</b>
(输出):指向原始输出帧数据的指针。<b>nTimeStamp</b>
(输入):要插值的帧的时间戳。<b>bHasFrameRepetitionOccurred</b>
(输出):如果插帧未达到某个质量标准,FRUC 库会将上一帧作为插帧返回。在这种情况下,FRUC 库会将此标志设置为 true。如果有用,应用程序可以监视此标志。
成功后,该函数返回 NvFRUC_SUCCESS
。如果您在 DirectX 应用程序中使用 FRUC 库,则需要在用户线程上等待直到 NvFRUCProcess()
完成。对于 CUDA API,函数调用是阻塞调用。
注销资源
使用 NvFRUCUnregisterResource
函数注销资源,如下所示。
NvFRUC_UNREGISTER_RESOURCE_PARAM stUnregisterResourceParam = { 0 };
memcpy(stUnregisterResourceParam.pArrResource,
regOutParam.pArrResource,
regOutParam.uiCount * sizeof(IUnknown*));
stUnregisterResourceParam.uiCount = regOutParam.uiCount;
status = NvFRUCUnregisterResource(
hFRUC,
&stUnregisterResourceParam);
按如下所示填写 NvFRUC_UNREGISTER_RESOURCE_PARAM
结构。
<b>pArrResource</b>
(输入):指向输入和输出资源数组的指针。<b>uiCount</b>
(输入):输入和输出资源的总数。
如果函数调用成功,它将返回 NvFRUC_SUCCESS
。
销毁 FRUC 实例
最后,使用 NvFRUCDestroy
函数销毁 FRUC 实例,如下所示
status = NvFRUCDestroy(hFRUC);
此函数销毁 FRUC 实例,如果成功,则返回 NvFRUC_SUCCESS
。
诊断
所有 FRUC API 在成功时都返回状态 NvFRUC_SUCCESS
。如果失败,API 将返回错误代码,提示可能的失败原因。FRUC 头文件 NvFRUC.h
包含所有此类错误代码的列表。
声明
本文档仅供参考,不得视为对产品的特定功能、条件或质量的保证。NVIDIA Corporation(“NVIDIA”)对本文档中包含的信息的准确性或完整性不作任何明示或暗示的陈述或保证,并且对本文档中包含的任何错误不承担任何责任。NVIDIA 对因使用此类信息或因其使用可能导致的侵犯第三方专利或其他权利的行为的后果或使用不承担任何责任。本文档不构成对开发、发布或交付任何材料(如下所定义)、代码或功能的承诺。
NVIDIA 保留随时修改、修正、增强、改进本文档以及进行任何其他更改的权利,恕不另行通知。
客户应在下订单前获取最新的相关信息,并应验证此类信息是否为最新且完整。
NVIDIA 产品的销售受订单确认时提供的 NVIDIA 标准销售条款和条件的约束,除非 NVIDIA 和客户的授权代表签署的个别销售协议(“销售条款”)另有约定。NVIDIA 特此明确反对将任何客户通用条款和条件应用于购买本文档中引用的 NVIDIA 产品。本文档不直接或间接地构成任何合同义务。
NVIDIA 产品并非设计、授权或保证适用于医疗、军事、航空器、航天或生命维持设备,也不适用于 NVIDIA 产品的故障或失灵可能合理预期会导致人身伤害、死亡或财产或环境损害的应用。对于在上述设备或应用中包含和/或使用 NVIDIA 产品,NVIDIA 不承担任何责任,因此,此类包含和/或使用由客户自行承担风险。
NVIDIA 不保证或声明基于本文档的产品将适用于任何特定用途。NVIDIA 不一定会对每种产品的所有参数进行测试。客户全权负责评估和确定本文档中包含的任何信息的适用性,确保产品适用于且适合客户计划的应用,并为该应用执行必要的测试,以避免应用或产品的默认设置。客户产品设计中的缺陷可能会影响 NVIDIA 产品的质量和可靠性,并可能导致超出本文档中包含的附加或不同条件和/或要求。对于可能基于或归因于以下原因的任何默认设置、损坏、成本或问题,NVIDIA 不承担任何责任:(i) 以任何与本文档相悖的方式使用 NVIDIA 产品或 (ii) 客户产品设计。
商标
NVIDIA、NVIDIA 徽标以及 cuBLAS、CUDA、CUDA Toolkit、cuDNN、DALI、DIGITS、DGX、DGX-1、DGX-2、DGX Station、DLProf、GPU、Jetson、Kepler、Maxwell、NCCL、Nsight Compute、Nsight Systems、NVCaffe、NVIDIA Deep Learning SDK、NVIDIA Developer Program、NVIDIA GPU Cloud、NVLink、NVSHMEM、PerfWorks、Pascal、SDK Manager、Tegra、TensorRT、TensorRT Inference Server、Tesla、TF-TRT、Triton Inference Server、Turing 和 Volta 是 NVIDIA Corporation 在美国和其他国家/地区的商标和/或注册商标。其他公司和产品名称可能是与其关联的各自公司的商标。