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

Posted

技术标签:

【中文标题】如何使用 NVidia NVEnc 硬件编码器通过 UDP 流式传输 H.264 视频?【英文标题】:How to stream H.264 video over UDP using the NVidia NVEnc hardware encoder? 【发布时间】:2016-01-16 02:53:54 【问题描述】:

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

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

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

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

【问题讨论】:

【参考方案1】:

就像我在问题中所说,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 提供了宝贵的洞察力,让您了解这些限制在各种情况下是什么。这里重要的是,虽然您不必自己处理这个问题,但您必须通过在创建编码器对象时设置以下参数来告诉 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,否则几乎可以播放任何东西,不会。最重要的原因是数据流中没有任何内容甚至指示正在发送的媒体类型(在本例中为视频)。我目前正在研究以找到满足公认标准的最简单方法。

【讨论】:

如果您采用 IETF 标准方式,您正在查看 rfc3550 (RTP/UDP) + rfc6184 (H.264 有效负载格式) 您可能希望实现 packetization-mode=0 (单 NAL 单元模式),因为您已经根据网络 MTU 配置了编码器,但如果您想跨多个 RTP 数据包聚合/分段 NAL 单元,则需要 packetization-mode=1(非交错)。跨度> 谢谢@Ralf。 RFC6184 仍然是一个选项,但我决定暂时 UDP 流是好的,因为流将由自定义软件生成和使用。 @JpNotADragon - 你能把你的联系信息发邮件给我吗@gmail.com,我想向你提出一些关于这个主题的问题(谢谢!) 关于如何分割 UDP 数据包的任何建议?我遇到了和你完全相同的问题。我不认为在编码器中设置切片模式实际上有帮助,因为我只是让 nvenc 写入一个向量,然后我将其作为纯字节读出。 @HugoZink 我自己的代码不再分解 NVENC 生成的 NALU,而是依赖 FFMPEG 库。它按原样获取 NVENC 的输出(可以是一个 NALU 或更多),将其放入 AVPacket(涉及分配时间戳)并通过 av_write_frame() 发送。 FFMPEG 负责其他一切。此外,自从写了这个答案后,我发现简单的 UDP 可能不是你的最佳选择——丢包比我想象的要糟糕得多。只需使用 TCP,除非您真的想实现自己的纠错/管理。

以上是关于如何使用 NVidia NVEnc 硬件编码器通过 UDP 流式传输 H.264 视频?的主要内容,如果未能解决你的问题,请参考以下文章

如何在ffmpeg中指定nvenc使用的GPU [关闭]

我可以在 nVidia GPU 中使用硬件编码器 NVEN 压缩 8 个 FullHD 30fps 视频流吗?

ffmpeg使用硬件加速hwaccelcuvidh264_cuvidh264_nvenc

ffmpeg使用硬件加速hwaccelcuvidh264_cuvidh264_nvenc

ffmpeg使用硬件加速hwaccelcuvidh264_cuvidh264_nvenc

ffmpeg使用硬件加速hwaccelcuvidh264_cuvidh264_nvenc