Linphone是一个开源的SIP客户端,底层协议栈使用osip和eXosip,在linphone的源码包中,包含了一个exosip目录,这里是完整的exosip代码,这个exosip大概是0.9版的,0.9版与最新的2.*版相差很大。所以在使用linphone之前,只要先安装好osip库就行了。
我在
www.antisip.com
注册了两个SIP用户,准备在我的机器上同时开两个linphone来试试,研究一下linphone的实现。
Linphone在编译后主要生成两个可执行的程序,一个是linphone,它是有图形界面的UA;另一个是linphonec,没有图形界面,是一个命令行的UA,我喜欢用命令行,所以只用linphonec。
我在本机注册了两个用户,一个raysan一个raypan,它们分别对应我在antisip服务器上的两个SIP帐户。在本机分别登录两个用户,分别启动linphonec程序,会自动在用户目录下生成.linphonerc文件,里面是一些配置信息,进行适当的修改后,重新启动linphonec,过一会儿提示已顺利注册。
使用call命令从raysan向raypan发起呼叫,raypan很快收到呼叫,但两边都开始报出很多alsa方面的错误,一定是因为两边都要响铃(一边是ringing,一边是coming call),造成了声卡资源争用,所以出了错。修改代码,从coreapi/exevents.c里的事件响应部分开始,找到在收到RINGING和NEW_CALL消息时所要调用的函数,在其中注掉几个发出声音的函数,就不会再响了,也不会再报错了,收到call的时候只会有一条文本信息打出来。不过要是回复answer命令来接听的话,还是会报错的,因为两边都要争设备嘛。
先从简单的研究入手。发现linphonec所提供的几种命令中没有用于即时消息的,于是想实现这个功能。Linphonec的代码比较简单,我对代码做了如下的添加和修改:
在linphonecore.c中加入函数:
int linphone_core_chat(LinphoneCore *lc, const char *url, const char *message) ...
{
char *route=NULL;
const char *from=NULL;
LinphoneProxyConfig *proxy=NULL;
osip_to_t *to_parsed=NULL;
char *to=NULL;
int err=0;
linphone_core_get_default_proxy(lc,&proxy);
if (!linphone_core_interpret_url(lc,url,&to,&to_parsed))...{
return -1;
}
if (proxy!=NULL) ...{
route=linphone_proxy_config_get_route(proxy);
from=linphone_proxy_config_get_identity(proxy);
}
/**//* if no proxy or no identity defined for this proxy, default to primary contact*/
if (from==NULL) from=linphone_core_get_primary_contact(lc);
printf("## : linphone_core_chat() : ");
printf("## : url = %s ", url);
printf("## : to = %s ", to);
printf("## : from = %s ", from);
printf("## : route = %s ",route);
printf("## : message = %s ",message);
eXosip_lock();
err = eXosip_message(to, (char *)from, route, (char *)message);
eXosip_unlock();
printf("## : err = %d ",err);
return 0;
}
char *route=NULL;
const char *from=NULL;
LinphoneProxyConfig *proxy=NULL;
osip_to_t *to_parsed=NULL;
char *to=NULL;
int err=0;
linphone_core_get_default_proxy(lc,&proxy);
if (!linphone_core_interpret_url(lc,url,&to,&to_parsed))...{
return -1;
}
if (proxy!=NULL) ...{
route=linphone_proxy_config_get_route(proxy);
from=linphone_proxy_config_get_identity(proxy);
}
/**//* if no proxy or no identity defined for this proxy, default to primary contact*/
if (from==NULL) from=linphone_core_get_primary_contact(lc);
printf("## : linphone_core_chat() : ");
printf("## : url = %s ", url);
printf("## : to = %s ", to);
printf("## : from = %s ", from);
printf("## : route = %s ",route);
printf("## : message = %s ",message);
eXosip_lock();
err = eXosip_message(to, (char *)from, route, (char *)message);
eXosip_unlock();
printf("## : err = %d ",err);
return 0;
}
在commands.c中修改命令表:
/**/
/*
* Commands table.
*/
LPC_COMMAND commands[] = ... {
...{ "help", lpc_cmd_help, "Print commands help", NULL },
...{ "call", lpc_cmd_call, "Call a SIP uri",
"'call <sip-url>' or 'c <sip-url>' "
": initiate a call to the specified destination."
},
...{ "terminate", lpc_cmd_terminate, "Terminate the current call",
NULL },
...{ "answer", lpc_cmd_answer, "Answer a call",
"Accept an incoming call."
},
...{ "proxy", lpc_cmd_proxy, "Manage proxies",
"'proxy list' : list all proxy setups. "
"'proxy add' : add a new proxy setup. "
"'proxy remove <index>' : remove proxy setup with number index. "
"'proxy use <index>' : use proxy with number index as default proxy. "
"'proxy unuse' : don't use a default proxy."
},
...{ "soundcard", lpc_cmd_soundcard, "Manage soundcards",
"'soundcard list' : list all sound devices. "
"'soundcard use <index>' : select a sound device. "
"'soundcard use files' : use .wav files instead of soundcard "
},
...{ "ipv6", lpc_cmd_ipv6, "Use IPV6",
"'ipv6 status' : show ipv6 usage status. "
"'ipv6 enable' : enable the use of the ipv6 network. "
"'ipv6 disable' : do not use ipv6 network."
},
...{ "refer", lpc_cmd_refer,
"Refer the current call to the specified destination.",
"'refer <sip-url>' or 'r <sip-url>' "
": refer the current call to the specified destination."
},
...{ "nat", lpc_cmd_nat, "Set nat address",
"'nat' : show nat settings. "
"'nat <addr>' : set nat address (and enable its use). "
"'nat use' : enable nat use (ip must be set). "
"'nat unuse' : disable nat use. "
},
...{ "call-logs", lpc_cmd_call_logs, "Calls history",
NULL },
...{ "friend", lpc_cmd_friend, "Manage friends",
"'friend list [<pattern>]' : list friends. "
"'friend call <index> : call a friend."
},
...{ "play", lpc_cmd_play, "play from a wav file",
"This feature is available only in file mode (see 'help soundcard') "
"'play <wav file>' : play a wav file."
},
...{ "record", lpc_cmd_record, "record to a wav file",
"This feature is available only in file mode (see 'help soundcard') "
"'record <wav file>' : record into wav file."
},
...{ "quit", lpc_cmd_quit, "Exit linphonec", NULL },
//Rayman
...{ "chat", lpc_cmd_chat, "Chat with someone","'chat <someone> <message>'" },
...{ (char *)NULL, (lpc_cmd_handler)NULL, (char *)NULL, (char *)NULL }
} ;
* Commands table.
*/
LPC_COMMAND commands[] = ... {
...{ "help", lpc_cmd_help, "Print commands help", NULL },
...{ "call", lpc_cmd_call, "Call a SIP uri",
"'call <sip-url>' or 'c <sip-url>' "
": initiate a call to the specified destination."
},
...{ "terminate", lpc_cmd_terminate, "Terminate the current call",
NULL },
...{ "answer", lpc_cmd_answer, "Answer a call",
"Accept an incoming call."
},
...{ "proxy", lpc_cmd_proxy, "Manage proxies",
"'proxy list' : list all proxy setups. "
"'proxy add' : add a new proxy setup. "
"'proxy remove <index>' : remove proxy setup with number index. "
"'proxy use <index>' : use proxy with number index as default proxy. "
"'proxy unuse' : don't use a default proxy."
},
...{ "soundcard", lpc_cmd_soundcard, "Manage soundcards",
"'soundcard list' : list all sound devices. "
"'soundcard use <index>' : select a sound device. "
"'soundcard use files' : use .wav files instead of soundcard "
},
...{ "ipv6", lpc_cmd_ipv6, "Use IPV6",
"'ipv6 status' : show ipv6 usage status. "
"'ipv6 enable' : enable the use of the ipv6 network. "
"'ipv6 disable' : do not use ipv6 network."
},
...{ "refer", lpc_cmd_refer,
"Refer the current call to the specified destination.",
"'refer <sip-url>' or 'r <sip-url>' "
": refer the current call to the specified destination."
},
...{ "nat", lpc_cmd_nat, "Set nat address",
"'nat' : show nat settings. "
"'nat <addr>' : set nat address (and enable its use). "
"'nat use' : enable nat use (ip must be set). "
"'nat unuse' : disable nat use. "
},
...{ "call-logs", lpc_cmd_call_logs, "Calls history",
NULL },
...{ "friend", lpc_cmd_friend, "Manage friends",
"'friend list [<pattern>]' : list friends. "
"'friend call <index> : call a friend."
},
...{ "play", lpc_cmd_play, "play from a wav file",
"This feature is available only in file mode (see 'help soundcard') "
"'play <wav file>' : play a wav file."
},
...{ "record", lpc_cmd_record, "record to a wav file",
"This feature is available only in file mode (see 'help soundcard') "
"'record <wav file>' : record into wav file."
},
...{ "quit", lpc_cmd_quit, "Exit linphonec", NULL },
//Rayman
...{ "chat", lpc_cmd_chat, "Chat with someone","'chat <someone> <message>'" },
...{ (char *)NULL, (lpc_cmd_handler)NULL, (char *)NULL, (char *)NULL }
} ;
并加入函数:
static int
lpc_cmd_chat(LinphoneCore *lc, char *args) {
char *url=args;
char *message =NULL;
while (*url && !isspace(*url)) ++url;
if (*url) {
*url='0';
message =url+1;
while (*message && isspace(*message)) ++message;
}
linphone_core_chat (lc,args,message);
return 1;
}
lpc_cmd_chat(LinphoneCore *lc, char *args) {
char *url=args;
char *message =NULL;
while (*url && !isspace(*url)) ++url;
if (*url) {
*url='0';
message =url+1;
while (*message && isspace(*message)) ++message;
}
linphone_core_chat (lc,args,message);
return 1;
}
上面程序最核心的目的就是要调用eXosip_message这个函数,这是0.9版exosip里的,在最新版中已面目全非。
顺利通过编译。运行!在raysan一端键入命令chat raypan hahaha,但遗憾的是在raypan这端并没有显示出有消息到来。用wireshark抓包,以下是其中两个包的截屏:
表明raysan这端的确发出了一个MESSAGE请求,但是从服务器回来了一个unauthored错误信息,有点想不通,试了很多次,结果一样,google一下,未果。
修改.linphonerc文件,把两个用户都作为本地用户,不向任何公共服务器注册,即本地用户raysan只向同是本地用户的raypan发MESSAGE请问,结果出人意料,MESSAGE消息收到了,但收到消息的不是raypan而是raysan自己,仔细想想,呵呵,也对,注意到上面的截图了吗,MESSAGE消息所在的UDP包的目标端口是5060,它的本意是SIP服务器开5060端口来接收SIP请求,但现在是在本机,raysan用的是5060,而raypan用的是5070,所以这个消息就又发给raysan了。如果是从raypan发给raysan的话,就一定能收到了。
尽管通过公共SIP服务器来发即时消息还没有实现,但经过上面的实验说明使用eXosip_message来发送即时消息是对的,离成功还差一步。