文章转载只能用于非商业性质,且不能带有虚拟货币、积分、注册等附加条件。转载须注明出处http://blog.csdn.net/flowingflying以及作者@恺风Wei。

Android有android.net.sip和android.net.rtp包。而在sdk\samples\android-19(API版本)\legacy\SipDemo提供一个SIP phone的例子。此外,Android的MediaPlayer在某程度上支持RTSP。

对SIPDemo的详细解释,可以阅读:

  • http://www.shuyangyang.com.cn/jishuliangongfang/qitajishu/2013-08-14/106.html
  • http://gushedaoren.blog.163.com/blog/static/17366340520136282316590/

虽然android.net.sip是在Android2.3就提供,android.net.rtp是在Android3.0提供,但就android.net.sip而言,属于高度封装的SIP呼叫(不是协议级别),在使用过程中可塑性很小,能否和SIP server兼容看运气了,除非SIP Server针对Android SIP来调测过。

权限和功能需求

<uses-permission android:name="android.permission.USE_SIP" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

<uses-feature android:name="android.hardware.sip.voip" android:required="true"/>

SIP初始化

//初始化包括两个步骤:1、获得sipManager的对象;2、通过SipProfile来配置我们的sipPhone
public SipManager sipManager = null;
public SipProfile mySipProfile = null;

private void initMySipPhone(String username, String domain, String password){
// 【1】获得SipManger的对象。需要注意,如果在模拟器,无法获得对象,得到的是null,因此必须在实体机中测试。但是不是所有的实体机都一定能获得有效的SipManager对象,例如在N909上,返回null。
if(sipManager == null){
sipManager = SipManager.newInstance(this);
}
if(sipManager == null){
Toast.makeText(this, "设备不支持SIP!", Toast.LENGTH_LONG).show();
return;
}

/*【2】进行SIP配置,配置了SIP的帐号、sip server的domain和账号密码。我们注意到:
(1)不能设置STUN,也就是Sip server需要支持SBC,才能解决NAT穿越问题,而手机在WiFi的情况下,都是NAT
(2)不支持本地SIP端口的设置,SIPManager将自动获取一个空闲端口作为SIP的端口号,这个问题在下面的REGISTER中将继续讨论 */

SipProfile.Builder builder = new SipProfile.Builder(username, domain);
builder.setPassword(password);

//builder.setPort(5060); //这是指SIP Server
mySipProfile = builder.build();
debug("mySipProfile = " + mySipProfile.toString());
}

开启

如果要收听来电,可通过PendingIntent在来电是触发一个intent,通过这个intent可以打开某个activity,也可以触发广播接收器。下面是开启的例子:

Intent i = new Intent();
i.setAction("cn.wei.flowingflying.mysipphone.INCOMING_CALL");
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, Intent.FILL_IN_DATA);
sipManager.open(mySipProfile, pi, null); //第三个参数为监听器,可以在REGISTER时在设定

如果我们不需要来电接听,可以简单如下:

sipManager.open(mySipProfile);

在实体机测试中,Nubia Z5S(百度云ROM)无法正确开启,报NullProinterExpection的异常,而在华为P6(Andriod 4.4.2)中则正常。我们不清楚OEM对android.net.sip的支持情况,不是每款手机都会支持,因此其使用受到制约,如果要实现SIP phone,采用其他方式恐怕更为合适。

REGISTER

sip客户端需要向SIP server报告其地址,以便SIP Server寻址。SIP Server有很多名称,早前叫软交换,后来叫IMS,其实没什么太大的区别。有自动和手动两种注册方式,我们先看看手动方式。

public void register(){
//人工注册的方式。设置的Expired时间为300秒。人工注册是只人工触发一个REGISTER的sesssion的执行,不会自动进行定期REGISTER,需要人工处理。第三个参数是register状态的回调函数对象,本例采用内部类的方式。SIP消息的发送(包括重复)和接收是在后台线程中进行的,经检查为Binder_1,所以我们有理由怀疑是使用了JNI。例子中,希望在UI中显示状态,但是后台线程是不能直接操纵UI的,因此debug中需要特别处理。
sipManager.register(mySipProfile, 300, new SipRegistrationListener() {
@Override
public void onRegistrationFailed(String localProfileUri, int errorCode,
String errorMessage) {
debug("Register Fail : code=" + errorCode + "," + errorMessage);
}

@Override
public void onRegistrationDone(String localProfileUri, long expiryTime) {
long secs = expiryTime - System.currentTimeMillis();
debug("Register Done : uri=" + localProfileUri + ",expired-time=" + secs);
try{ //人工注册过程请特别注意,即便是收到了REGISTER的200OK的恢复,即注册成功,但无法检查注册状态isRegistered(),因为不属于自动处理,相关的状态没有记录。由于相关的状态没有记录,导致不能定期自动注册,需要根据注册到期时间expiryTime,在此之前再次人工进行注册。
debug("check register : " + sipManager.isRegistered(mySipProfile.getUriString()));
}catch(Exception e){
}
}

@Override
public void onRegistering(String localProfileUri) {
Log.i("WEI",Thread.currentThread().getName());
debug("" + localProfileUri + " is registering");
}
});
}

private void debug(final String info){
this.runOnUiThread(new Runnable() { //可以参阅Android学习笔记(三一):线程:Message和Runnable中的例子5
@Override
public void run() {
String str = getCurTimeStr() + info;
Log.i("SIP",str);
tv.setText(str + "\n" + tv.getText());
}
});
}

在程序结束之前,我们需要进行close,否则在程序再次开启open(),会报告createSipSession()的错误。我们另外需要进行的向SIP server进行unregister。有些SIP server不支持其他位置进行unregister,由于每次SipManager分配的本地SIP协议端口不同,如果不能在退出之前及时注销,那下次开启将不能进行unregister以及register,会回复403 Forbidden的错误。上面的代码,我们的注册有效时间设置为5分钟,但是SIP的缺省值是3600(1小时)。即便是5分钟,在调测程序时会让人抓狂的。程序退出,通常在onDestroy()中进行,我们加入下面的代码:

if(sipManager == null)
return;
try{
if(mySipProfile != null){
//在人工注册,是无法获知是否注册成功,直接执行unregister即可。如果Sip Server支持异地unregister,可以在开启时,先unregister,然后register,但是合理的,我们仍应进行unregister。
sipManager.unregister(mySipProfile, null);
Thread.sleep(2000); //unregister的完成需要时间,而且是在线程进行的,主线程会马上执行下面一句,即sipManager.close(),未完成unregister动作,就直接关闭掉sipManager,或者App就已经destory了,这里延迟2秒,运行允许线程完成unregister的处理
sipManager.close(mySipProfile.getUriString());

}
}catch(Exception e){
Log.i("SIP","Close sipManager error:" + e.toString());
e.printStackTrace();
}

让我们看看有关的交互信息,在sip server的Log如下:

----------------注册-----------------
MAIN> 14-09-11 15:36:45.293 [NET]: Received Message from 192.168.1.11:56794 len(431)
MAIN> REGISTER sip:192.168.1.10 SIP/2.0
MAIN> Call-ID: 17778e2a6a8e670a0ec067e21deed4e8@192.168.1.11
MAIN> CSeq: 6960 REGISTER
MAIN> From: <sip:78000001@192.168.1.10>;tag=1882649854
MAIN> To: <sip:78000001@192.168.1.10>
MAIN> Via: SIP/2.0/UDP 192.168.1.11:56794;branch=z9hG4bKfbb820f0c65898834736fc1198523599363735;rport
MAIN> Max-Forwards: 70
MAIN> User-Agent: SIPAUA/0.1.001
MAIN> Contact: <sip:78000001@192.168.1.11:56794;transport=udp>
MAIN> Expires: 3600
MAIN> Content-Length: 0
MAIN>
MAIN>

MAIN> 14-09-11 15:36:45.304 [NET]: Send Packet to 192.168.1.11:56794
MAIN> SIP/2.0 401 Unauthorized
MAIN> Via: SIP/2.0/UDP 192.168.1.11:56794;branch=z9hG4bKfbb820f0c65898834736fc1198523599363735;rport
MAIN> From: <sip:78000001@192.168.1.10>;tag=1882649854
MAIN> To: <sip:78000001@192.168.1.10>;tag=908211099445681538
MAIN> Call-ID: 17778e2a6a8e670a0ec067e21deed4e8@192.168.1.11
MAIN> Cseq: 6960 REGISTER
MAIN> WWW-Authenticate: Digest realm="0.0.0.0",nonce="c756e447c91410421005304ed82e4646e",opaque="",stale=FALSE,algorithm=MD5
MAIN> Content-Length: 0
MAIN>
MAIN>
MAIN> 14-09-11 15:36:45.502 [NET]: Received Message from 192.168.1.11:56794 len(633)
MAIN> REGISTER sip:192.168.1.10:5060 SIP/2.0
MAIN> Call-ID: 17778e2a6a8e670a0ec067e21deed4e8@192.168.1.11
MAIN> CSeq: 6961 REGISTER
MAIN> From: <sip:78000001@192.168.1.10>;tag=1882649854
MAIN> To: <sip:78000001@192.168.1.10>
MAIN> Via: SIP/2.0/UDP 192.168.1.11:56794;branch=z9hG4bK5fc05d8ea72d360c6c15b86db1969a38363735;rport
MAIN> Max-Forwards: 70
MAIN> User-Agent: SIPAUA/0.1.001
MAIN> Contact: <sip:78000001@192.168.1.11:56794;transport=udp>
MAIN> Expires: 3600
MAIN> Authorization: Digest username="78000001",realm="0.0.0.0",nonce="c756e447c91410421005304ed82e4646e",uri="sip:192.168.1.10:5060",response="ce2a66e08ca377b0ebbc5783f1cd9515",algorithm=MD5,opaque=""
MAIN> Content-Length: 0
MAIN>
MAIN>

MAIN> 14-09-11 15:36:45.509 [NET]: Send Packet to 192.168.1.11:56794
MAIN> SIP/2.0 200 OK
MAIN> Via: SIP/2.0/UDP 192.168.1.11:56794;branch=z9hG4bK5fc05d8ea72d360c6c15b86db1969a38363735;rport
MAIN> From: <sip:78000001@192.168.1.10>;tag=1882649854
MAIN> To: <sip:78000001@192.168.1.10>;tag=908211099445681538
MAIN> Contact: <sip:78000001@192.168.1.11:56794>;expires=3600
MAIN> Call-ID: 17778e2a6a8e670a0ec067e21deed4e8@192.168.1.11
MAIN> Cseq: 6961 REGISTER
MAIN> Content-Length: 0
MAIN>
MAIN>


----------------注销-----------------
MAIN> 14-09-11 15:36:46.650 [NET]: Received Message from 192.168.1.11:56794 len(380)
MAIN> REGISTER sip:192.168.1.10 SIP/2.0
MAIN> Call-ID: cf604ddb1a827b0be379bf9e29c50688@192.168.1.11
MAIN> CSeq: 456 REGISTER
MAIN> From: <sip:78000001@192.168.1.10>;tag=947417185
MAIN> To: <sip:78000001@192.168.1.10>
MAIN> Via: SIP/2.0/UDP 192.168.1.11:56794;branch=z9hG4bK2910eed7fc8666adf4d77f5eed54e6ef363735;rport
MAIN> Max-Forwards: 70
MAIN> User-Agent: SIPAUA/0.1.001
MAIN> Contact: *
MAIN> Expires: 0
MAIN> Content-Length: 0
MAIN>
MAIN>

MAIN> 14-09-11 15:36:46.662 [NET]: Send Packet to 192.168.1.11:56794
MAIN> SIP/2.0 401 Unauthorized
MAIN> Via: SIP/2.0/UDP 192.168.1.11:56794;branch=z9hG4bK2910eed7fc8666adf4d77f5eed54e6ef363735;rport
MAIN> From: <sip:78000001@192.168.1.10>;tag=947417185
MAIN> To: <sip:78000001@192.168.1.10>;tag=444461521727471416
MAIN> Call-ID: cf604ddb1a827b0be379bf9e29c50688@192.168.1.11
MAIN> Cseq: 456 REGISTER
MAIN> WWW-Authenticate: Digest realm="0.0.0.0",nonce="91a98e159914104210066590de27c726a",opaque="",stale=FALSE,algorithm=MD5
MAIN> Content-Length: 0
MAIN>
MAIN>
MAIN> 14-09-11 15:36:46.806 [NET]: Received Message from 192.168.1.11:56794 len(582)
MAIN> REGISTER sip:192.168.1.10:5060 SIP/2.0
MAIN> Call-ID: cf604ddb1a827b0be379bf9e29c50688@192.168.1.11
MAIN> CSeq: 457 REGISTER
MAIN> From: <sip:78000001@192.168.1.10>;tag=947417185
MAIN> To: <sip:78000001@192.168.1.10>
MAIN> Via: SIP/2.0/UDP 192.168.1.11:56794;branch=z9hG4bK3dfab8e5a8763ac0efccd9b912ac89cc363735;rport
MAIN> Max-Forwards: 70
MAIN> User-Agent: SIPAUA/0.1.001
MAIN> Contact: *
MAIN> Expires: 0
MAIN> Authorization: Digest username="78000001",realm="0.0.0.0",nonce="91a98e159914104210066590de27c726a",uri="sip:192.168.1.10:5060",response="8b4b33d3bdccd20d6ffca2dcbe4b9d79",algorithm=MD5,opaque=""
MAIN> Content-Length: 0
MAIN>
MAIN>
MAIN> 14-09-11 15:36:46.813 [NET]: Send Packet to 192.168.1.11:56794
MAIN> SIP/2.0 200 OK
MAIN> Via: SIP/2.0/UDP 192.168.1.11:56794;branch=z9hG4bK3dfab8e5a8763ac0efccd9b912ac89cc363735;rport
MAIN> From: <sip:78000001@192.168.1.10>;tag=947417185
MAIN> To: <sip:78000001@192.168.1.10>;tag=444461521727471416
MAIN> Call-ID: cf604ddb1a827b0be379bf9e29c50688@192.168.1.11
MAIN> Cseq: 457 REGISTER
MAIN> Content-Length: 0
MAIN>
MAIN>

自动注册的代码如下:

sipManager.setRegistrationListener(mySipProfile.getUriString(), new SipRegistrationListener() {
public void onRegistering(String localProfileUri) {
debug("Registering with SIP Server...");
}
public void onRegistrationDone(String localProfileUri, long expiryTime) {
debug("Ready...");
}
public void onRegistrationFailed(String localProfileUri, int errorCode,
String errorMessage) {
debug("Registration failed. Please check settings. code=" + errorCode + ":" + errorMessage);
}
});

自动注册中,采用缺省的注册有效时间3600秒。在一开始会发送UNREGISTER消息,注销后,发送REGISTER消息。如果采用无监听在sipManager.close()的时候,没发现进行unregister。在注册成功后,会发送OPTIONS消息来检测sip server是否有效存在。如果OPTIONS消息没有得到回应,则会重新出发register流程。

如果我们采用了sipManager.open(mySipProfile);来开启,会报告注册失败,code=-10;no data connection,没有发送register消息,很是奇怪,但这是高度封装的包,很多内部处理我们并不清楚。

相关链接:我的Android开发相关文章

更多相关文章

  1. Android四大组件:BroadcastReceiver史上最全面解析
  2. android:处理ListView的条目长按事件
  3. Android(安卓)SurfaceFlinger服务启动过程源码分析1
  4. Android(安卓)Binder机制(2) ContextManager注册过程分析
  5. Android(安卓)应用程序发布流程---碗豆荚发布流程
  6. Android广播接收者使用总结
  7. [置顶] Android(安卓)app内存管理的16点建议
  8. Android(安卓)开发中遇到的 Exception & ANR
  9. Android注册界面设计

随机推荐

  1. c++类型转换
  2. .net是什么语言 视频
  3. 按位取反运算符的使用
  4. c++基础知识
  5. .net core和.net区别
  6. stdio.h是什么头文件
  7. memcpy函数用法
  8. c语言位运算符
  9. char是什么数据类型
  10. win10离线安装net35的方法技巧