Project Icon

mp4ff

Go语言实现的MP4文件解析与生成库

mp4ff是一个Go语言实现的MP4文件解析和生成库。它主要用于处理DASH、MSS和HLS fMP4等流媒体格式的分片MP4文件,支持AVC和HEVC视频、AAC和AC-3音频以及stpp和wvtt字幕。库提供API和命令行工具,可用于分析MP4结构、提取编解码器信息、重新分段和加解密。mp4ff优化了内存管理和I/O处理,能高效处理大型MP4文件。

Logo

测试 golangci-lint GoDoc Go 报告卡 许可证

mp4ff 包实现了 MP4 媒体文件的解析和写入,支持 AVC 和 HEVC 视频、AAC 和 AC-3 音频以及 stpp 和 wvtt 字幕。它主要针对 DASH、MSS 和 HLS fMP4 流媒体所使用的分段文件,但也可以解码和编码渐进式 MP4 文件所需的所有盒子。特别是,mp4ff-crop 工具可用于裁剪渐进式文件。

命令行工具

cmd 目录中提供了一些有用的命令行工具。

  1. mp4ff-info 打印 mp4 文件的盒子层级树结构,并提供盒子信息。可以使用 -l 选项增加详细程度,如 -l all:1 适用于所有盒子,或 -l trun:1,stss:1 适用于特定盒子。
  2. mp4ff-pslister 从 mp4 或字节流(Annex B)文件中提取并显示 AVC 或 HEVC 的 SPS 和 PPS。对于 HEVC,会打印部分信息。
  3. mp4ff-nallister 列出渐进式或分段文件中视频的 NALU 和图像类型。
  4. mp4ff-subslister 列出 wvtt 或 stpp(ISOBMFF 中的 WebVTT 或 TTML)字幕样本的详细信息。
  5. mp4ff-crop 将渐进式 mp4 文件裁剪到指定时长。
  6. mp4ff-encrypt 使用 cenc 或 cbcs 通用加密方案加密分段文件。
  7. mp4ff-decrypt 解密使用 cenc 或 cbcs 通用加密方案加密的分段文件。

你可以通过进入各个工具的目录并运行 go install . 来安装这些工具,或直接从仓库安装:

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

示例代码

examples 目录中提供了示例代码。 这些示例及其功能如下:

  1. initcreator 为视频和音频创建典型的初始化段(ftyp + moov)。
  2. resegmenter 读取分段文件(CMAF 轨道)并使用 fullSample 以其他段持续时间重新分段。
  3. segmenter 接受渐进式 mp4 文件并从中创建初始化段和媒体段。该工具已扩展以支持生成多轨道段,以及以惰性模式读写 mdat
  4. multitrack 解析具有多个轨道的分段文件。
  5. combine-segs 将单轨道初始化段和媒体段合并为多轨道段。

该库在 mp4ff/mp4 包中提供了解析(称为 Decode)和写入(Encode)的功能。它还包含 mp4ff.avc 包中的 AVC/H.264 编解码器特定解析,包括完整的 SPS 和 PPS 解析。HEVC/H.265 解析不太完整,可在 mp4ff.hevc 中使用。补充增强信息可以使用 mp4ff.sei 包进行解析和写入。

可以解析和解码传统的非分段多路复用 mp4 文件,但重点是 DASH、HLS 和 CMAF 中使用的分段 mp4 文件。

除了单轨道分段文件外,还增加了对解析和生成多轨道分段文件的支持,可以在 examples/segmentexamples/multitrack 中看到。

非分段和分段 mp4 文件的顶层结构都是 mp4.File

在渐进式(非分段)mp4.File 中,顶层属性 Ftyp、Moov 和 Mdat 指向相应的盒子。

分段 mp4.File 可以或多或少地完整,如单个初始化段、一个或多个媒体段,或两者的组合,如可渲染成可播放单轨道资产的 CMAF 轨道。它也可以有多个轨道。 对于分段文件,使用以下高级属性:

  • Init 包含 ftypmoov 盒子,提供分段文件的一般元数据。它对应于 CMAF 头部。它还可以包含一个或多个 sidx 盒子。
  • SegmentsMediaSegment 的切片,以可选的 styp 盒子开始,可能有一个或多个 sidx 盒子,然后是一个或多个 Fragment
  • Fragment 是一个 mp4 片段,恰好有一个 moof 盒子,后跟一个包含媒体数据的 mdat 盒子。它可以有一个或多个 trun 盒子,包含样本的元数据。

容器盒子(如 MoovBox)的所有子盒子都列在 Children 属性中,但最重要的子盒子有直接链接的名称,这使得可以写出如下路径:

fragment.Moof.Traf.Trun

以访问只有一个 traf 盒子的片段中的(唯一)trun 盒子,或

fragment.Moof.Trafs[1].Trun[1]

以获取第二个 traf 盒子的第二个 trun(如果存在)。必须注意确保中间指针都不为 nil,以避免 panic

创建新的分段文件

一个典型的用例是创建一个由初始化段后跟一系列媒体段组成的片段。

第一步是创建初始化段。这可以通过三个步骤完成,如 examples/initcreator 中所示:

init := mp4.CreateEmptyInit()
init.AddEmptyTrack(timescale, mediatype, language)
init.Moov.Trak.SetHEVCDescriptor("hvc1", vpsNALUs, spsNALUs, ppsNALUs)

这里第三步将编解码器特定参数填入单个轨道的样本描述符中。多个轨道也可以通过 Traks 切片属性而不是 Trak 来使用。

第二步是开始生成媒体段。它们应该使用创建初始化段时设置的时间刻度。通常,应选择时间刻度使样本持续时间具有精确值,无需四舍五入。

媒体段包含一个或多个片段,每个片段都有一个 moof 和一个 mdat 盒子。如果在创建段之前所有样本都可用,可以在每个段中使用单个片段。这方面的示例代码可以在 examples/segmenter 中找到。

创建媒体段的一种简单但不优化的方法是首先创建一个包含所需数据的 FullSample 切片。mp4.FullSample 的定义如下:

mp4.FullSample{
 Sample: mp4.Sample{
  Flags uint32 // 标记同步样本等
  Dur   uint32 // 样本持续时间(以 mdhd 时间刻度为单位)
  Size  uint32 // 样本数据大小
  Cto   int32  // 有符号合成时间偏移
 },
 DecodeTime uint64 // 绝对解码时间(偏移量 + 累积样本 Dur)
 Data       []byte // 样本数据
}

mp4.Sample 部分是将写入 trun 盒子的内容。DecodeTime 是累积的媒体时间线时间。片段第一个样本的 DecodeTime 值将被设置为 tfdt 盒子中的 BaseMediaDecodeTime

一旦有了一定数量的这样的完整样本,就可以将它们添加到媒体段中

seg := mp4.NewMediaSegment()
frag := mp4.CreateFragment(uint32(segNr), mp4.DefaultTrakID)
seg.AddFragment(frag)
for _, sample := range samples {
 frag.AddFullSample(sample)
}

最后可以将该片段输出到一个 w io.Writer 中:

err := seg.Encode(w)

对于多轨道片段,代码会稍微复杂一些。请查看 examples/segmenter 了解具体实现。处理媒体样本的更优方式是延迟处理,下面将对此进行解释。

mdat 数据的延迟解码和写入

对于视频和音频,mp4 文件的主要部分是存储在一个或多个 mdat box 中的媒体数据。在某些情况下,例如对大型渐进式文件进行分段时,只读取 moovmoof box 中的电影或片段数据,并将 mdat box 中的媒体数据读取推迟到后面,这种方式在内存使用上会更加高效。

要以延迟模式进行解码,可以如下运行 mp4.DecodeFile():

parsedMp4, err = mp4.DecodeFile(ifd, mp4.WithDecodeMode(mp4.DecModeLazyMdat))

在这种情况下,不会读取 mdat box 的媒体数据,只会设置其大小。要读取或复制与样本对应的实际数据,必须计算相应的字节范围,然后调用:

func (m *MdatBox) ReadData(start, size int64, rs io.ReadSeeker) ([]byte, error)

func (m *MdatBox) CopyData(start, size int64, rs io.ReadSeeker, w io.Writer) (nrWritten int64, err error)

包括延迟写入 mdat 在内的示例代码可以在 examples/segmenter 中找到,将 lazy 模式设置为开启。

使用 SliceReader 和 SliceWriter 实现更高效的 I/O

使用 io.Readerio.Writer 接口来读写 box 提供了很大的灵活性,但在内存分配方面并不是最佳选择。特别是 Read(p []byte) 方法需要一个适当大小的切片 p 来读取数据,这会导致大量的内存分配和数据复制。 为了获得更好的性能,将顶层 box 完整读入一个或几个切片中进行解码是更有利的。

为了启用该模式,代码的 0.27 版本为每个 box X 引入了 DecodeX(sr bits.SliceReader) 方法,其中 mp4ff.bits.SliceReader 是一个接口。 例如,TrunBox 除了原有的 DecodeTrun(r io.Reader) 方法外,还新增了 DecodeTrunSR(sr bits.SliceReader) 方法。bits.SliceReader 接口提供了从底层字节切片中读取各种数据结构的方法。它有一个实现 bits.FixedSliceReader,使用固定大小的切片作为底层切片,但也可以考虑实现一个可增长的版本,从某个外部源获取数据。

通过这种方式实现的内存分配和速度改进可能会有所不同,但应该是显著的,特别是与 0.27 版本之前使用额外的 io.LimitReader 层相比。

为了进一步减少读取渐进式文件的 mdat 数据时的内存分配,应该使用某种缓冲读取器。

基准测试

为了研究新的 SliceReader 和 SliceWriter 方法的效率,进行了基准测试。 基准测试定义在 mp4/benchmarks_test.gomp4/benchmarks_srw_test.go 文件中。 对于 DecodeFile,可以看到从 0.26 版本到 0.27 版本有很大的改进,这两个版本都使用 io.Reader 接口, 但使用 SliceReader 源又有了很大的提升。 后者的基准测试称为 BenchmarkDecodeFileSR,但在这里为了便于比较,使用了相同的名称。 请注意,这里的分配是指在基准测试循环内进行的堆分配。在循环外, 会分配一个切片来保存输入数据。

对于 EncodeFile,可以看到使用 io.Writer 接口时,v0.27 实际上比 v0.26 更差。 这是因为代码被重构,所有写入都通过 SliceWriter 层进行,以减少代码重复。 然而,如果直接使用 SliceWriter 方法,在分配方面可以看到很大的相对增益, 如最后一列所示。

名称 \ 操作时间v0.26v0.27v0.27-srw
DecodeFile/1.m4s-1621.9µs6.7µs2.6µs
DecodeFile/prog_8s.mp4-16143µs48µs16µs
EncodeFile/1.m4s-161.70µs2.14µs1.50µs
EncodeFile/prog_8s.mp4-1615.7µs18.4µs12.9µs
名称 \ 分配操作v0.26v0.27v0.27-srw
DecodeFile/1.m4s-16120kB28kB2kB
DecodeFile/prog_8s.mp4-16906kB207kB12kB
EncodeFile/1.m4s-161.16kB1.39kB0.08kB
EncodeFile/prog_8s.mp4-166.84kB8.30kB0.05kB
名称 \ 分配次数v0.26v0.27v0.27-srw
DecodeFile/1.m4s-1698.042.034.0
DecodeFile/prog_8s.mp4-16454180169
EncodeFile/1.m4s-1615.015.03.0
EncodeFile/prog_8s.mp4-16101861

Box 结构和接口

大多数 box 都有自己的以 box 命名的文件,但在某些情况下,可能有多个 box 具有相同的内容,这时代码文件会有一个通用名称,如 mp4/visualsampleentry.go

Box 接口在 mp4/box.go 中指定。它不包含解码(解析)方法,这些方法对每种 box 类型都有不同的名称并被分发。

解码分发的映射在 mp4.decoders 表中给出,用于 io.Reader 方法,在 mp4.decodersSR 中给出,用于 mp4ff.bits.SliceReader 方法。

如何实现新的 box

要实现一个新的 box fooo,需要以下步骤:

创建一个文件 fooo.go 并创建一个结构体类型 FoooBox

FoooBox 必须实现 Box 接口方法:

Type()
Size()
Encode(w io.Writer)
EncodeSW(sw bits.SliceWriter)  // v0.27.0 新增
Info()

它还需要自己的解码方法 DecodeFooo,必须将其添加到 box.go 中的 decoders 映射中, 以及 v0.27.0 新增的 DecodeFoooSR 方法添加到 decodersSR 中。 一个简单的例子,可以查看 prft.go 中的 PrftBox

还应该有一个测试文件 fooo_test.go,其中包含使用 boxDiffAfterEncodeAndDecode 方法的测试,以检查编码和解码后 box 信息是否相同。

直接更改属性

许多属性是公开的,因此可以自由更改。 这种方式的优点是可以编写代码以多种方式操作 box, 但必须谨慎,避免破坏与子 box 的链接或在 box 中创建不一致的状态。 例如,容器盒子如TrafBox有一个AddChild方法,该方法不仅将盒子添加到其子盒子切片Children中,还会设置一个特定的成员引用(如Tfdt)指向该盒子。如果直接操作Children,该链接可能会失效。

编码模式和优化

对于分片文件,可以选择编码mp4.File中的所有盒子,或只编码包含在初始化段和媒体段中的盒子。控制这一行为的属性称为FragEncMode。 另一个属性EncOptimize控制文件编码过程的可能优化。 目前,只有一种可能的优化,称为OptimizeTrun。 它可以通过在TfhdBox中查找并写入默认值,并从TrunBox中省略相应的值来减小TrunBox的大小。 请注意,这可能会改变trun所有祖先盒子的大小。

样本编号偏移

按照ISOBMFF标准,样本编号和其他编号从1开始(基于1)。 这适用于函数和方法的参数。 在切片中的实际存储是基于0的,所以 样本编号1在相应的切片中索引为0。

稳定性

API应该相当稳定,但在版本1之前可能会发生轻微的不向后兼容的变化。

规范

MP4文件格式的主要规范是ISO基本媒体文件格式(ISOBMFF)标准 ISO/IEC 14496-12第6版2020。一些盒子在其他标准中有规定,应在代码中注释说明。

许可证

MIT,请参见LICENSE

pkg/mp4中的一些代码来自或基于https://github.com/jfbus/mp4,其版权为 Copyright (c) 2015 Jean-François Bustarret

pkg/bits中的一些代码来自或基于https://github.com/tcnksm/go-casper/tree/master/internal/bits Copyright (c) 2017 Taichi Nakashima

更新日志和版本

请参见CHANGELOG.md

支持

加入我们的Slack社区,您可以在那里提出任何关于我们开源项目的问题。Eyevinn的咨询业务还可以为您提供:

  • 该组件的进一步开发
  • 该组件在您平台上的定制和集成
  • 支持和维护协议

如果您感兴趣,请联系sales@eyevinn.se

关于Eyevinn Technology

Eyevinn Technology是一家独立的咨询公司,专门从事视频和流媒体领域。我们独立的方式是不与任何平台或技术供应商有商业关系。作为推动行业前进的创新方式,我们开发概念验证和工具。我们学到的知识和编写的代码,我们通过博客与行业分享,并开源我们编写的代码。

想了解更多关于Eyevinn的信息以及在这里工作的感受吗?请联系我们:work@eyevinn.se

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号