0到1理解web音视频从采集到传输到播放系列之《Jessibuca系列篇音视频解封装》

本课程主要从

  • 音视频采集
  • 音视频编码
  • 音视频协议封装传输
  • 音视频协议解封装
  • 音视频解码
  • 音视频播放

关于Jessibuca

关于JessibucaPro

第四章:音视频解封装

通过网络请求,请求到了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 headertag 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标签进行播放。

相关推荐
恶猫2 小时前
javascript文本长度检测与自动截取,用于标题长度检测
javascript·css·css3·js·自动检测·文本长度
uhakadotcom3 小时前
入门教程:如何编写一个chrome浏览器插件(以jobleap.cn收藏夹为例)
前端·javascript·面试
给月亮点灯|3 小时前
Vue3基础知识-Hook实现逻辑复用、代码解耦
前端·javascript·vue.js
顽强d石头3 小时前
v-model与.aync的区别
前端·javascript·vue.js
xvmingjiang4 小时前
Vue 3 中监听多个数据变化的几种方法
前端·javascript·vue.js
我有一只臭臭4 小时前
ES5 和 ES6 类的实现
前端·javascript·es6
2301_821046524 小时前
Python的深度学习
开发语言·javascript·ecmascript
小高0074 小时前
🤔函数柯里化:化繁为简的艺术与实践
前端·javascript·面试
却尘4 小时前
React useMemo 依赖陷阱:组件重挂载,状态无限复原
前端·javascript·react.js