MP4FF-超越MP4盒
#go #视频 #mp4

mp4ff包括一个开源库和用于处理MP4文件的工具。它是用GO编写的,我开始编写它,因为我发现GO是编写后端服务和命令行工具的好语言,但没有找到可以在我需要的所有方面处理零散文件的表演库。随着时间的流逝,我的需求不断增长,mp4ff也有所增长。大多数代码仍然是我写的,但是同事也有许多贡献。欢迎更多!

mp4ff可以处理文件格式的构建块的大多数mp4 boxes的通常任务,但它超越了其中的代码和工具来分析和安排实际媒体示例。在简要介绍文件格式之后,我将更多地研究我认为该库和工具的独特或至少很少见的功能。

背景

数字视频和音频都由采样或生成的数据(例如像素)组成,这些数据使用各种标准或专有算法压缩。结果需要互补的元数据等二进制数据,例如时间戳记,语言,分辨率等。由于这是许多不同媒体的常见场景,因此有一些容器格式可用于将或运输此类媒体数据及其元数据一起存储或运输此类媒体数据。

非常广泛的这样的容器格式是MPEG-4(MP4)文件格式。它的构建块是盒子,所有盒子都具有4字节大小,4字节类型和长度尺寸8的简单结构(加上较大盒子的逃生机构)。可以描述为:

Image description

这样的盒子可以彼此堆叠,并且有大量的框类型来处理许多不同的元数据和用例。大多数盒子都是在MPEG标准中指定的,但是其他标准也有盒子以及专有扩展。 mp4ff库当前支持110多个不同的盒子。

格式的历史记录回到了Apple发明的QuickTime文件格式。在1990年代,MPEG选择以QuickTime为基础构建其新文件格式。稍后,文件格式规范分为ISO/IEC 14496 MPEG-4标准的多个部分,其中第12部分(ISO/IEC 14496-12)是基本部分(ISO基本媒体文件格式-ISOBMFF),第14部分包含MPEG-4特定部分,第15部分定义了MPEG视频编解码器封装,第30部分描述了字幕包装。

在此论坛中,已经对MP4/ISOBMFF文件格式进行了许多不错的介绍,因此该帖子的重点不是文件格式和框结构。相反,我们查看有关媒体,提供MP4FF的工具的数据以及编写有效的MP4文件格式应用程序所需的一些优化。

渐进式vs分段文件

MP4文件的原始设计有两个主要部分:一个带有元数据的部分,另一部分带有媒体数据。元数据是在[moov]框中结构的,并且媒体数据位于[mdat]框中。 [moov]框包含有关轨道,编解码器的信息,以及有关每个MP4样本的大小,偏移和时机的详细信息。 MP4样本通常是视频图片,由一组压缩音频采样值或一些字幕文本组成的音频框架。轨道提供了定时的样本序列,可以交织到progressive mp4文件中,该文件可以在下载文件的同时在媒体播放器中播放。

可以使用MP4FF工具mp4ff-info研究(渐进)MP4文件的框结构。它将打印所有框名,大小和数据的概述。使用命令行参数,可以获取有关特定框类型或所有框的更详细信息。

使用mp4ff-crop裁剪渐进式文件6

在使用媒体时,很快就会显而易见,编码和包装有很大变化。因此,重要的是收集可用于回归测试的测试资产目录。但是,视频资产通常很长,需要大量存储,因此资产的“精确”缩短版本非常方便。

这就是我开发mp4ff-crop工具的原因。它缩短了渐进式MP4文件,并尽可能将原始盒子的每个盒子保持不变。当涉及实际的示例信息时,该信息当然是截断的,但是它以使交织尽可能完好无损的方式完成。

MP4片段

渐进式文件适合于单双质电影,但对于HTTP流媒体系统(例如HLS和MPEG-DASH)中的实时内容和自适应比特率(ABR)切换过于僵化。这些需要进一步的MP4文件格式,称为片段。

在零碎的情况下,仍然有一个初始的[moov]元数据框,但不包含有关单个样本的任何信息。示例元数据和媒体数据可作为由[moof],Movie 片段框组成的MP4片段提供的,然后是一个包含相应媒体样本的[mdat]框。这种片段的典型持续时间为几秒钟,但持续时间可能短于单个样本。

低延迟和多碎片段

HTTP流媒体解决了使用现有基于HTTP的内容分布网络在一般IP基础架构上通过一般IP基础架构分发视频的问题。但是,出于效率原因,段持续时间通常被选择为几秒钟,例如如HLS所建议的6s。这违背了能够在较低延迟中分发实时视频的愿望。达到这种较低潜伏期的HLS和MPEG污垢解决方案均基于零散的段。

碎片或分裂的段本质上意味着每个流段不是一个,而是多个MP4片段。一个完整的细分市场看起来像

Image description

如果[styp]框向新段的开头发出信号,而仅需要第一个[mdat]框才能从关键框架开始。每个[moof][mdat]对都是一个新片段。使用这种结构,可以通过片段生成和分布片段片段,从而避免等待整个段。

重新包装媒体

从上述情况下,应该很明显,通常需要重新包装媒体数据。渐进式MP4文件是分发完整电影的好方法,而零散的MP4文件非常适合VOD和LIVE的ABR流。

mp4ff库不仅可以解析和编写框,而且还支持更改结构。为此,MP4FF在碎片的文件中添加了一个额外的图层,该文件将框架盒中的结构组合到InitSegmentMediaSegment类型的结构中,后者又包含Fragment的列表。

一些代码示例可用于:

第一个将渐进式文件片段片段段分段文件,第二个更改已经分割的文件的段持续时间。这些只是示例,但为产品实施提供了基本核心。

另一个复杂性是同一文件中的多个轨道,例如组合视频和一个或多个音频轨道。通常,这对于ABR流媒体来说不是一个好主意,因为它需要大量的曲目复制来提供所有组合。尽管如此,如examples/multitrack所示,MP4FF可以支持该用例,该用例可以通过视频和QuickTime CEA-608字幕读取简单的测试资产并提取标题数据。

视频编解码器,NAL单位和mp4ff-nallister

mp4ff库支持处理两个视频编解码器的元数据和样本H.264/AVCH.265/HEVC。这些编解码器具有双重名称,因为它们是由ITU-T和MPEG共同开发的。 H.264和H.265名称来自ITU-T,AVC和HEVC名称来自MPEG。在mp4ff代码中,编解码器称为AVC和HEVC。

从AVC标准开始,在称为Network Abstraction Layer的视频Bitstream语法中引入了一个新层。它由NALUs(网络抽象层单元)组成。这个想法是将媒体数据数据独立于运输,这不是MPEG-2视频,H.263等较早标准的情况,而有了这个概念,视频框架(图片)由一个或多个Nalus组成,每个NALU以NALU标头发信号开头。这些纳鲁斯的运输在不同的系统之间有所不同,但实际的nalu含量始终相同。

在MP4文件中,视频帧是一个MP4样本,每个样本都有时间,持续时间,尺寸和偏移。因此,无需开始代码即可找到帧的开始和长度。因此,MP4视频样本由一个或多个Nalus组成,前面是一个固定尺寸的长度字段,一个人可以通过向前跳动该长度来找到下一个nalu。这与没有位置或长度信息的MPEG-2传输流相反,因此依靠开始代码来找到Nalus的开始。

NALU类型提供有关图片类型的信息,但也可以揭示元数据信息。创建了该工具mp4ff-nallister以显示MP4文件视频样本中的Nalus的信息。

可以像
一样运行

> mp4ff-nallister 0.m4s
Sample 1, pts=0 (20762B): SEI_6 (9B), SEI_6 (756B), SEI_6 (7B), IDR_5 [I] (19974B)
Sample 2, pts=1536 (172590B): SEI_6 (7B), NonIDR_1 [P] (172575B)
Sample 3, pts=512 (7123B): SEI_6 (7B), NonIDR_1 [B] (7108B)
...

并为每个视频示例打印一行。在这里,人们可以看到第一个视频示例具有演示时间(PTS)0,并且在实际视频以IDR(类型5)NALU开始之前,有三个SEI(补充增强信息)(6型)Nalu。

SEI NAL单元可以包含许多不同的信息。一些示例包括时间安排,编码X264的设置,HEVC的HDR信息和CEA-608封闭标题。使用参数-sei 1,该工具提供了有关SEI Nalus的更多信息。 NALU类型对名称的MP4FF映射应完成,但解析仅限于一个较小的集合。

参数集和mp4ff-pslister

视频解码器正在解码视频给定上下文。最高级别的上下文(例如分辨率,编码设置等)由参数集的层次结构给出。在解码时使用的参数集与实际编码参数对齐。因此,在缝合视频时,重要的是要知道任何基本参数是否已更改。在这种情况下,必须使用先前传输的另一个参数集(它们编号),或者必须在视频流中发送新的参数集内置。参数集始终以特定类型的纳卢斯进行。

我经常发现这对于获取有关参数集的详细信息很重要,因此,MP4FF库中包含的工具称为mp4ff-pslister。它解码并打印参数集信息。对于H.264/AVC,它应该完成,但是H.265/HEVC

尚未这样做。

参数集可以在moov框中深处传达,也可以在视频样本本身中作为nalus传达。该工具将它们从两个地方提取,也可以解释十六进制字符串。

作为一个例子,在初始段上运行该工具(包含带有参数集的moov框)产量:

> mp4ff-pslister -v -i init.mp4
Video avc track ID=1
SPS 1 len 37B: 67640028acd940780227e5ffffffffffda808080980800026e8f00004dd1e8a4c01e30632c
{
  "Profile": 100,
  "ProfileCompatibility": 0,
  "Level": 40,
  "ParameterID": 0,
  "ChromaFormatIDC": 1,
  "SeparateColourPlaneFlag": false,
  "BitDepthLumaMinus8": 0,
  "BitDepthChromaMinus8": 0,
  ...
}
PPS 1 len 5B: 68ebecb22c
{
  "PicParameterSetID": 0,
  "SeqParameterSetID": 0,
  "EntropyCodingModeFlag": true,
  ...
}
Codecs parameter (assuming avc1) from SPS id 0: avc1.640028

sps和pps线之后的十六进制字符串是二进制有效载荷数据,而后面的块显示了参数集中携带的所有参数。

MP4文件中的字幕和mp4ff-wvttlister

MPEG-4标准的第30部分描述了如何在MP4文件中携带TTML和WebVTT字幕。这些方法大不相同。对于TTML,使用原始的XML结构,通常每个片段单个样本。该样本是带有内部时间戳的完整TTML XML文档。除文本外,它还可以提供图像字幕并携带诸如子样本之类的图像。该文件的编解码器属性是stpp,并且有一个[stpp]框应包含在轨道样本描述符中。 mp4ff支持所有相关的盒子。

WebVTT内容以更接近视频和音频的方式存储。字幕状态的每个更改都必须是一个新样本,并且必须用空的(无字幕)或提示(带有样式的文本)样本覆盖完整的间隔。样本本身是像[vtte]这样的MP4框,用于空样品,而[vttc]用于文本提示。碎片Webvtt的编解码器属性是wvtt,并且有一个[wvtt]示例描述框。为了提取定时和框结构的字幕,MP4FF中包含一个工具mp4ff-wvttlister。我发现使用WVTT文件时非常有用。

性能和优化

对于小文件和罕见的解析,任何GO代码都可能足够快以处理MP4文件。但是,当使用大文件和即时重新包装时,通常会有一些需要优化的瓶颈。

内存分配

在GO中,要考虑的一般性能领域是堆的堆。基准测试和分析表明,解析盒子时确实有很多记忆分配。部分原因是每个盒子使用io.LimitReader。该层引入了额外的切片分配。由于MP4-box具有一个尺寸字段,该字段在阅读之前揭示了盒子的大小,因此可以更进一步,分配一个大切片,其中包含顶级盒子及其所有子盒。通过使用称为SliceReader的结构来实现。该领域的性能改进是在V0.27版本中完成的,可以在下面的基准表中看到增益:

名称\ time/op v0.26 v0.27 v0.27-sr
decodefile/1.m4s-16 21.9âµs 6.7 µs 2.6âµs
decodefile/prog_8s.mp4-16 143 µs 48 µs 16âµs

从v0.26到v0.27时,每个文件读取的执行时间约为3倍,而使用sliceReader时的另一个因素为3。该库现在为每个盒子类型Fooo:一个DecodeFooo提供了两个解码功能,该功能使用了io.Reader和一个DecodeFoooSR,该DecodeFoooSR从slicereader中读取。建议是使用后一种变体。

懒惰解码

另一个优化领域是避免将媒体数据读为内存。该数据可能是巨大的(甚至超出了需要64位变体的32位尺寸字段)。因此,在解码文件时有一个选项

一个典型的电话看起来像:

    parsedFile, err := DecodeFile(file, WithDecodeMode(DecModeLazyMdat))

file必须实现io.ReadSeeker接口。

阅读多个样本

重新包装数据时提高性能的另一种方法是同时获取多个样本的数据。该领域的有用功能是

   func (f *Fragment) GetSampleInterval(trex *TrexBox, startSampleNr, endSampleNr uint32) (SampleInterval, error)

返回样本数据的间隔。

非最佳MP4结构

我想提出的最后一个区域是,MP4框的数据结构并不总是最佳的。例如,让我们讨论“示例框的构图时间” [ctts]

此框是渐进式MP4文件的一部分,并包含一个对(sample_count, sample_offset)的列表,该框告诉演示时间戳与Decode Timestamp在sample_count示例间隔中应有的不同。这些值往往因样品而异,因此每个列表条目基本上有一个条目。对于25架/s的2小时电影,桌上有大约18万个条目。如果一个人使用与标准中相同的结构存储数据,则找到特定样本的值非常低,因为必须通过线性搜索为每个样本读取sample_count。实际上,在[ctts]中查找值是CPU瓶颈部的主要CPU瓶颈,用于将大型进步的MP4文件转换为一个分段。通过重写代码以将sample_offset作为单个值存储并使用二方算法来查找正确的值,整个程序的CPU使用率下降了5倍。

缓冲i/o

要记住的一般一件事是I/O操作很昂贵,因此缓冲I/O通常有益于减少所需的系统资源。为此,bufio标准库是一个很好的起点。

结论

mp4ff是一个带有一组有用工具的GO库:

  • mp4ff-crop作物渐进式文件,更改
  • mp4ff-info探索了MP4文件的框结构
  • mp4ff-pslister提取参数集信息
  • mp4ff-nallister提供了有关Nalus的信息
  • mp4ff-wvttlister列出了wvtt文件中的字幕样本

如果您已经安装了1.16版或更高版本,则可以通过键入:
轻松安装任何工具,例如mp4ff-info

> go install github.com/Eyevinn/mp4ff/cmd/mp4ff-info@latest

mp4ff正在积极开发中,希望对使用GO中的MP4文件开发媒体服务的任何人,尤其是使用零碎的MP4文件(例如MPEG-DASH和HLS)的服务。

Go是一种以其可读性而闻名的出色语言。我希望您发现mp4ff代码易于使用。