实时音视频入门学习:开源工程WebRTC的技术原理和使用浅析

本文由ELab技术团队分享,原题“浅谈WebRTC技术原理与应用”,有修订和改动。

1、基本介绍

WebRTC(全称 Web Real-Time Communication),即网页即时通信。 是一个支持网页浏览器进行实时语音对话或视频对话的技术方案。从前端技术开发的视角来看,是一组可调用的API标准。

在WebRTC发布之前,开发实时音视频交互应用的成本是非常昂贵,需要考虑的技术问题很多,如音视频的编解码问题,数据传输问题,延时、丢包、抖动、回音的处理和消除等,如果要兼容浏览器端的实时音视频通信,还需要额外安装插件。

2010年5月:Google以6820万美元收购VoIP软件开发商Global IP Solutions的GIPS引擎,并改为名为“WebRTC”(见《了不起的WebRTC:生态日趋完善,或将实时音视频技术白菜化》)。旨在建立一个互联网浏览器间的实时通信的平台,让 WebRTC技术成为 H5标准之一。

2012年1月:谷歌已经把这款软件集成到Chrome浏览器中,Opera初步集成WebRTC。

2013年 6月:Mozilla Firefox[5]发布22.0版本正式集成及支持WebRTC。

2017年11月:W3C WebRTC 1.0 草案正式定稿。

2021年1月:WebRTC 被 W3C 和 IETF 发布为正式标准(见《WebRTC 1.0: Real-Time Communication Between Browsers》)。

2、重要意义

WebRTC的出现、发展和被业内标准组织(如W3C)等普遍认可,对于当下和未来大前端技术发展具有重要的意义。

降低在web端的音视频交互开发门槛:

1)以往的音视频交互开发对于Web开发者而言具有一定技术门槛;

2)现在借助于WebRTC,Web开发者通过调用JS接口,可快速的实现音视频交互应用。

避免依赖、插件造成的次生问题:

1)以往的音视频交互应用构建依赖于各种插件、软件和服务器等;

2)现在借助于主流浏览器即可形成端到端的音视频交互。

统一化和标准化对传统音视频交互环境差异性的规避:

1)以往音视频交互需要面对不同的 NAT 、防火墙对媒体 P2P 的建立带来了很大的挑战;

2)现在WebRTC 中有P2P 打洞的开源项目 libjingle ,支持 STUN,TURN 等协议。

更高效优化的算法、技术对于音视频交互性能的提升:

1)WebRTC 通过NACK、FEC技术,避免了经过服务端路由中转,减少了延迟和带宽消耗;

2)还有 TCC + SVC + PACER + JitterBuffer 等技术对于音视频流畅性进行了优化。

3、技术特征

WebRTC内容丰富,主要的技术特征包含以下几点。

1)实时通讯:

WebRTC是一项实时通讯技术,允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输。

2)无依赖/插件:

WebRTC包含的这些标准使用户在无需安装任何插件或者第三方的软件的情况下,创建点对点(Peer-to-Peer)的数据分享和电话会议成为可能。

3)协议栈 众多:

WebRTC并不是单一的协议,包含了媒体、加密、传输层等在内的多个协议标准以及一套基于 JavaScript的 API,它包括了音视频的采集、编解码、网络传输、显示等功能。通过简单易用的 JavaScript API ,在不安装任何插件的情况下,让浏览器拥有了 P2P音视频和数据分享的能力。

WebRTC依赖众多协议栈图:

同时WebRTC 并不是一个孤立的协议,它拥有灵活的信令,可以便捷的对接现有的SIP 和电话网络的系统。

4、兼容覆盖

目前大部分主流浏览器都正常兼容WebRTC:

▲ 上图引用自《WebRTC实时音视频技术的整体架构介绍》

更详细的浏览器及版本兼容情况,可以看看下图:

▲ 上图引用自《https://caniuse.com/rtcpeerconnection》

主流浏览器都支持 WebRTC 标准 API ,因此也让浏览器之间无插件化的音视频互通成为可能, 大大降低了音视频开发的门槛,开发者只需要调用 WebRTC API 即可快速构建出音视频应用。

5、技术框架

如下图所示:的技术框架描述了WebRTC的核心内容和面向不同开发者的API设计。

WebRTC技术框架图:

▲ 上图引用自《零基础入门:基于开源WebRTC,从0到1实现实时音视频聊天功能》

从图中可看到,WebRTC主要面向三类开发者的API设计:

1)对于Web开发者的API:框架包含了基于JavaScript 、 经过W3C认证了的一套API标准,使得web开发者可以基于这套API开发基于WebRTC的即时通讯应用;

2)对于浏览器厂商的API:框架同样包含了基于C++的底层WebRTC接口,对于浏览器厂商底层的接入十分友好;

3)浏览器厂商可自定义的部分:框架中还包含浏览器厂商可自定义的音视频截取等扩展部分。

6、技术核心

从上节框架中可以看到,WebRTC主要有音频、视频引擎和传输三部分组成,其中又包含众多的协议和方法等。

1)Voice Engine(音频引擎):

a、Voice Engine包含iSAC/iLBC Codec(音频编解码器,前者是针对宽带和超宽带,后者是针对窄带);

b、NetEQ for voice(处理网络抖动和语音包丢失);

c、Echo Canceler(回声消除器)/ Noise Reduction(噪声抑制)。

2)Video Engine(视频引擎):

a、VP8 Codec(视频图像编解码器);

b、Video jitter buffer(视频抖动缓冲器,处理视频抖动和视频信息包丢失);

c、Image enhancements(图像质量增强)。

3)Transport。

7、技术原理

7.1 基本情况

WebRTC主要的技术特征:

1)SRTP:安全的实时传输协议,用于音视频流传输;

2)Multiplexing:多路复用;

3)P2P:STUN+TURN+ICE,用于NAT网络和防火墙穿越;

4)DTLS:安全传输可能还会用到DTLS(数据报安全传输),用于加密传输和密钥协商;

5)UDP:整个WebRTC通信是基于UDP的。

限于篇幅,本文以下章节将不细致介绍音视频采集、编码和处理等内容,仅介绍实时通讯的建立过程原理的核心内容。

7.2 公网IP映射:明确网络定位信息

WebRTC是基于浏览器端到端的连接(P2P)实现的.

由于不需要服务器中转,所以获取连接对象的网络地址的方式,是借助于ICE、STUN、TURN等辅助内网穿透技术(NAT)得到对应主机的公网网络地址和端口等网络定位信息。

明确网络定位是建立端与端直接通讯的基础。

NAT穿透原理图:

STUN服务器用于辅助内网穿透得到对应主机的公网网络地址和端口信息图:

▲ 上图引用自《WebRTC实时音视频技术的整体架构介绍》

7.3 信令服务器:网络协商与信息交换

信令服务器的作用是基于双工通信来中转信息。

中转信息包括公网IP映射后的网络定位信息,比如:公网IP、端口和媒体数据流等。

概念图:

信令服务器信息交互过程图:

7.4 会话描述协议SDP:统一的媒体协商方式

SDP的作用:

1)不同端/浏览器对于媒体流数据的编码格式各异,如VP8、VP9等,参与会话的各个成员的能力不对等、用户环境与配置不一致等;

2)WebRTC通讯还需要确定和交换本地和远程音频和视频媒体信息,例如分辨率和编解码器功能。交换媒体配置信息的信令通过使用会话描述协议 (SDP) 交换Offer和Anwser来进行;

3)SDP的交换一定是先于音视频流交换的。其内容包括会话基本信息、媒体信息描述等。

//SDP的结构体

Session description(会话级别描述)

         v=  (protocol version)

         o=  (originator and session identifier)

         s=  (session name)

         c=* (connection information -- not required ifincluded inall media)

         One or moreTime descriptions ("t="and "r="lines; see below)

         a=* (zero or moresession attribute lines)

         Zero or moreMedia descriptions


Time description

         t=  (timethe session is active)


Media description(媒体级别描述), ifpresent

         m=  (media name and transport address)

         c=* (connection information -- optional ifincluded at session level)

         a=* (zero or moremedia attribute lines)

一个SDP例子如下:

v=0 //代表版本,目前一般是`v=0`.

o=- 3883943731 1 IN IP4 127.0.0.1

s=

t=0 0 //会话处于活动状态的时间

a=group:BUNDLE audio video //:描述服务质量,传输层复用相关信息

m=audio 1 RTP/SAVPF103 104 0 8 106 105 13 126 //...

a=ssrc:2223794119 label:H4fjnMzxy3dPIgQ7HxuCTLb4wLLLeRHnFxh81

7.5 一对一连接建立过程

以建立一对一的Web RTC连接过程为例来简要讲解。

一对一过程图:

简要过程图:

如上图所示,解释一下:

1)交换SDP,获取各自媒体配置信息;

2)STUN服务器交换网络地址和端口等网络信息;

3)Turn中转音视频媒体流数据。

工作流程图:

如上图所示,解释一下:

1)A和B双方先调用 getUserMedia 打开本地摄像头,作为本地待输出媒体流;

2)向信令服务器发送加入房间请求;

3)Peer B 接收到 Peer A 发送的 offer SDP 对象,并通过PeerConnection的SetLocalDescription方法保存 Answer SDP 对象并将它通过信令服务器发送给 Peer A;

4)在 SDP 信息的 offer/answer 流程中,Peer A 和 Peer B 已经根据 SDP 信息创建好相应的音频 Channel 和视频 Channel,并开启Candidate 数据的收集,Candidate数据(本地IP地址、公网IP地址、Relay服务端分配的地址);

5)当 Peer A 收集到 Candidate 信息后通过信令服务器发送给 Peer B。同样的过程 Peer B 对 Peer A 也会再发送一次。

7.6 多对多的建立

多对多建立点到点连接概念图,以三个用户点对点的连接为例:

7.7 WebRTC的主要JavaScrip接口

getUserMedia():访问数据流,例如来自用户的相机和麦克风

//请求媒体类型

const constraints = {

  video: true

  audio:true

};


const video = document.querySelector('video');


//挂载流到相应dom展示本地媒体流

function handleSuccess(stream) {

  video.srcObject = stream;

}


function handleError(error) {

  console.error('getUserMedia error: ', error);

}

//利用摄像头捕获多媒体流

navigator.mediaDevices.getUserMedia(constraints).

  then(handleSuccess).catch(handleError);

RTCPeerConnection:通过加密和带宽管理工具启用音频或视频通话

// 允许 RTC 服务器配置。

const server = {

    "iceServers":

            [{ "urls": "stun:stun.stunprotocol.org"}]

};


// 创建本地连接

const localPeerConnection = newRTCPeerConnection(servers);


// 收集Candidate 数据

localPeerConnection.onicecandidate=function(event){

    ...

}

// 监听到媒体流接入时的操作

localPeerConnection.ontack=function(event){

    ...

}

RTCDataChannel:支持通用数据的点对点通信,常用于数据点到点的传输

const pc = newRTCPeerConnection();

const dc = pc.createDataChannel("my channel");

//接受数据

dc.onmessage = function(event) {

  console.log("received: "+ event.data);

};

//打开传输

dc.onopen = function() {

  console.log("datachannel open");

};

//关闭传输

dc.onclose = function() {

  console.log("datachannel close");

};

8、应用案例

这里以WebRTC的多人视频案例为实践来大致演示一下。

8.1 设计框架

多人视频基本框架图:

8.2 关键代码

8.2.1)媒体捕获:

获取浏览器视频权限,捕获本地视频媒体流,在Video元素中附加媒体流,显示本地视频结果。代码如下。

//摄像头兼容性处理

navigator.getUserMedia = ( navigator.getUserMedia ||

               navigator.webkitGetUserMedia ||

               navigator.mozGetUserMedia ||

               navigator.msGetUserMedia);

// 获取本地音频和视频流

navigator.mediaDevices.getUserMedia({

                "audio": false,

                "video": true

}).then( (stream)=> {

//显示自己的输出流,挂到页面Video元素上

    document.getElementById("myVido").srcObject=stream

})

捕获本地视频媒体流的显示结果截图:

为每个新的客户端连接创建RTCPeerConnection对象:

// stun和turn服务器  const iceServer = {

    "iceServers": [{

        urls:"stun:stun.l.google.com:19302"

    }]

};

//为点到点的连接创建RTCPeerConnection

const peerRTCConn=newRTCPeerConnection(iceServer);

8.2.2)网络协商:

主要任务就是:创建对等连接,收集ICE候选,等待媒体流接入时挂载到dom。

交互式连通性建立(Interactive Connectivity Establishment — ICE)是一个允许实时对等端发现对方并且彼此连接的框架。此技术允许对等方发现有关彼此拓扑的足够信息,从而有可能在彼此之间找到一条或多条通信路径。ICE 代理负责:收集本地IP,端口元组候选、在同级之间执行连接检查和发送连接保持活动。(关于ICE的介绍,见《P2P技术之STUN、TURN、ICE详解》)

// 发送ICE候选到其他客户端 peerRTCConn.onicecandidate = function(event){

    if(event.candidate) {

        //向信令服务器转发收集到的ICE候选          socket.send(JSON.stringify({

            "event": "relayICECandidate",

            "data": {

                'iceCandidate': {

                    'sdpMLineIndex': event.candidate.sdpMLineIndex,

                    'candidate': event.candidate.candidate

                }

            },

            "fromID":signalMsg['data']['peerId']

        }));

    }

}

//有媒体流介入就挂载dom peerRTCConn.ontrack=function(event){

    let v=document.createElement("video")

    v.autoplay=true

    v.style="width:200px"


    document.getElementById("peer").appendChild(v)

    v.srcObject=event.streams[0]

}

8.1.3)媒体协商:

发起时创建Offer。peer利用setLocalDescription方法将会话信息加到RTCPeerConnection(),并由信令服务器中转。其他Peer会返回相应的Answer。SDP过程:

//新加入节点发起offer  if(canOffer){

    peerRTCConn.createOffer(

        function(localDescription) {

             peerRTCConn.setLocalDescription(localDescription,

                function() {

                    //发送描述信息给信令服务器                         socket.send(JSON.stringify({

                        "event":"relaySessionDescription",

                        "data":localDescription,

                        "fromID":peerId

                    }))

                 },

                function() { alert("offer failed"); }

            );

        },

        function(error) {

            console.log("error sending offer: ", error);

        }

    )

}

响应时创建Answer。会话描述包括音视频信息等内容,当发起者向响应者发出offer类型的描述后,响应者会返回answer类型的描述:

//创建Answer会话

peer.createAnswer(

function(_remoteDescription) {

     peer.setLocalDescription(_remoteDescription,

        function() {

               //发送描述信息给信令服务器                  socket.send(JSON.stringify({

                    "event":"relaySessionDescription",

                    "data":_remoteDescription,

                    "callerID":signalMsg['fromId'],

                    "fromID":signalMsg['fromId']

                }))        },

        function() { alert("answer failed"); }

    );

},

function(error) {

    console.log("error creating answer: ", error);

});

当收到ICE候选共享后,会把ICE候选添加到远程对等点描述中:

//对应的RTCPeerConnection

const peer = peers[signalMsg["fromID"]];

//ICE候选添加到远程对等点描述

peer.addIceCandidate(newRTCIceCandidate(signalMsg["data"].iceCandidate));

多人视频结果截图<本地模拟效果>:

8.2.4)信令中转:

信令服务部分关键代码:

wss.on('connection', function(ws) {

    ws.on('message', function(message) {

        let meeageObj=JSON.parse(message)

        //交换ICE候选         if (meeageObj['event'] =='relayICECandidate') {             wss.clients.forEach(function (client) {

                console.log("send iceCandidate")

                    client.send(JSON.stringify({

                        "event": "iceCandidate",

                        "data": meeageObj['data'],

                        "fromID": meeageObj['fromID']

                    }));         

            });

        }

        //交换SDP

         if (meeageObj['event'] =='relaySessionDescription') {

            console.log(meeageObj["fromID"],meeageObj["data"].type)

            wss.clients.forEach(function(client) {

                if(client!=ws) {

                    client.send(JSON.stringify({

                        "event": "sessionDescription",

                        "fromId":meeageObj["fromID"],

                        "data": meeageObj["data"],

                    }));

                }

            });

        }

    })

})

9、小结一下

WebRTC的优点主要是:

1)方便:对于用户来说,在WebRTC出现之前想要进行实时通信就需要安装插件和客户端,但是对于很多用户来说,插件的下载、软件的安装和更新这些操作是复杂而且容易出现问题的,现在WebRTC技术内置于浏览器中,用户不需要使用任何插件或者软件就能通过浏览器来实现实时通信。对于开发者来说,在Google将WebRTC开源之前,浏览器之间实现通信的技术是掌握在大企业手中,这项技术的开发是一个很困难的任务,现在开发者使用简单的HTML标签和JavaScript API就能够实现Web音/视频通信的功能。

2)免费:虽然WebRTC技术已经较为成熟,其集成了最佳的音/视频引擎,十分先进的codec,但是Google对于这些技术不收取任何费用。

3)强大的打洞能力:WebRTC技术包含了使用STUN、ICE、TURN、RTP-over-TCP的关键NAT和防火墙穿透技术,并支持代理。

WebRTC的缺点主要是:

1)缺乏服务器方案的设计和部署。

2)传输质量难以保证。WebRTC的传输设计基于P2P,难以保障传输质量,优化手段也有限,只能做一些端到端的优化,难以应对复杂的互联网环境。比如对跨地区、跨运营商、低带宽、高丢包等场景下的传输质量基本是靠天吃饭,而这恰恰是国内互联网应用的典型场景。

3)WebRTC比较适合一对一的单聊,虽然功能上可以扩展实现群聊,但是没有针对群聊,特别是超大群聊进行任何优化。

4)设备端适配,如回声、录音失败等问题层出不穷。这一点在安卓设备上尤为突出。由于安卓设备厂商众多,每个厂商都会在标准的安卓框架上进行定制化,导致很多可用性问题(访问麦克风失败)和质量问题(如回声、啸叫)。

5)对Native开发支持不够。WebRTC顾名思义,主要面向Web应用,虽然也可以用于Native开发,但是由于涉及到的领域知识(音视频采集、处理、编解码、实时传输等)较多,整个框架设计比较复杂,API粒度也比较细,导致连工程项目的编译都不是一件容易的事。

10、参考资料

[1] 开源实时音视频技术WebRTC的现状

[2] 简述开源实时音视频技术WebRTC的优缺点

[3] 访谈WebRTC标准之父:WebRTC的过去、现在和未来

[4] 良心分享:WebRTC 零基础开发者教程(中文)[附件下载]

[5] WebRTC实时音视频技术的整体架构介绍

[6] 新手入门:到底什么是WebRTC服务器,以及它是如何联接通话的?

[7] WebRTC实时音视频技术基础:基本架构和协议栈

[8] 浅谈开发实时视频直播平台的技术要点

[9] 基于开源WebRTC开发实时音视频靠谱吗?第3方SDK有哪些?

[10] 开源实时音视频技术WebRTC在Windows下的简明编译教程

[11] 网页端实时音视频技术WebRTC:看起来很美,但离生产应用还有多少坑要填?

[12] 了不起的WebRTC:生态日趋完善,或将实时音视频技术白菜化

[13] 零基础入门:基于开源WebRTC,从0到1实现实时音视频聊天功能

[14] P2P技术详解(一):NAT详解——详细原理、P2P简介

[15] P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解(基本原理篇)

(本文已同步发布于:http://www.52im.net/thread-3804-1-1.html )

你可能感兴趣的:(实时音视频入门学习:开源工程WebRTC的技术原理和使用浅析)