关于flv:flvjs系列三FLV格式解析

16次阅读

共计 2633 个字符,预计需要花费 7 分钟才能阅读完成。

flv.js 系列三:FLV 格局解析

flv.js中音视频额分离器是FLVDemuxer, 看懂这里的代码之前先要理解 FLV 文件的数据格式及 JS 中如何读取指定二进制数据。

1 FLV 文件格式

FLV 是 Flash Video 的简称,是 Adobe 公司推出的流媒体文件格式,咱们上面对数据的操作都基于 FLV 标准文档。整体构造如下图所示,由 FLVHeaderFLVBody组成。

1.1 FLVHeader

FLVHeader是固定的 9 字节,其定义如下图:

对上图做一些阐明,Type列定义该字段的长度,UI8代表 8 位无符号整型,一个字节。UI32代表 32 位无符号整型,四个字节。UB[5]代表一个字节的 5 位,UB[1]代表一个字节的 1 位。

  • 第一个字节固定是 0x46 示意 F
  • 第二个字节固定是 0x4c 示意 L
  • 第三个字节固定是 0x56 示意 V
  • 第四个字节 指定 FLV 格局版本
  • 第五个字节中 TypeFlagsAudio 示意是否有音频,TypeFlagsVideo 示意是否有视频
  • 最初四个字节示意 FLVHeader 的长度 固定是数字 9
1.2 FLVBody 与 Tag


FLVBody就是由多个 Previous TagSizeTag组成,其中 Previous TagSize 占 4 个字节,而且第一个 Previous TagSize 的值为 0。接下来咱们看 Tag 的构造,TagTag HeaderTag Data组成,其定义如下:

能够看到 Tag Header 的长度是 11 个字节。

2 二进制数据读取 API

咱们须要把 ArrayBuffer 类型的数据依照下面的 FLV 格局进行解析,须要借助于 DateView 视图类来实现二进制数据的读取和写入,上面看咱们会遇到的 API。

2.1 构造函数

来自 MDN:

new DataView(buffer [, byteOffset [, byteLength]])

参数阐明:
buffer
现有对象 ArrayBufferSharedArrayBuffer用作反对新 DataView 对象的存储。
byteOffset 可选的
到上述缓冲区中第一个字节的偏移量(以字节为单位),以供新视图援用。如果未指定,则缓冲区视图从第一个字节开始。
byteLength 可选的
字节数组中的元素数。如果未指定,则视图的长度将与缓冲区的长度匹配。

demo:

// create an ArrayBuffer with a size in bytes
const buffer = new ArrayBuffer(16);
// Create a couple of views
const view1 = new DataView(buffer);
const view2 = new DataView(buffer, 12, 4); //from byte 12 for the next 4 bytes
view1.setInt8(12, 42); // put 42 in slot 12
console.log(view2.getInt8(0));
// expected output: 42

2.2 获取数据的 API

dataview.getUint8(byteOffset)
参数
byteOffset,从视图开始处读取数据的偏移量(以字节为单位)。
返回值,一个无符号的 8 位整数。

dataview.getUint32(byteOffset [, littleEndian])
参数
byteOffset,从视图开始处读取数据的偏移量(以字节为单位)。
littleEndian,可选的批示 32 位 int 是按小字节序还是大字节序格局存储的。默认是 false。
返回值,一个无符号的 32 位整数。

这里只讲了两个用到的 API,当然 Dateview 还有设置和读取 有符号或者无符号 8 位 /16 位 /32 位的 API。

2.3 Endian 字节序

内存中每一个字节是一个单位,对应一个内存地址,8 位最大能示意 0 -255,如果要示意数字 258 就须要 2 个字节,字节序的作用就是规定这 2 个字节哪个示意高位哪个示意低位,如果内存地址低的示意低位就是小字节序littleEndian,相同就是大字节序。以示意 258 举例说明:

代码验证如下:

var buf = new ArrayBuffer(2);
var view = new DataView(buf);

view.setInt16(0, 258, true);  // little-endian write
let little = view.getUint8(0);
let big = view.getUint8(1);
    
 console.log(little) // 打印 2

 console.log(big) // 打印 1

3 位操作

二进制数据的位操作包含 按位非(~)、按位与(&)、按位或(|)、按位异或(^)、左移(<<)、右移(>>)、无符号右移(>>>)。js 的 API 中只提供了读取 8、16,32 位等固定位的数据,对于其余位数的数据则须要用位操作进行读取。

3.1 读取字节中某一位是 0 或 1

能够利用该字节的数与指标位所在的值进行与操作,这样就只保留字节中指标位的取值。例如读取第 6 和第 8 位的值。

let data = new Uint8Array(buffer)[0];
let hasAudio = (data & 4) === 4;
let hasVideo = (data & 1) === 1;
3.2 读取字节中间断几位所示意数值

先与这几位所示意的数值进行与操作,而后在右移到低位。
比方读取一个字节中的前 5 位所示意的数,前 5 位的值为 248。

let data = new Uint8Array(buffer)[0];
let target = (data & 248) >> 3;

读取字节中其余地位的值也是同理。

3.3 多字节拼接大数据

尽管有获取多字节数据的 API,但当不满足需要时,能够通过位移和按位或操作进行大数据的拼接。例如 FLV tag 中有一个工夫戳拼接的中央是 后 8 位的二进制应该拼接在 24 位后面 形成 32 位的数字。

let v = new DataView(chunk, offset);
let ts2 = v.getUint8(0);
let ts1 = v.getUint8(1);
let ts0 = v.getUint8(2);
let ts3 = v.getUint8(3);
// 获取工夫戳字段  ts3 是 扩大字段 是工夫戳的高 8 位
let timestamp = ts0 | (ts1 << 8) | (ts2 << 16) | (ts3 << 24);

4 总结

有了下面的 FLV 格局的文档,有了对二进制数据读取的办法,剩下的事件就是依照文档对每一个字段进行解析。

如果感觉有播种请关注微信公众号 前端良文 每周都会分享前端开发中的干货知识点。

正文完
 0