实现WebRTC P2P连接

作者:人人网FED

链接:https://juejin.cn/post/6844903684539678734

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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=0o=- 48091351167821288872 IN IP4 127.0.0.1s=-
t=00a=group:BUNDLE audio video
a=msid-semantic: WMS 6ReMVBFmnh4JhjzqjNO2AVBc26Ktg0R5jCFB
m=audio 9 UDP/TLS/RTP/SAVPF 11110310490810610513110112113126
...复制代码

然后把这个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的未来是非常光明的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如何使用ESP32-CAM或ESP32-EYE + Omnivision相机制作小型,低成本的监控摄像头 - 包括应用和设备源。 硬件组件: Seeed ESP-CAM× 1 Espressif ESP-EYE× 1 Espressif Generic ESP32 IC× 1 软件应用程序和在线服务: iOS P2P视频应用 Android P2P视频应用 简单安全的标准监控摄像头 一个简单的用例可能是您希望制作标准的低成本,简单(安全)监控摄像头。标准监控摄像机通常随附大量软件,然后必须通过更新,安全修复等来支持。因此,没有桌面/服务器规模操作系统和运行服务的简化环境因其小得多的攻击面而具有更高的安全性。但如果这还不够,低成本和小尺寸应该说服你。 其他应用中的远程视频输入 我们的许多客户将视频监控摄像头视为独立应用程序,即您安装它并在需要查看远程端发生的情况时将视频流式传输到手机。但是,我们看到越来越多的项目将流式视频作为另一个应用程序的一部分。例如,带有视频流的宠物喂食器,具有音频和视频功能的门铃,可以监控的3D打印机等。 我们开始研发M5Stack ESP32 Cam。它没有额外的外部RAM,而是有一个USB到板上的ESP32 UART,这使得编程更容易(你不必手动操作GPIO0等进入闪存编程模式) M5Stack的问题在于它缺少外部存储器,当你需要流式传输大量数据并快速完成时,你需要保留一个未经确认的数据包缓冲区从相机流向应用程序,如果数据包已准备好重新发送在运输过程中丢失了。此外,您需要从相机缓冲帧缓冲区。当然,这可以进行优化,因此所有内容都使用相同的缓冲区,但这会违反关注点分离原则,并使集成更加困难。
WebRTC (Web Real-Time Communication) 是一种用于实时通信的技术,特别适用于浏览器和移动设备之间的点对点(P2P)连接,比如视频聊天、文件共享等。在安卓应用中实现WebRTC P2P,你需要使用Google的开源库,如libjitsi-mobix(基于Jitsi Meet)或Janus Gateway。 以下是一个简单的步骤概述: 1. 添加依赖:在你的项目中引入WebRTC相关的库,例如`org.webrtc:webrtc:2.0.x`(具体版本根据Android Studio的要求选择)。 2. 初始化:在你的Activity或Fragment中初始化WebRTC引擎,创建PeerConnection实例,这通常在`onCreate`或`onResume`方法中完成。 ```java WebRtcEngineFactory engineFactory = new WebRtcEngineFactory(context); WebRtcEngine engine = engineFactory.createWebRtcEngine(); PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory.createDefaultPeerConnectionFactory(); ``` 3. 创建VideoCapturer:如果你的应用需要获取本地视频流,你需要创建一个`VideoCapturer`。 4. 建立连接:创建`RTCPeerConnection`,加入媒体流,然后调用`createOffer()`生成SDP offer。 ```java SessionDescriptionDescription offer = peerConnectionFactory.createOffer(); peerConnection.setLocalDescription(offer); ``` 5. 处理ICE候选:接收并处理对端发送的ICE候选,使用`addIceCandidate()`方法。 6. 加入/接听呼叫:对于接收呼叫,需要调用`setRemoteDescription()`,然后可能需要进行SDP协商。 7. 实现数据通道:如果需要双向数据传输,使用DataChannel。 8. 关闭连接:在不需要的时候,记得调用`close()`方法释放资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值