本课程主要从
- 音视频采集
- 音视频编码
- 音视频协议封装传输
- 音视频协议解封装
- 音视频解码
- 音视频播放
关于Jessibuca
- 官网地址:jessibuca.com
- Demo: Demo
- Doc:Doc
- Github地址:Github
关于JessibucaPro
- 地址:JessibucaPro
- Demo: Demo
- AI:AI
- 插件:插件
第四章:音视频解封装
通过网络请求,请求到了mp4/flv/hls/webm内容。
通过解封装协议可以解封装到音视频数据。
对于音频可以解封装到:aac/g711/mp3/opus 数据。
对于音频可以解封装到:h264/h265/v9 数据。
mp4
在MP4文件格式中,整个视频容器都是由多个box和子box组成,根据box类型主要分为3大类:视频类型(ftyp)
、视频数据(mdat)
、视频信息(moov)
。
视频信息(moov)用来描述视频数据(mdat)。
在web端,可以使用mp4Box.js
来封装mp4文件。
兼容性
目前web端 可以通过video
标签进行 mp4文件的播放。
flv
FLV(Flash Video)是目前最流行的流媒体格式
,其文件体积小、封装播放简单,非常适合在网络场景下应用。
对于FLV ,是由一个文件头(FLV header)
和 很多tag组成(FLV body)
,tag又可以分成三类:audio
,video
,script
,分别代表音频流
,视频流
,脚本流
,而每个tag又由tag header
和tag data
组成
文件头 (flv header)
在文件头内容里面:
- 前3个bytes是文件类型,总是"FLV",也就是(0x46 0x4C 0x56)。
- 第4btye是版本号,目前一般是0x01。
- 第5byte是流的信息,倒数第一bit是1表示有视频(0x01),倒数第三bit是1表示有音频(0x4),有视频又有音频就是0x01 | 0x04(0x05),其他都应该是0。
- 最后4bytes表示FLV 头的长度,
flv header 的长度是:3+1+1+4 = 9。
在web端,利用js 来解封装flv协议。
js
// 解析flv header
const flvHeader = flvData.slice(0,9);
// 前3字节是 'FLV' 三个字符
const f = flvHeader[0];
const l = flvHeader[1];
const v = flvHeader[2];
// 第4btye是版本号,目前一般是0x01。
const version = flvHeader[3];
// 第5字节是Flags ,用于鉴别是否含有音频、视频数据
const hasAudio = ((flvHeader[4] & 4) >>> 2) !== 0;
const hasVideo = (flvHeader[4] & 1) !== 0;
// 最后4bytes表示FLV 头的长度。
若干个tag(flv body)
对于 flv body 是由:previous tag size #0 (4 size)
+ tag #1
+ previous tag size #1 (4 size) (tag #1的大小 11+ datasize)
+ tag #2
+ previous tag size #1(4 size)
+ ... + tag #n
+ previous tag size #n(4 size)
组成。
tag header
在tag header 里面
- 第1个byte为记录着tag的类型,音频(0x8),视频(0x9),脚本(0x12)18;
- 第2到4bytes是数据区的长度,也就是tag data的长度(头部之后的数据体的大小);
- 再后面3个bytes是时间戳,单位是毫秒,类型为0x12则时间戳为0,时间戳控制着文件播放的速度,可以根据音视频的帧率类设置;
- 时间戳后面一个byte是扩展时间戳,时间戳不够长的时候用;
- 最后3bytes是streamID,但是总为0。
tag header 的长度是: 1+3+3+1+3=11。
在web端,利用js 来解封装flv协议。
js
const tagType = flvData[0] & 0x1F; // 5bit代表类型,8:audio 9:video 18:script other:其他
//
const tmp = new ArrayBuffer(4);
const dv = new DataView(tmp);
// 2-4 消息长度
dv.setUint8(0, flvData[3]);
dv.setUint8(1, flvData[2]);
dv.setUint8(2, flvData[1]);
dv.setUint8(3, 0);
// tag data的长度(头部之后的数据体的大小);
const payloadLen = dv.getUint32(0, true);
// 5-7 时间戳
dv.setUint8(0, flvData[6]);
dv.setUint8(1, flvData[5]);
dv.setUint8(2, flvData[4]);
// 8-8 时间戳扩展
dv.setUint8(3, flvData[7]);
const dts = dv.getUint32(0, true);
// 最后3bytes是streamID,但是总为0。
tag data
裸流数据
script tag
该类型Tag又通常被称为Metadata Tag,会放一些关于FLV视频和音频的元数据信息如:duration、width、height等。
通常该类型Tag会跟在File Header后面作为第一个Tag出现,而且只有一个。
主要结构: 由AMF1("onMetaData")
+ AMF2("width,height,...")
组成。
audio tag
通过 tag header
里面得知是audio tag
,接下来就是解析tag data
数据了
在 tag header 里面 第一个字节 表示 音频头(audio tag header)
音频头(audio tag header)
在音频头里面:
ini
1-4bit,音频格式【SoundFormat】
0 = Linear PCM, platform endian
1 = ADPCM
2 = MP3
3 = Linear PCM, little endian
4 = Nellymoser 16 kHz mono
5 = Nellymoser 8 kHz mono
6 = Nellymoser
7 = G.711 A-law logarithmic PCM , reserved
8 = G.711 mu-law logarithmic PCM , reserved
9 = reserved
10 = AAC (supported in Flash Player 9,0,115,0 and higher)
11 = Speex (supported in Flash Player 10 and higher)
14 = MP3 8 kHz , reserved
15 = Device-specific sound , reserved
5-6bit,采样率【SoundRate】
0 = 5.5kHz
1 = 11kHz
2 = 22kHz
3 = 44kHz
7-7bit,位宽,0 = 8bit samples, 1= 16bit samples【SoundSize】
8-8bit,通道,0 = Mono, 1 = Stereo【SoundType】
如果 音频格式是aac 的话,第二个字节表示aac音频类型
ini
0 = AAC sequence header
1 = AAC raw
剩下的字节数据就是纯音频数据
了
小结:
如果是aac tagheader(1) + 音频类型(1) + (n-2)
如果是g711a/u tagheader(1) + (n-1)
在web端,利用js 来解封装flv协议。
js
// 音频格式【SoundFormat】
let soundId = (flvData[0] >> 4) & 0x0F;
let soundrate = (flvData[0]>>2)&0x02;
let soundsize = (flvData[0]>>1)&0x01;
let soundtype = (flvData[0])&0x0F;
if(soundId === '10'){
// aac about sequence header and raw
// [2-2]:AAC音频类型,注,只有在SoundFormat=AAC 时,才有此数据
// 0 = AAC sequence header
// 1 = AAC raw
const packetType = flvData[1];
if (packetType === 0) {
const config = flvData.slice(0, this.needLen);
const aacSequenceHeader = flvData.slice(2, this.needLen);
console.log('AAC sequence header');
} else {
// AAC raw
const aacRaw = flvData.slice(2, this.needLen);
console.log('AAC raw')
}
else{
// g711 音频数据
const g711Raw = flvData.slice(1, this.needLen);
}
video tag
在 tag header 里面 第一个字节 表示 视频头(video tag header)
ini
1-4bit,帧类型【FrameType】
1 = key frame (for AVC, a seekable frame)
2 = inter frame (for AVC, a non-seekable frame)
3 = disposable inter frame (H.263 only)
4 = generated key frame (reserved for server use only)
5 = video info/command frame
5-8bit,编码类型【CodecID】
2 = Sorenson H.263
3 = Screen video
4 = On2 VP6
5 = On2 VP6 with alpha channel
6 = Screen video version 2
7 = AVC(H.264)
第 2-5 个字节是 CompositionTime
小结
video tag header(1) + compsitionTime(4) + 视频数据(n-5)
在web端,利用js 来解封装flv协议。
js
// 1-4bit,帧类型【FrameType】
let frameType = (flvData[0] >> 4) & 0x0F;
// 5-8bit,编码类型【CodecID】
let codecId = (flvData[0]) & 0x0F;
// h264 or h265
const packetType = flvData[1];
// compositionTime
dv.setUint8(0, flvData[4]);
dv.setUint8(1, flvData[3]);
dv.setUint8(2, flvData[2]);
dv.setUint8(3, 0);
let compositionTime = dv.getUint32(0, true);
this.pts = this.dts + compositionTime;
//
完整的demo:flv-demux
兼容性
目前web端 video
标签是不支持flv 文件进行播放的。需要借助浏览器提供的mediaSource
或者webcodec
或者wasm
来解封装 flv 文件。
hls
HLS 由 TS 和 M3U8 两部分组成:
- m3u8 文件:以 UTF-8 编码的 m3u 文件。
- ts 视频文件:一个 m3u8 文件对应着若干个 ts文件。
m3u8 文件
m3u8 只存放了一些 ts 文件的配置信息和相关路径。
ts 视频文件
ts 文件存放了视频的数据。
兼容性
webm
WebM 由 Google 提出,是一种专为 Web 设计的开放,免版税的媒体文件格式。WebM 文件包含使用 VP8 或 VP9 视频编解码器压缩的视频流和使用 Vorbis 或 Opus 音频编解码器压缩的音频流。
webm 格式的视频,直接可以通过video
标签进行播放。