实现WebRTC P2P连接

10,642 阅读10分钟

WebRTC是为了解决实时音视频传输问题,致力于提供免安装、免插件、免专利费,人人可用的高效便捷的实时流媒体传输。

1. 3种实时流媒体实现比较

目前实时流媒体主流有三种实现方式:WebRTC、HLS、RTMP,当你看直播网站的时候会发现很多采用了HLS(HTTP Live Streaming,http直播),它是一种把流媒体拆分成多个独立小文件的技术,按照播放时间请求不同文件,把hls的文件进行解复用之后取出音视频数据然后丢给video去播放(Safari和安卓版的Chrome能直接播放hls)。它的优点是:使用了传统http协议,所以兼容性和稳定都非常好,服务端可以把hls文件上传到cdn,进而能够应对百万级别观众的直播,缺点是延时比较大,通常在10s以上,适合观众和主播没有什么交互的场景。因为一个hls文件时间长度通常在10s以上,再加上生成文件的时间就导致延迟很大。

它是苹果推出的一种标准,而另一种RTMP是Adobe推出的,使用长连接,是一套完整的流媒体传输协议,使用flv视频容器,原生浏览器不支持(flash插件支持),不过可以使用websocket + MSE的方式,相关的类库比较少,在Android/IOS客户端上的直播应该用得比较多一点。相对于HLS请求分片的形式,RTMP由于使用长连接,接收不间断的数据流,它的延迟要比HLS小很多,通常是1~3秒,所以如果观众和主播之间有通话或者视频交互,这种方式的延迟是可以接受的。

第3种WebRTC(Web Real Time Communication)是谷歌在2012年推出的,到现在已经有6年的发展。今年2018年3月份WebRTC 1.0正式定稿,并得到了Safari在内的所有主流浏览器的支持(Edge弄了一个ORTC),WebRTC致力于高效的实时音视频通信,做到比RTMP提供更低的延迟和更小的缓冲率。并且官方还提供了配套的native的Andorid/IOS的库,不过实际的实现可能是套一个webview,由webview启动webrtc,再把数据给native层渲染。

先介绍下WebRTC的组成。

2. WebRTC的组成

WebRTC由三大块组成,如下图所示:

(1)getUserMedia是负责获取用户本地的多媒体数据,如调起摄像头录像等。

(2)RTCPeerConnection是负责建立P2P连接以及传输多媒体数据。

(3)RTCDataChannel是提供的一个信令通道,在游戏里面信令是实现互动的重要元素。

3. getUserMedia

getUserMedia负责获取用户本地的多媒体数据,包括调起麦克风录音、摄像头捕获的视频和屏幕录制这三种,我已经在《如何实现前端录音功能》用到了这个API——借助WebRTC的getUserMedia实现录音。调摄像头录制视频也是类似,方法很简单,如下代码所示:

window.navigator.mediaDevices
    .getUserMedia({video: true})
    .then(mediaStream => {
    // 画到一个video元素上面
    $('video')[0].srcObject = mediaStream;
});

如果想实现录屏(屏幕共享)的话,就把获取媒体的参数改一下,如下代码把参数由默认的摄像头改成屏幕:

navigator.mediaDevices
    .getUserMedia({video: {mediaSource: 'screen'}})
    .then(stream => {
    videoElement.srcObject = stream;
});

然后就会弹一个框询问要录制的应用窗口,如下图所示:

例如可以选PPT应用,就可以开始演讲了。这个目前只有firefox支持,Edge有一个类似叫getDisplayMedia,Chrome还在开发之中,但是可以装一个官方提供的浏览器插件。可见这个demo

通过getUserMedia调起之后拿到流对象mediaStream,这个流可以在本地渲染,同时通过RTCPeerConnection可以传给对方。

4. RTCPeerConnection

为了实现客户端的点到点连接(数据不需经过服务器转发),RTCPeerConnection做了很多工作。首先需要解决的问题是局域网穿透。

(1)NAT穿墙打洞

要建立一个连接需要知道对方的IP地址和端口号,在局域网里面一台路由器可能会连接着很多台设备,例如家庭路由器接入宽带的时候宽带服务商会分配一个公网的IP地址,所有连到这个路由器的设备都共用这个公网IP地址。如果两台设备都用了同一个端口号创建套接字去连接服务,这个时候就会冲突,因为对外的IP是一样的。因此路由器需要重写IP地址/端口号进行区分,如下图所示:

有两台设备分别用了相同的端口号建立连接,被路由器转换成不同的端口,对外网表现为相同IP地址不同端口号,当服务器给这两个端口号发送数据的时候,路由器再根据地址转换映射表把数据转发给相应的主机。

所以当你在本地监听端口号为55020,但是对外的端口号并不是这个,对方用55020这个端口号是连不到你的。这个时候有两种解决方法,第一种是在路由器设置一下端口映射,如下图所示:

上图的配置是把所有发往8123端口的数据包到转到192.168.123.20这台设备上。

但是我们不能要求每个用户都这么配他们的路由器,因此就有了穿墙打洞,基本方法是先由服务器与其中一方(Peer)建立连接,这个时候路由器就会建立一个端口号内网和外网的映射关系并保存起来,如上面的外网1091就可以打到电脑的55020的应用上,这样就打了一个洞,这个时候服务器把1091端口加上IP地址告诉另一方(Peer),让它用这个打好洞的地址进行连接。这就是建立P2P连接穿墙打洞的原理,最早起源于网络游戏,因为打网络游戏经常要组网,WebRTC对NAT打洞进行了标准化。

这个的有效性受制于用户的网络拓扑结构,因为如果路由器的映射关系既取决于内网的IP + 端口号,也取决于服务器的IP加端口号,这个时候就打不了洞了,因为服务器打的那个洞不能给另外一个外网的应用程序使用(会建立不同的映射关系)。相反如果地址映射表只取决于内网机器的IP和端口号那么是可行的。打不了洞的情况下WebRTC也提供了解决方法,即用一个服务器转发多媒体数据。

这套打洞的机制叫ICE(Interactive Connectivity Establishment),帮忙打洞的服务器叫TURN服务,转发多媒体数据的服务器叫STUN服务。谷歌提供了一个turn server,在我家的网络下只能拿到局域网的地址:

(2)建立P2P连接

为此笔者写了一个demo,可打开这个链接尝试P2P聊天(可以用两个tab或者两台电脑),效果如下图所示:

除了默认提供的TURN服务打洞之外,还需要有一个websocket服务交换互连双方的信息。所以需要写一个websocket服务,我用Node.js简单写了一个,代码已经传到github:webrtc-server-client-demo,包括浏览器端的代码。

这个过程如下图所示:

首先打开摄像头获取到本地的mediaStream,并把它添加到RTCPeerConnection的对象里面,然后创建一个本地的offer,这个offer主要是描述本机的一些网络和媒体信息,采用SDP( Session Description Protocol)格式,如下所示:

v=0
o=- 4809135116782128887 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS 6ReMVBFmnh4JhjzqjNO2AVBc26Ktg0R5jCFB
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
...

然后把这个offer通过websocket服务发送给要连接的对方,对方收到后创建一个answer,格式、作用和offer一样,发送给呼叫方告知被呼叫方的一些信息。当任意一方收到对方的sdp信息后就会调setRemoteDescription记录起来。而当收到默认的ice server发来的打洞信息candidate之后,把candidate发送给对方(在setRemoteDesc之后),让对方发起连接,成功的话就会触发onaddstream事件,把事件里的event.stream画到video上面即可得到对方的影像。

这就是整一个连接过程。

如果连接成功就开始传输多媒体数据,这里面WebRTC做了很多工作。

(3)WebRTC P2P传输

WebRTC整体的架构如下图所示(可见官网):

主要的工作包括:

(1)音视频的编解码(VP8/VP9/AV1)

(2)抗丢包和拥塞控制

(3)回声和噪音消除

WebRTC一个很大的作用就体现在这里了——提供可靠的传输、优质的编解码以及回声问题消除,笔者曾经还用了一个叫h323 plus的包做了一个项目,也是P2P连接。而现在这种实时多媒体传输功能直接内嵌到浏览器里面,对于开发人员来说无疑大大地提高了开发效率。

在实际的线上项目里面,由于P2P连通率和稳定性并不是特别乐观,所以更多地是采用P2SP的架构,S代表Server,如下图所示:

一方面能够提高稳定性,另一方面能够解决一对多和多对多视频聊天的问题。因为WebRTC比较适用于一对一的,在一对多场景让一个用户的流推给几个用户不管是性能还是上传带宽都可能会有问题。

可以做一个兼容方案,当P2P不行的时候就切到P2SP.

关于RTCDataChannel这里不展开讨论,实际场景还是使用WebSocket比较多。

5. WebRTC的未来

WebRTC已经被W3C发布了1.0标准,但是暂未成为RFC标准。WebRTC也在逐渐地发展,包括:

(1)Chrome 69使用了新的回声消除算法AEC3

(2)VP9编码提升了35%的质量,新的AV1编码可以在Chrome里面使用

(3)包括RTCRtpSender等更加丰富的操纵API

未来的RTC将会提供更多功能:

(1)直接操作媒体流数据的能力(现在得通过CaptureStream间接操作)

(2)自定义编解码参数的能力RTCRtpEncodeingParameters(Chrome 70)

等等。

相信WebRTC的未来是非常光明的。


【人人网招聘中高级前端】

1. 项目背景: 我们在做一个企业级海外的SAAS CRM(客户管理系统)产品, 前端的技术挑战很大, 比如在我们的网站让客户直接打网络电话(直接打手机那种), 发email, 自动根据用户场景处理业务等。

2. 技术栈背景: 也是采用比较流行的vue, vuex等框架, 通讯是WebRTC, 消息分发系统用Google的FCM和苹果的APN。服务部署在亚马逊或谷歌云上。服务全球客户。

3. 另外因为产品是一个企业级用户产品所以个方面要求比较高(比如性能,安全,多任务处理等)。所以对候选人技术要求比较高,如果您对技术特别在意,那么我们的空缺提供了很好的才能发挥空间和锻炼成长的机会。

请将简历发至shanshan.zhu@renren-inc.com