2013年9月7日星期六

[通讯聊天] [2010.10.15]视频通话很简单!有真相,不过没有抓图

之前有个坚定的果粉盆友说,android没有像face time 那样的视频聊天功能,所以鄙视之。我很奇怪,face time到底是 SIP还是 H.323,抑或私有协议的视频聊天我不管,难道android上就没有基于SIP的视频通话了吗?
于是Google之,很快找到了答案,并且测试通过8 \% x+ s! c; i4 N' ]2 k2 H
什么是SIP,大家就不用管了,反正是可以支持视频通话的协议,就算google没有在android中提供内置的SIP支持,我们一样可以用第三方的解决方案。
! H1 H0 m- W% \( z0 b; P1 j; @
目前,android上的SIP客户端比较流程的是SIP Droid与 IMS Droid,我测试之后,发现 SIP Droid 的注册不是很好用,建议使用 IMS Droid
它是个开源工程,在http://imsdroid.googlecode.com/files/imsdroid-1.0.302.apk 可以下载APK,安装即可
/ A- `0 |& E) W! L2 k0 h& `
安装时候,我们已经有了一个SIP客户端,不过要与另外一个SIP终端通信,测需要对方的SIP地址,这就需要我们注册一个互联网上的SIP帐号并告知给我们的联系人,其作用就像是MSN的帐号一样。
5 e* f) J5 l; Y& Z% _
目前,免费的SIP注册服务很多,我用过的,国内访问比较快的有 sip2sip.info 与 iptel.org,同鞋们可以到这两个网站免费申请SIP帐号,然后在 IMS Droid 中设置好,登录上去。然后两个用 IMS Droid 的人呼叫拨打对方的SIP帐号,即可视频通话。) p  ]: q/ @! u! r4 U
6 b( t# t5 o, C" n, J/ j" Z# B
我身边没有其它用 android 的人,就自己用手机拨打电脑(windows XP)上的SIP终端(我用xlite),可以正常视频通话

imsDroid 研究

1)架构:
基于doubango(Doubango 是一个基于3GPP IMS/RCS 并能用于嵌入式和桌面系统的开源框架。该框架使用ANSCI-C编写,具有很好的可移植性。并且已经被设计成非常轻便且能有效的工作在低内存和低处理能力的嵌入式系统上。苹果系统上的idoubs功能就是基于此框架编写) .音视频编码格式大部分都支持(H264(video),VP8(video),iLBC(audio),PCMA,PCMU,G722,G729)。NAT支持ICE(stun+turn)
2)效果实测
测试环境:公司局域网内两台机器互通,服务器走外网sip2sip
第一次测试:音频质量可以,但是AEC打开了还是有点回音(应该可以修复)。视频马赛克比较严重,延迟1秒左右。

第二次测试:音频质量可以,基本无回音,视频无马赛克,基本无延迟(低于1秒)。
3)优缺点
imsdroid目前来说还是算比较全面的,包括音视频编解码,传输(RTSP,ICE),音频处理技术等都有涉猎。doubango使用了webrtc的AEC技术,但是其调用webrtc部分没有开源,是用的编译出来的webrtc的库。如果要改善音频的话不太方便,Demo的音视频效果还可以



五) webrtc

imsdroid,csipsimple,linphone都想法设法调用webrtc的音频技术,本人也测试过Android端的webrtc内网视频通话,效果比较满意。但是要把webrtc做成一个移动端的IM软件的话还有一些路要走,不过webrtc基本技术都已经有了,包括p2p传输,音视频codec,音频处理技术。不过其因为目前仅支持VP8的视频编码格式(QQ也是)想做高清视频通话的要注意了。VP8在移动端的硬件编解码支持的平台没几个(RK可以支持VP8硬件编解码)。不过webrtc代码里看到可以使用外部codec,这个还是有希望调到H264的。


总结:sipdroid比较轻量级,着重基于java开发(音频codec除外),由于其音视频编码以及P2P传输这一块略显不足,不太好做定制化开发和优化。imsdroid,遗憾就是直接调用webrtc的库,而最近webrtc更新的比较频繁,开发比较活跃。如果要自己在imsdroid上更新webrtc担心兼容性问题,希望imsdroid可以直接把需要的webrtc相关源码包进去。csipsimple的话,都是围绕pjsip的,webrtc等都是以pjsip插件形式扩充的,类似gstreamer. webrtc如果有技术实力的开发公司个人还是觉得可以选择这个来做,一个是google的原因,一个是其视频通话相关关键技术都比较成熟的原因。个人觉得如果能做出来,效果会不错的。

开源sip项目doubango ,android平台编译——imsdroid



imsdroid 装载Android模拟器上,一下对整个安装过程以及遇到的问题进行总结:

1  利用svn下载整个imsdroid项目相关文件夹,地址:

http://imsdroid.googlecode.com/svn/branches/2.0

2  打开eclipse,File->Import->General ->Exsiting Project to Workspace->Select project path选择刚下载的文件中的android-ngn-stack工程。

3  android-ngn-stack点击鼠标右键properity->liberary->Add JARs->android-ngn-stack->lib->simole-xml-2.3.4.jar.

4 设定工程android版本为Android 2.1(minSDK-7),这个必须与project.properties中“target=android-7”p匹配,不匹配会出问题。

5 设定 Jave Compiler 为1.6

6  效仿2中方法,导入工程 IMSDroid,并进行3~5中的操作。

7更改项目IMSDroid->Resource->Linked Resources,将下面路径设定为..android-ngn-stack\src的位置。这一步很关键,guidence里面没有给出,但是如果不设定,就会出错。

在实际操作中还遇到多出问题
1 run->Android Project,报错"Conversion to dalvik format failed with error 1",这个错误网上结识很多,原因是因为java使用proguard(混编)技术,dalvik是一种虚拟机,把java文件转化成dalvik format,如果不成功就报错。按照网上的方法,都没有解决这个问题,最后发现是工程的java build path引用的多余的path导致冲突,删除不用的path问题就解决了!

++++++++++++++++++++++++
  IMSDroid介绍
分类: 开源项目2012-02-15 23:39 648人阅读 评论(1) 收藏 举报
支持视频的开源SIP客户端不多,IMSDroid看起来不错,准备研究下。(另外还有SIPDroid和linphone了,SIPDroid好像不支持视频,linphone怎么样? )。
有几个问题考虑:
1) IMSDroid的架构,哪些部分使用C/C++实现,及数据流?
2) 编解码是否可以使用硬件编码器?
3) 回声抑制问题?
4) 网络传输NAT问题?

下载源码,源码目录下有个android-ngn-stack.pdf文档介绍了软件的基本组成,一共有三层:
1) IMSDroid: 客户端界面
2) android-ngn-stack: 对doubangovoip框架的一个java层封装 。android-ngn-stack主要是为开发者在Android平台上开发VoIP应用提供软件栈(stack), 它为上层应用提供三种级别的接口:Low, Medium和High。Low最灵活,但是调用最复杂。android-ngn-stack包含一个org.doubango.tinyWRAP包,提供对底层doubangovoip的JNI调用。tinyWRAP.so是对底层doubango的封装。
3) doubangovoip:C/C++(?)实现的VoIP功能的库,包括以下库:
[html] view plaincopy
tinySAK (Swiss Army Knife): Utilities functions (SHA-1, MD5, HMAC, String, List, Timers, Thread, Mutex, Semaphore, ...)
tinyNET: Networking (DNS, DHCPv4/v6, STUN, TURN, ICE, ENUM, Sockets, ...)
tinyHTTP: HTTP stack (CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE, ...)
tinyXCAP: XCAP stack (AUID manager, URL generator) without XML parser (See Java code for parsers)
tinyIPSec: IPSec SA manager. Useless for Android but you MUST have it
tinySMS: SMS over IP (SM-TL, SM-RL) for IMS/LTE networks
tinySIGCOMP: Signaling Compression
tinySDP: SDP protocol
tinyRTP: RTP/RTCP protocols
tinyMSRP: MSRP protocol (Chat and File Transfer)
tinyMEDIA: Media plugins manager (Audio, video, Codecs, sessions, MSRP, QoS, ...)
tinyDAV(Doubango Audio Video): Media plugins implementation
tinySIP: SIP/IMS stack

下面是IMSDroid最新版本的特征:
IMSDroid v2.x preview is now available for developers

The source code is under branches/2.0 and depends and doubango v2.x



New features:

- The SIP/IMS Stack is 7 times faster

- NGN (Next Generation Network) stack for developers (android-ngn-stack)

- Better audio quality (Adaptive jitter buffer, noise suppression, automatic resampling, gain control, ...)

- Better video quality (low latency, low cpu usage, ...)

- VP8 video codec

- Multi-line

- MSRP chat

imsdroid 学习(初认识)


  由于工作的需要,麦洛最近都在研究语音通话的技术。工作中,一个同事无意发现了开源项目idoubs。idoubs是imsdroid的IOS版本。
  从google以及baidu来看,除了官网http://code.google.com/p/imsdroid/ 介绍外,有关imsdroid或idoubs貌似的帖子并不是很多。但从官网的介绍可以知道这个项目确实非常强大。于是赶紧check下来学习。注意check out时要使用http://imsdroid.googlecode.com/svn/这个地址,不要使用trunk目录,作者已经将项目放在了svn这个目录下了。
  check下来的源码结构:
  
  imsdroid或idoubs项目是基于doubango,关于这个项目可以到它的官网http://doubango.org/ 上看看介绍。
  麦洛与iphone的同事试用了一下这个安装包,看一下通话效果。不知道是不是网络原因还是服务器原因,语音通话是可以的,很清晰,有一点回音,但视频却是很难连接成功,或者传播的图像只有一台手机看得到,而另一台则看不到。从源码上看,它还支持高清(1080p),以及多种编码格式。而麦洛感兴趣的正是它丰富的编码格式以及网络传输的实现技术。
  由于介绍imsdroid使用的资料实在是少之又少。学习它的原理,只能从看代码开始。
  从编译项目开始。使用2.0目录下的imsdroid,在eclipse中新建一个android项目,可以命名为imsdroid2.0,将branches下的imsdroid下的res,src下的(不包含src)目录以及manifest文件都copy到新建的imsdroid2.0项目中来。这时候编译imsdroid2.0是会出错的。因为有很多类找不到。原因是由于imsdroid项目基于android-ngn-stack这个库的,作者将其放在了branches下的android-ngn-stack,这个主要是封装了底层的实现,使用jni来调用c库。
  所以使用同样的方法,在eclipse中使用将android-ngn-stack这个库新建为android项目,并把它作为一个库。
  方法是:右击android-ngn-stack 选择 properties 在弹出的窗口中把 is library 勾选上,如:
  
  然后就在imsdroid2.0中引用这个库就行了,引用的方法也是一样的,只是在打开的properties窗口中点击add...按钮,将android-ngn-stack添加进来就行了。这时候,imsdroid2.0就可以编译成功了。
  编译成功后就可以在手机安装试用了么?no!
  这里还有一个问题,就是android-ngn-stack是封装了jni,但真正的实现是c库,所以必须有一个libxxx.so文件才行。那这个文件在哪里呢?答案看下图:
  
  将native-debug/libs目录copy到我们的imsdroid2.0项目中来,就可以了。
  最后,对于doubango这个开源项目http://code.google.com/p/doubango/  同样的,要check out 这个项目, 只要http://doubango.googlecode.com/svn/  就行了,trunk目录下是空的。
  麦洛也是刚开始学习这个开源项目,如果出现问题也是很正常的。可以给麦洛留言,大家一起学习。

  本文是原创博文,如果转载请注明来源,谢谢!

SIP软电话开发基本条件和要点

    SIP软电话开发的基本条件和要点
    在VoIP中,我们知道SIP协议有很大的用处。这里,我们就针对SIP协议,来看看SIP软电话开发环境的建立都需要知道哪方面的知识。那么首先我们来看看这个开发平台的条件。要在windows或者linux平台下开发基于SIP软电话,需要以下软件。
    服务器端软件: 注册多个客户端到服务器上,可以进行通话测试
    SIP客户端软件:主要用于测试,可以对别人已经完成的客户端进行抓包,以比对自己程序的发包数据
    SIP协议栈:基于某个现成的SIP协议栈来开发会加快开发进度
    RTP栈:传输语言或者视频数据的协议栈
    抓包测试工具:调试网络程序最有效的办法
    以下介绍这些软件主要以开源软件为主
    一 服务器端软件
    1. Asterisk:Linux系统下开源的IPPBX,功能强大稳定,主要用c语言开发。配置稍麻烦。
    2. Vocal:Linux系统下开源的SIP服务器端。可以作为IPPBX也可以作为运营系统。很多voip虚拟运营商都用这个作为自己的运营系统。提供BS结构的管理界面。
    3. YATE:跨平台(Linux,Windows)的开源SIP服务器端。在windows下安装非常简单。其他方面没有进行过测试。
    4. SER:Linux平台下重量级的SIP服务器断。功能比较丰富,也是很多voip虚拟运营商的系统选择。不过据说配置比较麻烦,具体没有试过。
    5. sipX:Linux平台下的SIP服务器。这个好像不能作为客户端再次注册到其他SIP服务器上。提供BS结构的管理界面。
    二 SIP客户端软件
    1. Windows Messenger 5.1:微软出的SIP客户端,操作方便。
    2. YATE Client:跨平台(Linux,Windows)的开源SIP客户端软件。安装方便,功能简单。
    3. xten:windows平台下的SIP软电话。功能齐全,使用方便。
    三 SIP协议栈
    1. osip:跨平台的开源SIP协议栈。用c语言实现,体积小。
    2. exosip:对osip进行封装,使其方便SIP客户端软件开发。同样开源跨平台。
    3. sipXtackLib:由SIPfoundry提供的开源跨平台的SIP协议栈,功能齐全。C++开发,已经被用于开发数个商业SIP终端。
    四 RTP栈
    1. JRTPLIB: 开源的跨平台rtp栈,用C++语言开发,使用方便。
    五 抓包测试工具
    1. Wireshark:非常有名的开源跨平台网络抓包工具,以前叫做Ethereal。
    上述就是我们在SIP软电话开发中,需要了解的一些内容了,希望对大家能够有所帮助。

用yate建立VoIP服务器 实现免费语音通话(SIP协议)


介绍如何用yate2软件搭建VoIP服务器,并用SIP协议完成语音通话。在我的实例中,使用了局域网内的3台PC,Windows操作系统,接在同一个集线器上,并不是广域网或3G接入。
       网络电话是下一代网络(NGN)的重要应用之一。“下一代网络”是指10年以后的网络,全部基于软交换(Softswitch)技术。但是,拨打VoIP免费网络电话,并不需要在等待10年。基于现有的TCP/IP网络,我们已经可以使用开源的VoIP软件,实现PC-to-PC的免费语音通话。(注:这里的“免费”,是指不需要支付传统语音通话费用,ADSL/FTTB/3G等上网接入仍然是需要费用的)
下面,我将介绍如何用yate软件搭建VoIP服务器,并用SIP协议完成语音通话。在我的实例中,使用了局域网内的3台PC,Windows操作系统,接在同一个集线器上,并不是广域网或3G接入。
yate2建立VoIP服务器
Yate = Yet Another Telephony Engine,是一款开源的VoIP网络电话软件。它可以作为服务器、也可以作为客户端使用。yate2可以在Linux下运行、也可以在Windows下运行。yate2下载地址: www.lxvoip.net/softphone/yate.html 。
VoIP服务器可以干什么?
* 客户端使用各自的用户名和密码登录到服务器上
* 用户通过“电话号码”拨叫另一用户
* 服务器负责转发拨叫请求及此后的其他控制信令
* 服务器可以转发话音数据包
建立服务器这一步不是必须的。yate2支持两个客户端通过IP地址直接连接,而不需要建立服务器。如果你需要支持较多的用户能够互相通话,通常要建立服务器;如果只有2个用户,则可以采用直连方式。
建立VoIP服务器的前提条件
* 一台计算机,作为服务器
* 服务器通常需要拥有固定的公网IP地址
* 服务器上不能再运行客户端(也就是说,客户端、服务器不能同时启动,否则会冲突)
用yate2搭建VoIP服务器的步骤
1. 下载并安装yate2(最好完全安装所有组件,仅30MB)
2. 打开yate2安装目录的conf.d子目录
3. 将regfile.conf.sample复制一份,改名为regfile.conf,打开作下列修改:
* 找到;auth=100、;register=100、;route=100三行,分别去掉前面的分号
* 对需要建立的每一个用户,在文件末尾增加两行:
[用户名]             password=密码
例如建立用户sunny,密码为870212,则写成:             [sunny]            password=870212
这样,yate2服务器就有了身份认证功能
4. 将regexroute.conf.sample复制一份,改名为regexroute.conf,打开作下列修改:
* 找到[default],在后面增加一行             ${username}^$=-;error=noauth
这样未登录的用户就不能拨打电话
* 对需要建立的每一个电话号码,在刚才插入处之后增加一行
^电话号码$=return;called=用户名
例如当有人拨打号码15900941215,就呼叫用户sunny,则写成:             ^15900941215$=return;called=sunny
这样,yate2服务器就有了电话路由功能
5. 开始-管理工具-服务,重新启动Yet Another Telephony Engine服务
如果服务成功启动、没有错误提示,你已经正确建立了最简单的yate2网络电话服务器。
 通过yate2服务器打电话
再次提醒,不能在运行yate2服务器的计算机上打开yate2客户端软件,否则是无法正常运行的。
1. 开始-Yate-Yate Client,启动yate2客户端
2. Accounts页-New,打开新建帐户对话框,填写各项目:
* Protocol=sip           * Use provider,不要选择
* Account,可以随意输入
* Username=用户名,@后面留空
* Password=密码
* Server=服务器的IP地址
点击OK后,Status应该会显示“Registered”
3. Calls页,Account=前面填写的account名称,然后输入另一个用户的电话号码,点击Call就可以打电话了
4. 如果有电话打进来,选中它并点击Take the call就可以接听
5. 点击Hangup挂断
yate2直连打电话
如果只有2个用户需要相互通话,就不必劳神建立VoIP服务器了(何况服务器还要占据一台计算机)。yate2支持直连通话,配置方法如下:
1. 被叫用户只需开启Yate Client(当然要在防火墙中允许它),不需要配置
2. 主叫用户,Accounts页-New,打开新建帐户对话框,填写各项目:           * Protocol=sip           * Use provider,不要选择           * Account,可以随意输入           * Username,留空           * Password,留空           * Server=被叫用户的IP地址       点击OK后,Status并不会显示“Registered”,但是这没有关系
3. 主叫用户,Calls页,Account=前面填写的account名称,然后随便输入一个号码,点击Call就可以打电话了
4. 被叫用户,选中打进来的电话并点击Take the call就可以接听
5. 点击Hangup挂断
SIP协议
yate2支持SIP、H.323、jabber、iax等多种VoIP协议,而先前我选择的是SIP协议。SIP协议定义了一组VoIP网络电话信令,传输层基于UDP协议、端口号为5060;SIP只提供控制信令,并不负责语音数据的编码和传输。 通过yate2服务器通话,SIP协议分析
我用Wireshark抓包分析了一次通话过程。这次通话的情况如下:(省略)
上面的过程,建立了主叫方-服务器、服务器-被叫方的两个VoIP电话连接;主叫方不知道被叫方的用户名和IP地址,被叫方知道主叫方的用户名、但不知道IP地址 现在出现大量的双向RTP数据包,封装了语音数据(甚至可以解码并窃听语音内容);使用随机高端口(在SIP协议的INVITE、200两种报文中,用SDP协议声明了RTP使用的UDP端口、语音编码方式等),经过服务器转发 登录(Register)与退出(Unregister) 假设有人拨打电话号码1。根据regexroute.conf,服务器知道号码1对应于用户lxvoip1。那么,服务器怎么知道u1的IP地址呢?这就需要通过接收登录与退出消息来记录用户状态。
* 登录:       REGISTER sip:192.168.1.50       Contact:<sip:lxvoip1@192.168.1.183:5060>       Expires:600       To:<sip:lxvoip1@192.168.1.50>     * 退出:       REGISTER sip:192.168.1.50       Contact:<sip:lxvoip1@192.168.1.183:5060>       Expires:0       To:<sip:lxvoip1@192.168.1.50>     * 两者的区别就是Expires,0表示退出,非0表示登录     * 如果缺少身份认证,服务器会返回401;通过身份认证后,服务器返回200 yate2直连通话,SIP协议分析
直接贴出Wireshark的自动分析结果吧~Statistics-VoIP Calls-Graph就可以看到
|Time     | 192.168.1.101     | 192.168.1.183     |
|3.985    |         INVITE SDP                    |SIP From: sip:anonymous@192.168.1.183 To:sip:0@192.168.1.183
|         |(5060)   ------------------>  (5060)   |
|4.004    |         100 Trying|                   |SIP Status
|         |(5060)   <------------------  (5060)   |
|4.035    |         180 Ringing                   |SIP Status
|         |(5060)   <------------------  (5060)   |
|11.818   |         200 OK SDP                    |SIP Status
|         |(5060)   <------------------  (5060)   |
|11.826   |         ACK       |                   |SIP Request
|         |(5060)   ------------------>  (5060)   |
|11.857   |         RTP (g711U)                   |RTP Num packets:281  Duration:5.597s SSRC:0x1726C94E
|         |(27392)  <------------------  (27824)  |
|11.954   |         RTP (g711U)                   |RTP Num packets:280  Duration:5.550s SSRC:0x7805579C
|         |(27392)  ------------------>  (27824)  |
|17.495   |         BYE       |                   |SIP Request
|         |(5060)   <------------------  (5060)   |
|17.514   |         100 Trying|                   |SIP Status
|         |(5060)   ------------------>  (5060)   |
|17.526   |         200 OK    |                   |SIP Status
|         |(5060)   ------------------>  (5060)   |
与经过服务器的通话相比,直连通话就显得非常简单了:没有身份认证过程,不需要转发信令与RTP数据。

Yate 网络电话引擎 Linux下安装

Yate (Yet Another Telephony Engine)

主要支持功能:

VoIP 服务器
VoIP 客户端
VoIP to PSTN 网关
PC2Phone and Phone2PC 网关
H.323 网守
H.323 多端点服务器
H.323<->SIP 转换代理
SIP session border controller
SIP 路由
SIP 注册服务
Jingle 即时聊天
ISDN passive and active recorder
IAX2服务器客户端
电话服务器和客户端
呼叫中心服务器 (会议,队列)
IVR语音交互应答
预付费,后付费电话卡系统
兼容Asteirsk的zaptel中继卡测试环境准备:RedHat Linux AS 4 U4 DVD 镜像 或者Trixbox
安装包准备

cd /opt
wget http://downloads.sourceforge.net/openh323/pwlib-v1_10_3-src-tar.gz
wget http://downloads.sourceforge.net/openh323/openh323-v1_18_0-src-tar.gz
wget ftp://ftp.cn.postgresql.org/pub/postgresql//source/v8.2.5/postgresql-8.2.5.tar.bz2

下载yate源码,

wget http://voip.null.ro/tarballs/yate1/yate-1.3.0-1.tar.gz

安装h.323共享文件
tar zxvf pwlib-v1_10_3-src-tar.gz
cd pwlib_v1_10_3/
./configure
make opt
make install
export PWLIBDIR=`pwd`
cd ..

tar zxvf openh323-v1_18_0-src-tar.gz
./configure
make opt
make install
export OPENH323DIR=`pwd`
cd ..

#更新 /usr/local/lib共享库
echo “/usr/local/lib” >> /etc/ld.so.conf
ldconfig

#安装yate

tar zxvf yate-1.3.0-1.tar.gz

cd yate
./configure –with-pwlib=$PWLIBDIR –with-openh323=$OPENH323DIR
make
make install

#安装成功后。。启动yate测试
yate&
netstat -utnlp #执行结果会看到1720 4569 5060 端口被监听。说明启动成功
#这是yate只启动了h.323的EP,还没有运行GK,修改一下配置文件即可
cd /usr/local/etc/yate
vi h323chan.conf

[gk]
;server = false
;interface1=10.0.0.1
;port = 1719
;name = YateGatekeeper
;registeredonly=false

改为
[gk]
server = on
interface1=51.136.XX.XX ;你的网卡IP
port = 1719
name = YateGatekeeper

killall yate
yate&
netstat -utnlp #如果成功将会看到1719也被监听,这是yate就可以注册h323端点或网关
安装postgresql,
cd /opt
准备数据库
tar jxvf postgresql-8.2.5.tar.bz2
cd postgresql-8.2.5/
./configuer
make
make install
adduser postgres #添加用户
mkdir /usr/local/pgsql/data #创建目录
chown postgres /usr/local/pgsql/data   #添加权限
#操作数据库
su postgres //切换用户
/usr/local/pgsql/bin/initdb -D /usr/local/pgsql/data #初始化数据库
/usr/local/pgsql/bin/postmaster -D /usr/local/pgsql/data & #启动数据库
#添加yateadmin 数据库
bash-3.00$ /usr/local/pgsql/bin/createdb yateadmim
CREATE DATABASE
#数据库创建成功

安装yateadmin管理界面
cd /opt/yateadmin
./install
Installer for Yate Administrator v1
At the following prompts you can enter the word ‘no’ to disable defaults
Install Yate config file in: [/usr/local/etc/yate] 回车
Install Web pages in: [/var/www/html/yateadmin] 回车
Database host: [localhost] 回车
Database name: [yateadmin] 回车
Database user: [postgres] 回车
Database password: []      回车
PostgreSQL command: [] /usr/local/pgsql/bin/psql 输入
再次回车完成安装
进入http://your IP/yateadmin
帐号密码admin
点击Lines 添加
Operations with single line
Insert line [] 输入分机号码
Add 点击
Edit
Delete (use with care!)

按提示输入密码

现在可以用sip终端或者iaxlite测试了

sipdroid【源代码】

 
1.       sipdroid\src\org\zoolu 中是sip协议栈的实现
2.       sipdroid\src\org\sipdroid 中是软电话的实现
3.       sipdroid\src\com 中是stun相关的实现
4.        sipdroid默认使用的编码格式为G711-A率。
5.        直接用ant debug的方法编译出的程序,只支持A率和U率两种音频编码格式,其他的都需要通过NDK的方法导入后,才能使用。
6.        如果对端终端支持视频的话(如linphone),菜单如下:
保持,静音,
转移 发送视频 挂断
注意:只能发送视频,接收不到对端的视频。
7.        如果对端终端不支持视频的话(如yate),菜单如下:
保持,静音,
转移  挂断
8.       sipdroid\src\org\sipdroid\sipua\ui 中的VideoCamera.java,有视频捕获,发送,接收的实现。
9.       sipdroid\src\org\sipdroid\sipua\ui 中的CallScreen.java中的  VIDEO_MENU_ITEM 标识了 “发送视频”
10.       Activity2.java 实现了跳转到InCallScreen.java
11.        class InCallScreenextends CallScreen
12.       sipdroid.java 中有“关于 退出 设置”菜单的实现。
在AndroidManifest.xml中,
<intent-filter>
               <actionandroid:name="android.intent.action.MAIN" />
               <categoryandroid:name="android.intent.category.LAUNCHER" />
</intent-filter>
表明了哪个Activity先启动。
13.        网络传来的音频数据通过AudioTrack类进行播放。
14.        本地的音频数据通过AudioRecord类进行录制。
15.        在本地播放数据包中的视频流,可以先提取位图,再显示。由于系统没有提供直接播放的相关方法。
16.        线程同步的方法 – synchronized
17.       F:\sipdroid\res\drawable 中的图标可以更换
18.       sipdroid\res\values-zh-rCN 修改【关于】显示框的内容

19
在Sipdroid开源项目像服务器进行数据的发送统一是由SipProvider的sendMessage,因为首先得知道是什么连接是UDP啊,还是TCP,然后就是message的封装

20.
是无连接的包投递服务,为什么是无连接呢,客户端和服务器压根就没有建立连接,服务器只是开放了端口来接受数据,有了就接受,没有就悬挂阻塞.
21双边的视频观看,走的还是数据报包,有数据报包的ip和端口就行了

22 但是Sipdroid可以直接的从MediaRecord里面已经生成好的视频数据中提取出H264/H263的数据,这些数据已经经过了相应的编码
23如何观看视频:
mVideoFrame.setVideoURI(Uri.parse("rtsp://"+Receiver.engine(mContext).getRemoteAddr()+"/"+
         Receiver.engine(mContext).getRemoteVideo()+"/sipdroid"));
24 通过内置的videoview来通过RTSP来进行播放,那么也就是说服务器会将传递的RTP的视频数据流封装成RTSP的流传递给手机的videoview来实现观看,同样也不需要解码库,
所以Sipdroid开源代码里只有声音的编码库,没有视频的编码库.

25
最好的实现该软件的方法是,借助Android的MediaRecorder实时提取出H263/H264数据,然后经过RTP封装传给RTSP服务器,这种实现方式最理想,通过获取onPrewFrame来获取预览帧编码,无论怎么弄,不可避免的,延时,丢帧各种情况都会让你非常的棘手

sipdroid 源码下载编译


用svn从官网上下载sipdroid源码

  1). 进入doc命令, 切换到sdk安装目录的tool目录下(如果是别的目录,会导致android命令无法被识别!!) 然后运行命令: android update project -p SipDroid所在目录(我的目录: E:\android\workspace\SipUA) -t android-8

如:D:\android-sdk-windows\tools>android update project -p E:\android\workspace\SipU
A -t android-8
Updated default.properties
Updated local.properties
Added file E:\android\workspace\SipUA\build.xml

 2). 以上步骤完成之后,进入目绿只要执行: ant debug命令, 编译完成之后直接导入Eclipse就不会报错了.
 也可以先倒入eclipse再编译
如: 
E:\android\workspace\SipUA>ant debug
Buildfile: E:\android\workspace\SipUA\build.xml
    [setup] Android SDK Tools Revision 6
    [setup] Project Target: Android 2.2
    [setup] API level: 8
    [setup] WARNING: Attribute minSdkVersion in AndroidManifest.xml (3) is lower
 than the project target API level (8)
    [setup] Importing rules file: platforms\android-8\ant\ant_rules_r2.xml

Sipdroid

ipdroid初尝

今天把Sipdroid在手机上跑起来了,下面记录这次过程。
  • 首先下载yate服务器
http://yate.null.ro/pmwiki/index.php?n=Main.HomePage
到上面的yate主页下载到适合Windows的服务器,然后添加用户,启动服务。
  • 之后将手机上面的账号设置好,服务器那里直接填IP。
  • 最后在另一台电脑上装好yate,就可以实现通话了
不过这个过程中碰到了点问题,首先是501错误,后来不知道怎么好了,等继续深入研究的时候再解决吧。
还有个问题就是延迟比较大,音质比不上电话网络,估计是手机的3G网络不给力,在wifi环境下应该会好一点。不过随着无线通信的数据速率越来越高,这个问题应该不会很影响。

接下来的时间就是把Sipdroid源码看懂,改成自己的网络电话,如果可能再自己写一个注册服务器程序,这样就可以完全自己控制了。
下面记录一些关于Sipdroid参考地址:
http://sipdroid.org/   sipdroid的官网
http://blog.csdn.net/banketree/article/details/7979327
http://www.rosoo.net/a/201112/15477.html
http://www.apkbus.com/Android-161-1.html

再谈SipDroid

研究了SipDroid2.7,自己对它的理解也渐渐的清晰了。
那它是怎样实现电话拨打以及电话监听的?它的音频接收以及发送是怎么实现的?它的视频又是怎么一回事?它在模拟器上的端口为什么总是变化的?它又是如何处理登陆超时以及通话出错的?
带着这些疑问进入它的代码思想境界!
使用yate搭配服务器,然后使用了一个yate与SipDroid客户端进行通话!~至于怎么搭配服务器以及SipDroid的配置设置,此处就不讨论了!~ 
登陆后的标识效果如图:
然后我使用了yate客户端程序拨打了SipDroid程序段上的帐号(banketree),如图:
接通后的效果图像如图:
好了,进入我们的主题了!~
它是怎样实现电话拨打以及电话监听的?
程序进入时会进行服务注册,如下:
[java] view plaincopy
  1. // 实例化一个引擎 注册模式    由登陆时进行……  
  2. /   Receiver.engine(this).registerMore();  

我们知道Receiver.engine(this) 进行了SipUA引擎的实例化,并开启SipUA引擎,关键就SipUA引擎了!~
[java] view plaincopy
  1. // 构造引擎  
  2. public static synchronized SipdroidEngine engine(Context context)  
  3. {  
  4.     // 构造引擎的条件  
  5.     if (mContext == null  
  6.             || !context.getClass().getName()  
  7.                     .contains("ReceiverRestrictedContext"))  
  8.     {  
  9.         mContext = context;  
  10.     }  
  11.   
  12.     // 为空则构造  
  13.     if (mSipdroidEngine == null)  
  14.     {  
  15.         mSipdroidEngine = new SipdroidEngine();  
  16.   
  17.         // 开始   
  18.         mSipdroidEngine.StartEngine();  
  19.   
  20.         // 开启蓝牙服务  
  21.         if (Integer.parseInt(Build.VERSION.SDK) >= 8)  
  22.         {  
  23.             Bluetooth.init();  
  24.         }  
  25.     } else  
  26.     {  
  27.         // 引擎已经存在  
  28.         mSipdroidEngine.CheckEngine();  
  29.     }  
  30.   
  31.     // 开启服务  
  32.     context.startService(new Intent(context, RegisterService.class));  
  33.   
  34.     return mSipdroidEngine;  
  35. }  
而StartEngine()里有开启监听!~
[java] view plaincopy
  1. //注册  在此向服务器发送请求。  
  2. register();  
  3.   
  4. //实例化一个ExtendedCall 循环监听指定端口   
  5. listen();  

至于里面是怎么实现的,那就涉及到了SipUA封装的一些类以及消息了!~ 
消息以及协议的通信都是SipProvider类由完成的!~
[java] view plaincopy
  1. public void onReceivedMessage(Transport transport, Message msg)  
  2. {  
  3.     if (pm == null)  
  4.     {  
  5.         pm = (PowerManager) Receiver.mContext.getSystemService(Context.POWER_SERVICE);  
  6.         wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Sipdroid.SipProvider");  
  7.     }  
  8.     wl.acquire(); // modified  
  9.     //处理接收消息   当程序进行了监听,就会向此类添加一个监听事件标识   
  10.     //处理消息就是根据标识进行区分的,例如来电、接听、视频等等!~~~  
  11.     processReceivedMessage(msg);    
  12.     wl.release();  
  13. }  

向服务器发送信息(发送协议进行登陆以及获取对方信息等等)也是由该类完成的!~如下:
[java] view plaincopy
  1. /** 
  2.      * Sends a Message, specifing the transport portocol, nexthop address and 
  3.      * port. 
  4.      * 发送一条消息,指定传输协议、地址、以及端口。 
  5.      */  
  6.     private ConnectionIdentifier sendMessage(Message msg, String proto, IpAddress dest_ipaddr, int dest_port, int ttl)  
  7.     {  
  8.         if(bDebug)  
  9.         {  
  10.             android.util.Log.i("SipProvider发送消息""msg:"+msg.toString());  
  11.         }  
  12.           
  13.         ConnectionIdentifier conn_id = new ConnectionIdentifier(proto, dest_ipaddr, dest_port);  
  14.         if (log_all_packets || msg.getLength() > MIN_MESSAGE_LENGTH)  
  15.             printLog("Sending message to " + conn_id, LogLevel.MEDIUM);  
  16.   
  17.         if (transport_udp && proto.equals(PROTO_UDP))  
  18.         {   
  19.             // UDP  
  20.             // printLog("using UDP",LogLevel.LOW);  
  21.             conn_id = null;  
  22.             try  
  23.             {   
  24.                 // if (ttl>0 && multicast_address) do something?  
  25.                 udp.sendMessage(msg, dest_ipaddr, dest_port);  
  26.             } catch (IOException e)  
  27.             {  
  28.                 printException(e, LogLevel.HIGH);  
  29.                 return null;  
  30.             }  
  31.         } else if (transport_tcp && proto.equals(PROTO_TCP))  
  32.         {   
  33.             // TCP  
  34.             // printLog("using TCP",LogLevel.LOW);  
  35.             if (connections == null || !connections.containsKey(conn_id))  
  36.             {   
  37.                 // modified  
  38.                 printLog("no active connection found matching " + conn_id, LogLevel.MEDIUM);  
  39.                 printLog("open " + proto + " connection to " + dest_ipaddr + ":" + dest_port, LogLevel.MEDIUM);  
  40.                 TcpTransport conn = null;  
  41.                 try  
  42.                 {  
  43.                     conn = new TcpTransport(dest_ipaddr, dest_port, this);  
  44.                 } catch (Exception e)  
  45.                 {  
  46.                     printLog("connection setup FAILED", LogLevel.HIGH);  
  47.                     return null;  
  48.                 }  
  49.                   
  50.                 printLog("connection " + conn + " opened", LogLevel.HIGH);  
  51.                 addConnection(conn);  
  52.                 if (!msg.isRegister())  
  53.                     Receiver.engine(Receiver.mContext).register(); // modified  
  54.             } else  
  55.             {  
  56.                 printLog("active connection found matching " + conn_id, LogLevel.MEDIUM);  
  57.             }  
  58.             ConnectedTransport conn = (ConnectedTransport) connections.get(conn_id);  
  59.             if (conn != null)  
  60.             {  
  61.                 printLog("sending data through conn " + conn, LogLevel.MEDIUM);  
  62.                 try  
  63.                 {  
  64.                     conn.sendMessage(msg);  
  65.                     conn_id = new ConnectionIdentifier(conn);  
  66.                 } catch (IOException e)  
  67.                 {  
  68.                     printException(e, LogLevel.HIGH);  
  69.                     return null;  
  70.                 }  
  71.             } else  
  72.             {   
  73.                 // this point has not to be reached这一点还没有达到  
  74.                 printLog("ERROR: conn " + conn_id + " not found: abort.", LogLevel.MEDIUM);  
  75.                 return null;  
  76.             }  
  77.         } else  
  78.         { // otherwise  
  79.             printWarning("Unsupported protocol (" + proto + "): Message discarded", LogLevel.HIGH);  
  80.             return null;  
  81.         }  
  82.         // logs  
  83.         String dest_addr = dest_ipaddr.toString();  
  84.         printMessageLog(proto, dest_addr, dest_port, msg.getLength(), msg, "sent");  
  85.         return conn_id;  
  86.     }  

消息的信息格式如下:
……
回到问题中来,监听电话已经明白了,那是如何实现拨打的?
当用户登陆后就会把自己的信息发送给服务端,服务端记录下来,等用户需要时就返回给他,比如我打banketree电话,我没有他的信息我怎么打呀,是吧!~
拨打电话的关键在UserAgent中,其它的都是封装好处理信息的类!~
[java] view plaincopy
  1. public boolean call(String target_url, boolean send_anonymous)  
  2. {  
  3.   
  4.     if (Receiver.call_state != UA_STATE_IDLE)  
  5.     {  
  6.         // We can initiate or terminate a call only when  
  7.         // we are in an idle state  
  8.         //只有当我们处于闲置状态,我们可以开始或结束通话  
  9.         printLog("Call attempted in state" + this.getSessionDescriptor()  
  10.                 + " : Failing Request", LogLevel.HIGH);  
  11.         return false;  
  12.     }  
  13.       
  14.     //挂断  
  15.     hangup(); // modified  
  16.       
  17.     //改变状态  
  18.     changeStatus(UA_STATE_OUTGOING_CALL, target_url);  
  19.   
  20.     String from_url;  
  21.   
  22.     if (!send_anonymous)  
  23.     {  
  24.         from_url = user_profile.from_url;  
  25.     } else  
  26.     {  
  27.         from_url = "sip:anonymous@anonymous.com";  
  28.     }  
  29.   
  30.     // change start multi codecs  更改启动多编解码器  
  31.     createOffer();  
  32.     // change end  
  33.       
  34.     call = new ExtendedCall(sip_provider, from_url,  
  35.             user_profile.contact_url, user_profile.username,  
  36.             user_profile.realm, user_profile.passwd, this);  
  37.   
  38.     // in case of incomplete url (e.g. only 'user' is present), try to  
  39.     // complete it  
  40.     //在不完整的URL(例如,“用户”是存在的话)的情况下,尽量去完成它  
  41.     if (target_url.indexOf("@") < 0)  
  42.     {  
  43.         if (user_profile.realm.equals(Settings.DEFAULT_SERVER))  
  44.             target_url = "&" + target_url;  
  45.           
  46.         target_url = target_url + "@" + realm; // modified  
  47.     }  
  48.   
  49.     // MMTel addition to define MMTel ICSI to be included in INVITE (added  
  50.     // by mandrajg)  要包含在INVITE MMTel除了定义MMTel ICSI  
  51.     String icsi = null;  
  52.     if (user_profile.mmtel == true)  
  53.     {  
  54.         icsi = "\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\"";  
  55.     }  
  56.   
  57.     //从服务器中获得对方的地址  
  58.     target_url = sip_provider.completeNameAddress(target_url).toString();  
  59.       
  60.     //真的拨打电话  
  61.     if (user_profile.no_offer)  
  62.     {  
  63.         call.call(target_url);  
  64.     } else  
  65.     {  
  66.         call.call(target_url, local_session, icsi); // modified by mandrajg  
  67.     }  
  68.   
  69.     return true;  
  70. }  
那它的音频接收以及发送是怎么实现的?
管理音频有一个类,它是JAudioLauncher,当实例化它的时候,它会开启两个线程,一个是RtpStreamReceiver,另一个是RtpStreamReceiver!~
看标题就知道它们一个是发送的,一个是接收的!~
先来看下接收是怎么实现的!~
[java] view plaincopy
  1.     public void run()  
  2.     {  
  3.         boolean nodata = PreferenceManager.getDefaultSharedPreferences(  
  4.                 Receiver.mContext).getBoolean(  
  5.                 org.sipdroid.sipua.ui.Settings.PREF_NODATA,  
  6.                 org.sipdroid.sipua.ui.Settings.DEFAULT_NODATA);  
  7.         keepon = PreferenceManager.getDefaultSharedPreferences(  
  8.                 Receiver.mContext).getBoolean(  
  9.                 org.sipdroid.sipua.ui.Settings.PREF_KEEPON,  
  10.                 org.sipdroid.sipua.ui.Settings.DEFAULT_KEEPON);  
  11.   
  12.         if (rtp_socket == null)  
  13.         {  
  14.             if (DEBUG)  
  15.             {  
  16.                 println("ERROR: RTP socket is null(出错:rtp接受套接字出错!)");  
  17.             }  
  18.             return;  
  19.         }  
  20.   
  21.         //发送包 缓冲区  
  22.         byte[] buffer = new byte[BUFFER_SIZE + 12];  
  23.           
  24.         //构建包实例  
  25.         rtp_packet = new RtpPacket(buffer, 0);  
  26.   
  27.         if (DEBUG)  
  28.         {  
  29.             println("Reading blocks of max (读取快的最大值) = " + buffer.length + " bytes");  
  30.         }  
  31.   
  32.         running = true;  
  33.           
  34.         //开启蓝牙  
  35.         enableBluetooth(PreferenceManager.getDefaultSharedPreferences(  
  36.                 Receiver.mContext).getBoolean(  
  37.                 org.sipdroid.sipua.ui.Settings.PREF_BLUETOOTH,  
  38.                 org.sipdroid.sipua.ui.Settings.DEFAULT_BLUETOOTH));  
  39.           
  40.         restored = false;  
  41.   
  42.         //设置线程权限  
  43.         android.os.Process  
  44.                 .setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);  
  45.           
  46.         am = (AudioManager) Receiver.mContext  
  47.                 .getSystemService(Context.AUDIO_SERVICE);  
  48.         cr = Receiver.mContext.getContentResolver();  
  49.           
  50.         //保存设置  
  51.         saveSettings();  
  52.         Settings.System.putInt(cr, Settings.System.WIFI_SLEEP_POLICY,  
  53.                 Settings.System.WIFI_SLEEP_POLICY_NEVER);  
  54.         am.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER,  
  55.                 AudioManager.VIBRATE_SETTING_OFF);  
  56.         am.setVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION,  
  57.                 AudioManager.VIBRATE_SETTING_OFF);  
  58.         if (oldvol == -1)  
  59.         {  
  60.             oldvol = am.getStreamVolume(AudioManager.STREAM_MUSIC);  
  61.         }  
  62.   
  63.         //初始化模式  
  64.         initMode();  
  65.           
  66.         //设置音频编解码器  
  67.         setCodec();  
  68.   
  69.         //编辑音频  由发送包解码后放到此处 然后调整后放入到声音播放器中  
  70.         short lin[] = new short[BUFFER_SIZE];  
  71.         //它是负责清空lin的  
  72.         short lin2[] = new short[BUFFER_SIZE];  
  73.         int server, headroom, todo, len = 0, m = 1, expseq, getseq, vm = 1, gap, gseq;  
  74.           
  75.         ToneGenerator tg = new ToneGenerator(  
  76.                 AudioManager.STREAM_VOICE_CALL,  
  77.                 (int) (ToneGenerator.MAX_VOLUME * 2 * org.sipdroid.sipua.ui.Settings  
  78.                         .getEarGain()));  
  79.           
  80.         //播放  
  81.         track.play();  
  82.           
  83.         //指示虚拟机运行垃圾收集器,这将是一个好时机。  
  84.         //请注意,这仅是一个提示。有没有保证,垃圾收集器将实际运行。  
  85.         System.gc();  
  86.           
  87.         //清空  
  88.         empty();  
  89.           
  90.         lockFirst = true;  
  91.   
  92.         while (running)  
  93.         {  
  94.             lock(true);  
  95.               
  96.             if (Receiver.call_state == UserAgent.UA_STATE_HOLD)  
  97.             {  
  98.                 lock(false);  
  99.                   
  100.                 //停止  
  101.                 tg.stopTone();  
  102.                   
  103.                 //暂停  
  104.                 track.pause();  
  105.                   
  106.                 while (running  
  107.                         && Receiver.call_state == UserAgent.UA_STATE_HOLD)  
  108.                 {  
  109.                     try  
  110.                     {  
  111.                         sleep(1000);  
  112.                     } catch (InterruptedException e1)  
  113.                     {  
  114.                     }  
  115.                 }  
  116.   
  117.                 track.play();  
  118.                 System.gc();  
  119.                 timeout = 1;  
  120.                 luser = luser2 = -8000 * mu;  
  121.             }  
  122.               
  123.             try  
  124.             {  
  125.                 // 接受数据  
  126.                 rtp_socket.receive(rtp_packet);  
  127.   
  128.                 //超时  
  129.                 if (timeout != 0)  
  130.                 {  
  131.                     //停止音乐  
  132.                     tg.stopTone();  
  133.                       
  134.                     //暂停声音  
  135.                     track.pause();  
  136.                       
  137.                     //清空  
  138.                     for (int i = maxjitter * 2; i > 0; i -= BUFFER_SIZE)  
  139.                     {  
  140.                         write(lin2, 0, i > BUFFER_SIZE ? BUFFER_SIZE : i);  
  141.                     }  
  142.   
  143.                     cnt += maxjitter * 2;  
  144.                       
  145.                     //声音播放  
  146.                     track.play();  
  147.                     empty();  
  148.                 }  
  149.                 timeout = 0;  
  150.             } catch (IOException e)  
  151.             {  
  152.                 if (timeout == 0 && nodata)  
  153.                 {  
  154.                     tg.startTone(ToneGenerator.TONE_SUP_RINGTONE);  
  155.                 }  
  156.   
  157.                 //异常者 断开  
  158.                 rtp_socket.getDatagramSocket().disconnect();  
  159.                   
  160.                 if (++timeout > 60)  
  161.                 {  
  162.                     Receiver.engine(Receiver.mContext).rejectcall();  
  163.                     break;  
  164.                 }  
  165.             }  
  166.               
  167.             //在运行 且未超时  
  168.             if (running && timeout == 0)  
  169.             {  
  170.                 //得到数字字符  
  171.                 gseq = rtp_packet.getSequenceNumber();  
  172.                   
  173.                 //得到当前接受包的大小  
  174.                 if (seq == gseq)  
  175.                 {  
  176.                     m++;  
  177.                     continue;  
  178.                 }  
  179.                   
  180.                   
  181.                 gap = (gseq - seq) & 0xff;  
  182.                   
  183.                 if (gap > 240)  
  184.                 {  
  185.                     continue;  
  186.                 }  
  187.                   
  188.                 //返回帧中表示播放头位置。  
  189.                 server = track.getPlaybackHeadPosition();  
  190.                   
  191.                 //接受包的总大小 - 当前播放的位置  
  192.                 headroom = user - server;  
  193.   
  194.                 if (headroom > 2 * jitter)  
  195.                 {  
  196.                     cnt += len;  
  197.                 } else  
  198.                 {  
  199.                     cnt = 0;  
  200.                 }  
  201.   
  202.                 if (lserver == server)  
  203.                 {  
  204.                     cnt2++;  
  205.                 } else  
  206.                 {  
  207.                     cnt2 = 0;  
  208.                 }  
  209.   
  210.                 if (cnt <= 500 * mu || cnt2 >= 2 || headroom - jitter < len  
  211.                         || p_type.codec.number() != 8  
  212.                         || p_type.codec.number() != 0)  
  213.                 {  
  214.                     //有效负荷类型||改变类型  
  215.                     if (rtp_packet.getPayloadType() != p_type.number  
  216.                             && p_type.change(rtp_packet.getPayloadType()))  
  217.                     {  
  218.                         //保留声量  
  219.                         saveVolume();  
  220.                           
  221.                         //设置编解码器  
  222.                         setCodec();  
  223.                           
  224.                         //恢复声量  
  225.                         restoreVolume();  
  226.                           
  227.                         //头信息  
  228.                         codec = p_type.codec.getTitle();  
  229.                     }  
  230.                       
  231.                     //得到有效负荷长度  
  232.                     len = p_type.codec.decode(buffer, lin,  
  233.                             rtp_packet.getPayloadLength());  
  234.   
  235.                     // Call recording: Save incoming.Data is in buffer lin, from 0 to len.  
  236.                     //通话记录:保存传入。数据是在缓冲区林,从0到LEN。  
  237.                     if (call_recorder != null)  
  238.                     {  
  239.                         //写入  
  240.                         call_recorder.writeIncoming(lin, 0, len);  
  241.                     }  
  242.   
  243.                     //声音改变效果显示  
  244.                     if (speakermode == AudioManager.MODE_NORMAL)  
  245.                     {  
  246.                         calc(lin, 0, len);  
  247.                     } else if (gain > 1)  
  248.                     {  
  249.                         calc2(lin, 0, len);  
  250.                     }  
  251.                 }  
  252.   
  253.                 //  
  254.                 avgheadroom = avgheadroom * 0.99 + (double) headroom * 0.01;  
  255.                 if (avgcnt++ > 300)  
  256.                 {  
  257.                     devheadroom = devheadroom * 0.999  
  258.                             + Math.pow(Math.abs(headroom - avgheadroom), 2)  
  259.                             * 0.001;  
  260.                 }  
  261.                   
  262.                 //头大小  
  263.                 if (headroom < 250 * mu)  
  264.                 {  
  265.                     late++;  
  266.                     newjitter(true);  
  267. //                  System.out.println("RTP:underflow "  
  268. //                          + (int) Math.sqrt(devheadroom));  
  269.                     todo = jitter - headroom;  
  270.                     write(lin2, 0, todo > BUFFER_SIZE ? BUFFER_SIZE : todo);  
  271.                 }  
  272.   
  273.                 //  
  274.                 if (cnt > 500 * mu && cnt2 < 2)  
  275.                 {  
  276.                     todo = headroom - jitter;  
  277.                     if (todo < len)  
  278.                     {  
  279.                         write(lin, todo, len - todo);  
  280.                     }  
  281.                 } else  
  282.                 {  
  283.                     write(lin, 0, len);  
  284.                 }  
  285.   
  286.                 //丢失包统计  
  287.                 if (seq != 0)  
  288.                 {  
  289.                     getseq = gseq & 0xff;  
  290.                     expseq = ++seq & 0xff;  
  291.                     if (m == RtpStreamSender.m)  
  292.                     {  
  293.                         vm = m;  
  294.                     }  
  295.                       
  296.                     gap = (getseq - expseq) & 0xff;  
  297.                     if (gap > 0)  
  298.                     {  
  299.                     //  System.out.println("RTP:lost");  
  300.                         if (gap > 100)  
  301.                         {  
  302.                             gap = 1;  
  303.                         }  
  304.                         loss += gap;  
  305.                         lost += gap;  
  306.                         good += gap - 1;  
  307.                         loss2++;  
  308.                     } else  
  309.                     {  
  310.                         if (m < vm)  
  311.                         {  
  312.                             loss++;  
  313.                             loss2++;  
  314.                         }  
  315.                     }  
  316.                     good++;  
  317.                     if (good > 110)  
  318.                     {  
  319.                         good *= 0.99;  
  320.                         lost *= 0.99;  
  321.                         loss *= 0.99;  
  322.                         loss2 *= 0.99;  
  323.                         late *= 0.99;  
  324.                     }  
  325.                 }  
  326.                 m = 1;  
  327.                 seq = gseq;  
  328.   
  329.                 if (user >= luser + 8000 * mu  
  330.                         && (Receiver.call_state == UserAgent.UA_STATE_INCALL || Receiver.call_state == UserAgent.UA_STATE_OUTGOING_CALL))  
  331.                 {  
  332.                     if (luser == -8000 * mu || getMode() != speakermode)  
  333.                     {  
  334.                         //保留声量  
  335.                         saveVolume();  
  336.                           
  337.                         //设置模式  
  338.                         setMode(speakermode);  
  339.                           
  340.                         //恢复声量  
  341.                         restoreVolume();  
  342.                     }  
  343.                   
  344.                     luser = user;  
  345.                       
  346.                     if (user >= luser2 + 160000 * mu)  
  347.                     {  
  348.                         //抖动  
  349.                         newjitter(false);  
  350.                     }  
  351.                 }  
  352.                 lserver = server;  
  353.             }  
  354.         }  

再看下发送包是怎么实现的,发送声音肯定是先录制,后读取,再发送!~如下:
[java] view plaincopy
  1.     public void run()  
  2.     {  
  3.         //测试音频数据  
  4. //      testVoiceFile mvoiceFile = new testVoiceFile("voice");  
  5. //      testVoiceFile mvoiceRtpFile = new testVoiceFile("rtpdate");  
  6.           
  7.         //wifi管理  
  8.         WifiManager wm = (WifiManager) Receiver.mContext  
  9.                 .getSystemService(Context.WIFI_SERVICE);  
  10.         long lastscan = 0, lastsent = 0;  
  11.   
  12.         if (rtp_socket == null)  
  13.             return;  
  14.         int seqn = 0;  
  15.         long time = 0;  
  16.         double p = 0;  
  17.           
  18.         //改善  
  19.         boolean improve = PreferenceManager.getDefaultSharedPreferences(  
  20.                 Receiver.mContext).getBoolean(Settings.PREF_IMPROVE,  
  21.                 Settings.DEFAULT_IMPROVE);  
  22.           
  23.         //选择wifi  
  24.         boolean selectWifi = PreferenceManager.getDefaultSharedPreferences(  
  25.                 Receiver.mContext).getBoolean(  
  26.                 org.sipdroid.sipua.ui.Settings.PREF_SELECTWIFI,  
  27.                 org.sipdroid.sipua.ui.Settings.DEFAULT_SELECTWIFI);  
  28.           
  29.         int micgain = 0;  
  30.         long last_tx_time = 0;  
  31.         long next_tx_delay;  
  32.         long now;  
  33.         running = true;  
  34.         m = 1;  
  35.         int dtframesize = 4;  
  36.   
  37.         //改变线程权限  
  38.         android.os.Process  
  39.                 .setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);  
  40.           
  41.         //解码速率  
  42.         mu = p_type.codec.samp_rate() / 8000;  
  43.         int min = AudioRecord.getMinBufferSize(p_type.codec.samp_rate(),  
  44.                 AudioFormat.CHANNEL_CONFIGURATION_MONO,  
  45.                 AudioFormat.ENCODING_PCM_16BIT);  //最小缓冲大小  
  46.           
  47.         if (min == 640)  
  48.         {  
  49.             if (frame_size == 960)  
  50.                 frame_size = 320;  
  51.             if (frame_size == 1024)  
  52.                 frame_size = 160;  
  53.             min = 4096 * 3 / 2;  
  54.         } else if (min < 4096)  
  55.         {  
  56.             if (min <= 2048 && frame_size == 1024)  
  57.                 frame_size /= 2;  
  58.             min = 4096 * 3 / 2;  
  59.         } else if (min == 4096)  
  60.         {  
  61.             min *= 3 / 2;  
  62.             if (frame_size == 960)  
  63.                 frame_size = 320;  
  64.         } else  
  65.         {  
  66.             if (frame_size == 960)  
  67.                 frame_size = 320;  
  68.             if (frame_size == 1024)  
  69.                 frame_size *= 2;  
  70.         }  
  71.           
  72.         //速率  
  73.         frame_rate = p_type.codec.samp_rate() / frame_size;  
  74.         long frame_period = 1000 / frame_rate;  
  75.         frame_rate *= 1.5;  
  76.           
  77.         //发送包缓冲区  
  78.         byte[] buffer = new byte[frame_size + 12];  //  
  79.           
  80.         //实例化包  
  81.         RtpPacket rtp_packet = new RtpPacket(buffer, 0);  
  82.           
  83.         //设置有效复合  
  84.         rtp_packet.setPayloadType(p_type.number);  
  85.           
  86.         //调式输出信息  
  87.         if (DEBUG)  
  88.             println("Reading blocks of (读取块大小)" + buffer.length + " bytes");  
  89.   
  90.         println("Sample rate (采样率) = " + p_type.codec.samp_rate());  
  91.         println("Buffer size(缓冲区大小) = " + min);  
  92.   
  93.         //录制  
  94.         AudioRecord record = null;  
  95.   
  96.         //获得声音内容变量  
  97.         short[] lin = new short[frame_size * (frame_rate + 1)];  
  98.         int num, ring = 0, pos;  
  99.           
  100.         //随机数  
  101.         random = new Random();  
  102.           
  103.         //输入流  
  104.         InputStream alerting = null;  
  105.         try  
  106.         {  
  107.             //接受  
  108.             alerting = Receiver.mContext.getAssets().open("alerting");  
  109.         } catch (IOException e2)  
  110.         {  
  111.             if (!Sipdroid.release)  
  112.                 e2.printStackTrace();  
  113.         }  
  114.           
  115.         //初始化音频编码  
  116.         p_type.codec.init();  
  117.           
  118.         if(Sipdroid.VoiceDebug)  
  119.         {  
  120.             Log.i("RtpStreamSender""音频发送开始啦");  
  121.         }  
  122.           
  123.         //循环发送  先录制 然后发送  
  124.         while (running)  
  125.         {  
  126.             //录制没有 或已经改变  
  127.             if (changed || record == null)  
  128.             {  
  129.                 if (record != null)  
  130.                 {  
  131.                     //释放操作  
  132.                     record.stop();  
  133.                     record.release();  
  134.                     if (RtpStreamReceiver.samsung)  
  135.                     {  
  136.                         AudioManager am = (AudioManager) Receiver.mContext  
  137.                                 .getSystemService(Context.AUDIO_SERVICE);  
  138.                         am.setMode(AudioManager.MODE_IN_CALL);  
  139.                         am.setMode(AudioManager.MODE_NORMAL);  
  140.                     }  
  141.                 }  
  142.                   
  143.                 //未改变  
  144.                 changed = false;  
  145.                   
  146.                 //实例化录制  
  147.                 record = new AudioRecord(MediaRecorder.AudioSource.MIC,  
  148.                         p_type.codec.samp_rate(),  
  149.                         AudioFormat.CHANNEL_CONFIGURATION_MONO,  
  150.                         AudioFormat.ENCODING_PCM_16BIT, min);  
  151.                   
  152.                 if(Sipdroid.VoiceDebug)  
  153.                 {  
  154.                     Log.i("RtpStreamSender""构造音频录制实例");  
  155.                 }  
  156.                   
  157.                 //得到录制类型  
  158.                 if (record.getState() != AudioRecord.STATE_INITIALIZED)  
  159.                 {  
  160.                     //拒接  
  161.                     Receiver.engine(Receiver.mContext).rejectcall();  
  162.                     record = null;  
  163.                     break;  
  164.                 }  
  165.                   
  166.                 //开始录制  
  167.                 record.startRecording();  
  168.                   
  169.                 //计算  
  170.                 micgain = (int) (Settings.getMicGain() * 10);  
  171.             }  
  172.               
  173.               
  174.             //静音或挂断 则停止  
  175.             if (muted || Receiver.call_state == UserAgent.UA_STATE_HOLD)  
  176.             {  
  177.                 //挂断  
  178.                 if (Receiver.call_state == UserAgent.UA_STATE_HOLD)  
  179.                 {  
  180.                     //恢复模式  
  181.                     RtpStreamReceiver.restoreMode();  
  182.                 }  
  183.                   
  184.                 if(Sipdroid.VoiceDebug)  
  185.                 {  
  186.                     Log.i("RtpStreamSender""挂断电话则停止录制");  
  187.                 }  
  188.                   
  189.                 //停止录音  
  190.                 record.stop();  
  191.               
  192.                 //延时  
  193.                 while (running  
  194.                         && (muted || Receiver.call_state == UserAgent.UA_STATE_HOLD))  
  195.                 {  
  196.                     try  
  197.                     {  
  198.                         sleep(1000);  
  199.                     } catch (InterruptedException e1)  
  200.                     {  
  201.                     }  
  202.                 }  
  203.                   
  204.                 //开始录制  
  205.                 record.startRecording();  
  206.             }  
  207.               
  208.             // DTMF change start  
  209.             if (dtmf.length() != 0)  
  210.             {  
  211.                 //构造包  
  212.                 byte[] dtmfbuf = new byte[dtframesize + 12];  
  213.                 RtpPacket dt_packet = new RtpPacket(dtmfbuf, 0);  
  214.                   
  215.                 //设置有效负荷  
  216.                 dt_packet.setPayloadType(dtmf_payload_type);  
  217.                   
  218.                 //设置大小  
  219.                 dt_packet.setPayloadLength(dtframesize);  
  220.                 dt_packet.setSscr(rtp_packet.getSscr());  
  221.                 long dttime = time;  
  222.                 int duration;  
  223.   
  224.                 for (int i = 0; i < 6; i++)  
  225.                 {  
  226.                     time += 160;  
  227.                     duration = (int) (time - dttime);  
  228.                     dt_packet.setSequenceNumber(seqn++);  
  229.                     dt_packet.setTimestamp(dttime);  
  230.                     dtmfbuf[12] = rtpEventMap.get(dtmf.charAt(0));  
  231.                     dtmfbuf[13] = (byte0x0a;  
  232.                     dtmfbuf[14] = (byte) (duration >> 8);  
  233.                     dtmfbuf[15] = (byte) duration;  
  234.                     try  
  235.                     {  
  236.                         //发送包 声音包  
  237.                         rtp_socket.send(dt_packet);  
  238.                           
  239.                         if (Sipdroid.VoiceDebug)  
  240.                         {  
  241.                             Log.i("RtpStreamSender",  
  242.                                     "第一次发送声音包  大小" + dt_packet.getLength()  
  243.                                             + " " + dt_packet.getPacket());  
  244.                         }  
  245.                           
  246.                         if(bShowVoiceDecodeData)  
  247.                         {  
  248. //                          mvoiceRtpFile.write(dt_packet.getPacket());  
  249.                         }  
  250.   
  251.                         sleep(20);  
  252.                     } catch (Exception e1)  
  253.                     {  
  254.                     }  
  255.                 }  
  256.                   
  257.                 //发送回音?  
  258.                 for (int i = 0; i < 3; i++)  
  259.                 {  
  260.                     duration = (int) (time - dttime);  
  261.                     dt_packet.setSequenceNumber(seqn);  
  262.                     dt_packet.setTimestamp(dttime);  
  263.                     dtmfbuf[12] = rtpEventMap.get(dtmf.charAt(0));  
  264.                     dtmfbuf[13] = (byte0x8a;  
  265.                     dtmfbuf[14] = (byte) (duration >> 8);  
  266.                     dtmfbuf[15] = (byte) duration;  
  267.                     try  
  268.                     {  
  269.                         //发送包  
  270.                         rtp_socket.send(dt_packet);  
  271.                           
  272.                         if (Sipdroid.VoiceDebug)  
  273.                         {  
  274.                             Log.i("RtpStreamSender",  
  275.                                     "第二次发送声音包  大小" + dt_packet.getLength()  
  276.                                             + " " + dt_packet.getPacket());  
  277.                         }  
  278.                           
  279.                         if(bShowVoiceDecodeData)  
  280.                         {  
  281. //                          mvoiceRtpFile.write(dt_packet.getPacket());  
  282.                         }  
  283.                     } catch (Exception e1)  
  284.                     {  
  285.                     }  
  286.                 }  
  287.                 time += 160;  
  288.                 seqn++;  
  289.                 dtmf = dtmf.substring(1);  
  290.             }  
  291.             // DTMF change end  
  292.   
  293.             if (frame_size < 480)  
  294.             {  
  295.                 now = System.currentTimeMillis();  
  296.                 next_tx_delay = frame_period - (now - last_tx_time);  
  297.                 last_tx_time = now;  
  298.                 if (next_tx_delay > 0)  
  299.                 {  
  300.                     try  
  301.                     {  
  302.                         sleep(next_tx_delay);  
  303.                     } catch (InterruptedException e1)  
  304.                     {  
  305.                     }  
  306.                     last_tx_time += next_tx_delay - sync_adj;  
  307.                 }  
  308.             }  
  309.               
  310.             //获得发送的位置  
  311.             pos = (ring + delay * frame_rate * frame_size)  
  312.                     % (frame_size * (frame_rate + 1));  
  313.               
  314.             //得到大小  
  315.             num = record.read(lin, pos, frame_size);  
  316.               
  317.             if (num <= 0)  
  318.                 continue;  
  319.               
  320.             //是否有效  
  321.             if (!p_type.codec.isValid())  
  322.                 continue;  
  323.   
  324.             // Call recording: Save the frame to the CallRecorder.  
  325.             //通话记录:框架保存的CallRecorder的。  新的录制   
  326.             if (call_recorder != null)  
  327.             {  
  328.                 //写入 输出  
  329.                 call_recorder.writeOutgoing(lin, pos, num);  
  330.             }  
  331.   
  332.             if (RtpStreamReceiver.speakermode == AudioManager.MODE_NORMAL)  
  333.             {  
  334.                 calc(lin, pos, num);  
  335.                 if (RtpStreamReceiver.nearend != 0  
  336.                         && RtpStreamReceiver.down_time == 0)  
  337.                 {  
  338.                     noise(lin, pos, num, p / 2);  
  339.                 }  
  340.                 else if (nearend == 0)  
  341.                 {  
  342.                     p = 0.9 * p + 0.1 * s;  
  343.                 }  
  344.             } else  
  345.             {  
  346.                 switch (micgain)  
  347.                 {  
  348.                 case 1:  
  349.                     calc1(lin, pos, num);  
  350.                     break;  
  351.                 case 2:  
  352.                     calc2(lin, pos, num);  
  353.                     break;  
  354.                 case 10:  
  355.                     calc10(lin, pos, num);  
  356.                     break;  
  357.                 }  
  358.             }  
  359.               
  360.             iCount++;  
  361.               
  362.             //通话中  
  363.             if (Receiver.call_state != UserAgent.UA_STATE_INCALL  
  364.                     && Receiver.call_state != UserAgent.UA_STATE_OUTGOING_CALL  
  365.                     && alerting != null)  
  366.             {  
  367.                 try  
  368.                 {  
  369.                     if (alerting.available() < num / mu)  
  370.                     {  
  371.                         alerting.reset();  
  372.                     }  
  373.                     alerting.read(buffer, 12, num / mu);  
  374.                 } catch (IOException e)  
  375.                 {  
  376.                     if (!Sipdroid.release)  
  377.                     {  
  378.                         e.printStackTrace();  
  379.                     }  
  380.                 }  
  381.                 if (p_type.codec.number() != 8)  
  382.                 {  
  383.                     G711.alaw2linear(buffer, lin, num, mu);  
  384.                     num = p_type.codec.encode(lin, 0, buffer, num);  
  385.                       
  386. //                  if(bShowVoiceDecodeData)  
  387. //                  {  
  388. //                      byte[] sdf = lin.toString().getBytes();  
  389. //                      mvoiceFile.write(sdf);  
  390. //                        
  391. //                      String string = "";  
  392. //                        
  393. //                      for(short i:buffer)  
  394. //                          string+=i;  
  395. //                                
  396. //                      Log.i("p_type.codec.encode",iCount +"编码后的数据(buffer):"+string);  
  397. //                  }  
  398.                 }  
  399.             } else  
  400.             {  
  401.                 num = p_type.codec.encode(lin, ring  
  402.                         % (frame_size * (frame_rate + 1)), buffer, num); //进行了编码  
  403.                   
  404. //              if(bShowVoiceDecodeData)  
  405. //              {  
  406. //                  mvoiceFile.write(buffer);  
  407. //                    
  408. //                  String string = "";  
  409. //                    
  410. //                  for(short i:buffer)  
  411. //                      string+=i;  
  412. //                            
  413. //                  Log.i("p_type.codec.encode",iCount +"编码后的数据(buffer):"+string);  
  414. //              }  
  415.             }  
  416.               
  417.               
  418.   
  419.             //大小  
  420.             ring += frame_size;  
  421.             rtp_packet.setSequenceNumber(seqn++);  
  422.             rtp_packet.setTimestamp(time);  
  423.             rtp_packet.setPayloadLength(num);  
  424.               
  425.             //记录时间  
  426.             now = SystemClock.elapsedRealtime();              
  427.               
  428.             if (RtpStreamReceiver.timeout == 0 || Receiver.on_wlan  
  429.                     || now - lastsent > 500)  
  430.             {  
  431.                 try  
  432.                 {  
  433.                     lastsent = now;  
  434.                     rtp_socket.send(rtp_packet);  
  435.                   
  436.                     if (m > 1  
  437.                             && (RtpStreamReceiver.timeout == 0 || Receiver.on_wlan))  
  438.                     {  
  439.                         for (int i = 1; i < m; i++)  
  440.                             rtp_socket.send(rtp_packet);  
  441.                     }  
  442.                       
  443.                     if (Sipdroid.VoiceDebug)  
  444.                     {  
  445.                         Log.i("RtpStreamSender",  
  446.                                 "第三次发送声音包  大小" + rtp_packet.getLength()  
  447.                                         + " " + rtp_packet.getPacket());  
  448.                     }  
  449.                       
  450. //                  if(bShowVoiceDecodeData)  
  451. //                  {  
  452. //                      mvoiceRtpFile.write(rtp_packet.getPacket());  
  453. //                  }  
  454.                 } catch (Exception e)  
  455.                 {  
  456.                 }  
  457.             }  
  458.               
  459.             //编码数  
  460.             if (p_type.codec.number() == 9)  
  461.             {  
  462.                 time += frame_size / 2;  
  463.             }  
  464.             else  
  465.             {  
  466.                 time += frame_size;  
  467.             }  
  468.               
  469.             if (RtpStreamReceiver.good != 0  
  470.                     && RtpStreamReceiver.loss2 / RtpStreamReceiver.good > 0.01)  
  471.             {  
  472.                 if (selectWifi && Receiver.on_wlan && now - lastscan > 10000)  
  473.                 {  
  474.                     wm.startScan();  
  475.                     lastscan = now;  
  476.                 }  
  477.                 if (improve  
  478.                         && delay == 0  
  479.                         && (p_type.codec.number() == 0  
  480.                                 || p_type.codec.number() == 8 || p_type.codec  
  481.                                 .number() == 9))  
  482.                 {  
  483.                     m = 2;  
  484.                 }  
  485.                 else  
  486.                 {  
  487.                     m = 1;  
  488.                 }  
  489.             } else  
  490.             {  
  491.                 m = 1;  
  492.             }  
  493.         }  
好了,进入下一个问题,它的视频又是怎么一回事?此处不谈它的视频编码,直接介绍涉及它的流程!~
涉及视频的类有:VideoCamera、VideoCameraNew、VideoCameraNew2、VideoPreview!~ 而是否使用视频,关键在CallScreen类,CallScreen类是通话的界面!~
如下是否使用视频的代码,该代码在CallScreen中!~
[java] view plaincopy
  1. //是否开启视频流程关键地方     必须是在 电话中并且端口等设置正确   
  2. if (Receiver.call_state == UserAgent.UA_STATE_INCALL  
  3.         && socket == null  
  4.         && Receiver.engine(mContext).getLocalVideo() != 0  
  5.         && Receiver.engine(mContext).getRemoteVideo() != 0  
  6.         && PreferenceManager  
  7.                 .getDefaultSharedPreferences(this)  
  8.                 .getString(org.sipdroid.sipua.ui.Settings.PREF_SERVER,  
  9.                         org.sipdroid.sipua.ui.Settings.DEFAULT_SERVER)  
  10.                 .equals(org.sipdroid.sipua.ui.Settings.DEFAULT_SERVER))  
  11. {  
  12.     (new Thread()  
  13.     {  
  14.         public void run()  
  15.         {                     
  16.             // 视频包 在线包  
  17.             RtpPacket keepalive = new RtpPacket(new byte[12], 0);  
  18.             RtpPacket videopacket = new RtpPacket(new byte[1000], 0);  
  19.   
  20.             try  
  21.             {  
  22.                 if (intent == null || rtp_socket == null)  
  23.                 {  
  24.                     // 新建一个套接字  
  25.                     rtp_socket = new RtpSocket(  
  26.   
  27.                     // 初始化套接字  
  28.                             socket = new SipdroidSocket(Receiver  
  29.                                     .engine(mContext).getLocalVideo()),  
  30.   
  31.                             // 设置网络地址  
  32.                             InetAddress.getByName(Receiver.engine(  
  33.                                     mContext).getRemoteAddr()),  
  34.   
  35.                             // 接受远程视频  
  36.                             Receiver.engine(mContext).getRemoteVideo());  
  37.                     sleep(3000);  
  38.                 } else  
  39.                 {  
  40.                     // 接受数据  
  41.                     socket = rtp_socket.getDatagramSocket();  
  42.                 }  
  43.                 // 接受数据  
  44.                 rtp_socket.getDatagramSocket().setSoTimeout(15000);  
  45.             } catch (Exception e)  
  46.             {  
  47.                 if (!Sipdroid.release)  
  48.                 {  
  49.                     e.printStackTrace();  
  50.                 }  
  51.                 return;  
  52.             }  
  53.   
  54.             // 设置有效载荷类型  
  55.             keepalive.setPayloadType(126);  
  56.   
  57.             try  
  58.             {  
  59.                 // 发送数据  
  60.                 rtp_socket.send(keepalive);  
  61.             } catch (Exception e1)  
  62.             {  
  63.                 return;  
  64.             }  
  65.             for (;;)  
  66.             {  
  67.                 try  
  68.                 {  
  69.                     // 循环接收数据  
  70.                     rtp_socket.receive(videopacket);  
  71.                 } catch (IOException e)  
  72.                 {  
  73.                     // 异常则断开  
  74.                     rtp_socket.getDatagramSocket().disconnect();  
  75.                     try  
  76.                     {  
  77.                         // 发送在线包  
  78.                         rtp_socket.send(keepalive);  
  79.                     } catch (IOException e1)  
  80.                     {  
  81.                         return;  
  82.                     }  
  83.                 }  
  84.   
  85.                 // 得到有效负荷长度  
  86.                 if (videopacket.getPayloadLength() > 200)  
  87.                 {  
  88.                     if (intent != null)  
  89.                     {  
  90.                         // 发送数据  
  91.                         intent.putExtra("justplay"true);  
  92.                         mHandler.sendEmptyMessage(0);  
  93.                     } else  
  94.                     {  
  95.                         // 否则播放  
  96.                         Intent i = new Intent(mContext,  
  97.                                 org.sipdroid.sipua.ui.VideoCamera.class);  
  98.                         i.putExtra("justplay"true);  
  99.                         startActivity(i);  
  100.                     }  
  101.                     return;  
  102.                 }  
  103.             }  
  104.         }  
  105.     }).start();  
  106. }  

之后就进入到涉及视频的类VideoCamera中了!~
它在模拟器上的端口为什么总是变化的?
抓包分析下,如图:
yate服务端第一次记录了我的rport为1849,从图中发现它是与服务器通信的端口!~服务器中也把它当做端口记录!~而SipUA客户端是使用了UDP套接字自定义了一个37850端口,这个端口一直到退出才改变,而yate服务端第一次记录了我的rport为2251,也就是说服务器记录的rport的端口是一直在发生改变的!~所以下次用户拨打对方,向服务器索取对方信息时出错,就有可能会直接挂掉!~
rport在Sip中的定义是rport方式主要是对sip信令中Via字头的扩展,不过同时也要求SIP Proxy支持该功能。NAT之后的sip client在发送请求的时候在via字头中添加rport字段,该消息经发出后路由到SIP Proxy,SIP Proxy通过检查消息的源地址和Via字段中的地址,得知该client处于NAT之后,并且基于已有的rport,将消息的真实地址即公网上的地址通过received和rport字段返回给client端,这样client就知道自己真实的公网地址,可以解决信令穿越的问题。
而有网友提出,使用Android模拟器通过路由器时端口会发生变化!~ 不知道这是不是真的!

它又是如何处理登陆超时以及通话出错的?
这就涉及到封装处理Sip协议的类了!~涉及的类不多,感兴趣的童鞋就自己研究了,我累了!~
有些问题需要讨论!~~欢迎高手指点!~