avatar
SuemorのBlog
  • 首页
  • 文章
  • 归档
  • 更多
    • 友链
    • 关于
    • 友链
关于 >关于友链关于此项目

@2025 - 2026 Suemor

Powered by Marchen
|所谓自由就是可以说二加二等于四的自由

ArrayBuffer、TypedArray 和 DataView 在 MP4 Box 解析中的运用

2026 年 2 月 21 日|
2|
0|
编程|
TypeScript前端

JavaScript 处理二进制数据的 API 主要有三种:ArrayBuffer、TypedArray 和 DataView。在 MP4 Box 的解析和处理过程中,这些工具非常有用。本文结合实际的 MP4 box 结构,聊聊它们各自的定位和取舍。

三者的关系

ArrayBuffer

ArrayBuffer 是一块原始的、固定长度的二进制内存,你不能直接读写它,必须通过"视图"来操作。当你 fetch 一个 fMP4 文件后,对其解析会先转为 ArrayBuffer。

typescript
const response = await fetch('video.m4s'); const buffer = await response.arrayBuffer(); console.log(buffer);
arrayBuffer resultarrayBuffer result

TypedArray

TypedArray 是一组类型化数组视图(Uint8Array、Uint16Array、Uint32Array、Float32Array 等),它把 ArrayBuffer 当作同构的数组来访问——所有元素类型相同、等宽排列。它只是在已有的 ArrayBuffer 上建立一个视图,本身不复制也不额外分配数据内存。

typescript
const response = await fetch('video.m4s'); const buffer = await response.arrayBuffer(); const uint8 = new Uint8Array(buffer); // 创建视图,不会占用额外内存 console.log(uint8);
typedArray resulttypedArray result

DataView

DataView 也是建立在 ArrayBuffer 上的视图,同样不复制数据。与 TypedArray 不同的是,它不把 buffer 当数组看,而是提供了一组方法让你在任意偏移位置、以任意类型和字节序来读写数据——没有对齐限制,也不要求字段类型统一。

typescript
const response = await fetch('video.m4s'); const buffer = await response.arrayBuffer(); const view = new DataView(buffer); const size = view.getUint32(0); const type = String.fromCharCode( view.getUint8(4), view.getUint8(5), view.getUint8(6), view.getUint8(7) ); console.log(size, type);
dataView resultdataView result

三者的关系可以这样理解:ArrayBuffer 是仓库,TypedArray 和 DataView 是两种不同的取货方式——前者像传送带,只能运同一规格的货物;后者像叉车,想取什么取什么。

MP4 Box 的结构特点

MP4 文件遵循 ISO 14496-12(ISOBMFF)规范,整个文件由嵌套的 box 组成。每个 box 的基本结构是:

[4 bytes] size        (uint32)
[4 bytes] type        (4个ASCII字符)
[可选]    largesize   (uint64,当 size==1 时)
[可选]    version     (uint8) + flags (uint24)
[...]     payload     (各种混合类型字段)

这里有几个关键特征:

  1. 字段类型混杂:一个 box 里 uint8、uint16、uint24、uint32、uint64 混着来,没法用单一的 TypedArray 类型映射整个 box
  2. 字段偏移不对齐:比如 1 字节的 version 后面紧跟 3 字节的 flags,再接 uint32 字段——中间穿插了奇数长度的字段后,后续偏移很容易不是 2、4、8 的倍数,TypedArray 的对齐要求就满足不了
  3. 大端字节序:MP4 规范要求所有多字节整数使用大端字节序,而 TypedArray 使用平台原生字节序(绝大多数设备是小端),直接读出来的值是反的

这三条,直接决定了各 API 的适用程度。

Uint8Array 数据搬运和字节级操作

在 MP4 解析中,Uint8Array 是用得最多的 TypedArray。它没有对齐和字节序的问题——每个元素就是一个字节,强项是数据搬运(切片、拷贝、拼接)和逐字节扫描。

js
// 切出某个 box 的 payload const boxPayload = new Uint8Array(buffer, boxOffset + 8, boxSize - 8); // 拼接两段 segment 数据 const merged = new Uint8Array(a.length + b.length); merged.set(a, 0); merged.set(b, a.length); // 逐字节匹配 box type,找到 mdat box 的位置 const bytes = new Uint8Array(buffer); for (let i = 0; i < bytes.length - 7; i++) { if (bytes[i+4] === 0x6D && bytes[i+5] === 0x64 && bytes[i+6] === 0x61 && bytes[i+7] === 0x74) { console.log('mdat box at offset', i); break; } }

Uint32Array 为什么在 MP4 解析中几乎没用

直觉上,MP4 box 里到处是 uint32 字段,Uint32Array 应该很适合?实际上它在 MP4 解析里几乎没有用武之地,最核心的原因是它的字节序不对。

MP4 是大端,而 Uint32Array 使用平台原生字节序。绝大多数设备(x86、ARM)是小端,这意味着直接读出来的值是字节反转的。

举个例子,要把值 23 写入 buffer,按 MP4 大端格式应该是 00 00 00 17:

typescript
// DataView:直接写 23,指定大端 const view = new DataView(buf); view.setUint32(0, 23, false); // buffer: 00 00 00 17 ✓ // Uint32Array:写 23 会变成小端排列 const arr = new Uint32Array(buf); arr[0] = 23; // 小端机器上 buffer: 17 00 00 00 ✗ // 要得到正确的字节排列,你得手动翻转 arr[0] = 0x17000000; // 即 23 × 2²⁴ = 385875968 // buffer: 00 00 00 17 ✓

把一个简单的 23 转成 385875968 才能写入,这样就太复杂了。

DataView 的灵活性

DataView 完美解决了上述问题。它允许你在任意偏移位置,以任意类型和字节序来读写数据。

DataView 提供了 10 对 getter/setter:

方法 字节数 说明
getInt8 / getUint8 1 无字节序参数(单字节不需要)
getInt16 / getUint16 2 第二参数控制字节序
getInt32 / getUint32 4 第二参数控制字节序
getFloat32 / getFloat64 4 / 8 IEEE 754 浮点数
getBigInt64 / getBigUint64 8 返回 BigInt

每个 getter 都有对应的 setter,setter 多一个 value 参数。所有多字节方法的最后一个参数 littleEndian 默认为 false(大端),恰好和 MP4 的字节序一致。

总结

API MP4 解析中的角色 典型场景
ArrayBuffer 底层数据容器 承载所有二进制数据
Uint8Array 高频工具 切片、拷贝、扫描、读 box type
Uint32Array 等 几乎不用 字节序/对齐/混合类型三重限制
DataView 核心解析工具 读写任意类型、任意偏移、可控字节序

写 MP4 parser 时,一个简单的原则:搬运数据用 Uint8Array,解析字段用 DataView。

目录0%