深入淺出音視頻與 WebRTC
本文為來自 教育-成人與創(chuàng)新-前端團隊 成員的文章,已授權(quán) ELab 發(fā)布。
常見的音視頻網(wǎng)絡(luò)通信協(xié)議
普通直播協(xié)議
這類直播對實時性要求不那么高,使用CDN進行內(nèi)容分發(fā),會有幾秒甚至十幾秒的延時,主要關(guān)注畫面質(zhì)量、音視頻是卡頓等問題,一般選用 RTMP 和 HLS 協(xié)議
基本概念
RTMP
RTMP (Real Time Messaging Protocol),即“實時消息傳輸協(xié)議”, 它實際上并不能做到真正的實時,一般情況最少都會有幾秒到幾十秒的延遲,是 Adobe 公司開發(fā)的音視頻數(shù)據(jù)傳輸?shù)膶崟r消息傳送協(xié)議,RTMP 協(xié)議基于 TCP,包括 RTMP 基本協(xié)議及 RTMPT/RTMPS/RTMPE 等多種變種,RTMP 是目前主流的流媒體傳輸協(xié)議之一,對CDN支持良好,實現(xiàn)難度較低,是大多數(shù)直播平臺的選擇,不過RTMP有一個最大的不足 —— 不支持瀏覽器,且蘋果 ios 不支持,Adobe 已停止對其更新
RTMP目前在 PC 上的使用仍然比較廣泛
HLS
HLS (Http Live Streaming)是由蘋果公司定義的基于 HTTP 的流媒體實時傳輸協(xié)議,被廣泛的應用于視頻點播和直播領(lǐng)域,HLS 規(guī)范規(guī)定播放器至少下載一個 ts 切片才能播放,所以 HLS 理論上至少會有一個切片的延遲
HLS 在移動端兼容性比較好,ios就不用說了,Android現(xiàn)在也基本都支持 HLS 協(xié)議了,pc端如果要使用可以使用 hls.js 適配器
HLS 的原理是將整個流分為多個小的文件來下載,每次只下載若干個,服務(wù)器端會將最新的直播數(shù)據(jù)生成新的小文件,當客戶端獲取直播時,它通過獲取最新的視頻文件片段來播放,從而保證用戶在任何時候連接進來時都會看到較新的內(nèi)容,實現(xiàn)近似直播的體驗;HLS 的延遲一般會高于普通的流媒體直播協(xié)議,傳輸內(nèi)容包括兩部分:一部分 M3U8 是索引文件,另一部分是 TS 文件,用來存儲音視頻的媒體信息
RTMP 和 HLS 如何選擇
流媒體推流,一般使用 RTMP 協(xié)議
移動端的網(wǎng)頁播放器最好使用 HLS 協(xié)議,RTMP 不支持瀏覽器
iOS 要使用 HLS 協(xié)議,因為不支持 RTMP 協(xié)議
點播系統(tǒng)最好使用 HLS 協(xié)議,因為點播沒有實時互動需求,延遲大一些是可以接受的,并且可以在瀏覽器上直接觀看
普通直播基本架構(gòu)
由直播 客戶端 、 信令 服務(wù)器和 CDN 網(wǎng)絡(luò)這三部分組成
直播 客戶端主要包括音視頻數(shù)據(jù)的采集、編碼、推流、拉流、解碼與播放功能,但實際上這些功能并不是在同一個客戶端中實現(xiàn)的,為什么呢?因為作為主播來說,他不需要看到觀眾的視頻或聽到觀眾的聲音,而作為觀眾來講,他們與主播之間是通過文字進行交流的,不需要向主播分享自己的音視頻信息
對于主播客戶端來說,它可以設(shè)備的攝像頭、麥克風采集數(shù)據(jù),然后對采集到的音視頻數(shù)據(jù)進行編碼,最后將編碼后的音視頻數(shù)據(jù)推送給 CDN
對于觀眾客戶端來說,它首先需要獲取到主播房間的流媒體地址,觀眾進入房間后從 CDN 拉取音視頻數(shù)據(jù),并對獲取到的音視頻數(shù)據(jù)進行解碼,最后進行音視頻的渲染與播放
信令 服務(wù)器,主要用于接收信令,并根據(jù)信令處理一些和業(yè)務(wù)相關(guān)的邏輯,如創(chuàng)建房間、加入房間、離開房間、文字聊天等
CDN 網(wǎng)絡(luò),主要用于媒體數(shù)據(jù)的分發(fā),傳給它的媒體數(shù)據(jù)可以很快傳送給各地的用戶
實時直播協(xié)議
隨著人們對實時性、互動性的要求越來越高,傳統(tǒng)直播技術(shù)越來越滿足不了人們的需求,WebRTC 技術(shù)正是為了解決人們對實時性、互動性需求而提出的新技術(shù)
WebRTC
WebRTC(Web Real-Time Communication),即“網(wǎng)頁即時通信”,WebRTC 是一個支持瀏覽器進行實時語音、視頻對話的開源協(xié)議,目前主流瀏覽器都支持WebRTC,即便在網(wǎng)絡(luò)信號一般的情況下也具備較好的穩(wěn)定性,WebRTC 可以實現(xiàn)點對點通信,通信雙方延時低,使用戶無需下載安裝任何插件就可以進行實時通信
在WebRTC發(fā)布之前,開發(fā)實時音視頻交互應用的成本很高,需要考慮的技術(shù)問題很多,如音視頻的編解碼問題,數(shù)據(jù)傳輸問題,延時、丟包、抖動、回音的處理和消除等,如果要兼容瀏覽器端的實時音視頻通信,還需要額外安裝插件, WebRTC 大大降低了音視頻開發(fā)的門檻,開發(fā)者只需要調(diào)用 WebRTC API 即可快速構(gòu)建出音視頻應用
下面主要通過 WebRTC 的實時通信過程來對 WebRTC 有一個大概的了解
WebRTC 音視頻通信的大體過程
音視頻設(shè)備檢測
設(shè)備的基本原理
音頻設(shè)備
音頻輸入設(shè)備的主要工作是采集音頻數(shù)據(jù),而采集音頻數(shù)據(jù)的本質(zhì)就是模數(shù)轉(zhuǎn)換(A/D),即將模似信號轉(zhuǎn)換成數(shù)字信號,采集到的數(shù)據(jù)再經(jīng)過量化、編碼,最終形成數(shù)字信號,這就是音頻設(shè)備所要完成的工作
視頻設(shè)備
視頻設(shè)備,與音頻輸入設(shè)備很類似,視頻設(shè)備的模數(shù)轉(zhuǎn)換(A/D)模塊即光學傳感器, 將光轉(zhuǎn)換成數(shù)字信號,即 RGB(Red、Green、Blue)數(shù)據(jù),獲得 RGB 數(shù)據(jù)后,還要通過 DSP(Digital Signal Processer)進行優(yōu)化處理,如自動增強、色彩飽和等都屬于這一階段要做的事情,通過 DSP 優(yōu)化處理后獲得 RGB 圖像,然后進行壓縮、傳輸,而編碼器一般使用的輸入格式為 YUV,所以在攝像頭內(nèi)部還有一個專門的模塊用于將 RGB 圖像轉(zhuǎn)為 YUV 格式的圖像
那什么是 YUV 呢?
YUV 也是一種色彩編碼方法,它將亮度信息(Y)與色彩信息(UV)分離,即使沒有 UV 信息一樣可以顯示完整的圖像,只不過是黑白的,這樣的設(shè)計很好地解決了彩色電視機與黑白電視的兼容問題(這也是 YUV 設(shè)計的初衷)相對于 RGB 顏色空間,YUV 的目的是為了編碼、傳輸?shù)姆奖?,減少帶寬占用和信息出錯,人眼的視覺特點是對亮度更敏感,對位置、色彩相對來說不敏感,在視頻編碼系統(tǒng)中為了降低帶寬,可以保存更多的亮度信息,保存較少的色差信息
獲取音視頻設(shè)備列表
MediaDevices.enumerateDevices()
此方法返回一個可用的媒體輸入和輸出設(shè)備的列表,例如麥克風,攝像機,耳機設(shè)備等
navigator.mediaDevices.enumerateDevices().then(function(deviceInfos) {
deviceInfos.forEach(function(deviceInfo) {
console.log(deviceInfo);
});
})
返回的 deviceInfo 信息格式如下:
出于安全原因,除非用戶已被授予訪問媒體設(shè)備的權(quán)限(要想授予權(quán)限需要使用 HTTPS 請求),否則 label 字段始終為空
設(shè)備檢測方法
返回信息 deviceInfo 中的 kind 字段可以區(qū)分出設(shè)備是音頻設(shè)備還是視頻設(shè)備,同時音頻設(shè)備能區(qū)分出是輸入設(shè)備和輸出設(shè)備,我們平時使用的耳機它是一個音頻設(shè)備,但它同時兼有音頻輸入設(shè)備和音頻輸出設(shè)備的功能
對于音頻設(shè)備和視頻設(shè)備會設(shè)置各自的默認設(shè)備, 還是以耳機這個音頻設(shè)備為例,將耳機插入電腦后,耳機就變成了音頻的默認設(shè)備,將耳機拔出后,默認設(shè)備又切換成了系統(tǒng)的音頻設(shè)備
在獲取到所有的設(shè)備列表后,如果我們不指定某個具體設(shè)備,采集音視頻數(shù)據(jù)時,就會從設(shè)備列表中的默認設(shè)備上采集數(shù)據(jù),如果能從指定的設(shè)備上采集到音視頻數(shù)據(jù),那說明這個設(shè)備就是有效的設(shè)備,這樣我們就可以對音視頻設(shè)備進行一項一項檢測
通過調(diào)用 getUserMedia 方法 (下面音視頻采集的時候會講到) 進行設(shè)備檢測
視頻設(shè)備檢測:調(diào)用 getUserMedia API 采集視頻數(shù)據(jù)并將其展示出來,如果用戶能看到自己的視頻,說明視頻設(shè)備是有效的,否則,設(shè)備無效
音頻設(shè)備檢測:調(diào)用 getUserMedia API 采集音頻數(shù)據(jù),由于音頻數(shù)據(jù)不能直接展示,所以需要使用 JavaScript 將其處理后展示到頁面上,這樣當用戶看到音頻數(shù)值的變化后,說明音頻設(shè)備也是有效的
音視頻采集
基本概念
幀率
幀率表示1秒鐘視頻內(nèi)圖像的數(shù)量,一般幀率達到 10~12fps 人眼就會覺得是連貫的,幀率越高,代表著每秒鐘處理的圖像數(shù)量越高,因此流量會越大,對設(shè)備的性能要求也越高,所以在直播系統(tǒng)中一般不會設(shè)置太高的幀率,高的幀率可以得到更流暢、更逼真的動畫,一般來說 30fps 就是可以接受的,但是將性能提升至 60fps 則可以明顯提升交互感和逼真感,但是一般來說超過 75fps 一般就不容易察覺到有明顯的流暢度提升了
軌(Track)
WebRTC 中的“軌”借鑒了多媒體的概念,兩條軌永遠不會相交,“軌”在多媒體中表達的就是每條軌數(shù)據(jù)都是獨立的,不會與其他軌相交,如 MP4 中的音頻軌、視頻軌,它們在 MP4 文件中是被分別存儲的
音視頻采集接口
mediaDevices.getUserMedia
const mediaStreamContrains = {
video: true,
audio: true
};
const promise = navigator.mediaDevices.getUserMedia(mediaStreamContrains).then(
gotLocalMediaStream
)
const $video = document.querySelector('video');
function gotLocalMediaStream(mediaStream){
$video.srcObject = mediaStream;
}
function handleLocalMediaStreamError(error){
console.log('getUserMedia 接口調(diào)用出錯: ', error);
}
**srcObject[1]**:屬性設(shè)定或返回一個對象,這個對象提供了一個與 HTMLMediaElement 關(guān)聯(lián)的媒體源,這個對象通常是 MediaStream,根據(jù)規(guī)范也可以是 MediaSource, Blob 或者 File,但對于 MediaSource, Blob 和File類型目前瀏覽器的兼容性不太好,所以對于這幾種類型可以通過 URL.createObjectURL() 創(chuàng)建 URL,并將其賦值給 HTMLMediaElement.src
MediaStreamConstraints 參數(shù),可以指定MediaStream中包含哪些類型的媒體軌(音頻軌、視頻軌),并且可為這些媒體軌設(shè)置一些限制
const mediaStreamContrains = {
video: {
frameRate: {min: 15}, // 視頻的幀率最小 15 幀每秒
width: {min: 320, ideal: 640}, // 寬度最小是 320,理想的寬度是 640
height: {min: 480, ideal: 720},// 高度最小是 480,最理想高度是 720
facingMode: 'user', // 優(yōu)先使用前置攝像頭
deviceId: '' // 指定使用哪個設(shè)備
},
audio: {
echoCancellation: true, // 對音頻開啟回音消除功能
noiseSuppression: true // 對音頻開啟降噪功能
}
}
瀏覽器實現(xiàn)自拍
我們知道視頻是由一幅幅幀圖像和一組音頻構(gòu)成的,所以拍照的過程其實是從連續(xù)播放的視頻流(一幅幅畫面)中抽取正在顯示的那張畫面,上面我們講過可以通過 getUserMedia 獲取到視頻流,那如何從視頻流中獲取到正在顯示的圖片呢?
這里就要用到 canvas 的 drawImage[2]
const ctx = document.querySelector('canvas');
// 需要拍照時執(zhí)行此代碼,完成拍照
ctx.getContext('2d').drawImage($video, 0, 0);
function downLoad(url){
const $a = document.createElement("a");
$a.download = 'photo';
$a.href = url;
document.body.appendChild($a);
$a.click();
$a.remove();
}
// 調(diào)用 download 函數(shù)進行圖片下載
downLoad(ctx.toDataURL("image/jpeg"));
drawImage 的第一個參數(shù)支持 HTMLVideoElement 類型,所以可以直接將 $video 作為第一個參數(shù)傳入,這樣就通過 canvas 獲取到照片了
然后通過 a 標簽的 download 將照片下載下來保存到本地
通過 canvas 的 toDataURL 方法獲得圖片的 URL 地址
利用 a 標簽的 downLoad 屬性來實現(xiàn)圖片的下載
音視頻錄制
基本概念
ArrayBuffer
ArrayBuffer 對象表示通用的、固定長度的二進制數(shù)據(jù)緩沖區(qū),可以使用它存儲圖片、視頻等內(nèi)容,但ArrayBuffer 對象不能直接進行訪問,ArrayBuffer 只是描述有這樣一塊空間可以用來存放二進制數(shù)據(jù),但在計算機的內(nèi)存中并沒有真正地為其分配空間,只有當具體類型化后,它才真正地存在于內(nèi)存中
let buffer = new ArrayBuffer(16); // 創(chuàng)建一個長度為 16 的 buffer
let view = new Uint32Array(buffer);
ArrayBufferView
是Int32Array、Uint8Array、DataView等類型的總稱,這些類型都是使用 ArrayBuffer 類實現(xiàn)的,因此才統(tǒng)稱他們?yōu)?ArrayBufferView
Blob
(Binary Large Object)是 JavaScript 的大型二進制對象類型,WebRTC 最終就是使用它將錄制好的音視頻流保存成多媒體文件的,而它的底層是由上面所講的 ArrayBuffer 對象的封裝類實現(xiàn)的,即 Int8Array、Uint8Array 等類型
音頻錄制接口
const mediaRecorder = new MediaRecorder(stream[, options]);
stream參數(shù)是將要錄制的流,它可以是來自于使用 navigator.mediaDevices.getUserMedia 創(chuàng)建的流或者來自于 audio,video 以及 canvas DOM 元素
MediaRecorder.ondataavailable事件可用于獲取錄制的媒體資源 (在事件的 data 屬性中會提供一個可用的 Blob 對象)
錄制的流程如下:
使用 getUserMedia 接口獲取視頻流數(shù)據(jù)
使用 MediaRecorder 接口進行錄制(視頻流數(shù)據(jù)來源上一步獲取的數(shù)據(jù))
使用 MediaRecorder 的 ondataavailable 事件獲取錄制的 buffer 數(shù)據(jù)
將 buffer 數(shù)據(jù)轉(zhuǎn)成 Blob 類型,然后使用 createObjectURL 生成可訪問的視頻地址
利用 a 標簽的 download 屬性進行視頻下載
<video autoplay playsinline controls id="video-show"></video>
<video id="video-replay"></video>
<button id="record">開始錄制</button>
<button id="stop">停止錄制</button>
<button id="recplay">錄制播放</button>
<button id="download">錄制視頻下載</button>
let buffer;
const $videoshow = document.getElementById('video-show');
const promise = navigator.mediaDevices.getUserMedia({
video: true
}).then(
stream => {
console.log('stream', stream);
window.stream = stream;
$videoshow.srcObject = stream;
})
function startRecord(){
buffer = [];
// 設(shè)置錄制下來的多媒體格式
const options = {
mimeType: 'video/webm;codecs=vp8'
}
// 判斷瀏覽器是否支持錄制
if(!MediaRecorder.isTypeSupported(options.mimeType)){
console.error(`${options.mimeType} is not supported!`);
return;
}
try{
// 創(chuàng)建錄制對象
mediaRecorder = new MediaRecorder(window.stream, options);
console.log('mediaRecorder', mediaRecorder);
}catch(e){
console.error('Failed to create MediaRecorder:', e);
return;
}
// 當有音視頻數(shù)據(jù)來了之后觸發(fā)該事件
mediaRecorder.ondataavailable = handleDataAvailable;
// 開始錄制
mediaRecorder.start(2000); // 若設(shè)置了 timeslice 這個毫秒值,那么錄制的數(shù)據(jù)會按照設(shè)定的值分割成一個個單獨的區(qū)塊
}
// 當該函數(shù)被觸發(fā)后,將數(shù)據(jù)壓入到 blob 中
function handleDataAvailable(e){
console.log('e', e.data);
if(e && e.data && e.data.size > 0){
buffer.push(e.data);
}
}
document.getElementById('record').onclick = () => {
startRecord();
};
document.getElementById('stop').onclick = () => {
mediaRecorder.stop();
console.log("recorder stopped, data available");
};
// 回放錄制文件
const $video = document.getElementById('video-replay');
document.getElementById('recplay').onclick = () => {
const blob = new Blob(buffer, {type: 'video/webm'});
$video.src = window.URL.createObjectURL(blob);
$video.srcObject = null;
$video.controls = true;
$video.play();
};
// 下載錄制文件
document.getElementById('download').onclick = () => {
const blob = new Blob(buffer, {type: 'video/webm'});
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.style.display = 'none';
a.download = 'video.webm';
a.click();
};
創(chuàng)建連接
數(shù)據(jù)采集完成,接下來就要開始建立連接,然后進行數(shù)據(jù)通信了
要實現(xiàn)一套 1 對 1 的通話系統(tǒng),通常我們的思路會是在每一端創(chuàng)建一個 socket,然后通過該 socket 與對端相連,當 socket 連接成功之后,就可以通過 socket 向?qū)Χ税l(fā)送數(shù)據(jù)或者接收對端的數(shù)據(jù)了,WebRTC 中提供了 RTCPeerConnection 類,其工作原理和 socket 基本一樣,不過它的功能更強大,實現(xiàn)也更為復雜,下面就來講講 WebRTC 中的 RTCPeerConnection
RTCPeerConnection
在音視頻通信中,每一方只需要有一個 RTCPeerConnection 對象,用它來接收或發(fā)送音視頻數(shù)據(jù),然而在真實的場景中,為了實現(xiàn)端與端之間的通話,還需要利用信令服務(wù)器交換一些信息,比如交換雙方的 IP 和 port 地址,這樣通信的雙方才能彼此建立連接
WebRTC 規(guī)范對 WebRTC 要實現(xiàn)的功能、API 等相關(guān)信息做了大量的約束,比如規(guī)范中定義了如何采集音視頻數(shù)據(jù)、如何錄制以及如何傳輸?shù)?,甚至更細的,還定義了都有哪些 API,以及這些 API 的作用是什么,但這些約束只針對于客戶端,并沒有對服務(wù)端做任何限制,這就導致了我們在使用 WebRTC 的時候,必須自己去實現(xiàn) 信令 服務(wù), 這里就不專門研究怎么實現(xiàn)信令服務(wù)器了,我們只來看看 RTCPeerConnection 是如何實現(xiàn)一對一通信的
RTCPeerConnection 如何工作呢?
獲取本地音視頻流
為連接的每個端創(chuàng)建一個 RTCPeerConnection 對象,并且給 RTCPeerConnection 對象添加一個本地流,該流是從 getUserMedia 獲取的
// 調(diào)用 getUserMedia API 獲取音視頻流
navigator.mediaDevices.getUserMedia(mediaStreamConstraints).
then(gotLocalMediaStream)
function gotLocalMediaStream(mediaStream) {
window.stream = mediaStream;
}
// 創(chuàng)建 RTCPeerConnection 對象
let localPeerConnection = new RTCPeerConnection();
// 將音視頻流添加到 RTCPeerConnection 對象中
localPeerConnection.addStream(stream);
交換媒體描述信息
獲得音視頻流后,就可以開始與對端進行媒體協(xié)商了(媒體協(xié)商就是看看你的設(shè)備都支持哪些編解碼器,我的設(shè)備是否也支持?如果我的設(shè)備也支持,那么咱們雙方就算協(xié)商成功了),這個過程需要通過信令服務(wù)器完成
現(xiàn)在假設(shè) A 和 B 需要通訊
A 通過 createOffer[3] 方法啟動創(chuàng)建一個 SDP offer,即得到 A 的本地會話描述
A 通過 setLocalDescription ****方法保存本地會話描述
A 通過信令服務(wù)器發(fā)送信令給 B
localPeerConnection.createOffer([options])
.then((description) => {
// 將 offer 保存到本地
localPeerConnection.setLocalDescription(description)
.then(() => {
setLocalDescriptionSuccess(localPeerConnection);
});
})
B 接收到帶有 A offer 的信令,調(diào)用 setRemoteDescription,設(shè)置遠程會話描述
B 通過 createAnswer 方法將本地會話描述成功回調(diào)
B 調(diào)用 setLocalDescription 設(shè)置他自己的本地局部描述回調(diào)函數(shù)中保存本地會話描述
B 通過信令服務(wù)器發(fā)送信令給 A
// B 設(shè)置遠程會話描述
remotePeerConnection.setRemoteDescription(description)
.then(() => {
setRemoteDescriptionSuccess(remotePeerConnection);
});
remotePeerConnection.createAnswer()
.then((description)=> {
// B 保存本地會話描述
remotePeerConnection.setLocalDescription(description)
.then(() => {
setLocalDescriptionSuccess(remotePeerConnection);
});
});
A 通過 setRemoteDescription 將 B 的應答 answer 保存為遠程會話描述
// A 保存 B 的 應答 answer 為遠程會話描述
localPeerConnection.setRemoteDescription(description)
.then(() => {
setRemoteDescriptionSuccess(localPeerConnection);
});
至此就完成了媒體信息交換和協(xié)商
端與端建立連接
當 A 調(diào)用 setLocalDescription 函數(shù)成功后,會觸發(fā) icecandidate 事件(在建立通訊之前,我們需要獲得雙方的網(wǎng)絡(luò)信息,例如 IP、端口等,candidate 便是用于保存這些東西的)
localPeerConnection.onicecandidate= function(event) {
// 獲取到觸發(fā) icecandidate 事件的 RTCPeerConnection 對象
const peerConnection = event.target;
// 獲取到具體的 candidate
const iceCandidate = event.candidate;
// 將 candidate 包裝成需要的格式,然后通過信令服務(wù)器發(fā)送給B
}
B 接收到信令服務(wù)器傳遞過來的 A 的關(guān)于 candidate 的信息,把消息包裝成 RTCIceCandidate 對象,然后調(diào)用 addIceCandidate 保存起來
// 創(chuàng)建 RTCIceCandidate 對象
const newIceCandidate = new RTCIceCandidate(iceCandidate);
remotePeerConnection.addIceCandidate(newIceCandidate);
這樣就收集到了一個新的 Candidate,在真實的場景中,每當獲得一個新的 Candidate 后,就會通過信令服務(wù)器交換給對端,對端再調(diào)用 RTCPeerConnection 對象的 addIceCandidate() 方法將收到的 Candidate 保存起來,然后按照 Candidate 的優(yōu)先級進行連通性檢測,如果 Candidate 連通性檢測完成,那么端與端之間就建立了連接,這時媒體數(shù)據(jù)就可以通過這個連接進行傳輸了
音視頻編解碼
視頻是連續(xù)的圖像序列,由連續(xù)的幀構(gòu)成,一幀即為一幅圖像,由于人眼的視覺暫留效應,當幀序列以一定的速率播放時,我們看到的就是動作連續(xù)的視頻,由于連續(xù)的幀之間相似性極高,為便于儲存?zhèn)鬏?,我們需要對原始的視頻進行編碼壓縮,以去除空間、時間維度的冗余
視頻編解碼是采用算法將視頻數(shù)據(jù)的冗余信息去除,對圖像進行壓縮、存儲及傳輸, 再將視頻進行解碼及格式轉(zhuǎn)換, 追求在可用的計算資源內(nèi),盡可能高的視頻重建質(zhì)量和盡可能高的壓縮比,以達到帶寬和存儲容量要求的視頻處理技術(shù)
視頻流傳輸中最為重要的編解碼標準有H.26X系列(H.261、H.263、H.264),MPEG系列,Apple公司的 QuickTime 等
顯示遠端媒體流
通過 RTCPeerConnection 對象 A 與 B 雙方建立連接后,本地的多媒體數(shù)據(jù)經(jīng)過編碼以后就可以被傳送到遠端了,遠端收到了媒體數(shù)據(jù)解碼后,怎么顯示出來呢,下面以 video 為例,看看怎么讓 RTCPeerConnection 獲得的媒體數(shù)據(jù)與 video 標簽結(jié)合起來
當遠端有數(shù)據(jù)流到來的時候,瀏覽器會回調(diào) onaddstream 函數(shù),在回調(diào)函數(shù)中將得到的 stream 賦值給 video 標簽的 srcObject 對象,這樣 video 就與 RTCPeerConnection 進行了綁定,video 就能從 RTCPeerConnection 獲取到視頻數(shù)據(jù),并最終將其顯示出來了
localPeerConnection.onaddstream = function(event) {
$remoteVideo.srcObject = event.stream;
}
結(jié)語
WebRTC 相關(guān)的東西非常非常多,這里只是很淺顯地串講了一下利用 WebRTC 實現(xiàn)實時通信的大體過程,如果感興趣可以詳細研究里面的細節(jié)
參考資料
[1]
srcObject: https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLMediaElement/srcObject#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%BC%E5%AE%B9%E6%80%A7
[2]
drawImage: https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/drawImage
[3]
createOffer: https://developer.mozilla.org/zh-CN/docs/Web/API/RTCPeerConnection/createOffer
作者:ELab.liulili
歡迎關(guān)注微信公眾號 :前端民工