成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

web 直播流的解析

williamwen1986 / 3459人閱讀

摘要:反正就是相反的意思了異或當(dāng)兩者中只有一個(gè)那么結(jié)果才為。將指定根據(jù)的標(biāo)識(shí)轉(zhuǎn)換成為進(jìn)制。當(dāng)創(chuàng)建實(shí)例的構(gòu)造函數(shù)時(shí),內(nèi)部會(huì)同時(shí)創(chuàng)建一個(gè)用來作為數(shù)據(jù)的存儲(chǔ)。不過,根據(jù)上面的構(gòu)造函數(shù)上看,其實(shí),可以將一整個(gè)拆成不同的進(jìn)行讀取。

Web 進(jìn)制操作是一個(gè)比較底層的話題,因?yàn)槠匠W鰳I(yè)務(wù)的時(shí)候根本用不到太多,或者說,根本用不到。

老鐵,沒毛病

那什么情況會(huì)用到呢?

canvas

websocket

file

fetch

webgl

...

上面只是列了部分內(nèi)容?,F(xiàn)在比較流行的就是音視頻的處理,怎么說呢?

如果,有涉及直播的話,那么這應(yīng)該就是一個(gè)非常!非常!非常!重要的一塊內(nèi)容。我這里就不廢話了,先主要看一下里面的基礎(chǔ)內(nèi)容。

整體架構(gòu)

首先,一開始我們是怎么接觸到底層的 bit 流呢?

記住:只有一個(gè)對(duì)象我們可以搞到 bit 流 --> ArrayBuffer

這很似曾相識(shí),例如在 fetch 使用中,我們可以通過 res.arrayBuffer(); 來直接獲取 ArrayBuffer 對(duì)象。websocket 中,監(jiān)聽 message,返回來的 event.data 也是 arraybuffer。

let socket = new WebSocket("ws://127.0.0.1:8080");
socket.binaryType = "arraybuffer";

socket.addEventListener("message", function (event) {
    let arrayBuffer = event.data;
    ···
});

但是,ArrayBuffer 并不能直接提供底層流的獲取操作!?。?

你可以通過 TypeArray 和 DataView 進(jìn)行相關(guān)查看:

接下來,我們具體看一下 TypeArray 和 DataView 的具體細(xì)節(jié)吧。

TypedArray

首先聲明這并不是一個(gè)具體的 array 對(duì)象,而是一整個(gè)底層 Buffer 的概念集合。首先,我們了解一下底層的二進(jìn)制:

二進(jìn)制

在一般程序語言里面,最底層的數(shù)據(jù)大概就可以用 0 和 1 來表示:

00000000000000000000000100111010

根據(jù)底層的比特的數(shù)據(jù)還可以劃分為兩類:

signed: 從左到右第一位開始,如果為 0 則表示為正,為 1 則表示為負(fù)。例如:-127~+127

unsigned: 從左到右第一位不作為符號(hào)的表示。例如:0~255

而我們程序表達(dá)時(shí),為了易讀性和簡(jiǎn)便性常常會(huì)結(jié)合其他進(jìn)制一起使用。

八進(jìn)制(octet)

十進(jìn)制(Decimal)

十六進(jìn)制(Hexadecimal)

特別提醒的是:

在 JS 中:
使用 0x 字面上表示十六進(jìn)制。每一位代表 4bit(2^4)。
使用 0o 字面上表示八進(jìn)制。每一位代表 3bit(2^3)。還有一種是直接使用 0 為開頭,不過該種 bug 較多,不推薦。
使用 0b 字面上表示二進(jìn)制。每一位代表 1bit(2^1)。

了解了二進(jìn)制之后,接下來我們主要來了解一下 Web 比特位運(yùn)算的基本內(nèi)容。

位運(yùn)算

Web 中的位運(yùn)算和其它語言中類似,有基本的 7 個(gè)。

與 (&)

在相同位上,都為 1 時(shí),結(jié)果才為 1:

// 在 Web 中二進(jìn)制不能直接表示
001 & 101 = 001

并且,該運(yùn)算常常會(huì)和叫做 bitmask(屏蔽字)結(jié)合起來使用。比如,在音視頻的 Buffer 中,第 4 位 bit 表示該 media segments 里面是否存在 video。那么為了檢驗(yàn),則需要提取第 4 位,這時(shí)候就需要用到我們的 bitmask。

// 和 1000 進(jìn)行相與
buf & 8 
或 (|)

在相同位上,有一個(gè)為 1 時(shí),結(jié)果為 1。

// FROM MDN
    9 (base 10) = 00000000000000000000000000001001 (base 2)
    14 (base 10) = 00000000000000000000000000001110 (base 2)
                   --------------------------------
14 ^ 9 (base 10) = 00000000000000000000000000000111 (base 2) = 7 (base 10)
非 (~)

只和自己做運(yùn)算,如果為 0,結(jié)果為 1。如果為 1 結(jié)果為 0。反正就是相反的意思了:

// FROM MDN
 9 (base 10) = 00000000000000000000000000001001 (base 2)
               --------------------------------
~9 (base 10) = 11111111111111111111111111110110 (base 2) = -10 (base 10)
異或 (^)

當(dāng)兩者中只有一個(gè) 1 那么結(jié)果才為 1。

// FROM MDN
    9 (base 10) = 00000000000000000000000000001001 (base 2)
    14 (base 10) = 00000000000000000000000000001110 (base 2)
                   --------------------------------
14 ^ 9 (base 10) = 00000000000000000000000000000111 (base 2) = 7 (base 10)
左移 (<<)

基本格式為:x << y

將 x 向左移動(dòng) y 位數(shù)??粘鰜淼难a(bǔ) 0

// FROM MDN
9 (base 10): 00000000000000000000000000001001 (base 2)
                  --------------------------------
9 << 2 (base 10): 00000000000000000000000000100100 (base 2) = 36 (base 10)
帶位右移 (>>)

什么叫帶位呢?

上面我們提到過 signedunsigned。那么這里針對(duì)的就是 signed 的位移類型。

格式為: x >> y

將 x 向右移動(dòng) y 位數(shù)。左邊空出來的位置根據(jù)最左邊的第一位決定,如果為 1 則補(bǔ) 1,反之。

1001 >> 2 = 1110
直接右移 (>>>)

該方式和上面具體區(qū)別就是,該運(yùn)算針對(duì)的是 unsigned 的移動(dòng)。不管你左邊是啥,都給我補(bǔ)上 0。

格式為: x >> y

1001 >> 2 = 0010

上面這些運(yùn)算符主要是針對(duì) 32bit 的。不過有時(shí)候?yàn)榱撕?jiǎn)便,可以省去前面多余的 0。不過大家要清楚,這是針對(duì) 32 位的即可。

優(yōu)先級(jí)

上面簡(jiǎn)單介紹了位操作符,但是他們的優(yōu)先級(jí)是怎么樣的呢?詳情可以參考:precedence;

簡(jiǎn)單來說:(按照下列順序,優(yōu)先級(jí)降低)

~
>> << >>>
& ^ |

位運(yùn)算具體運(yùn)用 狀態(tài)改變

后臺(tái)在保存數(shù)據(jù)的時(shí)候,常常會(huì)遇到某一個(gè)字段有多種狀態(tài)。例如,填表狀態(tài):填完,未填,少填,填錯(cuò)等。一般情況下直接用數(shù)字來進(jìn)行代替就行,只要文檔寫清楚就沒事。例如:

0: 填完

1: 未填

2:少填

3:填錯(cuò)

不過,我們還可以通過比特位來進(jìn)行表示,每一位表示一個(gè)具體的狀態(tài)。

0001: 填完

0010: 未填

0100:少填

1000:填錯(cuò)

這樣我們只要找到每一位是否為 1 就可以知道里面有哪些狀態(tài)存在。并且,還可以對(duì)狀態(tài)進(jìn)行組合,例如,填完并且填錯(cuò),如果按照數(shù)字來說就沒啥說明這樣的情況。

那么基本的狀態(tài)值有了,接下來就是怎么進(jìn)行賦值和修改。

現(xiàn)在假設(shè),某人的填寫狀態(tài)為 填完 + 填錯(cuò)。那么結(jié)果可以表示為:

var mask = 0001 | 1000;

后面如果涉及條件判斷,例如:該人是否填錯(cuò),則可以使用 & 來表示:

// 是否填錯(cuò)
if(mask & 1000) doSth;

或者,是否即填完又填錯(cuò)

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

后面涉及到狀態(tài)改變的話,則需要用到 | 運(yùn)算。假設(shè),現(xiàn)在該人為填完,現(xiàn)在變?yōu)樯偬?。那么狀態(tài)改變應(yīng)該為:

// 取填完的反狀態(tài)
var done = ~0001; // 1110
mask &= done;

// 添加少填狀態(tài);
mask |= 0100
進(jìn)制轉(zhuǎn)換

在 JS 中進(jìn)制轉(zhuǎn)換有兩種方式:toStringparseInt。

toString(radix): 該可以將任意進(jìn)制轉(zhuǎn)換為 2-36 的進(jìn)制。radix 默認(rèn)為 10。

parseInt(string,radix): 將指定 string 根據(jù) radix 的標(biāo)識(shí)轉(zhuǎn)換成為 10 進(jìn)制。radix 默認(rèn)為 10。另外它主要用作于字符串的提取。

Number(string): 字面上轉(zhuǎn)換字符串為十進(jìn)制。

parseInt 用于字符串過濾,例如:

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

里面的字符不僅只有數(shù)字,而且還包括字母。

不過需要注意的是,parseInt 是不認(rèn)可,以 0 開頭的八進(jìn)制,但認(rèn)可 0o。所以,在使用的時(shí)候需要額外注意。

上面說過,parseInt 是將其它進(jìn)制轉(zhuǎn)換為 10 進(jìn)制,其第二個(gè)參數(shù)主要就是為了表示前面內(nèi)容的進(jìn)制,如果沒寫,引擎內(nèi)部會(huì)進(jìn)行相關(guān)識(shí)別,但不保證一定正確。所以,最好寫上。

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

如果你只是想簡(jiǎn)單轉(zhuǎn)換一下字符串,那么使用 Number() 無疑是最簡(jiǎn)單的。

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

toString

toString 里面的坑就沒有 parseInt 這么多了。它也是進(jìn)制轉(zhuǎn)換非常好用的一個(gè)工具。因?yàn)槭?字符串,所以,這里就只能針對(duì)字面量進(jìn)制進(jìn)行轉(zhuǎn)換了--2,8,(10),16。這四種進(jìn)制的相關(guān)之間轉(zhuǎn)換。

提醒:如果你是直接使用字面量轉(zhuǎn)換的話,需要注意使用 10 進(jìn)制轉(zhuǎn)換時(shí),隱式轉(zhuǎn)換會(huì)失效。即,100.toString(2) 會(huì)報(bào)錯(cuò)。

例如:

0b1101101.toString(8); // 155
0b1101101.toString(10); // 109
0b1101101.toString(8); // 6d

如上面所述,他們轉(zhuǎn)換后的結(jié)果一般沒有進(jìn)制前綴。這個(gè)時(shí)候,就需要手動(dòng)加上相關(guān)的前綴即可。

例如:16 進(jìn)制轉(zhuǎn)換

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

到這里,進(jìn)制轉(zhuǎn)換基本就講完了。后面我們來看一下具體的 TypeArray

整體架構(gòu)

TypeArray 不是一個(gè)可以用程序?qū)懗鰜淼母拍?,它是許多 TypeArray 的總稱。參考: TypeArray??梢粤私獾剑淖宇惾缦拢?/p>

Int8Array();

Uint8Array();

Uint8ClampedArray();

Int16Array();

Uint16Array();

Int32Array();

Uint32Array();

Float32Array();

Float64Array();

看上去很多,不過在 JS 中,因?yàn)樗焐疾皇怯脕硖幚?signed 類型的。所以,Uint 系列在 JS 中應(yīng)該算是主流。大概排個(gè)序:

Uint8Array > Uint16Array > Int8Array > ...

他們之間的具體不同,參照:

數(shù)據(jù)類型 字節(jié)長(zhǎng)度 含義 對(duì)應(yīng)的C語言類型
Int8 1 8位帶符號(hào)整數(shù) signed char
Uint8 1 8位不帶符號(hào)整數(shù) unsigned char
Uint8C 1 8位不帶符號(hào)整數(shù)(自動(dòng)過濾溢出) unsigned char
Int16 2 16位帶符號(hào)整數(shù) short
Uint16 2 16位不帶符號(hào)整數(shù) unsigned short
Int32 4 32位帶符號(hào)整數(shù) int
Uint32 4 32位不帶符號(hào)的整數(shù) unsigned int
Float32 4 32位浮點(diǎn)數(shù) float
Float64 8 64位浮點(diǎn)數(shù) double

雖然口頭上說 TypeArray 沒有一個(gè)具體的實(shí)例,但是私下,上面那幾個(gè) array 都是叫他爸爸。因?yàn)樗x了一些 uintArray 的基本功能。首先是實(shí)例化:

TypeArray 的實(shí)例化有 4 種:

new TypedArray(length); // 創(chuàng)建指定長(zhǎng)度的 typeArray
new TypedArray(typedArray); // 復(fù)制新的 typeArray
new TypedArray(object); // 不常用
new TypedArray(buffer [, byteOffset [, length]]); // 參數(shù)為 arrayBuffer。

上面 4 中最常用的應(yīng)該為 1 和 4。接著,我們了解一下,具體才創(chuàng)建的時(shí)候,TypeArray 到底做了些什么。

當(dāng)創(chuàng)建實(shí)例 TypeArray 的構(gòu)造函數(shù)時(shí),內(nèi)部會(huì)同時(shí)創(chuàng)建一個(gè) arrayBuffer 用來作為數(shù)據(jù)的存儲(chǔ)。如果是通過 TypedArray(buffer); 方式創(chuàng)建,那么 TypeArray 會(huì)直接使用該 buffer 的內(nèi)存地址。

接下來,我們就以 Uint8Array 為主要參照,來看一下基本的處理和操作。

該例直接來源于 MDN

// From a length
var uint8 = new Uint8Array(2);
uint8[0] = 42;
console.log(uint8[0]); // 42
console.log(uint8.length); // 2
console.log(uint8.BYTES_PER_ELEMENT); // 1

// From an array
var arr = new Uint8Array([21,31]);
console.log(arr[1]); // 31

// From another TypedArray
var x = new Uint8Array([21, 31]);
var y = new Uint8Array(x);
console.log(y[0]); // 21

// From an ArrayBuffer
var buffer = new ArrayBuffer(8); // 創(chuàng)建 8個(gè)字節(jié)長(zhǎng)度的 arrayBuffer
var z = new Uint8Array(buffer, 1, 4);

它上面的方法大家直接參考 MDN 的上的就 OK。一句話總結(jié)就是,你可以想操作 Array 一樣,操作里面的內(nèi)容。

根據(jù) ArrayBuffer 的描述,它本身的是從 files 和 base64 編碼來獲取的。如果只是初始化,他里面的每一位都是 0.不過,為了容易測(cè)試,我們可以直接自己指定:

var arrBuffer = Uint8Array.from("123"); // [1,2,3]

// 或者

var arrBuffer = Uint8Array.of(1,2,3); // [1,2,3]
多字節(jié)圖

假如一個(gè) Buffer 很長(zhǎng),假設(shè)有 80 位,算下來就是 10B。一開始我們的想法就是直接創(chuàng)建一個(gè) typeArray就 OK。不過,根據(jù)上面的構(gòu)造函數(shù)上看,其實(shí),可以將一整個(gè) buffer 拆成不同的 typeArray 進(jìn)行讀取。

buf; // 10B 的 buf

var firstB = new Uint8Array(buf,0,1); // buf 中第一個(gè)字節(jié)內(nèi)容

var theRestB = new Uint8Array(buf,1,9); // buf 中 2~10 的字節(jié)內(nèi)容
字節(jié)概念

在字節(jié)中,還有幾個(gè)相關(guān)的概念需要理解一下。一個(gè)是溢出,一個(gè)是字節(jié)序。同樣,還是根據(jù) Uint8 來說明。

Uint8 每一個(gè)數(shù)組位,表示 8 位二進(jìn)制,即范圍為 0~255。

溢出

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

然后我們做一下加法:

arrBuffer[0] += 1; // 7

arrBuffer[0] += 0xfe; // 6。因?yàn)?7 + 254 溢出 6 

然后是字節(jié)序。

字節(jié)序

在 JS,Java,C 等高級(jí)語言中,字節(jié)序一般都是大字節(jié)序。而一些硬件則會(huì)以小字節(jié)序作為標(biāo)準(zhǔn)。

大字節(jié)序:假如 0xAABB 被 Uint16 存儲(chǔ)為 2 位。那么按照大字節(jié)序就是按順序來,即 0: 0xAA, 1:0xBB。

小字節(jié)序:和上面相反,即,0:0xBB,1:0xAA。

當(dāng)然如果只是在 PC 上操作了的話,字節(jié)序可以使用 IIFE 檢測(cè)一下:

(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
})();

關(guān)于 TypeArray 的內(nèi)容差不多就是上面將的。接下來, 我們?cè)賮砜戳硗庖粋€(gè)重要的對(duì)象 DataView。

DataView

DataView 沒有 TypeArray 這么復(fù)雜,衍生出這么多個(gè) Uint/IntArray。它就是一個(gè)構(gòu)造函數(shù)。同樣,它的目的也是對(duì)底層的 arrayBuffer 進(jìn)行讀取。那么,為什么它會(huì)被創(chuàng)建出來呢?

是因?yàn)橛?字節(jié)序 的存在。上面說過字節(jié)序有兩種。通常,PC 和目前流行的電子設(shè)備都是大字節(jié)序,而如果是接收一些外部資源,就不能排除會(huì)接受一些小字節(jié)序的文件。為了解決這個(gè)問題,就出現(xiàn)了 DataView。它的實(shí)例格式為:

new DataView(buffer [, byteOffset [, byteLength]])

同樣,它的格式和 TypeArray 類似,也是用來作為 buffer 的讀寫對(duì)象。

buffer: 需要接入的底層 ArrayBuffer

byteOffset: 偏移量,單位為字節(jié)

byteLength: 獲取長(zhǎng)度,單位為字節(jié)

它的具體操作不是直接通過 [] 獲取,而是使用相關(guān)的 get/set 方法來完成。而他針對(duì) 字節(jié)序 的操作,主要是針對(duì) >=16 比特的流來區(qū)別,即,get/setInt8() 是沒有 字節(jié)序 的概念的。

先以 16 位的作為例子:

dataview.getInt16(byteOffset [, littleEndian]);
// 根據(jù)字節(jié)序,獲得偏移字節(jié)后的兩個(gè)字節(jié)。

byteOffset: 單位為 字節(jié)。

littleEndian[boolean]: 字節(jié)序。默認(rèn)為 false。表示大字節(jié)序。

var buffer = new ArrayBuffer(8);
var dataview = new DataView(buffer);
dataview.getInt16(1,true); // 0
Buffer 場(chǎng)景

如上面所述,Buffer 的場(chǎng)景有:

canvas

websocket

file

fetch

webgl

file

直接看代碼吧:

let fileInput = document.getElementById("fileInput");
let file = fileInput.files[0];
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function () {
   let arrayBuffer = reader.result;
   ···
};
AJAX

這里和 fetch 區(qū)分一下,作為一種兼容性比較好的選擇。

let xhr = new XMLHttpRequest();
xhr.open("GET", someUrl);
xhr.responseType = "arraybuffer";

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

xhr.send();
fetch
fetch(url)
.then(request => request.arrayBuffer())
.then(arrayBuffer => ···);
canvas
let canvas = document.getElementById("my_canvas");
let context = canvas.getContext("2d");
let imageData = context.getImageData(0, 0, canvas.width, canvas.height);
let uint8ClampedArray = imageData.data;
websocket
let socket = new WebSocket("ws://127.0.0.1:8080");
socket.binaryType = "arraybuffer";

socket.addEventListener("message", function (event) {
    let arrayBuffer = event.data;
    ···
});

上面這些都是可以和 Buffer 進(jìn)行交流的對(duì)象。那還有其他的嗎?有的,總的一句話:

能提供的 arrayBuffer 的都可以進(jìn)行底層交流。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/82518.html

相關(guān)文章

  • web音頻流轉(zhuǎn)發(fā)之音頻源

    摘要:前言音頻流轉(zhuǎn)發(fā)之音視頻直播音頻流轉(zhuǎn)發(fā)之能直播為什么不可以看完本系列文章,你就能做一個(gè)直播,真正的直播,包括音頻流的轉(zhuǎn)發(fā),這也是我最近查看發(fā)現(xiàn)有相關(guān)能實(shí)現(xiàn)音頻流的轉(zhuǎn)發(fā),所有打算分享系列文章供大家交流,如有不對(duì)之處請(qǐng)指正。 前言 web音頻流轉(zhuǎn)發(fā)之音視頻直播web音頻流轉(zhuǎn)發(fā)之AudioNodeapp能直播,web為什么不可以?看完本系列文章,你就能做一個(gè)直播,真正的直播,包括音頻流的轉(zhuǎn)發(fā),...

    FingerLiu 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<