一、SIPML5介绍
Doubango 推出了“世界上第一个HTML5 SIP客户端”:SipML5,实现了基于Chrome的SIP客户端,并与自己先前的开源产品Idoubs和IMSDroid实现互通。就像主页里的两个Demo视频显示的一样,你可以轻松实现Chrome和iOS/Android移动设备之间的实时视音频通话。
SipML5使用Chrome的实验功能WebRTC实现媒体功能,并用JavaScript封装了一个完整强大的javascript SIP/SDP stack 完成信令的管理,传输层通过Websocket与服务端Gateway通信接入SIP Server,最终通过Video TAG播放视音频内容。
WebRTC内置在浏览器中,提供接口供js调用。
1、SIP协议栈:用JS实现的SIP协议栈,用于形成SIP信令和解析SIP信令。
2、SDP协议栈:用JS实现的SDP协议栈,用于形成SDP信令和解析SDP信令。
3、媒体处理(WebRTC):Google、Firefox等浏览器内置的能够在网页中无插件形式处理多媒体数据,主要包括生成SDP、采集本机摄像头和麦克风的音视频数据、传输媒体数据、处理接收到的音视频数据等。
二、SIPML5源码分析
1、media模块下主要是对会话的管理和媒体的处理(包括SDP的生成和修改)
2、SDP模块主要实现的是SDP协议栈,包括SDP的修改函数和解析函数
3、SIP模块主要包括SIP协议栈的实现(SPP的修改函数和解析函数)和与多媒体网关进行信令交互的方式(WebSockt)。
一般而言,需要进行功能扩充和修改的地方一般在界面入口(call.htm)和媒体处理模块(tmedia_session_jsep.js)、WebSocket发送和接收的数据(tsip_transport.js)。
2.1注册
1、建立WebSocket连接;
2、生成SIP头
3、发送注册的SIP信息(用session管理)
4、收到200 OK
1)在生成SIP头文件时进行修改和添加
2)如果接收到的SIP协议格式不支持,将收到的进行修改
3)将发出的SIP协议进行修改
2.2呼叫
主要处理的是WebRTC提供的外部接口逻辑,本项目中进行处理的主要文件tmedia_session_jsep.js。
1、呼叫流程中SDP与流
a)Add Streams:添加本地流,对应上述函数onGetUserMediaSuccess()函数,如果本地不推流则在GetUserMediaSuccess = function (o_stream, _This)函数不调用WebRTC提供的This.o_pc.addStream(o_stream)函数。
b)__get_lo生成SDP,在GetUserMediaSuccess = function (o_stream, _This)调用CreateOffer则发送SDP
c)获得远端SDP,__set_ro()函数里进行处理
d)接收远端发送的流,则是在subscribe_stream_events()函数调用webRTC提供的this.o_pc.onaddstream方法
2、双向(默认):
音频(默认):直接发送带音频的SDP。
音视频(默认):直接发送带音视频的SDP。
视频:验证可以扩充视频功能。
3、单向(可扩充):
音频:web端对流只接收不发送,SDP修改成sendonly。
音视频:web端对流只接收不发送,SDP修改成sendonly。
视频:验证可以扩充视频功能。
三、SIPML5中的WebRTC
WebRTC的核心处理模块包括音频、视频、媒体传输和会话。
1. JSEP
建立会话最关键的就是媒体的协商,WebRTC虽然没有指定具体的信令协议,但是媒体协商采用了SDP协议。JSEP(javascript Session Establishment Protocol,JavaScript会话建立协议)是一个信令API,允许开发者构建更强大的应用程序以及增加在信令协议选择上的灵活性。谷歌还会提供一个JavaScript库以降低使用该API的复杂性。
2. Topologies(拓扑)
将支持多个独立的PeerConnections,每个PeerConnections将能够发送和接收多个独立的媒体来源。
3. ICE / STUN / TURN
ICE和STUN是互联网上建立点对点连接的标准方法。Chrome目前的栈偏离了官方标准,谷歌称正在努力解决这个问题。该技术还将支持TURN服务,以便能够透过防火墙进行连接,这需要进行中继转发和封装。
4. DTLS-SRTP
在Chrome中使用WebRTC将被强制加密,在首个WebRTC稳定版本中,将采用DTLS-SRTP方式。
5. VP8、iSAC、iLBC、G.711
在Chrome中,将通过VP8提供视频编解码支持。在音频方面,将支持iSAC、iLBC、G.711和DTMF,默认为iSAC。
6. 浏览器支持
谷歌,IE8以上,火狐,Safari,Opera,UC,360
四、WebRTC的HTML5的Demon程序
<%@ page language="java" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>Java后端WebSocket的Tomcat实现</title>
</head>
<body>
Welcome<br/><input id="text" type="text"/>
<button οnclick="send()">发送消息</button>
<hr/>
<button οnclick="closeWebSocket()">关闭WebSocket连接</button>
<hr/>
<div id="message"></div>
<video id="vid1" width="640" height="480" autoplay></video>
<video id="vid2" width="640" height="480" autoplay></video><br>
<a id="create" href="/Websocket/#true" οnclick="init()">点击此链接新建聊天室</a><br>
<p id="tips" style="background-color:red">请在其他浏览器中打开:http://此电脑 加入此视频聊天</p>
</body>
<script type="text/javascript">
var isCaller = window.location.href.split('#')[1];
var websocket = null;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/Websocket/websocket");
}
else {
alert('当前浏览器 Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket连接发生错误");
};
//连接成功建立的回调方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket连接成功");
}
// 创建PeerConnection实例 (参数为null则没有iceserver,即使没有stunserver和turnserver,仍可在局域网下通讯)
var pc = new webkitRTCPeerConnection(null);
// 发送ICE候选到其他客户端
pc.onicecandidate = function(event) {
setMessageInnerHTML("我看看 1");
if (event.candidate !== null) {
setMessageInnerHTML("我看看 2");
websocket.send(JSON.stringify({
"event" : "_ice_candidate",
"data" : {
"candidate" : event.candidate
}
}));
}
};
//接收到消息的回调方法
websocket.onmessage = function (event) {
setMessageInnerHTML("接收到的信息");
setMessageInnerHTML(event.data);
if(event.data=="new user") {
console.log("new user");
location.reload();
} else {
var json = JSON.parse(event.data);
console.log('onmessage: ', json);
//如果是一个ICE的候选,则将其加入到PeerConnection中,否则设定对方的session描述为传递过来的描述
if (json.event === "_ice_candidate") {
pc.addIceCandidate(new RTCIceCandidate(json.data.candidate));
} else {
pc.setRemoteDescription(new RTCSessionDescription(json.data.sdp));
// 如果是一个offer,那么需要回复一个answer
if (json.event === "_offer") {
pc.createAnswer(sendAnswerFn, function(error) {
console.log('Failure callback: ' + error);
});
}
}
}
}
//连接关闭的回调方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket连接关闭");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
closeWebSocket();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//关闭WebSocket连接
function closeWebSocket() {
websocket.close();
}
//发送消息
function send() {
var message = document.getElementById('text').value;
websocket.send(message);
}
// stun和turn服务器
var iceServer = {
"iceServers" : [ {
"url" : "stun:stun.l.google.com:19302"
}, {
"url" : "turn:numb.viagenie.ca",
"username" : "webrtc@live.com",
"credential" : "muazkh"
} ]
};
// 如果检测到媒体流连接到本地,将其绑定到一个video标签上输出
pc.onaddstream = function(event) {
document.getElementById('vid2').src = URL
.createObjectURL(event.stream);
};
// 发送offer和answer的函数,发送本地session描述
var sendOfferFn = function(desc) {
pc.setLocalDescription(desc);
websocket.send(JSON.stringify({
"event" : "_offer",
"data" : {
"sdp" : desc
}
}));
}, sendAnswerFn = function(desc) {
pc.setLocalDescription(desc);
websocket.send(JSON.stringify({
"event" : "_answer",
"data" : {
"sdp" : desc
}
}));
};
// 获取本地音频和视频流
navigator.webkitGetUserMedia({
"audio" : true,
"video" : true
},
function(stream) {
//绑定本地媒体流到video标签用于输出
document.getElementById('vid1').src = URL
.createObjectURL(stream);
//向PeerConnection中加入需要发送的流
pc.addStream(stream);
//如果是发起方则发送一个offer信令
if (isCaller) {
pc.createOffer(sendOfferFn, function(error) {
console.log('Failure callback: ' + error);
});
}
}, function(error) {
//处理媒体流创建失败错误
console.log('getUserMedia error: ' + error);
});
window.οnlοad=function(){
if(isCaller==null||isCaller==undefined) {
var tips = document.getElementById("tips");
tips.remove();
} else {
var create = document.getElementById("create");
create.remove();
}
};
function init() {
location.reload();
}
</script>
</html>
//设置分辨率
var video_constraints = {
mandatory: {
maxHeight: 480,
maxWidth: 640
},
optional: []
};
由于SipMl5是基于WebRTC与WebSocket,所以浏览器要支持WebRTC与WebSocket,SipMl5部署在Tomcat上用浏览器访问时,由于在进行呼叫业务需要借助WebRTC进行本地摄像头的访问,所以会涉及到安全机制,一般需要进行https部署访问。如果没有部署成https,则只能用localhost进行呼叫业务,或者用Firefox浏览器进行呼叫业务(Firefox上解除了https的安全机制),但是Firefox不同版本对于SipMl5的支持可能会存在一些问题,这个一定是要注意的。
分辨率最高的是WVGA:800×480
VGA为标准分辨率:640×480
WVGA 分辨率:800×480
WQVGA 分辨率:480×320
QVGA分辨率:320×240
D1 分辨率:720×576
CIF 分辨率: 352×288
QCIF 分辨率:176×144
PS:后续会介绍WebRTC的环境配置和注意事项、SIPML5的设置、联调、SIP网关的兼容