如今,大多数已工做的前端工做者的学习方式,要么直接到 Stackoverflow
上搜代码,要么直接看看相关博文。这样是快,可是零零碎碎只是一个一个孤立的知识点而已。有可能一下午都忘记了,惟一可能记住的收藏一下那个文章,而后就完全躺尸了。那有没有啥更好的办法能解决呢?html
固然有,第一,有时间,第二,有人指导,第三,找对资料。前端
这其实和看书是同样的,一本书,最有价值的地方不在它的内容或者做者,而在于它的目录
,是否真正的打动你。若是只是出现一些模糊而没有落地技术的目录的书籍,仍是别再上面浪费时间了。node
因此,本文主要给你们介绍一下当下 HTML5 直播所涵盖的技术范围,若是要深度学习每个技术,咱们后续能够继续讨论。web
直播是 16 年搭着短视频热火起来的。它的业务场景有不少,有游戏主播,才艺主播,网上教学,群体实验(前段时间,有人直播让观众来炒股)等等。不过,根据技术需求的划分,还能够分为低延迟和高延迟的直播,这里就主要是协议选择的问题。算法
如今,经常使用的直播协议有不少种,好比 RTMP,HLS,HTTP-FLV。不过,最经常使用的仍是 HLS 协议,由于支持度高,技术简单,可是延迟很是严重。这对一些对实时性比较高的场景,好比运动赛事直播来讲很是蛋疼。这里,咱们来细分的看一下每一个协议。vim
HLS 全称是 HTTP Live Streaming。这是 Apple 提出的直播流协议。(其实,Adobe 公司 FLV 播放器的没落,苹果也是幕后黑手之一。) 后端
HLS 由两部分构成,一个是 .m3u8
文件,一个是 .ts
视频文件(TS 是视频文件格式的一种)。整个过程是,浏览器会首先去请求 .m3u8
的索引文件,而后解析 m3u8
,找出对应的 .ts
文件连接,并开始下载。更加详细的说明能够参考这幅图:api
他的使用方式为:数组
<video controls autoplay> <source src="http://devimages.apple.com/iphone/samples/bipbop/masterplaylist.m3u8" type="application/vnd.apple.mpegurl" /> <p class="warning">Your browser does not support HTML5 video.</p> </video>
直接能够将 m3u8
写进 src
中,而后交由浏览器本身去解析。固然,咱们也能够采起 fetch
来手动解析并获取相关文件。HLS 详细版的内容比上面的简版多了一个 playlist
,也能够叫作 master
。在 master
中,会根据网络段实现设置好不一样的 m3u8 文件,好比,3G/4G/wifi 网速等。好比,一个 master 文件中为:浏览器
#EXTM3U #EXT-X-VERSION:6 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2855600,CODECS="avc1.4d001f,mp4a.40.2",RESOLUTION=960x540 live/medium.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=5605600,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1280x720 live/high.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1755600,CODECS="avc1.42001f,mp4a.40.2",RESOLUTION=640x360 live/low.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=545600,CODECS="avc1.42001e,mp4a.40.2",RESOLUTION=416x234 live/cellular.m3u8
你们只要关注 BANDWIDTH
(带宽)字段,其余的看一下字段内容大体就清楚了。假如这里选择 high.m3u8
文件,那么,里面内容为:
#EXTM3U #EXT-X-VERSION:6 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:26 #EXTINF:9.901, http://media.example.com/wifi/segment26.ts #EXTINF:9.901, http://media.example.com/wifi/segment27.ts #EXTINF:9.501, http://media.example.com/wifi/segment28.ts
注意,其中以 ts
结尾的连接就是咱们在直播中真正须要播放的视频文件。该第二级的 m3u8
文件也能够叫作 media
文件。该文件,其实有三种类型:
live playlist: 动态列表。顾名思义,该列表是动态变化的,里面的 ts 文件会实时更新,而且过时的 ts 索引会被删除。默认,状况下都是使用动态列表。
#EXTM3U #EXT-X-VERSION:6 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:26 #EXTINF:9.901, http://media.example.com/wifi/segment26.ts #EXTINF:9.901, http://media.example.com/wifi/segment27.ts #EXTINF:9.501, http://media.example.com/wifi/segment28.ts
event playlist: 静态列表。它和动态列表主要区别就是,原来的 ts 文件索引不会被删除,该列表是不断更新,并且文件大小会逐渐增大。它会在文件中,直接添加 #EXT-X-PLAYLIST-TYPE:EVENT 做为标识。
#EXTM3U #EXT-X-VERSION:6 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-PLAYLIST-TYPE:EVENT #EXTINF:9.9001, http://media.example.com/wifi/segment0.ts #EXTINF:9.9001, http://media.example.com/wifi/segment1.ts #EXTINF:9.9001, http://media.example.com/wifi/segment2.ts
VOD playlist: 全量列表。它就是将全部的 ts 文件都列在 list 当中。若是,使用该列表,就和播放一整个视频没有啥区别了。它是使用 #EXT-X-ENDLIST 表示文件结尾。
#EXTM3U #EXT-X-VERSION:6 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-PLAYLIST-TYPE:VOD #EXTINF:9.9001, http://media.example.com/wifi/segment0.ts #EXTINF:9.9001, http://media.example.com/wifi/segment1.ts #EXTINF:9.9001, http://media.example.com/wifi/segment2.ts #EXT-X-ENDLIST
里面相关字段解释能够参考: Apple HLS
HLS 啥都好,就是延迟性太大了,估计苹果一开始设计的时候,并不在意它的延时性。HLS 中的延时包括:
TCP 握手
m3u8 文件下载
m3u8 文件下全部 ts 文件下载
这里,咱们先假设每一个 ts 文件播放时长为 5s,每一个 m3u8 最多可携带的 ts 文件数为 3~8。那么最大的延迟则为 40s。注意,只有当一个 m3u8
文件下全部的 ts 文件下载完后,才能开始播放。这里还不包括 TCP 握手,DNS 解析,m3u8 文件下载。因此,HLS 总的延时是很是使人绝望的。那解决办法有吗? 有,很简单,要么减小每一个 ts 文件播放时长,要么减小 m3u8
的中包含 ts 的数量。若是超过平衡点,那么每次请求新的 m3u8 文件时,都会加上必定的延时,因此,这里须要根据业务指定合适的策略。固然,如今因为 mediaSource
的普及,自定义一个播放器也没有多大的难度,这样就能够保证直播延迟性的同时,完成直播的顺利进行。
RTMP 全称为:Real-Time Messaging Protocol
。它是基于 FLV
格式进行开发的,因此,第一反应就是,卧槽,又不能用了!!!
是的,在如今设备中,因为 FLV 的不支持,基本上 RTMP 协议在 Web 中,根本用不到。不过,因为 MSE
(MediaSource Extensions)的出现,在 Web 上直接接入 RTMP 也不是不可能的。基本思路是根据 WebSocket 直接创建长链接进行数据的交流和监听。这里,咱们就先不细说了。咱们主要目的是讲概念,讲框架。RTMP 协议根据不一样的套层,也能够分为:
纯 RTMP: 直接经过 TCP 链接,端口为 1935
RTMPS: RTMP + TLS/SSL,用于安全性的交流。
RTMPE: RTMP + encryption。在 RTMP 原始协议上使用,Adobe 自身的加密方法
RTMPT: RTMP + HTTP。使用 HTTP 的方式来包裹 RTMP 流,这样能直接经过防火墙。不过,延迟性比较大。
RTMFP: RMPT + UDP。该协议经常用于 P2P 的场景中,针对延时有变态的要求。
RTMP 内部是借由 TCP 长链接协议传输相关数据,因此,它的延时性很是低。而且,该协议灵活性很是好(因此,也很复杂),它能够根据 message stream ID 传输数据,也能够根据 chunk stream ID 传递数据。二者均可以起到流的划分做用。流的内容也主要分为:视频,音频,相关协议包等。
详细传输过程如图:
若是后期要使用到 RTMP 协议,能够直接参考
该协议和 RTMP 比起来其实差异不大,只是落地部分有些不一样:
RTMP 是直接将流的传输架在 RTMP 协议之上,而 HTTP-FLV 是在 RTMP 和客户端之间套了一层转码的过程,即:
因为,每一个 FLV 文件是经过 HTTP 的方式获取的,因此,它经过抓包得出的协议头须要使用 chunked
编码。
Content-Type:video/x-flv Expires:Fri, 10 Feb 2017 05:24:03 GMT Pragma:no-cache Transfer-Encoding:chunked
它用起来比较方便,不事后端实现的难度和直接使用 RTMP 来讲仍是比较大的。
上面简单介绍了一下三种协议,具体选择哪一种协议,仍是须要和具体的业务进行强相关,不然的话吃亏的仍是本身(本身挖坑)。。。
这里,简单的作个对比
协议 | 优点 | 缺陷 | 延迟性 |
---|---|---|---|
HLS | 支持性广 | 延时巨高 | 10s 以上 |
RTMP | 延时性好,灵活 | 量大的话,负载较高 | 1s 以上 |
HTTP-FLV | 延时性好,游戏直播经常使用 | 只能在手机 APP 播放 | 2s 以上 |
因为各大浏览器的对 FLV 的围追堵截,致使 FLV 在浏览器的生存情况堪忧,可是,FLV 凭借其格式简单,处理效率高的特色,使各大视频后台的开发者都舍不得启用,若是一旦更改的话,就须要对现有视频进行转码,好比变为 MP4,这样不只在播放,并且在流处理来讲都有点重的让人没法接受。而 MSE 的出现,完全解决了这个尴尬点,可以让前端可以自定义来实现一个 Web 播放器,确实完美。(不过,苹果老大爷以为没这必要,因此,在 IOS 上没法实现。)
MSE 全称就是 Media Source Extensions
。它是一套处理视频流技术的简称,里面包括了一系列 API:Media Source
,Source Buffer
等。在没有 MSE 出现以前,前端对 video 的操做,仅仅局限在对视频文件的操做,而并不能对视频流作任何相关的操做。如今 MSE 提供了一系列的接口,使开发者能够直接提供 media stream。
咱们来看一下 MSE 是如何完成基本流的处理的。
var vidElement = document.querySelector('video'); if (window.MediaSource) { var mediaSource = new MediaSource(); vidElement.src = URL.createObjectURL(mediaSource); mediaSource.addEventListener('sourceopen', sourceOpen); } else { console.log("The Media Source Extensions API is not supported.") } function sourceOpen(e) { URL.revokeObjectURL(vidElement.src); var mime = 'video/webm; codecs="opus, vp9"'; var mediaSource = e.target; var sourceBuffer = mediaSource.addSourceBuffer(mime); var videoUrl = 'droid.webm'; fetch(videoUrl) .then(function(response) { return response.arrayBuffer(); }) .then(function(arrayBuffer) { sourceBuffer.addEventListener('updateend', function(e) { if (!sourceBuffer.updating && mediaSource.readyState === 'open') { mediaSource.endOfStream(); } }); sourceBuffer.appendBuffer(arrayBuffer); }); }
上面的代码完成了相关的获取流和处理流的两个部分。其中,主要利用的是 MS 和 Source Buffer 来完成的。接下来,咱们来具体涉及一下详细内容:
MS(MediaSource) 只是一系列视频流的管理工具,它能够将音视频流完整的暴露给 Web 开发者来进行相关的操做和处理。因此,它自己不会形成过分的复杂性。
MS 整个只挂载了 4 个属性,3 个方法和 1 个静态测试方法。有:
4 个属性:
sourceBuffers: 得到当前建立出来的 SourceBuffer
activeSourceBuffers: 得到当前正处于激活状态的 SourceBuffer
readyState: 返回当前 MS 的状态,好比: closed
,open
,ended
.
duration: 设置当前 MS 的播放时长。
3 个方法:
addSourceBuffer(): 根据给定的 MIME 建立指定类型的 SourceBuffer
removeSourceBuffer(): 将 MS 上指定的 SourceBuffer 移除。
endOfStream(): 直接终止该流
1 个静态测试方法:
isTypeSupported(): 主要用来判断指定的音频的 MIME 是否支持。
最基本的就是使用 addSourceBuffer
该方法来得到指定的 SourceBuffer。
var sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E, mp4a.40.2"');
一旦利用 MS 建立好 SourceBuffer 以后,后续的工做就是将额外得到的流放进 Buffer 里面进行播放便可。因此,SourceBuffer 提供两个最基本的操做 appendBuffer
,remove
。以后,咱们就能够经过 appendBuffer
直接将 ArrayBuffer 放进去便可。
其中,SourceBuffer 还提供了一个应急的方法 abort()
若是该流发生问题的话能够直接将指定的流给废弃掉。
因此,整个流程图为:
音视频的 ArrayBuffer 经过 MediaSource 和 SourceBuffer 的处理直接将 <audio>
&& <video>
接入。而后,就能够实现正常播放的效果。
固然,上面介绍的仅仅只是一些概念,若是要实际进行编码的话,还得继续深刻下去学习。有兴趣的同窗,能够继续深刻了解,个人另一篇博客:全面进阶 H5 直播。
固然,若是后期有机会,能够继续来实现如下如何进行实际的编码。本文,主要是给你们介绍直播所需的必要技术和知识点,只有完备以后,咱们才能没有障碍的完成实际编码的介绍。
上面咱们已经讲解了在直播中,咱们怎样经过 MSE 接触到实际播放的流,那么,接下来,咱们就要开始脚踏实地的了解具体的流的操做处理。由于,视频流格式解协议中,最常涉及的就是拼包,修改字段,切包等操做。
在正式介绍以前,咱们须要先了解一下关于流的一些具体概念:
二进制没啥说的就是 比特流。可是,在 Web 中,有几个简写的进制方式:二进制,八进制,十六进制。
二进制(binary):使用 0b 字面上表示二进制。每一位表明 1bit(2^1)
八进制(octet): 使用 0o 字面上表示八进制。每一位表明 3bit(2^3)
十六进制(hexadecimal): 使用 0x 字面上表示十六进制。每一位表明 4bit(2^4)
上面说的每一位表明着,实际简写的位数,好比 0xff31; 这个就表明 2B 的长度。
位运算在处理流操做中,是很是重要的,不过因为前端 Buffer 提供的操做集合很少,因此,有些轮子咱们还得须要本身构造一下。
这里,我就不细致介绍,在 Web 中经常使用的位运算符有:
&
|
~
^
<<
>>
>>>
详细介绍能够参考 Web 位运算。
整个优先级为:
~ >> << >>> & ^ |
字节序说白了就是 bit 放置的顺序,由于历史遗漏缘由,字节的放置有两种顺序:
大字节序(BigEndian): 将数据从大到小放置,认为第一个字节是最高位(正常思惟)。
小字节序(LittleEndian):将数据从小到达防止,认为第一个字节是最低位。
这个概念在咱们后面写入过程当中,常常用到。固然,咱们如何了解到某台电脑使用的是大字节仍是小字节呢?(其实大部分都是小字节)。可使用 IIFE 进行简单的判断:
const LE = (function () { let buf = new ArrayBuffer(2); (new DataView(buf)).setInt16(0, 256, true); // little-endian write return (new Int16Array(buf))[0] === 256; // platform-spec read, if equal then LE })();
而在前端,咱们归根结底的就是操做 ArrayBuffer
。它也是咱们直接和 Buffer 交流的通道。
AB(ArrayBuffer) 不是像 NodeJS 的 Buffer 对象同样是一个纯粹的集合流处理的工具。它只是一个流的容器,这也是底层 V8 实现的内容。基本用法就是给实例化一个固定的内存区:
new ArrayBuffer(length)
建立指定的 length Byte 内存大小。此时,它里面只是空的内存,这时候你须要借用其余两个对象 TypedArray
和 DataView
来帮助你完成写入和修改的操做。不过,AB 提供了一个很是重要的方法:slice()
slice()
和 Array 对象上的 slice 方法同样也是将数组中的一部分新建立一个副本返回。这个方法为啥有用呢?
由于,经过 TypedArray
和 DataView
建立的对象底层的 AB 都是不能改变的,因此,若是你想对一个 Buffer 进行不一样的操做,好比,对 AB 的 4-8B 所有置 0,而且后面又置为 1。若是你想保留二者的话,就须要手动建立一个副本才行,这就须要用到 slice
方法了。
AB 具体的属性和方法我这里就很少说了,有兴趣的同窗能够参考 MDN ArrayBuffer
接下来,咱们就来看一下和 AB 实际对接最紧密的两个对象 TypedArray
和 DataView
。
TA(TypedArray) 是一套 ArrayBuffer 处理的集合。怎么说呢?它里面还能够细分为
Int8Array(); Uint8Array(); Uint8ClampedArray(); Int16Array(); Uint16Array(); Int32Array(); Uint32Array(); Float32Array(); Float64Array();
为何会有这么多呢?
由于 TA 是将 Buffer 根据指定长度分隔为指定大小的数组。好比:
var buf = new Uint8Array(arrayBuffer); buf[0] buf[1] ...
像这样具体经过 index
来获取指定的 Buffer 中的比特值。好比像上面 Uint8Array
每一位就是 1B。
出来分隔的长度不一样,剩下的内容,基本上就能够用 TA 来进行总体归纳。实际上,你们也能够把它理解为 Array 便可。为啥呢?你能够看一下它有哪些方法后,就完全明白了:
reverse() set() slice() some() sort() subarray() ...
不过,因为兼容性的缘由,对于某些方法来讲,咱们须要加上相关的 polyfill 才行。不过,这也不影响咱们的研究性学习,而且,由于 MSE 是针对现代手机浏览器开发的,因此,咱们在作 Web 播放器的时候,也并不须要过分关注浏览器的兼容。
TypedArray 最经常使用的操做方式,是直接根据 index 进行相关的写入操做:
buf[0] = fmt << 6 | 1; buf[1] = chunkID % 256 - 64; buf[2] = Math.floor(chunkID / 256);
须要注意在 TypedArray 中的字节序,是根据平台默认的字节序来读取 Buffer 的,好比 UintArray32()
。不过,大部分平台默认都是 little-endian
来进行读取。
DV(DataView) 和 TypedArray 很相似,也是用来修改底层的 Buffer 的。说白了,它俩就是 NodeJS Buffer 的两部分,可能因为某些缘由,将二者给分开建立。DataView 提供的 API 很简单,就是一些 get/set 之类的方法。基本用法为:
new DataView(Arraybuffer [, byteOffset [, byteLength]])
注意,前面那个参数只能是 ArrayBuffer ,你不能把 TypedArray 也给我算进去,否则的话...你能够试试。
一样须要提醒的是 DataView 的修改是和对象同样的,是进行引用类型的修改,即,若是对一个 Buffer 建立多个 DataView 那么,屡次修改只会在一个 Buffer 显现出来。
DV 最大的用处就可能够很方便的写入不一样字节序的值,这相比于使用 TypedArray 来作 swap()
(交换) 是很方便的事。固然,字节序相关也只能是大于 8 bit 以上的操做才有效。
这里以 setUInt32
为例子,其基本格式为:
setInt32(byteOffset, value [, littleEndian])
其中,littleEndian 是 boolean 值,用来表示写入字节序的方式,默认是使用大字节序。参考:
It is big-endian by default and can be set to little-endian in the getter/setter methods.
因此,若是你想使用小字节序的话,则须要手动传入 true
才行!
好比:
let view = new DataView(buffer); view.setUint32(0, arr[0] || 1, BE); // 使用 TypedArray 手动构造 swap buf = new Uint8Array(11); buf[3] = byteLength >>> 16 & 0xFF; buf[4] = byteLength >>> 8 & 0xFF; buf[5] = byteLength & 0xFF;
固然,若是你以为不放心,能够直接使用,一个 IIFE 进行相关判断:
const LE = (function () { let buf = new ArrayBuffer(2); (new DataView(buf)).setInt16(0, 256, true); // little-endian write return (new Int16Array(buf))[0] === 256; // platform-spec read, if equal then LE })();
上面是前端 Buffer 的部分,为了让你们更好的了解到 JS 开发工做者从前端到后端操做 Buffer 的区别,这里一并提一下在 NodeJS 中如何处理 Buffer。
Node Buffer 实际上才是前端最好用的 Buffer 操做,由于它是整合的 ArrayBuffer , TypedArray ,Dataview 一块儿的一个集合,该对象上挂载了全部处理的方式。详情能够参考一下:Node Buffer。
他能够直接经过 alloc
和 from
方法来直接建立指定的大小的 Buffer。之前那种经过 new Buffer
的方法官方已经不推荐使用了,具体缘由能够 stackoverflow 搜一搜,这里我就很少说了。这里想特别提醒的是,NodeJS 已经能够和前端的 ArrayBuffer 直接转换了。经过 from
方法,能够直接将 ArrayBuffer 转换为 NodeJS 的 Buffer。
格式为:
Buffer.from(arrayBuffer[, byteOffset[, length]])
参考 NodeJS 提供的 demo:
const arr = new Uint16Array(2); arr[0] = 5000; arr[1] = 4000; // 共享 arr 的缓存 const buf = Buffer.from(arr.buffer); // 打印结果: <Buffer 88 13 a0 0f> console.log(buf); // 直接改变原始的 Buffer 值 arr[1] = 6000; // 打印: <Buffer 88 13 70 17> console.log(buf);
在 Node Buffer 对象上,还挂载了好比:
buf.readInt16BE(offset[, noAssert]) buf.readInt16LE(offset[, noAssert]) buf.readInt32BE(offset[, noAssert]) buf.readInt32LE(offset[, noAssert])
有点不一样的是,它是直接根据名字的不一样而决定使用哪一种字节序。
BE 表明 BigEndian
LE 表明 LittleEndian
以后,咱们就可使用指定的方法进行写入和读取操做。
const buf = Buffer.from([0, 5]); // Prints: 5 console.log(buf.readInt16BE()); // Prints: 1280 console.log(buf.readInt16LE());
在实际使用中,咱们通常对照着 Node 官方文档使用便可,里面文档很详尽。
为了你们可以在学习中减小必定的不适感,这里先给你们介绍一下音视频的基本概念,以防止之后别人在吹逼,你能够在旁边微微一笑。首先,基本的就是视频格式和视频压缩格式。
视频格式应该不用多说,就是咱们一般所说的 .mp4
,.flv
,.ogv
,.webm
等。简单来讲,它其实就是一个盒子,用来将实际的视频流以必定的顺序放入,确保播放的有序和完整性。
视频压缩格式和视频格式具体的区别就是,它是将原始的视频码流变为可用的数字编码。由于,原始的视频流很是大,打个比方就是,你直接使用手机录音,你会发现你几分钟的音频会比市面上出现的 MP3 音频大小大不少,这就是压缩格式起的主要做用。具体流程图以下:
首先,由原始数码设备提供相关的数字信号流,而后经由视频压缩算法,大幅度的减小流的大小,而后交给视频盒子,打上相应的 dts
,pts
字段,最终生成可用的视频文件。经常使用的视频格式和压缩格式以下:
视频格式主要是参考 ISO 提供的格式文件进行学习,而后参照进行编解码便可。
这里,主要想介绍一下压缩算法,由于这个在你实际解码用理解相关概念很重要。
首先来看一下,什么叫作视频编码。
视频实际上就是一帧一帧的图片,拼接起来进行播放而已。而图片自己也能够进行相关的压缩,好比去除重复像素,合并像素块等等。不过,还有另一种压缩方法就是,运动估计和运动补偿压缩,由于相邻图片必定会有一大块是类似的,因此,为了解决这个问题,能够在不一样图片之间进行去重。
因此,总的来讲,经常使用的编码方式分为三种:
变换编码:消除图像的帧内冗余
运动估计和运动补偿:消除帧间冗余
熵编码:提升压缩效率
这里就涉及到图像学里面的两个概念:空域和频域。空域就是咱们物理的图片,频域就是将物理图片根据其颜色值等映射为数字大小。而变换编码的目的是利用频域实现去相关和能量集中。经常使用的正交变换有离散傅里叶变换,离散余弦变换等等。
熵编码主要是针对码节长度优化实现的。原理是针对信源中出现几率大的符号赋予短码,对于几率小的符号赋予长码,而后总的来讲实现平均码长的最小值。编码方式(可变字长编码)有:霍夫曼编码、算术编码、游程编码等。
上面那两种办法主要是为了解决图像内的关联性。另外,视频压缩还存在时间上的关联性。例如,针对一些视频变化,背景图不变而只是图片中部分物体的移动,针对这种方式,能够只对相邻视频帧中变化的部分进行编码。
接下来,再来进行说明一下,运动估计和运动补偿压缩相关的,I,B,P 帧。
I,B,P 其实是从运动补偿中引出来的,这里为了后面的方便先介绍一下。
I 帧(I-frame): 学名叫作: Intra-coded picture
。也能够叫作独立帧。该帧是编码器随机挑选的参考图像,换句话说,一个 I 帧自己就是一个静态图像。它是做为 B,P 帧的参考点。对于它的压缩,只能使用熵
和 变化编码
这两种方式进行帧内压缩。因此,它的运动学补偿基本没有。
P 帧(P‑frame): 又叫作 Predicted picture
--前向预测帧。即,他会根据前面一张图像,来进行图片间的动态压缩,它的压缩率和 I 帧比起来要高一些。
B 帧(B‑frame): 又叫作 Bi-predictive picture
-- 双向预测。它比 P 帧来讲,还多了后一张图像的预测,因此它的压缩率更高。
能够参考一下雷博士的图:
不过,这样理解 OK,若是一旦涉及实际编码的话,那么就不是那么一回事了。设想一下,视频中的 IBP 三帧,颇有可能会遇到 I B B P
的情形。这样其实也还好,不过在对数据解码的时候,就会遇到一个问题,B 帧是相对于先后两帧的,可是只有前一帧是固定帧,后面又相对于前面,即,B 帧只能相对于 I/P。可是,这时候 P 帧又尚未被解析。因此,为了解决这个问题,在解码的时候,就须要将他们换一个位置,即 I P B B。这样就能够保证解码的正确性。
那怎么进行保证呢?这就须要 DTS 和 PTS 来完成。这两个是咱们在进行视频帧编解码中最终要的两个属性(固然还有一个 CTS)。
解释一下:
pts(presentation time stamps):显示时间戳,显示器从接受到解码到显示的时间。
dts(decoder timestamps): 解码时间戳。也表示该 sample 在整个流中的顺序
因此视频帧的顺序简单的来表示一下就是:
PTS: 1 4 2 3 DTS: 1 2 3 4 Stream: I P B B
能够看到,咱们使用 DTS 来解码,PTS 来进行播放。OK,关于 Web 直播的大体基本点差很少就介绍完了。后面若是还有机会,咱们能够来进行一下 音视频解码的实战操练。