Android IOS WebRTC 音视频开发总结(九)-- webrtc入门001
下面这篇介绍webrtc的文章不错,我花了大半天翻译了一下.
翻译的时候不是逐字逐句的,而是按照自己的理解翻译的,同时为了便于理解,也加入一些自己组织的语言.
本文主要介绍webrtc的信令,stun,turn,转载请说明出处(博客园RTC.Blacker).
英文来自:http://www.html5rocks.com/en/tutorials/webrtc/infrastructure/
WEBRTC支持点对点通讯,但是WEBRTC仍然需要服务端,因为:
1,为了协调通讯过程客户端之间需要交换元数据,如一个客户端找到另一个客户端以及通知另一个客户端开始通讯.
2,需要处理NAT或防火墙,这是公网上通讯首要处理的问题.
在这篇文章里我们将告诉您怎么创建一个信令服务,怎么处理现实世界中两个客户端的连接,以及怎么处理多方通话和怎么与VOIP,PSTN的交互.如果您不了解webrtc,建议您读这篇文章前先看:http://www.html5rocks.com/en/tutorials/webrtc/basics/
什么是信令?
信令就是协调通讯的过程,为了建立一个webrtc的通讯过程,客户端需要交换如下信息:
1,会话控制消息:用来开始和结束通话(即开始视频,结束视频这些操作指令)
2,处理错误的消息.
3,元数据:如各自的音视频编解码方式,带宽.
4,网络数据:对方的公网IP,端口,内网IP,端口.
5,......
信令处理过程需要客户端能够来回传递消息,这个过程在webrtc里面是没有实现的,需要您自己创建,下面我们会告诉您怎么创建这样一个过程.
为什么WEBRTC没有定义信令处理?
为了避免重复定义和最大程度兼容现有技术,JSEP(JavaScript Session Establishment Protocol)上已有概述.
现有的SIP协议就可以较好地处理整个信令过程,另外不同的应用程序可能对信令处理有特别的要求,如我们做的很多项目信令处理都是自己写的,很灵活.
其实只要你能满足你自己的业务需求,信令处理你完全可以自己定义,实现起来也不难,就是客户端和服务端怎么通讯而已,用得最广的就是websocket了,后面会介绍.
如下是JSEP定义的客户端通讯架构:
JSEP要求客户端之间交换offer和answer:其实就是上面提到的元数据,他们是以SDP格式进行交换,格式如下:
v=0
o=- 7614219274584779017 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS
m=audio 1 RTP/SAVPF 111 103 104 0 8 107 106 105 13 126
c=IN IP4 0.0.0.0
a=rtcp:1 IN IP4 0.0.0.0
a=ice-ufrag:W2TGCZw2NZHuwlnf
a=ice-pwd:xdQEccP40E+P0L5qTyzDgfmW
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=mid:audio
a=rtcp-mux
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:9c1AHz27dZ9xPI91YNfSlI67/EMkjHHIHORiClQe
a=rtpmap:111 opus/48000/2
…
如果您对SDP格式有兴趣,可以参考:IETF examples
在webrtc架构里面调用setLocalDiscription,setRemoteDiscription前可通过编辑SDP里面的值来更改offer和anser.如apprtc.appspot.com 中得preferAudioCodec()能用来设置默认的音频编码和码率,sdp用javascript修改起来可能有点痛苦,W3C组织有在讨论通过jason方式来编辑,不过目前这种方式也有些优点(some advantages).
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到达.
Coding WebRTC for signaling
下面是一个W3C的例子(W3C code example)概括了一个完整的信令过程,他里面假设已经存在信令机制:SignalingChannel,信令在下面被详细讨论
var signalingChannel = new SignalingChannel();
var configuration = {
'iceServers': [{
'url': 'stun:stun.example.org'
}]
};
var pc; // call start() to initiate function start() {
pc = new RTCPeerConnection(configuration); // send any ice candidates to the other peer
pc.onicecandidate = function (evt) {
if (evt.candidate)
signalingChannel.send(JSON.stringify({
'candidate': evt.candidate
}));
}; // let the 'negotiationneeded' event trigger offer generation
pc.onnegotiationneeded = function () {
pc.createOffer(localDescCreated, logError);
} // once remote stream arrives, show it in the remote video element
pc.onaddstream = function (evt) {
remoteView.src = URL.createObjectURL(evt.stream);
}; // get a local stream, show it in a self-view and add it to be sent
navigator.getUserMedia({
'audio': true,
'video': true
}, function (stream) {
selfView.src = URL.createObjectURL(stream);
pc.addStream(stream);
}, logError);
} function localDescCreated(desc) {
pc.setLocalDescription(desc, function () {
signalingChannel.send(JSON.stringify({
'sdp': pc.localDescription
}));
}, logError);
} signalingChannel.onmessage = function (evt) {
if (!pc)
start(); var message = JSON.parse(evt.data);
if (message.sdp)
pc.setRemoteDescription(new RTCSessionDescription(message.sdp), function () {
// if we received an offer, we need to answer
if (pc.remoteDescription.type == 'offer')
pc.createAnswer(localDescCreated, logError);
}, logError);
else
pc.addIceCandidate(new RTCIceCandidate(message.candidate));
}; function logError(error) {
log(error.name + ': ' + error.message);
}
了解offer,anser,candidate交换过程,可通过simpl.info/pc上视频聊天的控制台日志,如果您想了解更多,可以下载完整的WebRTC signaling and stats from the chrome://webrtc-internals page in Chrome or the opera://webrtc-internals page in Opera.
怎么发现客户端
这里有一种很简单的表述方式---我怎么找到别人视频?
打电话的时候我们有电话号码和电话本,知道打给谁,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 提供了一个消息服务和库的综合清单.
使用Nodejs上的Socket.io实现一个信令服务
下面这个代码是一个简单的web应用,使用了 Socket.io on Node, socket.io的设计目标就是为了简化消息通讯服务的创建,特别适合作为webrtc的信令,因为他内嵌了房间的概念,下面这个样例设计主要是为了少量用户的使用,并没有考虑太多的扩展性.
下面代码主要用来介绍怎么创建信令服务,可以通过查看日志来了解客户端加入房间时交换的消息过程, WebRTC codelab提供了怎么集成这个例子到webrtc视频通讯中的一步步的完整说明.你能从 step 5 of the codelab repo 下载代码或直接进入 samdutton-nodertc.jit.su查看(用浏览器打开两个URL即可).
下面是客户端的 index.html:
<!DOCTYPE html>
<html>
<head>
<title>WebRTC client</title>
</head>
<body>
<script src='/socket.io/socket.io.js'></script>
<script src='js/main.js'></script>
</body>
</html>
客户端的JS
var isInitiator; room = prompt('Enter room name:'); var socket = io.connect(); if (room !== '') {
console.log('Joining room ' + room);
socket.emit('create or join', room);
} socket.on('full', function (room){
console.log('Room ' + room + ' is full');
}); socket.on('empty', function (room){
isInitiator = true;
console.log('Room ' + room + ' is empty');
}); socket.on('join', function (room){
console.log('Making request to join room ' + room);
console.log('You are the initiator!');
}); socket.on('log', function (array){
console.log.apply(console, array);
});
完整服务端代码:
var static = require('node-static');
var http = require('http');
var file = new(static.Server)();
var app = http.createServer(function (req, res) {
file.serve(req, res);
}).listen(2013); var io = require('socket.io').listen(app); io.sockets.on('connection', function (socket){ // convenience function to log server messages to the client
function log(){
var array = ['>>> Message from server: '];
for (var i = 0; i < arguments.length; i++) {
array.push(arguments[i]);
}
socket.emit('log', array);
} socket.on('message', function (message) {
log('Got message:', message);
// for a real app, would be room only (not broadcast)
socket.broadcast.emit('message', message);
}); socket.on('create or join', function (room) {
var numClients = io.sockets.clients(room).length; log('Room ' + room + ' has ' + numClients + ' client(s)');
log('Request to create or join room ' + room); if (numClients === 0){
socket.join(room);
socket.emit('created', room);
} else if (numClients === 1) {
io.sockets.in(room).emit('join', room);
socket.join(room);
socket.emit('joined', room);
} else { // max two clients
socket.emit('full', room);
}
socket.emit('emit(): client ' + socket.id + ' joined room ' + room);
socket.broadcast.emit('broadcast(): client ' + socket.id + ' joined room ' + room); }); });
如果需要运行上面这个app,需要用到node,详见 nodejs.org,很好很强大的一个东东,我后面会翻译一篇介绍nodejs的文章.
其实不管你用什么方式创建信令服务,您的后台和客户端最少需要具有样例代码中的功能.
使用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和端口.
javascript中ice配置如下:
{
'iceServers': [
{
'url': 'stun:stun.l.google.com:19302'
},
{
'url': 'turn:192.158.29.39:3478?transport=udp',
'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
'username': '28224511:1379330808'
},
{
'url': 'turn:192.158.29.39:3478?transport=tcp',
'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
'username': '28224511:1379330808'
}
]
}
一旦RTCPeerConnection取到了所要的信息,ICE过程就自动发生了,RTCPeerConnection使用ICE框架取到两点之间最好的路径,当然这个过程离不开STUN和TURN的支持.
STUN
NAT的作用就是提供内外网端口的映射,因为在公网上两个内网客户端要建立直接连接就不许先知道彼此对应的公网地址和端口,这时候知道对方内网IP和地址是没用的.
而STUN的作用就是让客户端发现自己的公网IP和端口,所以负载不大,同时目前免费得STUN服务器也很多.一搜一大把.
通过webrtcstats.com可知85%的情况下可以P2P,当然复杂NAT和网络环境下这个概率会更低.
TURN
RTCPeerConnection首先尝试使用P2P,如果失败,他将求助于TCP,使用turn转发两个端点的音视频数据.
重申:turn转发的是两个端点之间的音视频数据,不是信令数据.
因为TURN服务器是在公网上,所以他能被各个客户端找到,另外TURN服务器转发的是数据流,很占用带宽和资源.
部署STUN和TURN服务器
google提供了stun.l.google.com:19302供测试, apprtc.appspot.com用的就是这个stun服务器,实际应用中,我们推荐使用rfc5766-turn-server,同时也提供了一些连接源: VM image for Amazon Web Services
turn服务器的安装后面我专门写篇文章来介绍,作者写的那种方式我也没有尝试过,不过看起来比较复杂.有兴趣的可以去看原文.
下面这几部分我放到下一篇文章介绍,内容太多,大家会看得很晕
Beyond one-to-one: multi-party WebRTC
Multipoint Control Unit
Beyond browsers: VoIP, telephones and messaging
Android IOS WebRTC 音视频开发总结(九)-- webrtc入门001的更多相关文章
- 转:Android IOS WebRTC 音视频开发总结 (系列文章集合)
随笔分类 - webrtc Android IOS WebRTC 音视频开发总结(七八)-- 为什么WebRTC端到端监控很关键? 摘要: 本文主要介绍WebRTC端到端监控(我们翻译和整理的,译 ...
- Android IOS WebRTC 音视频开发总结(八十五)-- 使用WebRTC广播网络摄像头视频(下)
本文主要介绍WebRTC (我们翻译和整理的,译者:weizhenwei,校验:blacker),最早发表在[编风网] 支持原创,转载必须注明出处,欢迎关注我的微信公众号blacker(微信ID:bl ...
- Android IOS WebRTC 音视频开发总结(八十三)-- 使用WebRTC广播网络摄像头视频(上)
本文主要介绍WebRTC (我们翻译和整理的,译者:weizhenwei,校验:blacker),最早发表在[编风网] 支持原创,转载必须注明出处,欢迎关注我的微信公众号blacker(微信ID:bl ...
- Android IOS WebRTC 音视频开发总结(四六)-- 从另一个角度看国内首届WebRTC大会
文章主要从开发者角度谈国内首届WebRTC大会,支持原创,文章来自博客园RTC.Blacker,支持原创,转载必须说明出处,更多详见www.rtc.help. -------------------- ...
- Android IOS WebRTC 音视频开发总结(六)-- iOS开发之含泪经验
前段时间在搞webrtc iOS开发,所以将标题改为了Android IOS WebRTC 音视频开发总结, 下面都是开发过程中的经验总结,转载请说明出处(博客园RTC.Blacker): 1. IO ...
- WebRTC 音视频开发
WebRTC 音视频开发 webrtc Android IOS WebRTC 音视频开发总结(七八)-- 为什么WebRTC端到端监控很关键? 摘要: 本文主要介绍WebRTC端到端监控(我们翻译 ...
- Android IOS WebRTC 音视频开发总结(三二)-- WebRTC项目开发建议
本文主要介绍WEBRTC开发过程中的一些现象,文章来自博客园RTC.Blacker,支持原创,欢迎关注微信公众号blacker,更多详见www.rtc.help 随着移动互联网和智能硬件的快速发展,音 ...
- Android IOS WebRTC 音视频开发总结(二四)-- p2p调用堆栈
本文主要分析webrtc音视频点对点部分的代码结构,文章来自博客园RTC.Blacker,转载请说明出处. 前段时间在查一个偶尔断线的问题(这种问题最蛋疼,不好重现,只能凭经验去搞),所以理了下web ...
- Android IOS WebRTC 音视频开发总结(二三)-- hurtc使用说明
本文主要介绍如何测试基于浏览器和手机的视频通话程序,转载请说明出处,文章来自博客园RTC.Blacker,更多详见www.blackerteam.com 很多人想测试浏览器(包括浏览器版本和桌面e ...
- Android WebRTC 音视频开发总结
www.cnblogs.com/lingyunhu/p/3621057.html 前面介绍了WebRTCDemo的基本结构,本节主要介绍WebRTC音视频服务端的处理,,转载请说明出处(博客园RTC. ...
随机推荐
- Linux重置mysql密码(转载)
From:http://hi.baidu.com/mcspring/item/6358ee27afe7e1c8a5275ab7 首先,必须拥有MySQL操作的所有权限: 其次,停止MySQL服务: / ...
- ubuntu 格式化U盘,并制作系统镜像
1. 先要卸载U盘,使用如下命令: #umount /dev/sdb1 注意:/dev/后面的设备要根据你的实际情况而定,否则后面格式化,丢失数据!! 格式化U盘,并建立vfat文件系统 #mkfs. ...
- centos5安装在大硬盘上面的问题
硬盘空间大小: 3TB 原始硬盘是GPT格式的,系统安装现象如下: 此时按 ctrl + alt + F2 ,进入命令行界面,输入如下: 发现 用parted修改硬盘分区格式为msdos,报错!基本可 ...
- java异常分类(运行时异常,可检查异常)
NullPointerException:是运行时异常(RuntimeException),也叫非检查异常 所以我们抛出该类异常实例时,方法声明处无需添加throws来列举该类异常的抛出,编译器在编译 ...
- 山东省第三届ACM省赛
Solved ID PID Title Accepted Submit A 2407 Impasse (+) 0 0 B 2415 Chess 0 0 C 2414 An interest ...
- Oracle 11g新参数USE_LARGE_PAGES与AMM使用 (转载)
在之前的文章(http://space.itpub.net/17203031/viewspace-774843)中,笔者介绍了如何在Linux 2.6内核中配置HugePage以及AMM与其的不兼容性 ...
- PHP截取IE浏览器并缩小原图的方法
这篇文章主要介绍了PHP截取IE浏览器并缩小原图的方法,涉及PHP调用com组件实现图像截取的相关技巧,需要的朋友可以参考下 本文实例讲述了PHP截取IE浏览器并缩小原图的方法.分享给大家供大家参考, ...
- JQuery基础教程:选择元素(上)
jQuery最强大的特性之一就是它能够简化在DOM中选择元素的任务,DOM中的对象网络与家谱有几分类似,当我们提到网络中元素之间的关系时,会使用类似描述家庭关系的术语,比如父元素.子元素,等等.通过一 ...
- Fragments | Android Developer
Definition A Fragment represents a behavior or a potion of user interface in an Activity. You can co ...
- JavaScript对象的创建之动态原型方式
为了让定义的方式更加符合java的需求,就把定义方法的原型代码放置在Person这个构造函数中. function Person(name,age,friends){ //属性在构造函数中定义 thi ...