当使用 H264 编码的 MP4 文件设置为 slices=n 时,我在哪里可以知道当前 NALU 有多少个切片?

When MP4 files encoded with H264 are set to slices=n, where can I find out how many slices the current NALU is?

提问人:Gaowan Liang 提问时间:11/10/2023 更新时间:11/18/2023 访问量:163

问:

我正在做一个为网络视频生成缩略图的实验。我计划通过模拟解码器的工作原理,从二进制流中提取I帧,并将原始视频的PPS和SPS信息相加,形成H264原始信息,然后交给ffmpeg生成图像。我差不多解决了很多问题,甚至还写了一个demo来实现我的功能,但是当多个NALU形成一帧时,我找不到任何关于哪里有标识符的信息(严格来说,有一点,但不能解决我的问题,我后面再说)。

您可以使用以下命令生成我提到的视频类型:

 ffmpeg -i input.mp4 -c:v libx264 -x264-params slices=8 output.mp4

这将生成一个每帧有 8 个切片的视频。由于我稍后将使用此文件,因此我还将使用以下命令生成 H264 原始信息文件:

 ffmpeg -i output.mp4 -vcodec copy -an output.h264

当我将其放入分析程序时,我可以看到多个 IDR NALU 连接在一起,其中非第一个 IDR NALU 的 Slice Header 中的first_mb_in_slice不是 0:

但是当我回到 MP4 中的 mdat 并查看 NALU 时,所有first_mb_in_slice都变成了 0:

0x9a= 1001 1010,根据指数哥伦布编码,first_mb_in_slice == 0( ueg(1B) == 0 ), slice_type == P 帧 (ueg(00110B) == 5),但在 H264 raw 文件中使用相同的算法,结果与程序给出的结果相同。

有没有其他地方有此信息的标识符?假设我随机得到一个 NALU,我能知道这个视频是否被切片,或者我的操作是否错误?

PS:在解码器中只放一个NALU是可行的,但只能解析1/8的图像

如果你需要参考,我写的演示程序的地址是:https://github.com/gaowanliang/web-video-thumbnailer

python ffmpeg 编码 mp4 h.264

评论


答:

2赞 Markus Schumann 11/12/2023 #1

H.264 有两种格式。我们称它们为 mp4 和附录 B。

在附录 B 中,您的视频帧是接入单元 (AU)。

在磁盘或线路上,它看起来像这样:

|Access Unit Delimiter| |PPS| |SPS| |Slice (1)| |Slice (2)| ... |Slice (n)| |Access Unit Delimiter|

您必须解析切片标头以确定 I、P 或 B 帧。 您必须计算切片,直到命中下一个访问单元分隔符或流结束。

在附件B中,各种NAL单元(接入单元分隔符、PPS和切片)由分隔符“0x00 0x00 0x00 0x01”分隔

在 MP4 中,NAL 单位是其大小的前缀。 所以读取大小,读取 NAL 单元的类型(例如切片),解析切片标头以确定 I、P 或 B,转到下一个 NAL 单元。 在 MP4 中,容器会告诉您访问单元有多大 - 因此您不必寻找访问单元分隔符。

在这两种情况下(附件 B 或 MP4),您都可以简单地计算一个访问单元中的切片数量。

评论

0赞 Gaowan Liang 11/13/2023
非常感谢您的回答,但是,在 mp4 中,目前主流的绝大多数编码都是 avcC,而不是附录 B。
2赞 VC.One 11/17/2023 #2

“我打算提取 I 帧”

确保使用 IDR 关键帧(而不是 I 帧关键),因为 IDR 字节可以解码为完整的图像。一些 I 帧实际上可能需要其他 P/B 帧来制作完整的图像。

“当多个 NALU 形成一个帧时,我找不到有关标识符位置的任何信息”

MP4 级别的处理:

(1) 使用 SEI:(NALU 类型 6,通常为字节0x06)

溶液:在 SEI 文本中查找文本。slices=

MP4 可能包含 SEI 字节,该字节将位于第一个视频帧的字节前面。
SEI 是文本数据,如果使用 libx264 作为编码器,则它包含一个条目。
"slices="

libx264 编码中的 SEI 文本示例:

cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex ... (other texts)
sliced_threads=0 slices=8 nr=0 decimate=1 ... (other texts)
constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 ... (other texts)

通常 IDR 是一个大切片(即:真的是 libx264?我们现在对 IDR 帧进行切片?不过有什么好处呢?

using 将覆盖 1 切片默认值。正如你所看到的,现在有一个文本条目告诉我们要查找:每帧 8 个 NAL 单元(即使包括 IDR)。-x264-params slices=8"slices=8"

(2) 使用 STSS 和 STSZ:

溶液:STSZ 中列出的大小(或字节长度)将包括每帧的所有 NALU 切片。

  • “stss” == 字节(十六进制) == 整数 .73 74 73 730x73747373
  • “stsz” == 字节(十六进制) == 整数 .73 74 73 7A0x7374737A

MP4 的视频轨道将有一个示例表。它有一个 STSS 部分,用于列出所有 IDR 关键帧。然后它有一个 STSZ 部分,用于列出所有帧的字节长度。

使用 MP4 标头的这两个部分,您可以找出代表 IDR 的帧号,然后通过将 STSZ 条目的编号与相关帧号匹配来检查大小。

按 STSZ 中显示的大小提取关键帧以获得完整帧(包含所有 NALU 切片)。

H.264 级别的处理:

(1) 使用 SEI:(NALU 类型 6,通常为字节0x06)

溶液:在 SEI 文本中查找文本。slices=

与 MP4 相同的过程(如上所述)。

(2) 使用first_mb_in_slice:

溶液:对于第一个切片,值为 1,对于帧的所有其他切片,值为 0first_mb_in_slice

您可以通过检查 NALU 标头0x65后下一个字节的第一位来获得first_mb_in_slice

在 8 个切片(每帧)的情况下,当您找到一个 IDR 帧时,第一个切片的first_mb_in_slice将是 1,然后是另外 7 个 IDR 单元,每个单元的 0。first_mb_in_slice

在以下情况下,您将知道您有足够的 NALU 用于一帧:

  • 下一个 IDR 的 ID 再次变为 1(这意味着现在这是不同的 IDR)。first_mb_in_slice
  • 或者,当您获得 P/B 帧的 NALU 标头类型时。

评论

1赞 Gaowan Liang 11/17/2023
谢谢你的回答。一开始我想说的是,SEI 标头中会有一个 Slices=8 标识符,但我在写😅的时候忘记了。我之所以说这不能解决我的问题,是因为即使知道了slices=8,当我随机得到一个nalu时,我也无法分辨出是哪个切片。今天我详细对比了 mp4 和 ffmpeg 解析的 .h264 原始流文件,发现 slices=n 会导致样本包含 n nalus。我一直认为样本中只有一个 nalu,所以我一直认为 stsz 是 nalu 的长度。