提问人:Gaowan Liang 提问时间:11/10/2023 更新时间:11/18/2023 访问量:163
当使用 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?
问:
我正在做一个为网络视频生成缩略图的实验。我计划通过模拟解码器的工作原理,从二进制流中提取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
答:
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),您都可以简单地计算一个访问单元中的切片数量。
评论
“我打算提取 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 73
0x73747373
- “stsz” == 字节(十六进制) == 整数 .
73 74 73 7A
0x7374737A
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,对于帧的所有其他切片,值为 0。first_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 标头类型时。
评论