MjSip 软件分析
一、背景
1.1 MjSip 介绍
MjSip 为意大利两所高校和研究所共同开发的基于 SIP 协议的开源 VOIP 软件,其使用纯 Java
编写,软件最后更新时间为 2006 年 12 月。
软件主页:http://www.mjsip.org/download/index.html。
此软件有客户端和服务端两套,并附有 J2ME/CLDC 平台上编译的源码,同时附有关于 SIP
协议栈开发的全部源代码,方便定制功能开发。
我们使用其客户端的源代码即可:MjUA v1.6 (based on mjsip 1.6)。
1.2 其他 VOIP 软件
Linphone:PC 上的 VOIP 软件。
X-lite:PC 上的 VOIP 软件,分为免费和付费两个版本,免费版略有小的延迟,软件初始化
时间也较长。
Shtoom:Linux 下开发的 python 开源软件,目前在 windows 下没有图形界面支持。
SIP COMMUNICATOR:Java 开源代码,如下网站有详细说明:
http://jitsi.org/index.php/Documentation/DeveloperDocumentation
二、开发环境搭建
2.1 下载与安装
下载对应的程序包,并查看帮助文件 mjsip-to-j2me-HOWTO.txt。
在 Eclipse 中新建项目,拷贝 MjSip 对应的源码。
由 于 需 要 对 应 的 库 , 下 载 sun_java_wireless_toolkit-2_5_2-ml-windows 和
jmf-2_1_1e-windows-i586 两个库并安装,在项目配置信息中添加上述两个库的 jar 文件。
编 译 时 若 报 错 , 可 能 的 解 决 办 法 是 : ( 可 能 有 两 个 项 目 同 时 使 用 了 jmf 的 包 ?) 选 择
preference->complier->errors/warnings->deprecated and resticted api-> forbidden reference,选
ignore 或者 warning。
以下的开发都是基于上述搭建好的平台进行。
2.2 配置文件
初始配置文件为 mjsip.cfg.txt,文件内分为六部分。若作为 SIP 客户端使用,只需要修改第
二、三和五部分。
第二部分
debug_level 日志等级,高的等级输出详细的信息,建议取值较高
日志存放路径
程序使用的端口,同时使用多个程序时需指定不同端口
log_path
第三部分
host_port
第五部分
from_url
username
passwd
realm
SIP 协议中指定的发送者 from 字段,格式为 sip: username @ url
用户名,由 VOIP 服务商提供
用户密码,由 VOIP 服务商提供
服务器域,大多 VOIP 服务器使用 asterisk
开启 GUI 界面时需要的图片和音频资源路径
ua_jar
contacts_file 联系人文件,方便快速拨打电话
do_register 是否在软件启动时向服务器注册用户
do_unregister 是否在软件启动注册前,撤销所有已注册的信息
audio
audio_port
send_file
recv_file
send_only
recv_only
是否开启音频
音频使用端口
通话接通时,发送给对方的音频文件,在前期测试时可用来方便检测通话质
量,此文件格式未详细分析,只知道可将 recv_file 录制的文件作为此文件
通话联通时,录制对方发送的音频到此文件
是否只发送语音包
是否只接受语音包
三、SIP 协议
主要的 SIP 协议参见 RFC3428 中文说明文件,这里只以图示的方式介绍几个部分。
3.1 注册
3.2 邀请通话
UACUASREGISTER401 UnauthorizedREGISTER200 OK发出注册请求由于注册信息不全,返回用户错误补充全用户信息后再次注册返回注册成功UACUASINVITE401 UnauthorizedINVITE200 OK发出邀请由于注册信息不全,返回用户错误补充全用户信息后再次邀请100 Trying返回邀请成功ACKINVITE100 Trying补充全用户信息后再次邀请UAC180 Ringing100 Trying200 OKACK
3.3 取消正在进行的邀请
3.4 结束通话
四、主要流程
4.1 消息处理一般流程
MjSip 的消息处理封装完善。假设类 A 要监听服务器发送的消息,对消息进行处理。在逻辑
层次类 A 将监听命令发送到类 B,类 B 传递给类 C,类 C 里有 listen 方法和 onReceivedMessage
UACUASCANCEL487 Request Terminated200 OK发出取消信令收到取消信令,发送487回复和200OK发出收到确认UACCANCEL发出取消信令ACK200 OKACK487 Request Terminated发出收到确认收到取消信令,发送487回复和200OKUACUASBYE200 OK发出结束信令发出收到确认UAC200 OK发出收到确认BYE发出结束信令
方 法 , listen 方 法 里 创 建 一 个 SipProvider 对 象 , 并 以 类 C 为 参 数 开 启 其
SipProvider.addSipProviderListener 方法。
addSipProviderListener 方法会将要监听的具体信息加入到过滤器中,当收到服务器的消息
时,一步步的用过滤器过滤,若有对应的过滤器找到消息,则用调用加入过滤器时设置的方
法(这里统一封装成 onReceivedMessage 方法)。
若收到服务器发送的监听消息,则会调用类 C 的 onReceivedMessage 方法进行处理。
这种处理消息的思路如图所示。
onReceivedMessage处理消息业务1业务2SipProvideronReceivedMessage控制类ListenListenaddSipProviderListeneronReceivedMessageprocessReceivedMessage过滤器)开始监听业务1事件收到业务1信息
4.2 注册流程
注册流程如上图所示,第一次注册若返回 401 未授权,则会整理摘要字段重新发送注册信息。
重新发送的注册信息时走的流程类似上图中的虚线所指。
GraphicalUA构造函数里产生RegisterAgent对象初始化参数,调用GraphicalUA的方法runGraphicalUA的方法run里调用方法loopRegister方法loopRegister里调用RegisterAgent的loopRegister方法RegisterAgent的loopRegister方法启动本类中的线程线程内循环调用register方法,直到注册超时或者成功register方法内生成注册消息(包括摘要),生成TransactionClient对象,并调用其request方法request方法中用addSipProviderListener添加对注册结果的监听,并用sendMessage发送注册信息监听到消息后,SipProvider会调用TransactionClient的onReceivedMessage方法处理,并判断注册成功与否处理成功/失败的消息是在RegisterAgent的方法中onTransSuccessResponse方法onTransFailureResponse方法
4.3 监听来电流程
监听来电即监听服务器发送的 INVITE 信令。
在程序启动时,创建 UserAgent 对象,并使用此对象的 listen 方法,此方法层层向下调用对
应的 listen 方法,直到到达 InviteTransactionServer 类。
此类的 listen 方法调用了 addSipProviderListener 添加 Sip 的监听器。以后每当收到数据时,
均会检查此监听器。若有监听器监听,则按照 transaction,dialog,method 层的顺序检查收
到数据是否与已有的监听器匹配。当无匹配的监听器时丢弃数据。
匹配到监听器后,会调用产生监听器的类的 onReceivedMessage 方法来处理数据。
对于收到的信令,会经过一些列的过滤器(是否是请求,是否是 INVITE),然后根据当前的
状态来判断处理信令。
GraphicalUA构造函数里产生UserAgent对象,UserAgent调用其listen方法监听来电其listen方法内产生ExtendedCall对象,调用其listen方法InviteDialog的listen方法内产生InviteTransactionServer对象,调用其listen方法InviteTransactionServer的listen方法内调用addSipProviderListenerExtendedCall集成InviteDialog类,InviteDialog有listen方法
4.4 语音处理
由于是 PC 端上的应用,语音的编解码都使用了现有的 SourceDataLine 库,调用 InputStream
的 read 方法读取麦克风获得数据,调用 OutputStream 的 write 方法将数据写入到声卡发声。
MjSip 音频的编码格式为 ulaw(也就是 PCMU)编码,帧率 8000,单声道,每个采样点 1Byte,
帧长度默认 160B(配置文件默认 160B,程序内默认 500B,实际还以配置文件为主)。
MjSip 也可通过修改配置文件来录制通话内容,或在接通电话时播放制定的音频内容给对
方。
录制内容格式为 ulaw 编码,帧率 8000,单声道,每个采样点 1 个 Byte,帧率 8000。
发 送 内 容 的 处 理 根 据 格 式 不 同 而 不 同 , 对 于 wav 格 式 的 文 件 , 调 用 函 数
AudioSystem.getAudioInputStream 转 成 流 送 出 去 ; 对 于 非 wav 格 式 文 件 , 直 接 通 过
FileInputStream 将数据转成流发送给对方。若录制为非 wav 格式的文件,则应保证录制的格
式和对方的语音解码格式相同。
注意在供写入的 I/O 流关闭前,需进行 flush 刷新处理,避免结束语音播放时出现循环噪音。
launchMediaApplication调用JAudioLauncherGraphicalUA构造函数里产生UserAgent对象,UserAgent调用其launchMediaApplication由AudioFormat生成格式format调用AudioInput传入format生成audio_input由所有信息调用RtpStreamSenderJAudioLauncher方法内调用JAudioLauncher的startMedia方法startMedia方法内调用AudioInput的play方法播放固定音频play方法调用rt.jar库中的线程startMedia方法内启动RtpStreamSender线程RtpStreamSender线程内用SourceDataLine包中InputStream类的的read方法读取麦克,用RTP发送出去startMedia方法内启动RtpStreamReceiver线程RtpStreamReceiver线程内用SourceDataLine包中OutputStream类的的write方法将数据发送到声卡发声