发布于 2026-01-06 1 阅读
0

快速了解 MP4 参考资料和资源

快速了解 MP4

参考资料和资源

什么是MP4?我们都知道它是一种用于播放带声音的视频的文件格式。Netflix、YouTube、Instagram等平台都使用MP4进行视频流传输,iPhone也用它来录制视频。但是,它是如何工作的?如何使用?它的字节结构是什么?什么是容器?

本指南是对 MP4 文件格式的介绍和快速入门,MP4 文件格式也称为 ISO 基本媒体文件格式 ( ISO-BMFF MPEG-4 Part 14 )。我知道,这名字听起来很专业。

本指南不会详细介绍播放细节,但会更多地介绍通常被称为 MP4 盒结构的 MP4 字节格式。

介绍

MPEG-4 Part 14 (MP4) 是最常见的视频容器格式之一,其扩展名为 .mp4 .mp4。您可能已经了解其他容器格式,例如wav.cd mov、.mdmp3或最近出现的webm.md。容器“包含”视频或音频轨道,或两者兼有。它还可以支持嵌入式字幕轨道。

MP4 是 ISO 基本媒体文件格式 ( ISOBMFF,MPEG-4 第 12 部分)的扩展,该格式旨在包含定时媒体信息。

ISO-BMFF 格式直接基于QuickTime,因此 MP4 本质上与 QuickTime 文件格式相同。

     MPEG-4 Part 14 - MP4 File Format
 ┌──────────────────────────────────────┐
 ├────────────────────┐                 │
 │  ISO Base Media    │                 │
 │    File Format     │  MP4 Extension  │
 │  (MPEG-4 Part 12)  │                 │
 └────────────────────┴─────────────────┘
Enter fullscreen mode Exit fullscreen mode

为了充分了解 MP4 的结构,您需要获取一份ISO文档:

  • 14496-12 – MPEG-4 第 12 部分
  • 14496-14 - MPEG-4 第 14 部分

通过谷歌搜索应该可以找到一些资源,获取PDF文件。

容器里装的是什么?

由于 MP4 是一种容器格式,它实际上并不处理视频和音频流的解码,它只是将它们作为轨道及其元数据包含在内。

容器可以存储以下部分信息:

  • 通用元数据,例如文件类型和兼容性
  • 视频、音频和字幕轨道以及编解码器详情
  • 元数据:时长、时间刻度、比特率、宽度/高度等
  • 渐进式和碎片化的元数据细节
  • 一系列视频帧或音频样本,称为“样本数据”。

这是玩家解码和播放内容所需的所有信息。

从宏观层面来看,MP4 文件的结构通常如下所示:

video.mp4
├───general file metadata
├───movie data
├───tracks
│   ├───video
│   │   ├───video metadata
│   │   └───video sample data
│   └───audio
│       ├───audio metadata
│       └───audio sample data
└───more metadata
Enter fullscreen mode Exit fullscreen mode

电影盒子

根据 QuickTime 规范,MP4 字节结构由一系列称为“原子”的“盒子”组成。每个盒子描述并包含用于构建 MP4 容器格式的数据。

盒子通常有一个四字母名称,也称为FourCC。这是完整盒子名称的缩写形式,足以容纳在 4 个字节中。这对于以字节格式读取和写入盒子数据至关重要。

在深入探讨字节结构细节之前,这里提供一个比上面的高级视图更技术性的 MP4 盒树视图:

video.mp4
├───ftyp -------------------> FileType Box
├───mdat -------------------> Movie Data Box
├───moov -------------------> Movie Boxes
│   ├───trak ---------------> Track Box
│   │   ├─── tkhd ----------> Track Header
│   │   └─── mdia ----------> Media Box
│   │        └─── ...
│   └───trak
│   │   ├─── tkhd ----------> Track Header
│   │   └─── mdia ----------> Media Box
│   │        └─── ...
└───udta -------------------> Userdata Box
Enter fullscreen mode Exit fullscreen mode

这只是一个简化的视图。然而,MP4规范中定义了更多类型的框。

盒子里装的是什么?

MP4“盒子”包含的信息刚好足以读取和解析盒子名称、大小和数据。

每个方框都有不同的用途,包含特定数据的详细信息。有些方框描述文件类型,有些则会描述编解码器细节、图像分辨率、帧速率、时长、采样大小等等。此外,还有一些方框包含编码后的视频和音频数据。

一个盒子通常包含以下基本信息:

  • 盒子大小(以字节为单位)
  • 盒子名称(FourCC)
  • 盒子数据
┌─────────────────────┐
|      Box Header     |
| Size (4) | Type (4) | Box Header = 8 Bytes
| --------------------|
|     Box Data (N)    | Box Data = N Bytes
└─────────────────────┘
           └─────────── Box Size = 8 + N bytes
Enter fullscreen mode Exit fullscreen mode

这些信息足以让我们知道如何解析一个视频框,再加上 MP4 规范文档,就能理解视频框的各个字段。

解析一个盒子

那么,让我们来解析第一个盒子吧!

如上所述,每个方框的前 8 个字节称为“方框头”,其中前 4 个字节表示方框的大小,后 4 个字节表示方框的名称。您需要知道这两个值才能逐字节地遍历和解析每个方框。

以下是一个盒子头部结构示例:

type Box struct {
  Size    int32
  Name    string
}
Enter fullscreen mode Exit fullscreen mode

读取每个原子中的盒子数据需要知道每个待解析盒子的大小、名称和字节结构。您可以参考 MPEG-4 第 14 部分规范来获取每个已知盒子的字节结构,或者直接参考一些现有的 MP4 解析开源代码。

根据规格说明,该ftyp包装盒具有以下结构:

aligned(8) class FileTypeBox
  extends Box(ftyp) {
  unsigned int(32) major_brand;
  unsigned int(32) minor_version;
  unsigned int(32) compatible_brands[]; // to end of the box
}
Enter fullscreen mode Exit fullscreen mode
  • major_brand– 是品牌标识符
  • minor_version– 是一个信息丰富的整数,表示主品牌的次要版本。
  • compatible_brands——是方框末尾列出的品牌列表。

例如,读取 FileTypeBox( ftyp) 函数的代码如下所示(使用 Golang):

type FtypBox struct {
  *Box
  MajorBrand       string
  MinorVersion     uint32
  CompatibleBrands []string
}

func (b *FtypBox) parse() {
  data := b.ReadBoxData() // Read box header.
  b.MajorBrand = string(data[0:4])
  b.MinorVersion = binary.BigEndian.Uint32(data[4:8])
  if len(data) > 8 {
    for i := 8; i < len(data); i += 4 {
      b.CompatibleBrands = append(b.CompatibleBrands, string(data[i:i+4]))
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

回顾以上内容:

  • 将盒子头部的前 4 个字节读取为无符号 32 位整数(大端序),即可得到盒子大小:32 bytes
  • 接下来的 4 个字节给出:0x66747970十六进制值,或ftyp字符串值。
  • 接下来的 4 个字节给出了主要品牌:0x69736F6D以十六进制或isom字符串形式表示。
  • 接下来的 4 个字节表示次版本号:512
  • 接下来的 16 个字节,一次读取一个uint32be字符串,得到一个兼容品牌数组:isom,,,iso2avc1mp41
  • 32 bytes我们已读取了框标题中定义的总数。

请参阅以下最小示例:
https://gist.github.com/alfg/7375aee32fda490de4bf62fbced49d2e#file-mp4_example-go

$ go run mp4.go tears-of-steel.mp4
ftyp.name:  ftyp
ftyp.major_brand:  isom
ftyp.minor_version:  512
ftyp.compatible_brands:  [isom iso2 avc1 mp41]
Enter fullscreen mode Exit fullscreen mode

如果用十六进制编辑器打开mp4文件,你会看到类似这样的方框内容ftyp

0x00 00 00 00 20 66 74 79 70 | 69 73 6F 6D 00 00 02 00 ... ftypisom....
0x10 69 73 6F 6D 69 73 6F 32 | 61 76 63 31 6D 70 34 31 isomiso2avc1mp41
Enter fullscreen mode Exit fullscreen mode

下一个盒子!

现在我们已经阅读完了这个ftyp框,可以继续阅读下一个框的标题,也就是这个moov框:

type MoovBox struct {
  *Box
  Mvhd  *MvhdBox
}

func (b *MoovBox) parse() {
  boxes := readBoxes(b.Reader, b.Start+BoxHeaderSize, b.Size-BoxHeaderSize)

  for _, box := range boxes {
    switch box.Name {
    case "mvhd":
    b.Mvhd = &MvhdBox{Box: box}
    b.Mvhd.parse()
  }
}
Enter fullscreen mode Exit fullscreen mode

这个moov盒子里包含一个嵌套的mvhd盒子,所以我们还需要定义mvhd

type MvhdBox struct {
  *Box
  Flags            uint32
  Version          uint8
  CreationTime     uint32
  ModificationTime uint32
  Timescale        uint32
  Duration         uint32
  Rate             Fixed32
  Volume           Fixed16
}

func (b *MvhdBox) parse() {
  data := b.ReadBoxData()
  b.Version = data[0]
  b.Timescale = binary.BigEndian.Uint32(data[12:16])
  b.Duration = binary.BigEndian.Uint32(data[16:20])
  b.Rate = fixed32(data[20:24])
  b.Volume = fixed16(data[24:26])
}
Enter fullscreen mode Exit fullscreen mode

moov请查看包含方框的示例mvhd
https://gist.github.com/alfg/7375aee32fda490de4bf62fbced49d2e#file-mp4_example_2-go

$ go run mp4.go tears-of-steel.mp4
ftyp.name:  ftyp
ftyp.major_brand:  isom
ftyp.minor_version:  512
ftyp.compatible_brands:  [isom iso2 avc1 mp41]
moov.name:  moov 3170
moov.mvhd.name:  mvhd
moov.mvhd.version:  0
moov.mvhd.volume:  1
Enter fullscreen mode Exit fullscreen mode

EOF

现在我们已经解析了 3 个方框,希望您能对如何实现更多解析有所了解。使用读取器时,这个过程是迭代的:

  • 阅读盒子标题,其中包含盒子尺寸和名称。
  • 请参考规范以阅读和/或跳过字段。
  • 跳过盒子大小中剩余的字节。
  • 阅读下一个方框(或跳过)。
需要注意以下几点:
  • 有些盒子有多个版本,因此盒子的结构和整体尺寸可能有所不同。
  • 您可以跳过属性,但读者必须知道要跳过多少字节。
  • MPEG-4 Part 14随着这些年来越来越多的设备被加入到MP4产品中,MP4的规格也出现了多种变化。
  • 分段式 MP4 文件 (fMP4) 被分割成一系列的moof方框mdat。这种方式更常见,也更适合流媒体传输。我将在以后的文章中详细介绍。

感谢阅读!

想要查看更完整的 Go 语言读取 MP4 盒子的示例,请访问:
https://github.com/alfg/mp4

我还有一个用 Rust 编写的更高级的 MP4 读取器和写入器:
https://github.com/alfg/mp4rs

我强烈推荐使用以下一些工具来检查 MP4 文件:

我的GitHub地址是:https://github.com/alfg

祝您编程愉快!🎥

参考资料和资源

文章来源:https://dev.to/alfg/a-quick-dive-into-mp4-57fo