10

This is going to be a self-answered question, because it has driven me nuts over the course of a full week and I wish to spare fellow programmers the frustration I went through.

The situation is this: you wish to use NVidia's NVEnc hardware encoder (available on Kepler and Maxwell cards, i.e. GT(x) 7xx and GT(x) 9xx, respectively) to stream the output of your graphics application via UDP. This is not a trivial path to take, but it can be very efficient as it circumvents the need to "download" frames from video memory to system memory until after the encoding stage, because NVEnc has the ability to access video memory directly.

I had already managed to make this work insofar as to generate a .h264 file by simply writing NVEnc's output buffers to it, frame after frame. VLC had no trouble playing such a file, except that the timing was off (I didn't try to fix this, as I only needed that file for debugging purposes).

The problem came when I tried to stream the encoded frames via UDP: neither VLC nor MPlayer were able to render the video. It turned out there were two reasons for that, which I'll explain in my answer.

4

1 回答 1

14

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

第一个原因与打包有关。NVEnc 用称为 NALU 的数据块填充其输出缓冲区,并用主要用于比特流同步的“起始码”分隔这些数据块。(如果您想了解有关附件 B 及其竞争对手 AVCC 的更多信息,请访问szatmary 的优秀SO 答案)。

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

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

打包的另一个问题是 IP 数据包通常不能超过一定的大小。同样,一个SO 答案提供了有价值的洞察力,了解这些限制在各种情况下是什么。这里重要的是,虽然您不必自己处理这个问题,但您必须通过在创建编码器对象时设置以下参数来告诉 NVEnc “切片”其输出:

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

m_stEncodeConfig作为将传递给的参数结构NvEncInitializeEncoder(),1500 是以太网数据包的 MTU,28 是 IP4 标头和 UDP 标头的添加大小)。

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

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

m_stEncodeConfig.encodeCodecConfig.h264Config.repeatSPSPPS = 1;

这告诉 NVEnc 不时地重新生成 SPS/PPS NALU(我认为默认情况下,这意味着每个 IDR 帧)。

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

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

于 2015-10-17T12:27:59.990 回答