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 实现录音. 调摄像头录制视频也是类似, 方法很简单, 如下代码所示:
- Windows.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: , 包括浏览器端的代码.
这个过程如下图所示:
首先打开摄像头获取到本地的 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 整体的架构如下图所示(可见 官网 https://webrtc.org/architecture/ ):
主要的工作包括:
(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 的未来是非常光明的.
Post Views: 1
来源: http://www.tuicool.com/articles/2iiQriE