如何使用 NVidia NVEnc 硬件编码器通过 UDP 流式传输 H.264 视频?

How to stream H.264 video over UDP using the NVidia NVEnc hardware encoder?

这将是一个自我回答的问题,因为它在整整一周的时间里让我抓狂,我希望让其他程序员免去我经历的挫败感。

情况是这样的:您希望使用 NVidia 的 NVEnc 硬件编码器(在 Kepler 和 Maxwell 卡上可用,即分别为 GT(x) 7xx 和 GT(x) 9xx)通过流式传输图形应用程序的输出UDP。这不是一个简单的路径,但它可以非常有效,因为它避免了 "download" 帧从视频内存到系统内存的需要,直到编码阶段之后,因为 NVEnc 有能力直接访问视频内存。

我已经设法通过简单地逐帧写入 NVEnc 的输出缓冲区来生成 .h264 文件 来完成这项工作。 VLC 播放这样的文件没有问题,只是时间不对(我没有尝试解决这个问题,因为我只需要那个文件用于调试目的)。

当我尝试通过 UDP 流式传输编码帧时出现问题:VLC 和 MPlayer 都无法渲染视频。原来有两个原因,我会在我的回答中解释。

正如我在问题中所说,MPlayer 无法播放我的 UDP 流有两个(实际上是三个)原因。

第一个原因与打包有关。 NVEnc 用称为 NALU 的数据块填充其输出缓冲区,它用 "start codes" 分隔,主要用于比特流同步。 (如果您想了解有关附件 B 及其竞争对手 AVCC 的更多信息,请转至 szatmary's excellent SO answer)。

现在的问题是 NVEnc 有时会在单个输出缓冲区中提供多个这样的 NALU。尽管大多数 NALU 包含编码的视频帧,但有时也有必要(并且在流的开头是强制性的)发送一些元数据,如分辨率、帧率等。NVEnc 通过生成那些特殊的 NALU 来帮助实现这一点(更多再往下)。

事实证明,播放器软件不支持在单个 UDP 数据包中获取多个 NALU。这意味着您必须编写一个简单的循环来查找起始代码(两个或三个“0”字节后跟一个“1”字节)以分割输出缓冲区并在其自己的 UDP 数据包中发送每个 NALU。 (但请注意,UDP 数据包仍必须包含这些起始代码。)

打包的另一个问题是IP数据包通常不能超过一定的大小。同样,SO answer 提供了有关这些限制在各种情况下的宝贵见解。这里重要的是虽然你不必自己处理这个,但你必须告诉 NVEnc "slice" 它的输出,通过在创建编码器时设置以下参数 object:

m_stEncodeConfig.encodeCodecConfig.h264Config.sliceMode = 1;
m_stEncodeConfig.encodeCodecConfig.h264Config.sliceModeData = 1500 - 28;

m_stEncodeConfig 是将传递给 NvEncInitializeEncoder() 的参数结构,1500 是以太网数据包的 MTU,28 是 IP4 header 和一个 UDP header).

MPlayer 无法播放我的流的第二个原因与流视频的性质有关,而不是将其存储在文件中。当播放器软件开始播放 H.264 文件时,它会找到包含分辨率、帧率等的所需元数据 NALU,存储该信息,因此再也不需要它了。而当被要求播放流时,它将错过该流的开头并且在发送者 re-sends 元数据之前无法开始播放。

这就是问题所在:除非另有说明,否则 NVEnc 只会在编码 session 的最开始生成元数据 NALU。下面是需要设置的编码器配置参数:

m_stEncodeConfig.encodeCodecConfig.h264Config.repeatSPSPPS = 1;

这会不时地告诉 NVEnc re-generate SPS/PPS NALU(我认为默认情况下,这意味着每个 IDR 帧)。

瞧!清除这些障碍后,您将能够体会到生成压缩视频流的强大功能,同时几乎不会对 CPU 造成负担。

编辑: 我意识到不鼓励这种 ultra-simple UDP 流式传输,因为它实际上不符合任何标准。 Mplayer 将播放这样的流,但 VLC 则不会,否则它几乎可以播放任何东西。最重要的原因是数据流中没有任何内容甚至可以指示正在发送的媒体类型(在本例中为视频)。我目前正在研究以找到满足公认标准的最简单方法。