当前位置: 首页 > 工具软件 > eXosip > 使用案例 >

基于exosip 编写呼叫流程实例

周浩博
2023-12-01

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zzqgtt/article/details/87179815
1、已经编译生成号exosip相关库,

2、复制如下代码生成UAC,该实例使用的是windows控制台程序,对于sdp的解析部分需修改一下

代码如下:

#include <eXosip2/eXosip.h>
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
 
int main(int argc, char *argv[])
{
 
	struct eXosip_t *context_eXosip;
 
	eXosip_event_t *je;
	osip_message_t *reg = NULL;
	osip_message_t *invite = NULL;
	osip_message_t *ack = NULL;
	osip_message_t *info = NULL;
	osip_message_t *message = NULL;
 
	int call_id, dialog_id;
	int i, flag;
	int flag1 = 1;
 
	char *identity = "sip:140@192.168.0.115";   //UAC1,端口是15060
	char *registar = "sip:133@192.168.0.115:15061"; //UAS,端口是15061
	char *source_call = "sip:140@192.168.0.115";
	char *dest_call = "sip:814@192.168.0.103:15061";
	//identify和register这一组地址是和source和destination地址相同的
	//在这个例子中,uac和uas通信,则source就是自己的地址,而目的地址就是uac1的地址
	char command;
	char tmp[4096];
 
	printf("r   向服务器注册\n\n");
	printf("c   取消注册\n\n");
	printf("i   发起呼叫请求\n\n");
	printf("h   挂断\n\n");
	printf("q   推出程序\n\n");
	printf("s   执行方法INFO\n\n");
	printf("m   执行方法MESSAGE\n\n");
 
	//初始化
	struct eXosip_t *excontext;
	excontext = eXosip_malloc();
	i = eXosip_init(excontext);
 
	if (i != 0)
	{
		printf("Couldn't initialize eXosip!\n");
		osip_free(excontext);
		return -1;
	}
	else
	{
		printf("eXosip_init successfully!\n");
	}
 
	//绑定uac自己的端口15060,并进行端口监听
	i = eXosip_listen_addr(excontext, IPPROTO_UDP, NULL, 15060, AF_INET, 0);
	if (i != 0)
	{
		eXosip_quit(excontext);
		fprintf(stderr, "Couldn't initialize transport layer!\n");
		osip_free(excontext);
		return -1;
	}
	flag = 1;
 
	while (flag)
	{
		//输入命令
		printf("Please input the command:\n");
		scanf_s("%c", &command);
		getchar();
 
		switch (command)
		{
		case 'r':
			printf("This modal is not completed!\n");
			break;
		case 'i'://INVITE,发起呼叫请求
			i = eXosip_call_build_initial_invite(excontext, &invite, dest_call, source_call, NULL, "This is a call for conversation");
			if (i != 0)
			{
				printf("Initial INVITE failed!\n");
				break;
			}
			//符合SDP格式,其中属性a是自定义格式,也就是说可以存放自己的信息,
			//但是只能有两列,比如帐户信息
			//但是经过测试,格式vot必不可少,原因未知,估计是协议栈在传输时需要检查的
			_snprintf_s(tmp, 4096,
				"v=0\r\n"
				"o=anonymous 0 0 IN IP4 0.0.0.0\r\n"
				"t=0 0\r\n"
				"m=audio 62100 RTP/AVP 114 0 8 101\r\n"
				"a=rtpmap:114 AMR/8000\r\n"
				"a=fmtp:114 octet-align=1;mode-set=7,0\r\n"
				"a=rtpmap:0 PCMU/8000\r\n"
				"a=rtpmap:8 PCMA/8000\r\n"
				"m=video 62102 RTP/AVP 102 99\r\n"
				"a=rtpmap:102 H264/90000\r\n"
				"a=rtpmap:99 MP4V-ES/90000\r\n"
				);
 
			osip_message_set_body(invite, tmp, strlen(tmp));
			osip_message_set_content_type(invite, "application/sdp");
 
			eXosip_lock(excontext);
			i = eXosip_call_send_initial_invite(excontext, invite); //invite SIP INVITE message to send
			eXosip_unlock(excontext);
 
			//发送了INVITE消息,等待应答
			flag1 = 1;
			while (flag1)
			{
				je = eXosip_event_wait(excontext, 0, 200); //Wait for an eXosip event
				//(超时时间秒,超时时间毫秒) 
				if (je == NULL)
				{
					printf("No response or the time is over!\n");
					break;
				}
				switch (je->type)   //可能会到来的事件类型
				{
				case EXOSIP_CALL_INVITE:   //收到一个INVITE请求
					printf("a new invite received!\n");
					break;
				case EXOSIP_CALL_PROCEEDING: //收到100 trying消息,表示请求正在处理中
					printf("proceeding!\n");
					break;
				case EXOSIP_CALL_RINGING:   //收到180 Ringing应答,表示接收到INVITE请求的UAS正在向被叫用户振铃
					printf("ringing!\n");
					printf("call_id is %d,dialog_id is %d \n", je->cid, je->did);
					break;
				case EXOSIP_CALL_ANSWERED: //收到200 OK,表示请求已经被成功接受,用户应答
					printf("ok!connected!\n");
					call_id = je->cid;
					dialog_id = je->did;
					printf("call_id is %d,dialog_id is %d \n", je->cid, je->did);
 
					//回送ack应答消息
					eXosip_call_build_ack(excontext, je->did, &ack);
					eXosip_call_send_ack(excontext, je->did, ack);
					flag1 = 0; //推出While循环
					break;
				case EXOSIP_CALL_CLOSED: //a BYE was received for this call
					printf("the other sid closed!\n");
					break;
				case EXOSIP_CALL_ACK: //ACK received for 200ok to INVITE 
					printf("ACK received!\n");
					break;
				default: //收到其他应答
					printf("other response!\n");
					break;
				}
				eXosip_event_free(je); //Free ressource in an eXosip event
			}
			break;
 
		case 'h':   //挂断
			printf("Holded!\n");
 
			eXosip_lock(excontext);
			eXosip_call_terminate(excontext, call_id, dialog_id);
			eXosip_unlock(excontext);
			break;
 
		case 'c':
			printf("This modal is not commpleted!\n");
			break;
 
		case 's': //传输INFO方法
			eXosip_call_build_info(excontext, dialog_id, &info);
			_snprintf_s(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(excontext, dialog_id, info);
			break;
 
		case 'm':
			//传输MESSAGE方法,也就是即时消息,和INFO方法相比,我认为主要区别是:
			//MESSAGE不用建立连接,直接传输信息,而INFO消息必须在建立INVITE的基础上传输
			printf("the method : MESSAGE\n");
			eXosip_message_build_request(excontext, &message, "MESSAGE", dest_call, source_call, NULL);
			//内容,方法,      to       ,from      ,route
			_snprintf_s(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(excontext, message);
			break;
 
		case 'q':
			eXosip_quit(excontext);
			printf("Exit the setup!\n");
			flag = 0;
			break;
		}
	}
	osip_free(excontext);
	return(0);
}
 
 

invite sdp 格式参照如下:

INVITE sip:609099@10.4.54.2:5060 SIP/2.0
Via: SIP/2.0/UDP 10.4.46.1:49552;rport;branch=z9hG4bK2032385714
From: <sip:8006@10.4.46.1:49552>;tag=1798441523
To: <sip:609099@10.4.54.2:5060>
Call-ID: 3012165000
CSeq: 21 INVITE
Contact: <sip:8006@10.4.46.1:49552>
Proxy-Authorization: Digest username="8006", realm="BCC", nonce="0af1a282e6687dc7fe2442989bf23ea4", uri="sip:609099@10.4.54.2:5060", response="51f136f0c58570397ba1b94c256d12b9", algorithm=MD5
Content-Type: application/sdp
Call-Info: <witen:609099>;type=videoupload;fmt=D1/CIF/QCIF;camera=1;user_confirm=0;mute=1
Max-Forwards: 70
User-Agent: #SDK Console 3.0
Subject: ## Dispatch Console!
Content-Length:   307

v=0
o=DConsole 1 1 IN IP4 10.4.46.1
s=##DConsole demo
c=IN IP4 10.4.46.1
t=0 0
m=audio 62100 RTP/AVP 114 0 8 101
a=rtpmap:114 AMR/8000
a=fmtp:114 octet-align=1;mode-set=7,0
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
m=video 62102 RTP/AVP 102 99
a=rtpmap:102 H264/90000
a=rtpmap:99 MP4V-ES/90000

 

3、复制如下代码生成UAS,该实例使用的是windows控制台程序,对于sdp的解析部分需修改一下,请参照音频的获取方式修改视频sdp解析代码。

代码如下:

# include <eXosip2/eXosip.h>
# include <stdio.h>
# include <stdlib.h>
# include <Winsock2.h>
 
 
int main(int argc, char *argv[])
{
	eXosip_event_t *je = NULL;
	osip_message_t *ack = NULL;
	osip_message_t *invite = NULL;
	osip_message_t *answer = NULL;
	sdp_message_t *remote_sdp = NULL;
	int call_id, dialog_id;
	int i, j;
	int id;
	char *sour_call = "sip:140@192.168.0.115";
	char *dest_call = "sip:133@192.168.0.115:15060";//client ip
	char command;
	char tmp[4096];
	char localip[128];
	int pos = 0;
	//初始化sip
	struct eXosip_t *excontext;
	excontext = eXosip_malloc();
	i = eXosip_init(excontext);
	if (i != 0)
	{
		printf("Can't initialize eXosip!\n");
		return -1;
	}
	else
	{
		printf("eXosip_init successfully!\n");
	}
	i = eXosip_listen_addr(excontext, IPPROTO_UDP, NULL, 15061, AF_INET, 0);
	if (i != 0)
	{
		eXosip_quit(excontext);
		fprintf(stderr, "eXosip_listen_addr error!\nCouldn't initialize transport layer!\n");
	}
	for (;;)
	{
		//侦听是否有消息到来
		je = eXosip_event_wait(excontext, 0, 50);
		//协议栈带有此语句,具体作用未知
		eXosip_lock(excontext);
		eXosip_default_action(excontext, je);
		//eXosip_automatic_refresh();
		eXosip_automatic_action(excontext);
		eXosip_unlock(excontext);
		if (je == NULL)//没有接收到消息
			continue;
		// printf ("the cid is %s, did is %s/n", je->did, je->cid);
		switch (je->type)
		{
		case EXOSIP_MESSAGE_NEW://新的消息到来
			printf(" EXOSIP_MESSAGE_NEW!\n");
			if (MSG_IS_MESSAGE(je->request))//如果接受到的消息类型是MESSAGE
			{
				{
					osip_body_t *body;
					osip_message_get_body(je->request, 0, &body);
					printf("I get the msg is: %s\n", body->body);
					//printf ("the cid is %s, did is %s/n", je->did, je->cid);
				}
				//按照规则,需要回复OK信息
				eXosip_message_build_answer(excontext, je->tid, 200, &answer);
				eXosip_message_send_answer(excontext, je->tid, 200, answer);
			}
			break;
		case EXOSIP_CALL_INVITE:
		{	//得到接收到消息的具体信息
								   printf("Received a INVITE msg from %s:%s, UserName is %s, password is %s\n", je->request->req_uri->host,
									   je->request->req_uri->port, je->request->req_uri->username, je->request->req_uri->password);
								   //得到消息体,认为该消息就是SDP格式.
								   remote_sdp = eXosip_get_remote_sdp(excontext, je->did);
								   call_id = je->cid;
								   dialog_id = je->did;
 
								   eXosip_lock(excontext);
								   eXosip_call_send_answer(excontext, je->tid, 180, NULL);
								   i = eXosip_call_build_answer(excontext, je->tid, 200, &answer);
								   if (i != 0)
								   {
									   printf("This request msg is invalid!Cann't response!\n");
									   eXosip_call_send_answer(excontext, je->tid, 400, NULL);
								   }
								   else
								   {
									   _snprintf_s(tmp, 4096,
										   "v=0\r\n"
										   "o=anonymous 0 0 IN IP4 0.0.0.0\r\n"
										   "t=1 10\r\n");
 
									   //设置回复的SDP消息体,下一步计划分析消息体
									   //没有分析消息体,直接回复原来的消息,这一块做的不好。
									   osip_message_set_body(answer, tmp, strlen(tmp));
									   osip_message_set_content_type(answer, "application/sdp");
 
									   eXosip_call_send_answer(excontext, je->tid, 200, answer);
									   printf("send 200 over!\n");
								   }
								   eXosip_unlock(excontext);
 
								   //显示出在sdp消息体中的attribute 的内容,里面计划存放我们的信息
								   printf("the INFO is :\n");
 
 
								   //sdp_media_t *connection = eXosip_get_audio_media(remote_sdp);
 
								   //if (!connection)
 
									  // return false;
 
								   //char*  audio_ip = connection->c_addr; //audio_ip
 
								   sdp_media_t * audio_sdp = eXosip_get_audio_media(remote_sdp);
 
								   if (!audio_sdp)
 
									   return false;
 
								   char* audio_port = audio_sdp->m_port; //audio_port
 
								   for (int i = 0; i < audio_sdp->a_attributes.nb_elt; i++)
 
								   {
									   sdp_attribute_t *attr = (sdp_attribute_t*)osip_list_get(&audio_sdp->a_attributes, i);
									   printf("%s : %s\n", attr->a_att_field, attr->a_att_value);
								   }
 
								   while (!osip_list_eol(&(remote_sdp->a_attributes), pos))
								   {
									   sdp_attribute_t *at;
 
									   at = (sdp_attribute_t *)osip_list_get(&remote_sdp->a_attributes, pos);
									   printf("%s : %s\n", at->a_att_field, at->a_att_value);//这里解释了为什么在SDP消息体中属性a里面存放必须是两列
 
									   pos++;
								   }
								   while (!osip_list_eol(&(remote_sdp->m_medias), pos))
								   {
									   sdp_attribute_t *at;
 
									   at = (sdp_attribute_t *)osip_list_get(&remote_sdp->m_medias, pos);
									   printf("%s : %s\n", at->a_att_field, at->a_att_value);//这里解释了为什么在SDP消息体中属性a里面存放必须是两列
 
									   pos++;
								   }
		}
			break;
		case EXOSIP_CALL_ACK:
			printf("ACK recieved!\n");
			// printf ("the cid is %s, did is %s/n", je->did, je->cid); 
			break;
		case EXOSIP_CALL_CLOSED:
			printf("the remote hold the session!\n");
			// eXosip_call_build_ack(dialog_id, &ack);
			//eXosip_call_send_ack(dialog_id, ack); 
			i = eXosip_call_build_answer(excontext, je->tid, 200, &answer);
			if (i != 0)
			{
				printf("This request msg is invalid!Cann't response!\n");
				eXosip_call_send_answer(excontext, je->tid, 400, NULL);
 
			}
			else
			{
				eXosip_call_send_answer(excontext, je->tid, 200, answer);
				printf("bye send 200 over!\n");
			}
			break;
		case EXOSIP_CALL_MESSAGE_NEW://至于该类型和EXOSIP_MESSAGE_NEW的区别,源代码这么解释的
			/*
			// request related events within calls (except INVITE)
			EXOSIP_CALL_MESSAGE_NEW,          < announce new incoming request.
			// response received for request outside calls
			EXOSIP_MESSAGE_NEW,          < announce new incoming request.
			我也不是很明白,理解是:EXOSIP_CALL_MESSAGE_NEW是一个呼叫中的新的消息到来,比如ring trying都算,所以在接受到后必须判断
			该消息类型,EXOSIP_MESSAGE_NEW而是表示不是呼叫内的消息到来。
			该解释有不妥地方,仅供参考。
			*/
			printf(" EXOSIP_CALL_MESSAGE_NEW\n");
			if (MSG_IS_INFO(je->request)) //如果传输的是INFO方法
			{
				eXosip_lock(excontext);
				i = eXosip_call_build_answer(excontext, je->tid, 200, &answer);
				if (i == 0)
				{
					eXosip_call_send_answer(excontext, je->tid, 200, answer);
				}
				eXosip_unlock(excontext);
				{
					osip_body_t *body;
					osip_message_get_body(je->request, 0, &body);
					printf("the body is %s\n", body->body);
				}
			}
			break;
		default:
			printf("Could not parse the msg!\n");
		}
	}
}

UAS 回复的sdp 格式请参照

SIP/2.0 200 OK
Via: SIP/2.0/UDP 10.4.46.1:49552;rport=49552;branch=z9hG4bK2032385714
From: <sip:8006@10.4.46.1:49552>;tag=1798441523
To: <sip:609099@10.4.54.2:5060>;tag=229035134
Call-ID: 3012165000
CSeq: 21 INVITE
Contact: <sip:609099@10.4.54.2:5060>
Content-Type: application/sdp
Call-Info: <witen:609099>;src_vpn_out=0;type=videoupload;fmt=D1;camera=1;user_confirm=9;mute=1;emergency_callInd=0;priority=5
User-agent: BCC/3.0
Content-Length:   215

v=0
o=B2BUA 1 1 IN IP4 10.4.54.2
s=BCC Switch
c=IN IP4 10.4.54.2
t=0 0
m=audio 26438 RTP/AVP 114
a=rtpmap:114 AMR/8000
a=fmtp:114 octet-align=1;mode-set=7
m=video 26442 RTP/AVP 102
a=rtpmap:102 H264/90000

4、以上程序的ip请修改成自己实际电脑的ip,根据UAC和UAS协商好的端口即可进行收流解析播放。

5、有问题可以给我留言 一起探讨。

————————————————
版权声明:本文为CSDN博主「zzqgtt」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zzqgtt/article/details/87179815

 

 类似资料: