最近業務中需要做網絡電話,在研究sip協議,藉助osip2和eXosip2進行學習,
以下是初步編譯和實踐經驗,分享在此:
使用庫的版本:libosip2-3.6.0.tar,libeXosip2-3.6.0.tar
解壓libosip2-3.6.0.tar ,比如解壓目錄爲 /usr/local/libosip2-3.6.0,進入該目錄,
編譯該lib庫:
#./configure
#make
#make install
同理編譯libeXosip2
#./configure
#make
#make install
注意最後要更新庫,否則在程序執行時,可能會提示找不到庫,編譯後的庫文件及頭文件會默認放在/usr/local目錄下:
#ldconfig
在運行測試程序時,如果輸出類似以下的提示信息時,原因可能是沒有配置默認網關:
Debug=... network is unreachable
測試程序:uac.c 客戶端,可以實現註冊,撥號,振鈴,掛機等網絡電話事務, 調試已通過(sip服務器藉助 mjserver6 ,我的下載資源中提供)
/*
gcc -o exosip_phone exosip_phone.c -losip2 -leXosip2 -lpthread
*/
#include <eXosip2/eXosip.h>
#include <osip2/osip_mt.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
// #include <mediastreamer2/mediastream.h>
#include <osipparser2/osip_message.h>
#include <osipparser2/osip_parser.h>
#include <pthread.h>
#define WAIT_TIMER 200
#define REG_TIMER 30*1000
int doing;
int rtp_port;
int dtmfing, calling,picked;
int call_id ,dialog_id;
char *m_strAuth;
char *dtmf_str="2002";
//CString dtmf_str;
char *server_url="192.168.1.106";
char *dest_url="192.168.1.102";
char *server_port="5060";
char *local_port="5000";
char *username="10007";
char *password="10007";
char *telNum="10008";
// AudioStream *audio = NULL;
// RtpProfile *profile = NULL;
// RtpSession *session = NULL;
// OrtpEvQueue *q = NULL;
eXosip_event_t *uac_e; /* 事件處理 */
osip_message_t *ack = NULL; /* 響應消息 */
osip_message_t *answer = NULL; /* 請求消息的迴應 */
eXosip_event_t *uac_call; /* answer事件處理 */
osip_message_t *answer_call = NULL; /* answer請求消息的迴應 */
int build_media(int local_port, const char *remote_ip, int remote_port, int payload, const char *fmtp, int jitter, int ec, int bitrate)
{
if(payload != 0 && payload != 8)
{
/* 目前僅支持0,8 711ulaw,711alaw */
return -1;
}
// PayloadType *pt;
// profile = rtp_profile_clone_full(&av_profile);
// q = ortp_ev_queue_new();
/*
pt = rtp_profile_get_payload(profile, payload);
if (pt==NULL)
{
printf("codec error!");
return -1;
}
*/
// if (fmtp != NULL) payload_type_set_send_fmtp(pt, fmtp);
//if (bitrate > 0) pt->normal_bitrate = bitrate;
/*
if (pt->type != PAYLOAD_VIDEO)
{
printf("audio stream start!");
audio = audio_stream_start(profile, local_port, remote_ip, remote_port, payload, jitter, ec);
if (audio)
{
session = audio->session;
}
else
{
printf("session error!");
return -1;
}
}
else
{
printf("codec select error!");
return -1;
}
rtp_session_register_event_queue(session, q);
*/
return 0;
}
int real_send_register(int expires)
{
char identity[100];
char registerer[100];
char localip[128];
int id;
int ret;
eXosip_guess_localip (AF_INET, localip, 128);
printf("%s\n",localip);
//sprintf(identity,"sip:%s@%s:%s",username,localip,local_port);
sprintf(identity,"sip:%s@%s",username,server_url);
sprintf(registerer,"sip:%s:%s",server_url,server_port);
osip_message_t *reg = NULL;
eXosip_lock ();
id = eXosip_register_build_initial_register(identity, registerer, NULL,expires, ®);
eXosip_unlock ();
if(0 > id)
{
printf("register init Failed!\n");
return -1;
}
eXosip_lock ();
eXosip_clear_authentication_info(); //去除上次加入的錯誤認證信息,再應用新輸入的鑑權信息
eXosip_add_authentication_info(username, username,password, "md5", NULL);
ret = eXosip_register_send_register(id, reg);
eXosip_unlock ();
if(0 != ret)
{
printf("register send Failed!\n");
return -1;
}
return id;
}
int sip_ua_monitor()
{
int ret = -1;
char *payload_str; /* 服務器優先編碼值 */
char localip[128];
char tmp[4096];
char dtmf[50] = {0};
int reg_remain = REG_TIMER;
usleep(500);
printf("Event monitor for uac/uas start!\n");
/* 響應SDP(用於UAC) */
sdp_message_t * msg_rsp = NULL;
sdp_connection_t * con_rsp = NULL;
sdp_media_t * md_rsp = NULL;
/* 請求SDP(用於UAS) */
sdp_message_t * msg_req = NULL;
sdp_connection_t * con_req = NULL;
sdp_media_t * md_req = NULL;
char out_str[100] = {0};
eXosip_lock ();
eXosip_automatic_action();
eXosip_unlock ();
while(doing)
{
eXosip_lock ();
uac_e = eXosip_event_wait (0, WAIT_TIMER);
eXosip_unlock ();
reg_remain = reg_remain - WAIT_TIMER;
if(reg_remain < 0)
{
//超時,重新註冊
eXosip_lock ();
eXosip_automatic_refresh();
eXosip_unlock ();
reg_remain = REG_TIMER;
printf("register timeout,retry!");
}
if(dtmfing)
{
strcpy(dtmf, dtmf_str);
int index;
for( index=0; index<50; index++)
{
//依次讀取字符
if(dtmf[index] == '\0') break;
/* 發送DTMF */
eXosip_lock();
//audio_stream_send_dtmf(audio, dtmf[index]);
eXosip_unlock();
sprintf(out_str, "DTMF send <%c> OK!", dtmf[index]);
printf("%s",out_str);
usleep(500);
}
dtmfing = 0;
}
if (uac_e == NULL)
{
//DEBUG_INFO("nothing");
continue;
}
eXosip_lock ();
eXosip_default_action(uac_e); /* 處理407加入鑑權信息 */
eXosip_unlock ();
if(NULL != uac_e->response)
{
//UAC 消息處理前檢查
sprintf(out_str, "%d %s\n", uac_e->response->status_code, uac_e->response->reason_phrase);
printf(out_str);
if(487 == uac_e->response->status_code)
{
printf("qu xiao hujiao chenggong\n");
continue;
}
if(480 == uac_e->response->status_code)
{
//480 無應答
printf("wu ying da\n");
picked = 0;
calling = 0;
call_id = 0;
dialog_id = 0;
continue;
}
if(401 == uac_e->response->status_code)
{
//480 無應答
printf("register again!\n");
eXosip_add_authentication_info(username, username,password, NULL, NULL);
osip_message_t *rereg;
eXosip_register_build_register(uac_e->rid, 300, &rereg);//
//取回認證的字符串authorization
{
printf("取回認證字符串,重新註冊");
osip_authorization_t * auth;
char *strAuth=NULL;
osip_message_get_authorization(rereg,0,&auth);
osip_authorization_to_str(auth,&strAuth);
m_strAuth=strAuth;//保存認證字符串
}
eXosip_register_send_register(uac_e->rid,rereg);
continue;
}
}
if(NULL != uac_e->request)
{
}
if(NULL != uac_e->ack)
{
}
switch (uac_e->type)
{
case EXOSIP_CALL_SERVERFAILURE:
case EXOSIP_CALL_RELEASED:
/* 服務器錯誤或對方忙 */
printf("busy");
call_id = 0;
dialog_id = 0;
picked = 0;
calling = 0;
printf("Dest or Server Busy!");
break;
/* UAS 處理事件 */
case EXOSIP_MESSAGE_NEW: //新的消息到來
{
printf("EXOSIP_MESSAGE_NEW!\n");
if(MSG_IS_MESSAGE(uac_e->request)) //如果接受到的消息類型是MESSAGE
{
osip_body_t *body;
osip_message_get_body (uac_e->request, 0, &body);
printf ("I get the a msg : %s\n", body->body);
}
//按照規則,需要回復200 OK信息
eXosip_message_build_answer (uac_e->tid, 200,&answer);
eXosip_message_send_answer (uac_e->tid, 200,answer);
}
break;
case EXOSIP_CALL_INVITE:
sprintf(out_str, "recevfrom %s",uac_e->request->from->url->string);
printf("%s\n",out_str);
eXosip_lock ();
eXosip_call_send_answer(uac_e->tid, 180, NULL);
if(0 != eXosip_call_build_answer(uac_e->tid, 200, &answer))
{
eXosip_call_send_answer(uac_e->tid, 603, NULL);
printf("error build answer!");
continue;
}
eXosip_unlock ();
call_id = uac_e->cid; //供掛斷電話上下文操作
dialog_id = uac_e->did;
//構建本地SDP消息供媒體建立
eXosip_guess_localip(AF_INET, localip, 128);
snprintf (tmp, 4096,
"v=0\r\n"
"o=youtoo 1 1 IN IP4 %s\r\n"
"s=##youtoo demo\r\n"
"c=IN IP4 %s\r\n"
"t=0 0\r\n"
"m=audio %d 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-15\r\n", localip, localip, rtp_port);
//設置回覆的SDP消息體,下一步計劃分析消息體
eXosip_lock ();
osip_message_set_body(answer, tmp, strlen(tmp));
osip_message_set_content_type(answer, "application/sdp");
//// 解析SDP
msg_req = eXosip_get_remote_sdp(uac_e->did);
con_req = eXosip_get_audio_connection(msg_req);
md_req = eXosip_get_audio_media(msg_req);
eXosip_unlock ();
//payload_str = (char *)osip_list_get(&md_req->m_payloads, 0); //取主叫媒體能力編碼
//暫時只支持0/8 711u/711a
calling = 1;
uac_call=uac_e;
answer_call=answer;
/* while(!picked)
{
//未接通進入循環檢測
usleep(200);
}
eXosip_unlock ();
eXosip_call_send_answer(uac_e->tid, 200, answer);
eXosip_unlock ();
printf("200 ok 發送"); */
break;
case EXOSIP_CALL_CANCELLED:
/* 拒絕接聽 */
call_id = 0;
dialog_id = 0;
picked = 0;
calling = 0;
printf("hujiao jujue\n");
break;
case EXOSIP_CALL_ACK:
/* 返回200後收到ack才建立媒體 */
if(calling)
{
/* 建立RTP連接 */
printf("this is answer\n");
/*
ret = build_media(rtp_port, con_req->c_addr, atoi(md_req->m_port), 0, NULL, 0, 0, 0);
if(!ret)
{
printf("媒體建立失敗,無法創建通話,請掛斷!");
//pMainWnd->OnHang();
}
*/
}
break;
/* UAC 處理事件 */
case EXOSIP_REGISTRATION_SUCCESS:
printf("textinfo is %s\n", uac_e->textinfo);
break;
case EXOSIP_CALL_CLOSED:
/* if(audio)
{
//被動關閉媒體連接(遠端觸發)
eXosip_lock ();
audio_stream_stop(audio);
ortp_ev_queue_destroy(q);
rtp_profile_destroy(profile);
eXosip_unlock ();
audio = NULL;
q = NULL;
profile = NULL;
printf("audio stream stoped!");
}
*/
printf("(對方已掛斷)");
call_id = 0;
dialog_id = 0;
picked = 0;
calling = 0;
break;
case EXOSIP_CALL_PROCEEDING:
printf("(查找連接中..)");
break;
case EXOSIP_CALL_RINGING:
printf("(對方振鈴)");
call_id = uac_e->cid;
dialog_id = uac_e->did;
/*
RingStream *r;
MSSndCard *sc;
sc=ms_snd_card_manager_get_default_card(ms_snd_card_manager_get());
r=ring_start("D:\\mbstudio\\vcwork\\YouToo\\dial.wav",2000,sc);
Sleep(10);
ring_stop(r);
*/
break;
case EXOSIP_CALL_ANSWERED:
//ring_stop(ring_p);
printf("(對方已接聽)");
call_id = uac_e->cid;
dialog_id = uac_e->did;
eXosip_lock ();
eXosip_call_build_ack (uac_e->did, &ack);
eXosip_call_send_ack (uac_e->did, ack);
/* 響應SIP消息中SDP分析 */
msg_rsp = eXosip_get_sdp_info(uac_e->response);
con_rsp = eXosip_get_audio_connection(msg_rsp);
md_rsp = eXosip_get_audio_media(msg_rsp);
/* 取服務器支持的最優先的編碼方式 */
// payload_str = (char *)osip_list_get(&md_rsp->m_payloads, 0);
eXosip_unlock ();
/* 建立RTP連接 */
printf("this is call\n");
/*
ret = build_media(rtp_port, con_rsp->c_addr, atoi(md_rsp->m_port), atoi(payload_str), NULL, 0, 0, 0);
if(!ret)
{
printf("媒體建立失敗,無法創建通話,請掛斷!");
//pMainWnd->OnHang();
}
*/
break;
default:
break;
}
eXosip_event_free (uac_e);
fflush(stdout);
}
return 0;
}
void OnHang()
{
int ret;
/*
if(audio)
{
//主動關閉媒體連接(本地端)
eXosip_lock ();
audio_stream_stop(audio);
ortp_ev_queue_destroy(q);
rtp_profile_destroy(profile);
eXosip_unlock ();
printf("audio stream stoped!");
audio = NULL;
q = NULL;
profile = NULL;
}
*/
eXosip_lock ();
ret = eXosip_call_terminate(call_id, dialog_id);
eXosip_unlock ();
if(0 != ret)
{
printf("hangup/terminate Failed!\n");
}
else
{
printf("(已掛斷)");
call_id = 0;
dialog_id = 0;
picked = 0;
calling = 0;
}
}
int sip_init()
{
int ret = 0;
ret = eXosip_init ();
eXosip_set_user_agent("liangzi1.0");
if(0 != ret)
{
printf("Couldn't initialize eXosip!\n");
return -1;
}
ret = eXosip_listen_addr (IPPROTO_UDP, NULL, 5000, AF_INET, 0);
if(0 != ret)
{
eXosip_quit ();
printf("Couldn't initialize transport layer!\n");
return -1;
}
//AfxBeginThread(sip_ua_monitor,(void *)this);
pthread_t id;
//改動
int i=pthread_create(&id,NULL,(void *) sip_ua_monitor,NULL);
if(i!=0)
{
printf ("Create pthread error!\n");
return -1;
}
/* rtp */
// ortp_init();
//ortp_set_log_level_mask(ORTP_MESSAGE|ORTP_WARNING|ORTP_ERROR|ORTP_FATAL);
/* media */
//ms_init();
return 0;
}
int main()
{
char *payload_str;
char localip[128];
char tmp[4096];
char command;
char dtmf[50] = {0};
int reg_remain = REG_TIMER;
char out_str[100] = {0};
osip_message_t *invite=NULL;
osip_message_t *info=NULL;
osip_message_t *message=NULL;
char source_call[100];
char dest_call[100];
doing = 1; /* 事件循環開關 */
calling = 0; /* 是否正在被叫(判斷ack類型) */
picked = 0;
dtmfing = 0;
if(0 != sip_init ())
{
printf("Quit!\n");
return -1;
}
call_id = 0;
dialog_id = 0;
printf("r zhu ce \n\n");
printf("c qu xiao zuce \n\n");
printf("i hujiao qingqiu\n\n");
printf("h guaduan\n\n");
printf("a jieting\n\n");
printf("q tuichu\n\n");
printf("s INFO\n\n");
printf("m MESSAGE\n\n");
int flag=1;
while(flag)
{
//輸入命令
printf("Please input the command:\n");
scanf("%c",&command);
getchar();
switch(command)
{
case 'r':
real_send_register(2000);
//usleep(500);
//real_send_register(2000);
break;
case 'i':
eXosip_guess_localip (AF_INET, localip, 128);
sprintf(source_call,"sip:%s@%s:%s",username,localip,local_port);
sprintf(dest_call,"sip:%s@%s:%s",telNum,server_url,server_port);
printf("src_call:%s \n dest_call:%s \n",source_call,dest_call);
//char tmp[4096];
bzero(tmp,4096);
int i = eXosip_call_build_initial_invite (&invite, dest_call, source_call, NULL, "This is a call invite");
if (i != 0)
{
printf("Intial INVITE failed!\n");
}
// char localip[128];
// eXosip_guess_localip (AF_INET, localip, 128);
snprintf (tmp, 4096,
"v=0\r\n"
"o=liangzi 0 0 IN IP4 %s\r\n"
"s=##liangzi demo\r\n"
"c=IN IP4 %s\r\n"
"t=0 0\r\n"
"m=audio %d 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-15\r\n", localip, localip, rtp_port);
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 ();
break;
case 'h': //掛斷
OnHang();
break;
case 'c':
// real_send_register(0);
//printf("This modal is not commpleted!\n");
break;
case 's': //傳輸INFO方法
eXosip_call_build_info(dialog_id,&info);
snprintf(tmp,4096,"\nThis is a sip message(Method:INFO)");
osip_message_set_body(info,tmp,strlen(tmp));
//格式可以任意設定,text/plain代表文本信息
osip_message_set_content_type(info,"text/plain");
eXosip_call_send_request(dialog_id,info);
break;
case 'm':
bzero(tmp,4096);
sprintf(source_call,"sip:%s@%s:%s",username,localip,local_port);
sprintf(dest_call,"sip:%s@%s:%s",telNum,dest_url,server_port);
//傳輸MESSAGE方法,也就是即時消息,和INFO方法相比,我認爲主要區別是:
//MESSAGE不用建立連接,直接傳輸信息,而INFO消息必須在建立INVITE的基礎上傳輸
printf("the method : MESSAGE\n");
eXosip_message_build_request(&message,"MESSAGE",dest_call,source_call,NULL);
//內容,方法, to ,from ,route
snprintf(tmp,4096,"This is a sip message(Method:MESSAGE)");
osip_message_set_body(message,tmp,strlen(tmp));
//假設格式是xml
osip_message_set_content_type(message,"text/xml");
eXosip_message_send_request(message);
break;
case 'q':
doing = -1;
flag=0;
usleep(1000); //保證事件線程能退出(線程循環檢測時間毫秒級<1000)
//主動關閉媒體連接(本地端)
printf("Bye!\n");
break;
case 'a':
picked = 1;
printf("answer the call\n");
eXosip_lock ();
eXosip_call_send_answer(uac_call->tid, 200, answer_call);
eXosip_unlock ();
printf("200 ok send");
printf("connection completed!\n");
break;
case 'd':
dtmfing=1;
break;
default:
printf("input error! please input again\n");
break;
}
}
return(0);
}