再也不碎片化学习,快速掌握 H5 直播技术

如今,大多数已工做的前端工做者的学习方式,要么直接到 Stackoverflow 上搜代码,要么直接看看相关博文。这样是快,可是零零碎碎只是一个一个孤立的知识点而已。有可能一下午都忘记了,惟一可能记住的收藏一下那个文章,而后就完全躺尸了。那有没有啥更好的办法能解决呢?html

固然有,第一,有时间,第二,有人指导,第三,找对资料。前端

这其实和看书是同样的,一本书,最有价值的地方不在它的内容或者做者,而在于它的目录,是否真正的打动你。若是只是出现一些模糊而没有落地技术的目录的书籍,仍是别再上面浪费时间了。node

因此,本文主要给你们介绍一下当下 HTML5 直播所涵盖的技术范围,若是要深度学习每个技术,咱们后续能够继续讨论。web

直播协议

直播是 16 年搭着短视频热火起来的。它的业务场景有不少,有游戏主播,才艺主播,网上教学,群体实验(前段时间,有人直播让观众来炒股)等等。不过,根据技术需求的划分,还能够分为低延迟和高延迟的直播,这里就主要是协议选择的问题。算法

如今,经常使用的直播协议有不少种,好比 RTMP,HLS,HTTP-FLV。不过,最经常使用的仍是 HLS 协议,由于支持度高,技术简单,可是延迟很是严重。这对一些对实时性比较高的场景,好比运动赛事直播来讲很是蛋疼。这里,咱们来细分的看一下每一个协议。vim

HLS

HLS 全称是 HTTP Live Streaming。这是 Apple 提出的直播流协议。(其实,Adobe 公司 FLV 播放器的没落,苹果也是幕后黑手之一。) 后端

HLS 由两部分构成,一个是 .m3u8 文件,一个是 .ts 视频文件(TS 是视频文件格式的一种)。整个过程是,浏览器会首先去请求 .m3u8 的索引文件,而后解析 m3u8,找出对应的 .ts 文件连接,并开始下载。更加详细的说明能够参考这幅图:api

image.png-93.1kB

他的使用方式为:数组

<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 啥都好,就是延迟性太大了,估计苹果一开始设计的时候,并不在意它的延时性。HLS 中的延时包括:

  • TCP 握手

  • m3u8 文件下载

  • m3u8 文件下全部 ts 文件下载

这里,咱们先假设每一个 ts 文件播放时长为 5s,每一个 m3u8 最多可携带的 ts 文件数为 3~8。那么最大的延迟则为 40s。注意,只有当一个 m3u8 文件下全部的 ts 文件下载完后,才能开始播放。这里还不包括 TCP 握手,DNS 解析,m3u8 文件下载。因此,HLS 总的延时是很是使人绝望的。那解决办法有吗? 有,很简单,要么减小每一个 ts 文件播放时长,要么减小 m3u8 的中包含 ts 的数量。若是超过平衡点,那么每次请求新的 m3u8 文件时,都会加上必定的延时,因此,这里须要根据业务指定合适的策略。固然,如今因为 mediaSource 的普及,自定义一个播放器也没有多大的难度,这样就能够保证直播延迟性的同时,完成直播的顺利进行。

RTMP

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 传递数据。二者均可以起到流的划分做用。流的内容也主要分为:视频,音频,相关协议包等。

详细传输过程如图:

image.png-29.3kB

若是后期要使用到 RTMP 协议,能够直接参考

HTTP-FLV

该协议和 RTMP 比起来其实差异不大,只是落地部分有些不一样:

RTMP 是直接将流的传输架在 RTMP 协议之上,而 HTTP-FLV 是在 RTMP 和客户端之间套了一层转码的过程,即:

image.png-8.2kB

因为,每一个 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

MSE 全称就是 Media Source Extensions。它是一套处理视频流技术的简称,里面包括了一系列 API:Media SourceSource 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 来完成的。接下来,咱们来具体涉及一下详细内容:

MediaSource

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"');

Source Buffer

一旦利用 MS 建立好 SourceBuffer 以后,后续的工做就是将额外得到的流放进 Buffer 里面进行播放便可。因此,SourceBuffer 提供两个最基本的操做 appendBufferremove。以后,咱们就能够经过 appendBuffer 直接将 ArrayBuffer 放进去便可。

其中,SourceBuffer 还提供了一个应急的方法 abort() 若是该流发生问题的话能够直接将指定的流给废弃掉。

因此,整个流程图为:

image.png-16kB

音视频的 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 交流的通道。

ArrayBuffer

AB(ArrayBuffer) 不是像 NodeJS 的 Buffer 对象同样是一个纯粹的集合流处理的工具。它只是一个流的容器,这也是底层 V8 实现的内容。基本用法就是给实例化一个固定的内存区:

new ArrayBuffer(length)

建立指定的 length Byte 内存大小。此时,它里面只是空的内存,这时候你须要借用其余两个对象 TypedArrayDataView 来帮助你完成写入和修改的操做。不过,AB 提供了一个很是重要的方法:slice()

slice() 和 Array 对象上的 slice 方法同样也是将数组中的一部分新建立一个副本返回。这个方法为啥有用呢?

由于,经过 TypedArrayDataView 建立的对象底层的 AB 都是不能改变的,因此,若是你想对一个 Buffer 进行不一样的操做,好比,对 AB 的 4-8B 所有置 0,而且后面又置为 1。若是你想保留二者的话,就须要手动建立一个副本才行,这就须要用到 slice 方法了。

AB 具体的属性和方法我这里就很少说了,有兴趣的同窗能够参考 MDN ArrayBuffer

接下来,咱们就来看一下和 AB 实际对接最紧密的两个对象 TypedArrayDataView

TypedArray

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 来进行读取。

DataView

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

Node Buffer 实际上才是前端最好用的 Buffer 操做,由于它是整合的 ArrayBuffer , TypedArray ,Dataview 一块儿的一个集合,该对象上挂载了全部处理的方式。详情能够参考一下:Node Buffer

他能够直接经过 allocfrom 方法来直接建立指定的大小的 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 音频大小大不少,这就是压缩格式起的主要做用。具体流程图以下:

image.png-37.1kB

首先,由原始数码设备提供相关的数字信号流,而后经由视频压缩算法,大幅度的减小流的大小,而后交给视频盒子,打上相应的 dtspts 字段,最终生成可用的视频文件。经常使用的视频格式和压缩格式以下:

image.png-136.4kB

视频格式主要是参考 ISO 提供的格式文件进行学习,而后参照进行编解码便可。

这里,主要想介绍一下压缩算法,由于这个在你实际解码用理解相关概念很重要。

首先来看一下,什么叫作视频编码。

视频编码

视频实际上就是一帧一帧的图片,拼接起来进行播放而已。而图片自己也能够进行相关的压缩,好比去除重复像素,合并像素块等等。不过,还有另一种压缩方法就是,运动估计和运动补偿压缩,由于相邻图片必定会有一大块是类似的,因此,为了解决这个问题,能够在不一样图片之间进行去重。

因此,总的来讲,经常使用的编码方式分为三种:

  • 变换编码:消除图像的帧内冗余

  • 运动估计和运动补偿:消除帧间冗余

  • 熵编码:提升压缩效率

变换编码

这里就涉及到图像学里面的两个概念:空域和频域。空域就是咱们物理的图片,频域就是将物理图片根据其颜色值等映射为数字大小。而变换编码的目的是利用频域实现去相关和能量集中。经常使用的正交变换有离散傅里叶变换,离散余弦变换等等。

熵编码

熵编码主要是针对码节长度优化实现的。原理是针对信源中出现几率大的符号赋予短码,对于几率小的符号赋予长码,而后总的来讲实现平均码长的最小值。编码方式(可变字长编码)有:霍夫曼编码、算术编码、游程编码等。

运动估计和运动补偿

上面那两种办法主要是为了解决图像内的关联性。另外,视频压缩还存在时间上的关联性。例如,针对一些视频变化,背景图不变而只是图片中部分物体的移动,针对这种方式,能够只对相邻视频帧中变化的部分进行编码。

接下来,再来进行说明一下,运动估计和运动补偿压缩相关的,I,B,P 帧。

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 帧来讲,还多了后一张图像的预测,因此它的压缩率更高。

能够参考一下雷博士的图:

image.png-54kB

不过,这样理解 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 直播的大体基本点差很少就介绍完了。后面若是还有机会,咱们能够来进行一下 音视频解码的实战操练。

相关文章
相关标签/搜索