一直没空仔细研究下oSIP,最近看到其版本已经到了3.x版本,看到网上的许多帮助说明手册都过于陈旧,且很多文档内容有点误人子弟的嫌疑~~
Linux下oSIP的编译使用应该是很简单的,其Install说明文档里也介绍的比较清楚,本文主要就oSIP在Windows平台下VC6.0开发环境下的使用作出描述。
虽然oSIP的开发人员也说明了,oSIP只使用了标准C开发库,但许多人在Windows下使用oSIP时,第一步就被卡住了,得不到oSIP的LIB库和DLL库,也就没有办法将oSIP使用到自己的程序中去,所以第一步,我们将学习如何得到oSIP的静态和动态链接库,以便我们自己的程序能够使用它们来成功编译和执行我们的程序。
第一阶段:
------------------------------------------------------
先创建新工程,网上许多文档都介绍创建一个Win32动态链接库工程,我们这里也一样,创建一个空白的工程保存。
同样,将oSIP2版本3.0.1 src目录下的Osipparser2目录下的所有文件都拷到我们刚创建的工程的根目录下,在VC6上操作:
Project-Add To Project-Files
将所有的源程序和头文件都加入到工程内,保存工程。
这时,我们可以尝试编译一下工程,你会得到许多错误提示信息,其内容无非是找不到osipparser2/xxxxx.h头文件之类。
处理:在Linux下,我们一般是将头文件,lib库都拷到/usr/inclue;/usr/lib之类的目录下,c源程序里直接写#include 时,能直接去找到它们,在VC里,同样的,最简单的方法就是将oSIP2源码包中的Include目录下的osipparser2目录直接拷到我们的Windows下默认包含目录即可,这个目录在VC6的Tool-Options-Directories里设置,(当然,如果你知道这一步,也可以不用拷贝文件,直接在这里把oSIP源码包所在目录加进来就可以了),默认如果装在C盘,目录则为C:\Program Files\Microsoft Visual Studio\VC98\Include。
这时,我们再次编译我们的工程,顺利编译,生成osipparser2.dll,这时,网上很多文档里可能直接就说,这一步也会生成libs目录,里面里osipparser2.lib文件,但我们这里没有生成:)
最简单的方法,不用深究,直接再创建一个工程,同上述创建动态链接库方法,创建一个Win32静态链接库工程,直接编译,即可得到osipparser2.lib。
------------------------------------------------------
上面,我们得到了Osip的解析器开发库,下面再编译完整的Osip协议栈开发库,同样照上述方法,分别创建动态链接库工程和静态链接库工程,只是要拷的文件换成src下的osip目录下文件和include下的osip目录,得到osip2.dll和osip2.lib。
在编译osip2.dll这一步可能会再次得到错误,内容含义是找不到链接库,所以,我们要把前面编译得到的osipparser2.lib也拷到osip工程目录下,并在VC6中操作:
Project-Setting-Link中的Object/Library Modules:
kernel32.lib user32.lib ... xxx.lib之类的内容最后增加: osipparser2.lib
保存工程后再次编译,即可成功编译osip2.dll。
------------------------------------------------------
至此,我们得到了完整的oSIP开发库,使用时,只需在我们的程序里包含oSIP的头文件,工程的链接参数里增加osipparser2.lib和osip2.lib即可。
------------------------------------------------------
下面我们验证一下我们得到的开发库,并大概了解一下OSIP的语法规范。
在VC里创建win32控制台程序工程,将libosip源码包的SRC目录下的Test目录内的C源程序随便拷一个到工程时,直接编译(工程设置里照前文方法在link选项里增加osip2.lib,osipparser2.lib引用我们之前成功编译得到的静态库文件)就可以运行(带参数运行,参数一般为一个文本文件,同样从Test目录的res目录里拷一个与源文件同名的纯文本文件到工程目录下即可)。
该目录下的若干文件基本上是测试了Osip的一些基本功能函数,例如URI解析之类,可以大概了解一下oSIP的语法规范和调用方法,同时也能校验一下之前编译的OSIP开发库能否正常使用,成功完成本项工作后,可以进入下一步具体的oSIP的使用学习了。
------------------------------------------------------
由于oSIP是比较底层的SIP协议栈实现,新手较难上手,而官方的示例大都是一些伪代码,需要有实际的例子程序参考学习,而最好的例子就是同样官方发布的oSIP的扩展开发库exosip2,使用exoSIP可以很方便地快速创建一个完整的SIP程序(只针对性地适用于SIP终端开发用,所以我们这里只是用它快速开发一个SIP终端,用来更方便地学习oSIP,要想真正掌握SIP的开发,需要掌握oSIP并熟读RFC文档才行,exoSIP不是我们的最终学习目的),通过成功编译运行一个自己动手开发出的程序,再由浅入深应该是初学都最好的学习方法通过对使用exosip开发库的使用创建自己的SIP程序,熟悉后再一个函数一个函数地深入学习exosip提供的接口函数,就可以深入理解osip 了,达到间接学习oSIP的目的,同时也能从eXoSIP中学习到正确使用oSIP的良好的编程风格和语法格式。
而要成功编译ExoSIP,似乎许多人被难住了,直接在XP-sp2上,用VC6,虽然你使用了eXoSIP推荐的winsock2.h,但是会得到一个sockaddr_storage结构不能识别的错误,因为vc6自带的开发库太古董了,需要升级系统的Platform SDK,下载地址如下:
PSP2FULLInstall.htm(VC6的支持已经停止,这是VC6能使用的最新SDK)
成功安装后编译前需加OSIP_MT宏,以启用线程库,否则在程序中使用eXoSIP库时会出错,而编译时也会得到许多函数未定义的Warning提示,编译得到exosip2.lib供我们使用,当然,在此之前需要成功编译了osip2和osipparser2,而在之后的实际使用时,发现oSIP也需要增加OSIP_MT宏,否则OSIP_MT调用oSIP的线程库时会出错,所以我们需要重新编译oSIP了:),因为eXosip是基于oSIP的(同上方式创建静态和动态链接库工程,并需在Link中手工添加oSIP和oSIPparser的lib库)。
------------------------------------------------------
创建新工程,可以是任意工程,我们从最简单的Win32控制台程序开始,为了成功使用oSIP,我们需要引用相关库,调用相关头文件,经过多次试验,发现需要引用如下的库:
exosip2.lib osip2.lib osipparser2.lib WSock32.Lib IPHlpApi.Lib WS2_32.Lib Dnsapi.lib
其中,除了我们上面编译得到的三个oSIP库外,其它库都是系统库,其中有一些是新安装的Platform SDK所新提供的。
至此,我们有了一个简单的开发环境了,可以充分利用网上大量的以oSIP为基础的代码片段和官方说明文档开始具体函数功能的测试和使用了:)
------------------------------------------------------
我们先进行一个简单的纯SIP信令(不带语音连接建立)的UAC的SIP终端的程序开发的试验(即一个只能作为主叫不能作为被叫的的SIP软电话模型),我们创建一个MFC应用程序,对话框模式,照上面的说明,设置工程包含我们上面得到的oSIP的相关开发库及SDK的一些开发库,并且由于默认LIBC的冲突,需要排除MSVCRT[D]开发库(其中D代表Debug模式下,没有D表示Release模式下),直接使用eXosip的几个主要函数就可以创建一个基本的SIP软电话模型。
其主要流程为:
初始化eXosip库-启动事件监听线程-向SIP Proxy注册-向某SIP终端(电话号码)发起呼叫-建立连接-结束连接
初始化代码:
int ret = 0;
ret = eXosip_init ();
eXosip_set_user_agent("##YouToo0.1");
if(0 != ret)
{
AfxMessageBox("Couldn't initialize eXosip!\n");
return false;
}
ret = eXosip_listen_addr (IPPROTO_UDP, NULL, 0, AF_INET, 0);
if(0 != ret)
{
eXosip_quit ();
AfxMessageBox("Couldn't initialize transport layer!\n");
return false;
}
启动事件监听线程:
AfxBeginThread(sip_uac,(void *)this);
向SIP Proxy注册:
eXosip_clear_authentication_info();
eXosip_add_authentication_info(uname, uname, upwd, "md5", NULL);
real_send_register(30); /* 自定义函数代码请见源码 */
发起呼叫(构建假的SDP描述,实际软电话使用它构建RTP媒体连接):
osip_message_t *invite = NULL; /* 呼叫发起消息体 */
int i = eXosip_call_build_initial_invite (&invite, dest_call, source_call, NULL, "## YouToo test demo!");
if (i != 0)
{
AfxMessageBox("Intial INVITE failed!\n");
}
char localip[128];
eXosip_guess_localip (AF_INET, localip, 128);
snprintf (tmp, 4096,
"v=0\r\n"
"o=josua 0 0 IN IP4 %s\r\n"
"s=conversation\r\n"
"c=IN IP4 %s\r\n"
"t=0 0\r\n"
"m=audio %s RTP/AVP 0 8 101\r\n"
"a=rtpmap:0 PCMU/8000\r\n"
"a=rtpmap:8 PCMA/8000\r\n"
"a=rtpmap:101 telephone-event/8000\r\n"
"a=fmtp:101 0-11\r\n", localip, localip, "9900");
osip_message_set_body (invite, tmp, strlen(tmp));
osip_message_set_content_type (invite, "application/sdp");
eXosip_lock ();
i = eXosip_call_send_initial_invite (invite);
eXosip_unlock ();
挂断或取消通话:
int ret;
ret = eXosip_call_terminate(call_id, dialog_id);
if(0 != ret)
{
AfxMessageBox("hangup/terminate Failed!");
}
可以看到非常简单,再借助于oRTP和Mediastreamer开发库,来快速为我们的SIP软电话增加RTP和与系统语音API接口以及交互及语音编码功能,即可以快速开发出一个可用的SIP软电话,关于oRTP和Mediastreamer的相关介绍不是本文重点,将在有空的时候考虑增加相应使用教程,文章前提到的地方可以下载基本可用的完整SIP软电话的VC源码工程文件供参考使用,完全CopyLeft,欢迎转载,但请在转载时注明作者信息,谢谢!
第二阶段:
---------------------------------------------------
得到了一个SIP软电话模型后,我们可以根据软电话的实际运行表现(结合用Ethereal抓包分析)来进行代码的分析,以达到利用eXoSIP来辅助我们学习oSIP的最终目的(如要快速开发一个可用的SIP软电话,请至前面提到的论坛去下载使用oRTP和Mediastreamer快速搭建的一个基本完整可用的SIP软电话##YouToo 0.1版本的VC源码工程文件作参考)。
现在从eXosip的初始化函数开始入手,来分析oSIP的使用,这是第二阶段,第三阶段就是深入学习oSIP的源码了,但大多数情况下应该没有必要了,因为在第二阶段就有部分涉及到第三阶段的工作了,而且oSIP的源码也就大多是一些SIP数据的语法解析和状态机的实现,能深入理解了SIP协议后,这些只是一种实现方式,没必要完全去接受,而是可以用自己的方式和风格来实现一套,比如,更轻量化更有适用目的性的方式,oSIP则只起参考作用了。
eXosip_init()是eXosip的初始化函数,我们来看看它的内部实现:
首行是定义的 osip_t *osip,这在oSIP的官方手册里我们看到,所有使用oSIP的程序都要在最开始处声明一个osip_t的指针,并使用osip_init(&osip)来初始化这个指针,销毁这个资源使用osip_release(osip)即可。
我们可以在代码中看到很多OSIP_TRACE,这是调试输出宏调用了函数osip_trace,可以用ENABLE_TRACE宏来打开调试以方便我们开发调试。
其它就是很多的eXosip_t的全局变量eXosip的一些初始化操作,包括最上面的memset (&eXosip, 0, sizeof (eXosip))完全清空和下面的类似eXosip.user_agent = osip_strdup ("eXosip/" EXOSIP_VERSION)的exosip变量的一些初始值设置,其中有一个eXosip.j_stop_ua = 0应该是一个状态机开关,后面可以看到很多代码检测这个变量来决定是否继续流程处理,默认置成了0表示现在exosip的处理流程是就绪的,即ua是not stop的。
osip_set_application_context (osip, &eXosip)是比较有意思的,它让下面的eXosip_set_callbacks (osip)给osip设置大量的回调函数时,能让osip能访问到eXosip这个全局变量中设置的大量程序运行时交互的信息,相当于我们在VC下开启一个线程时,给线程传入的一个void指针指向我们的MFC应用程序的当前dialog对象实例,可以用void *osip_get_application_context (osip_t * osip)这个函数来取出指针来使用,不过好象exosip中并没有用到它,可能是留给个人自已扩展的吧:)
还能看到初始化代码前面有一段WIN32平台下的SOCK的初始化代码,可以知道eXosip是用的原生的winsock api函数,也就是我们可能以前学过的用VC和WINAPI写sock程序时(不是MFC),用到的那段SOCK初始代码,还有一段有意思的代码,就是jpipe()函数,它们返回的是一个管道,一个有2个整型数值的数组(一个进一个出),查看其代码发现,非WIN32平台是直接使用的pipe系统函数,而WIN32下则是用一对TCP的本地SOCK连接来模拟的管道,一个SOCK写一个SOCK读,这段代码是比较有参考价值的:)
j = 50;
while (aport++ && j-- > 0)
{
raddr.sin_port = htons ((short) aport);
if (bind (s, (struct sockaddr *) &raddr, sizeof (raddr)) < 0)
{
OSIP_TRACE (osip_trace (__FILE__, __LINE__, OSIP_WARNING, NULL,
"Failed to bind one local socket %i!\n", aport));
} else
break;
}
含义即,依次检测50个端口,从static int aport = 10500;即10500~10550端口找出一个可用的本地端口来绑定listen模拟pipe的一对sock。
eXosip_set_callbacks (osip)没有什么好看的,无非是和oSIP官方文档介绍的一样,设置一大堆的回调函数,关键是回调函数的实现,这也是许多初学者使用oSIP被卡壳的主要原因,不知道oSIP构建的程序是怎样跑起来的,随便选几个回调函数看一下eXosip是怎样实现的,有许多是形如下文的函数:
static void
cb_sndbye (int type, osip_transaction_t * tr, osip_message_t * sip)
{
OSIP_TRACE (osip_trace
(__FILE__, __LINE__, OSIP_INFO3, NULL, "cb_sndbye (id=%i)\r\n",
tr->transactionid));
}
即,只是打印一下调试,并没有完整实现什么功能,我们学习时,完全可以用相同的方法,定义一大堆回调函数,并不忙想怎么完全实现,先都是只打印一下调试信息,看具体的应用逻辑根据抓包测试分析和看调试看程序走到了哪一步,调用了哪一个回调,来明白具体回调函数要实现什么用途,再来实现代码就方便多了,当然,如果看透了RFC文档,应该从字面就能知道各个回调函数的用途了,这是后话,不是谁都能快速完全看懂RFC的,所以我们要参考eXosip:)
我们对其中的重要的回调函数进行逐个的分析:
---------------------------
osip_set_cb_send_message (osip, &cb_snd_message) SIP消息发送回调函数
这个函数可能是最重要的回调函数之一,消息发送,包括请求消息和回应消息,一般情况下,状态机的状态就是由它控制的,发起一个消息初始化一个状态机,回应一个消息对状态机修改,终结消息发送结束状态机……
看cb_snd_message的函数实现,要以发现,其主要代码是对参数中的要发送的消息osip_message_t * sip进行分析,找出消息要发送的真实char *host,int port的值(这些参数可以省略,但要发送消息肯定需要host和port,所以要从sip中解析),最后根据sip中解析出的传输方式是TCP还是UDP选择最终进行消息发送处理的函数cb_udp_snd_message,cb_tcp_snd_message处理(它们的参数一致,即本函数只是补全一些省略的参数并对消息进行合法性检查)。
**毕竟eXosip是一个通用的开发库,它考虑了要支持TCP,UDP,TCPs,IPV4,IPV6,WIN32,*nix,WINCE等等多样化的复杂环境,所以,我们可以略过我们暂时不需要的部分,比如,IPV6相关的代码实现等。
由于我们大多数情况下SIP是用的UDP,所以先来看一下cb_udp_snd_message的实现,它从全局变量exosip中获取可用的sock,并尽最大能力解析出host和port(??难道前面的函数还不够解析彻底??如最终仍无port信息则默认设置为5060),使用osip_message_to_str (sip, &message, &length)函数将要发送的格式化的SIP消息转换成能用SOCK传输的简单数据并发送即完成消息发送,代码中有许多复杂的环境探测和错误控制等等等等,我们可以暂时不用过多关注,可以继续向下,结尾处有一个keeplive相关代码,从代码字面分析,可能是SIP的Register消息的自动重发相关代码,可以在后面再细化分析。
cb_tcp_snd_essage的函数实现要比上文的udp的实现简单很多,主要是环境探测错误控制方面,因为毕竟tcp是稳定连接的,对比一下代码,可以看到主要流程还是将SIP消息转换后,发送到从SIP消息中解析出的host和port对应的目标。
看完两个函数,可以知道,eXosip需要有两个sock,是一个数组,0是给UDP用的,1是给TCP用的,要用SOCK当然要初始化,就是下文要介绍的eXosip的网络相关的初始化了,上面的exosip_init可以看成是这个开发库的系统初始化吧:)
至些,我们应该知道了oSIP开发的SIP应用程序的消息是从哪里发出的吧,对了,就是从这个回调函数里,所谓万事开头难,就象开发WIN32应用程序时,找到了WIN32程序的main函数入口下面的工作就好办了,下面就都是为一些事件消息开发对应的处理函数而已了:)
osip_set_kill_transaction_callback 事务终结回调函数
对应ICT,IST,NICT,NIST客户/服务器注册/非注册事务状态机的终结,主要是使用osip_remove_transaction (eXosip.j_osip, tr)将当前tr事务删除,再加上一系列的清理工作,其中,NICT即客户端的非Invite事务的清理比较复杂一些,要处理的内容也比较多,可以根据实际应用的情况进行有必要的清理工作:)
cb_transport_error 传输失败处理回调
对应于上面说到的四种事务状态机,如果它们在处理时失败,则在这时进行统一处理。
从代码可知,只是在NOTIFY,SUBSCRIBE,OPTION操作失败才进行处理,其它错误可直接忽略。
osip_set_message_callback 消息发送处理回调
根据type不同,表示不同的消息发送状态
OSIP_XXX_AGAIN 重发相关消息
OSIP_ICT_INVITE_SENT 发起呼叫
OSIP_ICT_ACK_SENT ACK回应
OSIP_NICT_REGISTER_SENT 发起注册
OSIP_NICT_BYE_SENT BYE发出
OSIP_NICT_CANCEL_SENT Cancel发出
OSIP_NICT_INFO_SENT,OSIP_NICT_OPTIONS_SENT,OSIP_NICT_SUBSCRIBE_SENT,OSIP_NICT_NOTIFY_SENT,OSIP_NICT_UNKNOWN_REQUEST_SENT
我们可以看到,eXosip没有对它们作任何处理,我们可以根据自己需要,比如,重发2xx消息前记录一下日志之类的,扩展一下retransmission的处理方式,发起Invite前记录一下通话日志等等。
OSIP_ICT_STATUS_1XX_RECEIVED uac收到1xx消息,一般是表示对端正在处理中,这时,主要是设置一下事务状态机的状态值,并对会话中的osip的一些参数根据返回值进行相应设置,里面有许多条件判断,但我们常用的一般是100,180,183的判断而已,暂时可以忽略里面复杂的判断代码。
OSIP_ICT_STATUS_2XX_RECEIVED uac收到2xx消息,这里主要跟踪一下Register情况下的2xx,表示注册成功,这时会更新一下exosip的注册字段值,以便让eXosip能自动维护uac的注册,BYE的2xx回应是终结消息,Invite的2xx回应,则主要是初始化一下会话相关的数据,表示已成功建立连接。
其它4xx,5xx,6xx则分别是对应的处理,根据实现情况进行概要的查看即可。
report_event (je, sip)是代码中用来进行事件处理的一个函数,跟踪后发现,其最终是使用了我们上文提到的jpipe管道,以便在状态机外实时观测状态机内的处理信息。
OSIP_NIST_STATUS_XXX_SENT即对应于上面的uac的处理,这里是uas的对应的消息处理,相比较于uac简单一点。
前面简单介绍了一下大量的回调函数及它们的概要处理逻辑,可能会比较混乱,暂时不用管它,只需要记得一个大概的形象,知道一个SIP处理程序是通过osip_set_cb_send_message回调函数来实现真实地发送各种SIP消息,并且SIP的标准事务模型是由oSIP实现好了,我们只需要给不同的事务状态设置不同的回调处理函数来处理事务,具体的状态变化和内部逻辑不用管就可以了。
下面来说一下消息处理回调函数用到的SOCK的初始化函数,即我们上面说的除了系统初始化外的网络初始化函数eXosip_listen_addr:
从上文知道了,系统将初始化两个SOCK,一个UDP一个TCP,但查看代码发现还有第三个,TCPs的,但好象还不能实用,现在不管它,代码首先是根据传输是UDP还是TCP来设置对应的数组值,并且如果没有提供IP地址和端口号,系统会自动取出本机网络接口并创建可用的SOCK(http_port的方式暂不用考虑)。
SOCK初始化后,如何开始SIP事务的呢?看到这个调用eXosip.j_thread = (void *) osip_thread_create (20000, _eXosip_thread, NULL),对的,这里启用了一个线程,即,eXosip是调用oSIP的线程函数(没用系统提供的线程函数,是为了跨平台)进行事务处理的状态机逻辑是在一个线程中处理的,这样就明白了为什么一直没能看到顺序执行下来的程序启动代码了,接下去看,线程实际处理函数是_eXosip_thread,这里面的代码中,我们看到了上文提到的状态机控制开关变量while (eXosip.j_stop_ua == 0),即,当j_stop_ua设置为1时,osip_thread_exit ()结束事务处理即程序终结,再接下去看,_eXosip_execute是最终的处理函数了,而且它在程序未终结情况下是一直逻辑在执行,注意,要启用oSIP的多线程宏OSIP_MT。
看到_eXosip_execute的代码中有很多时间函数和变量,仔细看,除去一些控制代码,主要处理函数是eXosip_read_message (1, lower_tv.tv_sec, lower_tv.tv_usec),即取出消息,1表示只取出一条消息,其代码量非常的大,但同样的,其中也许多的控制代码和错误检测代码,我们在查看时可以暂时忽略掉它们。
eXosip_read_message读取消息时,即没有采用sock的block也没有用非block方式,而是采用了select方式,具体应用可查询fd_set相关文档。
根据jpipe_read (eXosip.j_socketctl, buf2, 499),我们可以估计,buf2中应该是保存的我们的控制管道的数据,具体作用至些还没有表现出来,应该是用来反映一些状态机内部的警示之类的信息,实际的SIP的处理的状态机的数据是存放在buf中,使用_eXosip_recvfrom获取的,获取后sipevent = osip_parse (buf, i)解析,使用osip_find_transaction_and_add_event (eXosip.j_osip, sipevent)来查询事件对应的事务状态机,找到后就如同其注解所说明的,/* handled by oSIP ! */,即我们上文设置的那一大堆回调函数,至此,我们知道了整个SIP应用所处理的大概流程了。
如果没有找到事务状态机呢?直接丢弃吗?不是的,如果这是一个回应消息,但没有事务状态机处理它,那它是一个错误的,要进行清理后才能丢弃,而如果是一个请求,那更不能丢弃了,因为UAS事务状态机要由它来启动创建的(回应消息表示本地发出了请求消息,即UAC行为,事务状态机应是由启动UAC的代码初始化启动的),整个逻辑应该是很简单的,但eXosip的实现代码却非常多,可见其花了非常多的精力在保证会话的稳定性和应付网络复杂情况上,我们可以对其进行大量的精简来构建满足我们需求的代码实现。
先来看错误的回应消息的处理函数eXosip_process_response_out_of_transaction,可以看到其代码就是一大堆的赋值语句,XXX= NULL,即将一大堆的运行时变量清空,再调用osip_event_free清空事件,或者就是一些复杂的情况下,需要通过解析现在的运行时数据,从中分析出“可能”的正在等待回应的对端,并发送相关终结通知消息等等,可以根据实际需要进行简化。
请求事件的处理eXosip_process_newrequest,首先是对事件进行探测,MSG_IS_INVITE、MSG_IS_ACK、MSG_IS_REQUEST……,对事件进行所属状态机分类,随后使用_eXosip_transaction_init (&transaction,(osip_fsm_type_t) tx_type,eXosip.j_osip, evt->sip)根据探测结果进行状态机初始化,实际调用的是osip_transaction_init,初始化后即将事件入状态机osip_transaction_add_event (transaction, evt),由状态机自动处理后调用相应回调函数处理逻辑了。当然,eXosip为方便快速开发SIP终端应用,在下面又添加了许多自动化的处理代码,来和我们在回调函数中设置的处理代码相区分。
线程调用的事件处理函数代码最后是
if (eXosip.keep_alive > 0)
{
_eXosip_keep_alive ();
}
这段代码印证了上文提到了,keep_alive是用来设置是否自动重新注册,由_eXosip_keep_alive函数来实现自动将eXosip全局变量中保存的注册消息解析后自动根据需要重新向SIP服务器发起Register注册。
同样,因为注册消息发起是UAC的行为,将它放在这里,可以看出来所有事件消息的事务状态机处理都是在这里,只不过这里只创建UAS的事务状态机,UAC的事务状态机的创建则要继续到下面找了,从我们的YouToo软电话代码中可知,发起呼叫和发起注册分别调用了eXosip_call_send_initial_invite,eXosip_register_send_register这两个函数(另外用到的两个build函数则是分别构建这两个send函数要发送的SIP消息),查看这两个函数可知,UAC的事务处理状态机是在这里进行初始化的。
eXosip_register_send_register中可以看到是_eXosip_transaction_init (&transaction, NICT, eXosip.j_osip, reg)初始化UAC状态机,实际也同UAS是调用的osip_transaction_init函数,同样使用osip_transaction_add_event (transaction, sipevent)将事件入状态机,状态机随后将自动处理调用相应回调函数处理逻辑了。
另有osip_new_outgoing_sipmessage(reg),表示发送消息,到这里,我们应该可以理解,真实的发送操作,是要到由状态机处理后,调用了消息发送回调函数才真正地将注册消息发送出去的。
同注册消息发送,它是NICT状态机,呼叫消息的发送是ICT,由eXosip_call_send_initial_invite处理,_eXosip_transaction_init (&transaction, ICT, eXosip.j_osip, invite)初始化了状态机,之前还有一个eXosip_call_init是用来初始化eXosip的一些参数的,暂时不管它,同样osip_new_outgoing_sipmessage (invite)发送呼叫消息,但实际还是要状态机处理后调用消息发送回调函数真实发送呼叫请求函数的,osip_transaction_add_event (transaction, sipevent)则标准地,将事件入状态机,状态机将能处理随后的应用逻辑调用相应的回调函数了。
好了,作了这么多的分析,我们了解了eXosip是怎样调用oSIP来形成被我能方便地再次调用的了,可以看到,为了实现最大限度的跨平台和兼容性,代码中有大量的测试代码,宏定义和错误再处理代码,看起来非常吃力,但了解了其主要的调用框架:
初始化,回调函数设置,UAC和UAS事务处理状态机的启动,事件处理流程等,就可以基本明白了oSIP各个函数的主要作用和正确的用法了,下一步,可以参考eXosip来针对某个应用,去除掉大量暂时用不到的代码,来构建一个简单的SIP软电话和SIP服务器,来进一步深入oSIP学习应用了。
ortp的编译:
假如你现在采用的是D盘(本人采用ortp-0.9.1,VC6.0,Windows XP):
1.在D盘上新建一个ortp文件夹D:\ortp;
2.把ortp-0.9.1文件夹下的include文件夹整个拷到D盘的ortp文件夹里;
3.新建VC工程,选Win32 Dynamic-Link Library,Project name:ortp,
Location:D:\ortp\,然后选An empty DLL project,
接着在 Tools\options\Directories\include files设置如下:
C:\PROGRAM FILES\MICROSOFT PLATFORM SDK\INCLUDE
C:\Program Files\Microsoft Visual Studio\VC98\INCLUDE
C:\Program Files\Microsoft Visual Studio\VC98\MFC\INCLUDE
C:\Program Files\Microsoft Visual Studio\VC98\ATL\INCLUDE
D:\ortp\INCLUDE
D:\ortp\ortp
按如上设置即可。
注:以上Project\Setting按默认设置。
platform sdk一定要放到最上面,否则会出错。
4.删除新建的vc工程的默认的source files和header files,将ortp-0.9.1\src\ortp\
里面的所有*.c和*.h文件拷到D:\ortp\ortp\目录下,即和*.dsw和*.dsp文件放一起。
5.在vc工程中加入这些文件(.c,.h)
6.还要在D:\ortp\ortp,也就是当前文件夹中添加ortp-config.h,ortp-config-win32.h,glib.h,glibconfig.h这四个头文件。
其中有些地方有重定义,我做了一些修改。可以到如下地址下载:
这四个文件也可以从网上下载,google一下便可找到!
编译出现的问题的解决方法:
1.在posixtimer.c中加上mmsystem.h
在port.c中加上winsock2.h
并且连接头文件winmm.lib和ws2_32.lib
2.可能会遇到long long类型错误,可以改成long或者unsigned long,相应的1LL也要改成1L或者1UL
不过这样只是解决了编译的问题,变量如果可以为负那么unsigned long就会不可行,需要具体的去看程序
而且long的容量有限和作者初衷还是有差别的,不知道大家有什么更好的方法,请联系我,也欢迎讨论!
3.以下两个函数没有返回值,加上return 0;
WIN_cond_init()
WIN_cond_wait()
4.再有就是一些版本输出信息,如ortp.c中
ortp_message("oRTP-" ORTP_VERSION " initialized.");
我的做法是改为ortp_message("oRTP-0.9.1 initialized.");
ortp_min_version_required中的版本信息也没有定义,自己手工加上
#define ORTP_MAJOR_VERSION 0
#define ORTP_MINOR_VERSION 9
#define ORTP_MICRO_VERSION 1
5.对于integral size mismatch in argument错误是因为程序定义的参数类型和windows定义的不一样,
在调用windows库函数的时候要将变量用windows的类型强制转换一下,
如:将uint16_t类型的变量转换为WORD类型的(其实都是unsigned short)
按照如上的方法可以最终编译得到.dll库文件 (照理说应该可以得到.lib和.dll两个文件的,为什么这里得不到.lib文件,希望大虾告之)
至于要得到.lib文件的话,编译第3步要选择Win32 Static Library就可以了!
1. 抓取 Source Code :
Library for SIP :
libosip2-2.2.1.tar.gz
libeXosip-0.9.0.tar.gz
A easy program of SIP_call on oSIP library :
test.cpp
2. 安裝 oSIP
tar zxvf libosip2-2.2.1.tar.gz
cd ./libosip2-2.2.1
./configure
make
make install
預設安裝路徑 :
/usr/local/lib
-libosip2.a
-libosip2.la
-libosip2.so -> libosip2.so.3.0.0
-libosip2.so.3 -> libosip2.so.3.0.0
-libosip2.so.3.0.0
-libosipparser2.a
-libosipparser2.la
-libosipparser2.so -> libosipparser2.so.3.0.0
-libosipparser2.so.3 -> libosipparser2.so.3.0.0
-libosipparser2.so.3.0.0
/-pkgconfig
-libosip2.pc
3. 編譯範例 - Parser Via Header
--- 自己編 ---
cd ./src/test
範例程式都在 ./src/test 下
而 ./src 為 oSIP 的 source code
gcc tvia.c -o tvia
error : tvia.c:28:28: osip2/internal.h: No such file or directory
餵給他 gcc -I[include path] 代表增加 #include 時 search 路徑
gcc tvia.c -o tvia -I../../include
error : /tmp/cc1oGZ3o.o(.text+0x5b):
In function `main': : undefined reference to `osip_malloc_func' /tmp/cc1oGZ3o.o(.text+0x6b):
In function `main': : undefined reference to `osip_malloc_func' /tmp/cc1oGZ3o.o(.text+0xd1):
In function `main': : undefined reference to `osip_strncpy' /tmp/cc1oGZ3o.o(.text+0xfd):
In function `main': : undefined reference to `osip_via_init' /tmp/cc1oGZ3o.o(.text+0x131):
In function `main': : undefined reference to `osip_via_parse' /tmp/cc1oGZ3o.o(.text+0x14c):
In function `main': : undefined reference to `osip_via_to_str' /tmp/cc1oGZ3o.o(.text+0x173):
In function `main': : undefined reference to `osip_free_func' /tmp/cc1oGZ3o.o(.text+0x181):
In function `main': : undefined reference to `osip_free_func' /tmp/cc1oGZ3o.o(.text+0x1b6):
In function `main': : undefined reference to `osip_via_free' /tmp/cc1oGZ3o.o(.text+0x1f3):
In function `main': : undefined reference to `osip_free_func' /tmp/cc1oGZ3o.o(.text+0x201):
In function `main': : undefined reference to `osip_free_func' collect2:
ld returned 1 exit status
可以得知是 linker error
驗證 :
先編成 .o 檔
gcc tvia.c -c -o tvia.o -I../../include
發現沒有錯誤,但在 linker 就出錯了
ld tvia.o -o tvia –lc
所以,老樣子的餵給他
gcc -l[library name] -L[library path]
gcc tvia.c -o tvia -I../../include -losipparser2 -L/usr/local/lib
./tvia
error : ./tvia: error while loading shared libraries: libosipparser2.so.3: cannot open shared object file: No such file or directory
哭說找不到 libosipparser2.so.3 這 library
這裡基本上可以用複製的(/usr/lib),但我都用以下這招 :
vi /etc/ld.so.conf
加入 oSIP 預設安裝路徑 /usr/local/lib
ldconfig
更新
./tvia
error : Failed to open (null) file.
Usage: tvia vias.txt
哭說沒有這檔案
find / -name vias.txt
/usr/src/osip/libosip2-2.2.1/src/test/res/vias.txt
得到一點,各範例程式需要匯入的 *.txt 都放在 ./src/test/res 下
./tvia ./res/vias.txt
就可以看到 sipparser 的測試
--- 安裝時編 ---
./configure --enable-test
make
即可在 ./src/test 下看到已經編譯好的各個檔案
--- 靜態編譯 ---
gcc tvia.c -o tvia -I../../include -losipparser2 -L/usr/local/lib --static
4. 安裝 eXoSIP
tar zxvf libeXosip-0.9.0.tar.gz
cd ./libeXosip-0.9.0
./configure
這裡不加入任何參數,在 make 時會一直哭
所以我們加參數去 disable 一些沒有用到的
./configure \
--disable-phapi \
--disable-miniua \
--disable-josua \
--disable-glib \
--disable-ms \
--disable-ortp \
--disable-gsm \
--disable-ilbc
miniua : example
josua : example
phapi : softphone 面板上的按鍵
glib : 圖形 lib
ortp : rtp
ms : media streaming
ilbc : codec
gsm : codec
make
make install
預設安裝路徑 :
/usr/local/lib
-libeXosip.a
-libeXosip.la
-libeXosip.so -> libeXosip.so.3.0.0
-libeXosip.so.3 -> libeXosip.so.3.0.0
-libeXosip.so.3.0.0
5. 編譯範例 - 註冊
--- 自己編譯 ---
cd ./tools
與 oSIP 不同,eXoSIP 範例程式都放在 ./tools 下
gcc sip_reg.c -o sip_reg -leXosip -L/usr/local/lib
這裡一樣會哭說找不到某個 library
所以我們用 ldconfig 更新一下
./sip_reg
跳出 help內容
表示要輸入參數
Usage:
sipreg [required_options] [optional_options]
[required_options]
-r --proxy sip:proxyhost[:port]
-u --from sip:user@host[:port]
-c --contact sip:user@host[:port]
[optional_options]
-d --debug (log to stderr and do not fork
-e --expiry number (default 3600)
-f --firewallip N.N.N.N
-h --help
-l --localip N.N.N.N (force local IP address)
-p --port number (default 5060)
-U --username authentication username
-P --password authentication password
./sip_reg \
--proxy sip:140.125.33.240 \
--from sip:240100@140.125.33.240 \
--contact sip:240100@140.125.33.252 \
--username 240100 \
--password 240100 \
--expiry 180
抓出註冊封包來看
注意 : 執行後,程式會在 background 執行,所以需要改一下 expiry time 方便觀察
--- 安裝時編 ---
./configure --enable-tools
make
--- 靜態編譯 ---
gcc sip_reg.c -o sip_reg -leXosip -losip2 -losipparser2 -lpthread -L/usr/local/lib --static
PS. gcc 參數說明
~zbwei12b/program/gcc1.html
~zbwei12b/program/gcc-2.html
6. 建立 SIP_call
g++ test.cpp -o test
檔名為 *.cpp 且 include header 有 stdafx.h 檔
所以可以得知發展平台是在 winodows 的 c++ ( vc 之類的)
所以我們要用 g++ 編譯
test.cpp:23:20: stdafx.h: No such file or directory
test.cpp:29:19: conio.h: No such file or directory
test.cpp: In function `void __exit(int)':
test.cpp:103: `exit' undeclared (first use this function)
test.cpp:103: (Each undeclared identifier is reported only once for each function it appears in.)
test.cpp: In function `int main(int, char**)':
test.cpp:520: `_kbhit' undeclared (first use this function)
test.cpp:522: `_getch' undeclared (first use this function)
說找不到和看不懂一些檔案和程式,修改一下原始碼 :
vi test.cpp
刪除 _kbhit
將 _getch() 改為 getchar()
//#include "stdafx.h"
//#include ( DOS 用 - _getch())
g++ test.cpp -o test
又跳出 error :
test.cpp: In function `void __exit(int)':
test.cpp:103: `exit' undeclared (first use this function)
test.cpp:103: (Each undeclared identifier is reported only once for each function it appears in.)
應該是有些 header 檔沒有餵
利用之前有編譯過其他可以動的程式,如 tvia.c 和 sip_reg.c 的 include header
tvia.c
#include "stdio.h"
#include "stdlib.h"
#include "osip2/internal.h"
#include "osipparser2/osip_message.h"
sip_reg.c
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "netdb.h"
#include "syslog.h"
#include "pthread.h"
#include "eXosip/eXosip.h"
所以我新增 header :
vi test.cpp
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "netdb.h"
#include "syslog.h"
#include "pthread.h"
因為 #include "eXosip/eXosip.h" 重覆,所以不加
g++ test.cpp -o test
/tmp/ccMdnyE9.o(.text+0x14): In function `__exit(int)':
: the `gets' function is dangerous and should not be used.
/tmp/ccMdnyE9.o(.text+0x56): In function `josua_event_get()':
: undefined reference to `eXosip_event_wait'
.
.
. /tmp/ccMdnyE9.o(.gnu.linkonce.t._ZN5jcall5buildEP12eXosip_event+0xef)
: In function `jcall::build(eXosip_event*)': : undefined reference to `osip_strncpy' /tmp/ccMdnyE9.o(.gnu.linkonce.t._ZN5jcall5buildEP12eXosip_event+0x11a)
: more undefined references to `osip_strncpy'
follow collect2: ld returned 1 exit status
大概可以猜到是 linker 出錯
g++ test.cpp -o test -leXosip -losip2
利用之前編譯 sip_reg 的概念
餵給他 library
執行後會出現
: /tmp/ccCRNT0p.o(.text+0x14): In function `__exit(int)':
: the `gets' function is dangerous and should not be used.
基本上已經成功了 compiler
只是在哭說最好不要用 gets 這 function
如果真要去除這 warning
可以將原始碼內的函式 - __exit(int){} 殺掉
並將有呼叫到 __exit 改為 exit(1)
./test
沒有動作
只有出現 Hello World!
所以應該是編譯成功了
看一下 code
vi test.cpp
送 INVITE 位址
修改以下 :
i = eXosip_build_initial_invite(&invite,
"sip:130.139.45.174:5060", //to
"sip:130.139.44.249:5060", //from
NULL, "hello");
進degub mode
修改以下 :
//FILE* logfile = fopen( "logfile.txt", "w");
//osip_trace_initialize( (_trace_level)8, logfile );
osip_trace_initialize( (_trace_level)8, stdout );
g++ test.cpp -o test -leXosip -losip2
./test
看一下 code 就知道這支程式是用一些 hot key 來對應動作,如下 :
a - answering call
h - hangup
r - ringing
c - call
q - quit
LINPHONE ON ARM-LINUX (cross-compiling on host for target architecture)
编译linphone 需要库的支持,这其中就需要osip2,ogg,speex,ortp库的支持,这几种库分别支持各种通讯协议,如osip2支持的为sip协议,ortp支持的为rtp协议(即rtp协议的软件版)。
其中speex库另需要ogg库的支持,在编译过程中可以看到。
废话少说,下面开始交叉编译:
软件包: (在网上下载如下软件包)
1) linphone-1.2.0
2) libosip2-2.2.2
3) libogg-1.1.0
4) speex-1.1.11.1
5) oRTP ( linphone 包中自带,如果没有可以下载ortp-0.7.0版本)
到网站下载
其他工具见:http://telestarnotes.blogspot.com/2004_12_01_archive.html
A) 编译环境设置:
arm交叉编译工具::
下载 arm-linux-gcc-3.4.1.tar.bz2
直接解压到usr/local 中,或链接到/usr/local中
1)把arm-linux-gcc-3.4.1.tar.bz2 文件copy到usr/local 下,右击解压即可
2)任意目录下解压arm-linux-gcc-3.4.1.tar.bz2
#tar -xvjf arm-linux-gcc-3.4.1.tar.bz2 //解压
#export PATH=$PATH:/root/usr/local/arm/3.4.1/bin //环境变量设置:路径,链接,库
#export LD=/root/usr/local/arm/3.4.1/bin/arm-linux-ld
#export LDFLAGS=-L/root/usr/local/arm/3.4.1/arm-linux/lib
B) 交叉编译 libosip2-2.2.2
#cd libosip2-2.2.2 //到解压后的 libosip 目录下,以下同
#./configure --prefix=/root/armbuild -host=arm-linux --target=arm-linux --disable-static //配置文件
#make //编译
#make install //安装
// --prefix=/... :指定文件编译安装目录
//--host=..... :指定编译工具,默认的为gcc,此处为arm-linux(编译到arm上用的)
//--disable-static :禁止静态库(.a)链接,编译生成动态库(.so)
C) 交叉编译 libogg-1.1.0
#cd ../libogg-1.1.0
#./configure --prefix=/root/armbuild --host=arm-linux --target=arm-linux --disable-static --enable-fixed-point
#make
#make install
D) 交叉编译speex-1.1.11.1
#cd ../speex-1.1.11.1
#./configure --prefix=/root/armbuild --host=arm-linux --target=arm-linux --disable-static --enable-fixed-point --enable-arm-asm --with-ogg=/root/armbuild --with-ogg-libraries=/roo/armbuild/lib --with-ogg-headers=/root/armbuild/include/ogg
//要把/root/armbuild/lib 下的生成的ogg相应的库copy到交叉编译工具/usr/local/arm-linux/lib 下面,speex的编译需要ogg库的支持。
#make
#make install
//--with-ogg : 指定ogg生成的库的目录
// --with-ogg-libraries : ogg库,若没有,把生成的文件copy目录下面
//--with-ogg-headers :指定ogg头文件
E) 将libspeex编译成功的库文件copy到编译工具下的库中
#cp /root/armbuild/usr/lib/libspeex.so.2.0.0
/root/usr/local/arm/3.4.1/arm-linux/lib
#cd /root/usr/local/arm/3.4.1/arm-linux/lib //建立链接
#ln -s libspeex.so.2.0.0 libspeex.so
#ln -s libspeex.so.2.0.0 libspeex.so.2
//以上的 步骤 E),可以直接手动把libspeex.so.2.0.0 , libspeex.so, libspeex.so.2三个文件复制到加查编译工具下的库(lib)目录下即可
F) 交叉编译 linphone-1.2.0:
在编译linphone之前先将ortp复制到 linphone-1.2.0目录下
#cd /root/arm/linphone-1.2.0
#cd oRTP
##./configure --prefix=/root/armbuild --host=arm-linux --target=arm-linux --disable-static --enable-fixed-point --disable-glib
//需要加上 --disable-glib(禁止库),否则会应为缺少gthread 库而无法编译。
#make
#make install
G)将生成的库文件及链接复制到交叉编译工具的库中(同步骤E)
##cp /root/armbuild/usr/lib/libortp.so.2.0.0
/root/usr/local/arm/3.4.1/arm-linux/lib
#cd /root/usr/local/arm/3.4.1/arm-linux/lib
#ln -s libortp.so.2.0.0 libortp.so.2
#ln -s libortp.so.2.0.0 libortp.so
以此,就可以在没有图形界面(GUI)支持情况下编译linphone
H) #cd /root/arm/linphone-1.2.0
#./configure --prefix=/root/armbuild --host=arm-linux --target=arm-linux --disable-static --disable-glib --enable-gnome_ui=no --disable-manual
--enable-ipv6 --enable-alsa --with-osip=/root/armbuild --with-speex=/root/armbuild
#make
#make install
//--enable-alsa :使能alsa语音编解码方式,语音传输的另种方式为oss
//--enable-ipv6 :使能ipv6
以上的--disable-static 语句使编译不能生成静态库文件,即不能使库和可执行文件和为一体,而生成了另外单独的库支持文件。如果需要把库和可执行文件合成一个文件,那么不要使用这项。
就此编译完毕,在/root/armbuild/bin 文件中就可以找到可执行文件linphonec,把改文件及相应的库下载到开发板中,
在sch中相应的目录下输入./linphonec,即可以运行程序
如不能运行,则缺少库支持,把刚生成的相应的 *.so.*文件复制到开发板的lib目录下即可。
arm-linux编译osip,ortp-0.7.0,exosip2:
对osip2,ortp,exosip2 的编译可以生成josua软件
1)osip2-2.2.1
2)ortp-0.7.0
3)exosip2-1.9.1-pre16
准备:
在/root下新建文件夹josua
1)交叉编译osip:
在/home/libosip2-2.2.1文件夹下编译:
$CC=arm-linux-gcc CFLAGS=-O2 ./configure --prefix=/root/josua --disable-trace --disable-debug --disable-josua --host=arm-linux
$make
$make install
2)交叉编译ortp:
#cd /home/ortp-0.7.0
##./configure --prefix=/root/josua --host=arm-linux --target=arm-linux --disable-static --enable-fixed-point --disable-glib
//需要加上 --disable-glib(禁止glib库),否则会应为缺少gthread 等库而无法编译。 changed by Myth Wu
#make clean
#make
#make install
3)交叉编译exosip:
#cd /home/libeXosip2-1.9.1-pre16
#./configure --prefix=/root/josua --host=arm-linux --target=arm-linux --disable-static --disable-glib --enable-gnome_ui=no --disable-manual
--enable-ipv6 --enable-alsa --with-osip=/root/josua --disable-josua
#make
#make install
//--enable-alsa :使能alsa语音编解码方式,语音传输的另种方式为oss(一般为oss) changed by Myth Wu
//--enable-ipv6 :使能ipv6
至此交叉编译osip成功,生成可执行文件在/root/josua/bin中,链接库文件在/root/josua/lib中,下载可执行文件及相应的库文件到开发板即可以运行
阅读(1431) | 评论(0) | 转发(0) |