先看看大神的解析
下面主要是介绍WebRTC自带的一个传输模块,实际应用中您可以根据自己的需求注册不同的传输模块。
其中:udp_socket_wrapper.h主要负责Socket相关操作,如Socket创建、启动、端口绑定、停止。
udp_socket2_windows.h主要负责windows平台上的Socket相关操作,与之对应的就是linux平台上的udp_socket_posix.h
udp_transport.h主要负责包的发送和接收,如果你想实现自己的数据包收发逻辑,可重写该类,如他里面的LoopBack方式就是通过重写该模块来实现的。
对客户端调用来说主要就是做四件事情:
1、设置音视频远端地址和端口(包括远端音视频的RTP、RTCP端口和本地接收音视频的RTP、RTCP端口)。
2、启动音视频数据的发送。
3、启动音视频数据的接收。
4、启动音视频数据的播放。
具体代码结构如下:
1 public void start() { 2 this.setRemoteIp(WebRTCClient.str_remote_ip);WebRTCClient.str_to); 3 if (audioEnabled) { 4 startVoE(); 5 } 6 if (receiveVideo || sendVideo) { 7 startViE(); 8 } 9 }
1 public void startVoE() { 2 check(!voeRunning, "VoE already started"); 3 check(voe.startListen(audioChannel) == 0, "Failed StartListen"); 4 check(voe.startPlayout(audioChannel) == 0, "VoE start playout failed"); 5 check(voe.startSend(audioChannel) == 0, "VoE start send failed"); 6 voeRunning = true; 7 }
1 public void startViE() { 2 check(!vieRunning, "ViE already started"); 3 4 if (receiveVideo) { 5 if (viewSelection == context.getResources().getInteger(R.integer.openGl)) { 6 svRemote = ViERenderer.CreateRenderer(context, true); 7 } else if (viewSelection == context.getResources().getInteger(R.integer.surfaceView)) { 8 svRemote = ViERenderer.CreateRenderer(context, false); 9 } else { 10 externalCodec = new MediaCodecVideoDecoder(context); 11 svRemote = externalCodec.getView(); 12 } 13 if (externalCodec != null) { 14 check(vie.registerExternalReceiveCodec(videoChannel, VCM_VP8_PAYLOAD_TYPE, 15 externalCodec, true) == 0, "Failed to register external decoder"); 16 } else { 17 check(vie.addRenderer(videoChannel, svRemote, 0, 0, 0, 1, 1) == 0, 18 "Failed AddRenderer"); 19 check(vie.startRender(videoChannel) == 0, "Failed StartRender"); 20 } 21 check(vie.startReceive(videoChannel) == 0, "Failed StartReceive"); 22 } 23 if (sendVideo) { 24 startCamera(); 25 check(vie.startSend(videoChannel) == 0, "Failed StartSend"); 26 } 27 vieRunning = true; 28 }
RTCPeerConnection + signaling: offer, answer and candidate
RTCPeerConnection就是webrtc应用程序用来创建客户端连接和视频通讯的API.为了初始化这个过程 RTCPeerConnection有两个任务:
1,确定本地媒体条件,如分辨率,编解码能力,这些需要在offer和answer中用到.
2,取到应用程序所在机器的网络地址,即称作candidates.
一旦上面这些东西确定了,他们将通过信令机制和远端进行交换.
想象一下Alice呼叫Eve的过程( Alice is trying to call Eve.),下面就是完整offer/answer机制的细节:
1,Alice创建一个 RTCPeerConnection对象.
2,Alice创建一个offer(即SDP会话描述)通过RTCPeerConnection createOffer()方法.
3,Alice调用setLocalDescription()方法用他的offer.
4,Alice通过信令机制将他的offer发给Eve.
5,Eve调用setRemoteDescription()方式设置Alice的offer,因此他的RTCPeerConnection知道了Alice的设置.
6,Eve调用方法createAnswer(),然后会触发一个callback,这个callback里面可以去到自己的answer.
7,Eve设置他自己的anser通过调用方法setLocalDescription().
8,Eve通过信令机制将他的anser发给Alice.
9,Alice设置Eve的anser通过方法setRemoteDescription().
另外Alice和Eve也需要交换网络信息(即candidates),发现candidates参考了ICE framework.
1,Alice创建RTCPeerConnection对象时设置了onicecandidate handler.
2,hander被调用当candidates找到了的时候.
3,当Eve收到来自Alice的candidate消息的时候,他调用方法addIceCandidate(),添加candidate到远端描述里面.
JSEP支持ICE Candidate Trickling,他允许呼叫方在offer初始化结束后提供candidates给被叫方.而被叫方开始建立呼叫和连接而不需要等到所有candidate到达.
怎么发现客户端
这里有一种很简单的表述方式---我怎么找到别人视频?
打电话的时候我们有电话号码和电话本,知道打给谁,QQ聊天的时候,我们可以通过通讯录找到要聊天的人,webrtc也一样,他的客户端需要通过一种方式找到要聊天的人或要加入的会议.
webrtc没有定义这样一个发现过程,这个其实很简单,可以参考 talky.io, tawk.com and browsermeeting.com,另外Chris Ball创建了serverless-webrtc,他可以通过Emai,IM来参与视频.
怎么创建信令服务?
再次重申:webrtc没有定义信令机制,因此无论你选择什么机制你都的需要一台中间服务端,用来在客户端之间交换数据,你总不可能直接说:"跟我朋友视频?",
由于信令消息很小,大多数交互都是在开始通话之前,可以参考 apprtc.appspot.com and samdutton-nodertc.jit.su, 测试发现:一个视频通话过程大概有35~40消息,数据量在10K左右,
所以相对来说信令服务器不怎么占带宽,也不需要消耗多大的CPU和内存.
从服务端推送消息给客户端
信令服务器推送消息需要时双向的,即客户端能发消息给服务器,服务器也能发消息给服务端,这种双向机制就将Http给排除了(当然可以使用长连接,而且很多人都是这么做的,只不过比较占资源).
说到这里很多人会想到WebSocket,没错,这是一种很好的解决方案,而且后台实现框架也很多,如PHP,Python,Ruby.
大约3/4的浏览器支持webSocekt,更重要的是支持WEBRTC的浏览器都支持WebSocket,包括PC和手机, TLS应该被使用为了所有连接,他能确保为被加密的消息不被截获,同时也能减少使用代理带来的问题(reduce problems with proxy traversal),更多这方面的知识请参考 WebRTC chapter和WebSocket Cheat Sheet .
apprtc.appspot.com中的视频通讯使用的信令是 Google App Engine Channel API,他采用的是 Comet技术, HTML5 Rocks WebRTC article有详细的介绍(detailed code walkthrough)
当然你也可以通过Ajax来实现这样一个长连接,不过这样会产生很多重复的网络请求,而且应用在移动端会有很多问题.
扩展信令的实现
尽管信令服务占用的CPU和带宽资源都比较少,但实际应用中如果要考虑到高并发,信令服务还是有很大负载的.这些我们不深入讨论了,下面有一些不错的选择供参考:
1,eXtensible Messaging and Presence Protocol(XMPP):主要是用来给即时通讯用的,开源服务端包括ejabberd and Openfire. 客户端包括 Strophe.js use BOSH(但因为 various reasons,BOSH没有WebSocket高效),补充说明:Jingle是XMPP的扩展,支持音视频,webrtc项目里面的network和transort组件就是来自 libjingle库.
Developer Phil Leggetter's Real-Time Web Technologies Guide 提供了一个消息服务和库的综合清单.
使用RTCDataChannel控制信令
一旦信令服务建立好了,两个客户端之间建立了连接,理论上他们就可以使用RTCDataChannel进行点对点通讯了,这样可以减轻信令服务的压力和消息传递的延迟,这部分没有提供Demo.
使用已有信令服务
如果您不想自己动手,这里还有提供几个webrtc信令服务器,与上述代码类似他们使用socket.io. 与webrtc客户端的javascript集成到一起了.
webRTC.io:webrtc的第一个抽想库.
easyRTC:一个完整的webrtc库.
Signalmaster:信令服务器,和 SimpleWebRTC作为客户端脚本库配套使用.
如果您不想写任何代码的花,可以直接使用现有商业产品:vLine, OpenTok and Asterisk.
如果您想实现录制功能,可参考 signaling server using PHP on Apache,虽然已经过时了,但代码可供参考.
信令安全性问题
因为信令使我们自己定义的,所以安全性问题跟webrtc无关,需要自己处理.一旦黑客掌握了你的信令,那他就是控制会话的开始,结束,重定向等等.
最重要的因素在信令安全中还是要靠使用安全协议,如HTTPS,WSS(如TLS),他们能确保未加密的消息不能被截取.
为确保信令安全,强烈推荐使用TLS.
使用ICE处理NATs和防火墙
元数据是通过信令服务器中转发给另一个客户端,但是对于流媒体数据,一旦会话建立,RTCPeerConnection将首先尝试使用点对点连接.
简单一点说就是:每个客户端都有一个唯一的地址,他能用来和其他客户端进行通讯和数据交换.
现实生活中客户端都位于一个或多个NAT之后,或者一些杀毒软件还阻止了某些端口和协议,或者在公司还有防火墙或代理,等等,防火墙和NAT或许是同一个设备,如我们家里用的路由器.
webrtc就是通过 ICE这套框架来处理复杂的网络环境的,如果想启用这个功能,你必须让你得应用程序传ice服务器的URL给RTCPeerConnection,描述如下:
ICE试着找最好的路径来让客户端建立连接,他会尝试所有可能的选项,然后选择最合适的方案,ICE首先尝试P2P连接,如果失败就会通过Turn服务器进行转接.
换一个说法就是:
1,STUN服务器是用来取外网地址的.
2,TURN服务器是在P2P失败时进行转发的.
每个TURN服务器都支持STUN,ICE处理复杂的NAT设置,同时NAT打洞要求不止一个公网IP和端口.