这将是一个自我回答的问题,因为它使我整整一个星期都发疯了,并且我希望避免让其他程序员感到沮丧。

情况是这样的:您希望使用NVidia的NVEnc硬件编码器(分别在Kepler和Maxwell卡上可用,分别为GT(x)7xx和GT(x)9xx)通过UDP传输图形应用程序的输出。这不是简单的方法,但是它非常高效,因为它避免了将帧从视频存储器“下载”到系统存储器直到编码阶段之后的需要,因为NVEnc可以直接访问视频存储器。

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

当我尝试通过UDP流编码的帧时,问题就来了:VLC和MPlayer都无法渲染视频。事实证明,有两个原因,我将在回答中予以解释。

最佳答案

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

第一个原因与打包有关。 NVEnc用称为NALU的数据块填充其输出缓冲区,并使用主要用于位流同步的“起始代码”将其分开。 (如果您想了解更多有关附件B及其竞争对手AVCC的信息,请转至szatmary's优秀SO answer)。

现在的问题是,NVEnc有时会在单个输出缓冲区中提供多个这样的NALU。尽管大多数NALU都包含编码的视频帧,但有时也必须(在流的开头强制发送)一些元数据,例如分辨率,帧速率等。NVEnc还通过生成那些特殊的NALU来提供帮助(更多信息)在更远的地方)。

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

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

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

(其中m_stEncodeConfig是将传递给NvEncInitializeEncoder()的参数struct,1500是以太网数据包的MTU,28是IP4 header 和UDP header 的增加大小)。

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

这就是问题所在:除非另行通知,否则NVEnc只会在编码 session 的开始就生成元数据NALU。这是需要设置的编码器配置参数:
m_stEncodeConfig.encodeCodecConfig.h264Config.repeatSPSPPS = 1;

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

和瞧!消除了这些障碍,您将能够欣赏到生成压缩视频流的强大功能,而几乎不会增加CPU的负担。

编辑:
我意识到不鼓励使用这种超简单的UDP流,因为它实际上不符合任何标准。 Mplayer会播放这样的流,但是可以播放几乎所有内容的VLC不会播放。首要原因是数据流中没有任何内容甚至指示正在发送的媒体的类型(在这种情况下为视频)。我目前正在进行研究,以找到满足公认标准的最简单方法。

10-08 05:19