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

10

这将是一个自我回答的问题,因为在整整一周的过程中它让我发疯了,我希望能够减轻程序员们面对的挫败感。

情况是这样的:您希望使用NVidia的NVEnc硬件编码器(适用于Kepler和Maxwell卡,即GT(x)7xx和GT(x)9xx)通过UDP流传输图形应用程序的输出。这不是一条简单的路径,但它可以非常高效地绕过需要在编码阶段之后才“下载”视频内存到系统内存的需求,因为NVEnc具有直接访问视频内存的能力。

我已经成功使此功能工作,只需将NVEnc的输出缓冲区逐帧写入.h264文件即可。VLC可以轻松播放这样的文件,但时间不准确(我没有尝试修复这个问题,因为我只需要该文件进行调试)。

问题出现在我尝试通过UDP流传输编码帧时:无论是VLC还是MPlayer都无法呈现视频。事实证明,这有两个原因,我将在我的答案中解释。

1个回答

15

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

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

现在的问题是,NVEnc有时会在单个输出缓冲区中传递多个NALU。虽然大多数NALU包含编码的视频帧,但有时需要(并且在流的开始处是强制性的)发送一些元数据,例如分辨率、帧速率等。NVEnc通过生成这些特殊的NALU来帮助解决此问题(稍后将详细介绍)。

结果发现,播放器软件不支持在单个UDP数据包中获取多个NALU。这意味着您必须编写一个简单的循环,查找起始码(由两个或三个“0”字节跟随一个“1”字节组成),以切割输出缓冲区并将每个NALU发送到自己的UDP数据包中。(请注意,UDP数据包仍必须包括这些起始码。)

数据包处理的另一个问题是,IP数据包通常不能超过一定的大小。同样,SO答案提供了有关不同情境下这些限制的宝贵见解。重要的是,在您不必自己处理此问题的同时,您必须在创建编码器对象时设置以下参数,告诉NVEnc对其输出进行“切片”:

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

(使用m_stEncodeConfig作为参数结构传递给NvEncInitializeEncoder(),以太网数据包的MTU为1500,IP4头和UDP头大小之和为28)。

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

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

m_stEncodeConfig.encodeCodecConfig.h264Config.repeatSPSPPS = 1;

这会告诉NVEnc定期重新生成SPS/PPS NAL单元(我认为默认情况下,这意味着每个IDR帧都会重新生成)。

大功告成!克服了这些障碍后,您将能够欣赏到在几乎不影响CPU的情况下生成压缩视频流的强大功能。

编辑: 我意识到这种超级简单的UDP流式传输是不被鼓励的,因为它实际上并没有符合任何标准。Mplayer可以播放这样的流,但VLC却不能,尽管它几乎可以播放任何东西。主要原因是数据流中甚至没有表明发送的介质类型(在本例中是视频)。我目前正在研究最简单的方法以满足公认的标准。


如果您遵循IETF标准,您需要查看rfc3550(RTP / UDP)+ rfc6184(H.264负载格式)。您可能希望实现packetization-mode = 0(单个NAL单元模式),因为已根据网络MTU配置了编码器,但是如果您想要在多个RTP数据包中聚合/分段NAL单元,则需要使用packetization-mode = 1(非交错)。 - Ralf
感谢 @Ralf。RFC6184仍然是一个选择,但我决定暂时使用UDP流媒体,因为流将由自定义软件生成和消费。 - JPNotADragon
@JpNotADragon - 你能发邮件给我吗?我的联系方式是<mySOusername>@gmail.com,我想向你请教一些关于这个主题的问题(谢谢!) - jordan.baucke
@HugoZink 我自己的代码不再切割NVENC生成的NALU,而是依赖于FFMPEG库。它将NVENC的输出(可以是一个NALU或多个)直接放入AVPacket中(涉及分配时间戳),并通过av_write_frame()发送。FFMPEG会处理其他所有事情。此外,我发现自从写下这个答案以来,简单的UDP可能并不是你最好的选择 - 数据包丢失比我预期的要严重得多。除非你真的想实现自己的错误纠正/管理,否则只需使用TCP即可。 - JPNotADragon
1
@JPNotADragon,我在查找ffmpeg内容时偶然发现了答案,并且在输出中得到了一堆“non-existing PPS 1 referenced”,而内容正在播放时这些输出会一直持续。当然,SO有答案,那是因为缺少SPSPPS。我确认包含repeatSPSPPS=1的存档内容会减少这些问题,而且即使出现这些问题,也只有很少的情况,直到最后它变成一个,此时就会平息。 - LB2
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接