Web 直播流的解析

标签: geek | 发表时间:2017-04-17 00:00 | 作者:
出处:http://itindex.net/admin/pagedetail

Web 进制操作是一个比较底层的话题,因为平常做业务的时候根本用不到太多,或者说,根本用不到。

老铁,没毛病

那什么情况会用到呢?

  • canvas
  • websocket
  • file
  • fetch
  • webgl

上面只是列了部分内容。现在比较流行的就是音视频的处理,怎么说呢?

如果,有涉及直播的话,那么这应该就是一个非常!非常!非常!重要的一块内容。我这里就不废话了,先主要看一下里面的基础内容。

整体架构

首先,一开始我们是怎么接触到底层的 bit 流呢?

记住:只有一个对象我们可以搞到 bit 流 --> ArrayBuffer

这很似曾相识,例如在 fetch 使用中,我们可以通过 res.arrayBuffer(); 来直接获取 ArrayBuffer 对象。websocket 中,监听 message,返回来的 event.data也是 arraybuffer。

  letsocket =newWebSocket('ws://127.0.0.1:8080');
socket.binaryType ='arraybuffer';

socket.addEventListener('message',function(event){letarrayBuffer = event.data;
    ···
});

但是,ArrayBuffer 并不能直接提供底层流的获取操作!!!

你可以通过 TypeArray 和 DataView 进行相关查看:

image.png-7kB

接下来,我们具体看一下 TypeArray 和 DataView 的具体细节吧。

TypedArray

首先声明这并不是一个具体的 array 对象,而是一整个底层 Buffer 的概念集合。首先,我们了解一下底层的二进制:

二进制

在一般程序语言里面,最底层的数据大概就可以用 0 和 1 来表示:

  00000000000000000000000100111010

根据底层的比特的数据还可以划分为两类:

  • signed: 从左到右第一位开始,如果为 0 则表示为正,为 1 则表示为负。例如:-127~+127
  • unsigned: 从左到右第一位不作为符号的表示。例如:0~255

而我们程序表达时,为了易读性和简便性常常会结合其他进制一起使用。

  • 八进制(octet)
  • 十进制(Decimal)
  • 十六进制(Hexadecimal)

特别提醒的是:

在 JS 中: 使用 0x字面上表示十六进制。每一位代表 4bit(2^4)。 使用 0o字面上表示八进制。每一位代表 3bit(2^3)。还有一种是直接使用 0为开头,不过该种 bug 较多,不推荐。 使用 0b字面上表示二进制。每一位代表 1bit(2^1)。

了解了二进制之后,接下来我们主要来了解一下 Web 比特位运算的基本内容。

位运算

Web 中的位运算和其它语言中类似,有基本的 7 个。

与 ( &)

在相同位上,都为 1 时,结果才为 1:

  // 在 Web 中二进制不能直接表示001&101=001

并且,该运算常常会和叫做 bitmask(屏蔽字)结合起来使用。比如,在音视频的 Buffer 中,第 4 位 bit 表示该 media segments 里面是否存在 video。那么为了检验,则需要提取第 4 位,这时候就需要用到我们的 bitmask。

  // 和 1000 进行相与buf &8

或 ( |)

在相同位上,有一个为 1 时,结果为 1。

  // FROM MDN9(base10) =00000000000000000000000000001001(base2)14(base10) =00000000000000000000000000001110(base2)
                   --------------------------------14^9(base10) =00000000000000000000000000000111(base2) =7(base10)

非 ( ~)

只和自己做运算,如果为 0,结果为 1。如果为 1 结果为 0。反正就是相反的意思了:

  // FROM MDN9(base10) =00000000000000000000000000001001(base2)
               --------------------------------
~9(base10) =11111111111111111111111111110110(base2) = -10(base10)

异或 ( ^)

当两者中只有一个 1 那么结果才为 1。

  // FROM MDN9(base10) =00000000000000000000000000001001(base2)14(base10) =00000000000000000000000000001110(base2)
                   --------------------------------14^9(base10) =00000000000000000000000000000111(base2) =7(base10)

左移 ( <<)

基本格式为:x << y

将 x 向左移动 y 位数。空出来的补 0

  // FROM MDN9(base10):00000000000000000000000000001001(base2)
                  --------------------------------9<<2(base10):00000000000000000000000000100100(base2) =36(base10)

带位右移 ( >>)

什么叫 带位呢?

上面我们提到过 signedunsigned。那么这里针对的就是 signed的位移类型。

格式为: x >> y

将 x 向右移动 y 位数。左边空出来的位置根据最左边的第一位决定,如果为 1 则补 1,反之。

  1001>>2=1110

直接右移 ( >>>)

该方式和上面具体区别就是,该运算针对的是 unsigned的移动。不管你左边是啥,都给我补上 0。

格式为: x >> y

  1001>>2=0010

上面这些运算符主要是针对 32bit的。不过有时候为了简便,可以省去前面多余的 0。不过大家要清楚,这是针对 32 位的即可。

优先级

上面简单介绍了位操作符,但是他们的优先级是怎么样的呢?详情可以参考: precedence;

简单来说:(按照下列顺序,优先级降低)

~ >> << >>> & ^ |

位运算具体运用

状态改变

后台在保存数据的时候,常常会遇到某一个字段有多种状态。例如,填表状态:填完,未填,少填,填错等。一般情况下直接用数字来进行代替就行,只要文档写清楚就没事。例如:

  • 0: 填完
  • 1: 未填
  • 2:少填
  • 3:填错

不过,我们还可以通过比特位来进行表示,每一位表示一个具体的状态。

  • 0001: 填完
  • 0010: 未填
  • 0100:少填
  • 1000:填错

这样我们只要找到每一位是否为 1 就可以知道里面有哪些状态存在。并且,还可以对状态进行组合,例如,填完并且填错,如果按照数字来说就没啥说明这样的情况。

那么基本的状态值有了,接下来就是怎么进行赋值和修改。

现在假设,某人的填写状态为 填完 + 填错。那么结果可以表示为:

  varmask =0001|1000;

后面如果涉及条件判断,例如:该人是否填错,则可以使用 &来表示:

  // 是否填错if(mask &1000) doSth;

或者,是否即填完又填错

  if(mask & (1000|0001)) doSth;

后面涉及到状态改变的话,则需要用到 |运算。假设,现在该人为填完,现在变为少填。那么状态改变应该为:

  // 取填完的反状态vardone = ~0001;// 1110mask &= done;// 添加少填状态;mask |=0100

进制转换

在 JS 中进制转换有两种方式: toStringparseInt

  • toString(radix): 该可以将任意进制转换为 2-36 的进制。radix 默认为 10。
  • parseInt(string,radix): 将指定 string 根据 radix 的标识转换成为 10 进制。radix 默认为 10。另外它主要用作于字符串的提取。
  • Number(string): 字面上转换字符串为十进制。

parseInt 用于字符串过滤,例如:

  parseInt('15px',10);// return 15

里面的字符不仅只有数字,而且还包括字母。

不过需要注意的是,parseInt 是不认可,以 0 开头的八进制,但认可 0o。所以,在使用的时候需要额外注意。

上面说过,parseInt 是将其它进制转换为 10 进制,其第二个参数主要就是为了表示前面内容的进制,如果没写,引擎内部会进行相关识别,但不保证一定正确。所以,最好写上。

  parseInt(' 0xF',16);// return 15

如果你只是想简单转换一下字符串,那么使用 Number()无疑是最简单的。

  Number('0x11')// 17Number('0b11')// 3Number('0o11')// 9

toString

toString 里面的坑就没有 parseInt 这么多了。它也是进制转换非常好用的一个工具。因为是 字符串,所以,这里就只能针对字面量进制进行转换了–2,8,(10),16。这四种进制的相关之间转换。

提醒:如果你是直接使用字面量转换的话,需要注意使用 10 进制转换时,隐式转换会失效。即,100.toString(2) 会报错。

例如:

  0b1101101.toString(8);// 1550b1101101.toString(10);// 1090b1101101.toString(8);// 6d

如上面所述,他们转换后的结果一般没有进制前缀。这个时候,就需要手动加上相关的前缀即可。

例如:16 进制转换

  functionhexConvert(str){return"0x"+ str.toString(16);
}

到这里,进制转换基本就讲完了。后面我们来看一下具体的 TypeArray

整体架构

TypeArray 不是一个可以用程序写出来的概念,它是许多 TypeArray 的总称。参考: TypeArray。可以了解到,它的子类如下:

  • Int8Array();
  • Uint8Array();
  • Uint8ClampedArray();
  • Int16Array();
  • Uint16Array();
  • Int32Array();
  • Uint32Array();
  • Float32Array();
  • Float64Array();

看上去很多,不过在 JS 中,因为它天生都不是用来处理 signed类型的。所以, Uint系列在 JS 中应该算是主流。大概排个序:

Uint8Array > Uint16Array > Int8Array > …

他们之间的具体不同,参照:

|数据类型| 字节长度| 含义| 对应的C语言类型| |:—|:—|:—| |Int8| 1| 8位带符号整数| signed char| |Uint8| 1| 8位不带符号整数| unsigned char| |Uint8C| 1| 8位不带符号整数(自动过滤溢出)| unsigned char| |Int16| 2| 16位带符号整数| short| |Uint16| 2| 16位不带符号整数| unsigned short| |Int32| 4| 32位带符号整数| int| |Uint32| 4| 32位不带符号的整数| unsigned int| |Float32| 4| 32位浮点数| float| |Float64| 8| 64位浮点数| double|

虽然口头上说 TypeArray 没有一个具体的实例,但是私下,上面那几个 array 都是叫他爸爸。因为他定义了一些 uintArray 的基本功能。首先是实例化:

TypeArray 的实例化有 4 种:

  newTypedArray(length);// 创建指定长度的 typeArraynewTypedArray(typedArray);// 复制新的 typeArraynewTypedArray(object);// 不常用newTypedArray(buffer [, byteOffset [, length]]);// 参数为 arrayBuffer。

上面 4 中最常用的应该为 1 和 4。接着,我们了解一下,具体才创建的时候,TypeArray 到底做了些什么。

当创建实例 TypeArray 的构造函数时,内部会同时创建一个 arrayBuffer 用来作为数据的存储。如果是通过 TypedArray(buffer);方式创建,那么 TypeArray 会直接使用该 buffer的内存地址。

接下来,我们就以 Uint8Array为主要参照,来看一下基本的处理和操作。

该例直接来源于 MDN

  // From a lengthvaruint8 =newUint8Array(2);
uint8[0] =42;console.log(uint8[0]);// 42console.log(uint8.length);// 2console.log(uint8.BYTES_PER_ELEMENT);// 1// From an arrayvararr =newUint8Array([21,31]);console.log(arr[1]);// 31// From another TypedArrayvarx =newUint8Array([21,31]);vary =newUint8Array(x);console.log(y[0]);// 21// From an ArrayBuffervarbuffer =newArrayBuffer(8);// 创建 8个字节长度的 arrayBuffervarz =newUint8Array(buffer,1,4);

它上面的方法大家直接参考 MDN 的上的就 OK。一句话总结就是,你可以想操作 Array 一样,操作里面的内容。

根据 ArrayBuffer 的描述,它本身的是从 files 和 base64 编码来获取的。如果只是初始化,他里面的每一位都是 0.不过,为了容易测试,我们可以直接自己指定:

  vararrBuffer =Uint8Array.from('123');// [1,2,3]// 或者vararrBuffer =Uint8Array.of(1,2,3);// [1,2,3]

多字节图

假如一个 Buffer 很长,假设有 80 位,算下来就是 10B。一开始我们的想法就是直接创建一个 typeArray就 OK。不过,根据上面的构造函数上看,其实,可以将一整个 buffer 拆成不同的 typeArray 进行读取。

  buf;// 10B 的 bufvarfirstB =newUint8Array(buf,0,1);// buf 中第一个字节内容vartheRestB =newUint8Array(buf,1,9);// buf 中 2~10 的字节内容

字节概念

在字节中,还有几个相关的概念需要理解一下。一个是溢出,一个是字节序。同样,还是根据 Uint8 来说明。

Uint8 每一个数组位,表示 8 位二进制,即范围为 0~255。

溢出

  vararrBuffer =Uint8Array.from('61545');
arrBuffer;// [6, 1, 5, 4, 5]

然后我们做一下加法:

  arrBuffer[0] +=1;// 7arrBuffer[0] +=0xfe;// 6。因为 7 + 254 溢出 6

然后是字节序。

字节序

在 JS,Java,C 等高级语言中,字节序一般都是 大字节序。而一些硬件则会以 小字节序作为标准。

  • 大字节序:假如 0xAABB 被 Uint16 存储为 2 位。那么按照大字节序就是按顺序来,即 0: 0xAA, 1:0xBB。
  • 小字节序:和上面相反,即,0:0xBB,1:0xAA。

当然如果只是在 PC 上操作了的话,字节序可以使用 IIFE 检测一下:

  (function(){letbuf =newArrayBuffer(2);
    (newDataView(buf)).setInt16(0,256,true);// little-endian writereturn(newInt16Array(buf))[0] ===256;// platform-spec read, if equal then LE})();

关于 TypeArray 的内容差不多就是上面将的。接下来, 我们再来看另外一个重要的对象 DataView

DataView

DataView 没有 TypeArray这么复杂,衍生出这么多个 Uint/IntArray。它就是一个构造函数。同样,它的目的也是对底层的 arrayBuffer 进行读取。那么,为什么它会被创建出来呢?

是因为有 字节序的存在。上面说过字节序有两种。通常,PC 和目前流行的电子设备都是大字节序,而如果是接收一些外部资源,就不能排除会接受一些小字节序的文件。为了解决这个问题,就出现了 DataView。它的实例格式为:

  newDataView(buffer [, byteOffset [, byteLength]])

同样,它的格式和 TypeArray 类似,也是用来作为 buffer 的读写对象。

  • buffer: 需要接入的底层 ArrayBuffer
  • byteOffset: 偏移量,单位为字节
  • byteLength: 获取长度,单位为字节

它的具体操作不是直接通过 []获取,而是使用相关的 get/set方法来完成。而他针对 字节序的操作,主要是针对 >=16 比特的流来区别,即,get/setInt8() 是没有 字节序的概念的。

先以 16 位的作为例子:

  dataview.getInt16(byteOffset [, littleEndian]);// 根据字节序,获得偏移字节后的两个字节。
  • byteOffset: 单位为 字节。
  • littleEndian[boolean]: 字节序。默认为 false。表示大字节序。
  varbuffer =newArrayBuffer(8);vardataview =newDataView(buffer);
dataview.getInt16(1,true);// 0

Buffer 场景

如上面所述,Buffer 的场景有:

  • canvas
  • websocket
  • file
  • fetch
  • webgl

file

直接看代码吧:

  letfileInput =document.getElementById('fileInput');letfile = fileInput.files[0];letreader =newFileReader();
reader.readAsArrayBuffer(file);
reader.onload =function(){letarrayBuffer = reader.result;
   ···
};

AJAX

这里和 fetch 区分一下,作为一种兼容性比较好的选择。

  letxhr =newXMLHttpRequest();
xhr.open('GET', someUrl);
xhr.responseType ='arraybuffer';

xhr.onload =function(){letarrayBuffer = xhr.response;
    ···
};

xhr.send();

fetch

  fetch(url)
.then(request => request.arrayBuffer())
.then(arrayBuffer => ···);

canvas

  letcanvas =document.getElementById('my_canvas');letcontext = canvas.getContext('2d');letimageData = context.getImageData(0,0, canvas.width, canvas.height);letuint8ClampedArray = imageData.data;

websocket

  letsocket =newWebSocket('ws://127.0.0.1:8080');
socket.binaryType ='arraybuffer';

socket.addEventListener('message',function(event){letarrayBuffer = event.data;
    ···
});

上面这些都是可以和 Buffer 进行交流的对象。那还有其他的吗?有的,总的一句话:

能提供的 arrayBuffer 的都可以进行底层交流。

相关 [web 直播 解析] 推荐:

Web 直播流的解析

- - IT瘾-geek
Web 进制操作是一个比较底层的话题,因为平常做业务的时候根本用不到太多,或者说,根本用不到. 现在比较流行的就是音视频的处理,怎么说呢. 如果,有涉及直播的话,那么这应该就是一个非常. 我这里就不废话了,先主要看一下里面的基础内容. 首先,一开始我们是怎么接触到底层的 bit 流呢. 记住:只有一个对象我们可以搞到 bit 流 --> ArrayBuffer.

解析web网页并保存页面中的图片

- - Marshal's Blog
这个功能,是很多类似爬虫的应用需要实现的. 如果使用node.js,你会惊讶的发现,这个任务实现起来很容易,就象写嵌入在页面中的javascript一样. 建议没接触过node.js的,可以先看看 node.js能干什么. ,里面已经包含了最简单的解析web网页的功能. 这次做的是更具体的事情,我想把维基百科的坦克词条文章抽取出标题和一个图片:.

简单***的实现,利用js解析把web网页转换成自己的网页,加快开发

- - ITeye博客
利用开源js引擎rhino+jsoup进行web裁制,使用javascript来解析页面. 已有 0 人发表留言,猛击->> 这里<<-参与讨论. —软件人才免语言低担保 赴美带薪读研.

现场直播

- 纸条 - 新闻跟帖局
核心提示:8月29日上午,福建气象台发布“沿海地区台风警报”与“台风黄色预警信号”称,台风“南玛都”可能于30日白天在福建省中南部沿海登陆. 受台风影响,福建自29日起3日内多地将有大雨到暴雨. [查看原文]好友向您推荐这条跟贴. 网易福建省泉州市网友 [小小小书童] 的原贴:1. 你好,主持人,我现在在泉州惠安西沙湾,现在风力非常大,屁股对准海边保你菊花都被吹开,现场的情况就是这样,现在把画面交给演播室.

Web未死

- Sinan - GeekPark 捕风捉影
App的极限已经浮现,而Web则是突破此极限,推动下一个数字时代革命的起点. 距离美国《连线》杂志发表《Web已死,互联网永生》这篇文章还不到一年的时间,业界为Web平反的声音渐起. 2010年1月,苹果发布iPad,紧随其后在6月又发布了iPhone4. 没有人质疑过苹果的iTunes+App的商业模式,App可谓如日中天.

web的演变

- 酿泉 - 前端观察
这是一个基于GAE的项目,有mgmt design、GOOD、Hyperakt和Vizzuality开发,也有Google chrome团队的参与,记录了浏览器与互联网技术的演变. 不多介绍,直接去看看吧:Evolution Of Web. 值得一提的是,这个项目的代码很不错,值得学习一下.

Web Apps来袭

- - HTML5研究小组
如同历史上任何一次互联网基础标准的变化都会在随后几年中带来应用创新的大爆发一样,当HTML5在2011年逐渐被主流厂商所接受之后,围绕Web Apps领域的创新风暴正山雨欲来. 2012年1月12日,老牌传媒集团《金融时报》(Financial Times,以下简称FT)宣布收购为其开发移动Web App的研发公司Assanka ,这样,FT将不再以外包的形式雇佣Assanka为其打造移动Web App,而可以直接让它在内部进行开发.

Web Service入门

- - 博客 - 伯乐在线
本文来自文章作者 @Jeremy黄国华 的投稿. 伯乐在线也欢迎其他朋友投稿,投稿时记得留下您的新浪微博账号哦~. 目前对Web Service没有统一的定义,定义一:Web Service是自包含的、模块化的应用程序,它可以在Web中被描述、发布、查找以及调用. 定义二:Web Service是基于网络的、分布式的模块化组件,它执行特定的任务,遵守具体的技术规范,这些规范使得Web Service能与其他兼任的组件进行操作.

Google 的 Web Desinger

- - 极客公园-GeekPark
[核心提示]Google 的免费 Web 设计工具虽然现在主要目的是为广告设计,今后会不会成为 Chrome 应用的开发工具. 听到 Google 推出了一个名为 Google Web Designer 的网页设计还有点惊讶. 虽然 Google 是 Web 技术的大力倡导者,毕竟自己严重依赖这个平台,但市面上相关的产品太多了,从专业的开发工具到小白的所见即所得软件数不胜数,还有 Adobe 这个专业玩家.