在NUC972上利用pjsip实现VOIP网关

     如果有问题,请加QQ群 891339868 进行交流

    上次在NUC972上移植好了pjsip库后,实现了一个简单的VOIP网关的功能,由于前一段时间再忙别的事情,没有来得及整理,今天忙里偷闲,总结一下,废话少说,直接步入正题。

    pjsip库里面提供了好多例程,实现简单的VOIP功能还是挺简单的,主要包含以下几个部分:

1、初始化,废话少说,直接上代码:

int my_pjsua_init(void)
{
	pjsua_acc_id acc_id;
    pj_status_t status;

    /* Create pjsua first! */
    status = pjsua_create();
    if (status != PJ_SUCCESS) 
		error_exit("Error in pjsua_create()", status);
    /* Init pjsua */
    {
		pjsua_config cfg;
		pjsua_logging_config log_cfg;
		pjsua_media_config media_cfg;
		
		pjsua_config_default(&cfg);
		cfg.cb.on_incoming_call = &on_incoming_call;
		cfg.cb.on_call_media_state = &on_call_media_state;
		cfg.cb.on_call_state = &on_call_state;
		
		pjsua_logging_config_default(&log_cfg);
		log_cfg.console_level = 4;
		
		pjsua_media_config_default(&media_cfg);
		media_cfg.clock_rate = 8000;
		media_cfg.snd_clock_rate = 8000;
		media_cfg.no_vad = PJ_TRUE;
		media_cfg.ec_options = 2;
		media_cfg.quality = 5;
		media_cfg.channel_count = 2;

		status = pjsua_init(&cfg, &log_cfg, &media_cfg);
		if (status != PJ_SUCCESS) 
			error_exit("Error in pjsua_init()", status);

		pj_str_t host = pj_str("speex");
		status = pjsua_codec_set_priority(&host, 0 );
		if (status != PJ_SUCCESS) 
			error_exit("pjsua_codec_set_priority()", status);
		host = pj_str("G722");
		status = pjsua_codec_set_priority(&host, 0 );
		if (status != PJ_SUCCESS) 
			error_exit("pjsua_codec_set_priority()", status);
    }
    /* Add UDP transport. */
    {
		pjsua_transport_config cfg;

		pjsua_transport_config_default(&cfg);
		cfg.port = 8060;
		status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg, NULL);
		if (status != PJ_SUCCESS) 
			error_exit("Error creating transport", status);
    }
	
    /* Initialization is done, now start pjsua */
    status = pjsua_start();
    if (status != PJ_SUCCESS) 
		error_exit("Error starting pjsua", status);
	
    /* Register to SIP server by creating SIP account. */
    {
		pjsua_acc_config cfg;
		pjsua_acc_config_default(&cfg);
		cfg.id = pj_str("sip:"SIP_USER"@"SIP_DOMAIN);
		cfg.reg_uri = pj_str("sip:"SIP_DOMAIN);
		cfg.cred_count = 1;
		cfg.cred_info[0].realm = pj_str("*");//域名最好设置成*,要不然很容易注册不上
		cfg.cred_info[0].scheme = pj_str("Digest");
		cfg.cred_info[0].username = pj_str(SIP_USER);
		cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
		cfg.cred_info[0].data = pj_str(SIP_PASSWD);

		status = pjsua_acc_add(&cfg, PJ_TRUE, &acc_id);
		if (status != PJ_SUCCESS) 
			error_exit("Error adding account", status);
    }
	
    return 0;
}

    这个是我的初始化函数,下面详细分析一下:

1、使用pjsua_create()这个函数创建一个sip用户代理;

2、对sip用户代理的基本配置、日志打印配置和媒体配置进行初始化,具体流程都是使用相对应的默认配置函数进行默认配置,分别为pjsua_config_default,pjsua_logging_config_default,pjsua_media_config_default。

基本配置的功能主要是配置在运行过程中,需要调用的回调函数,这些回调函数是库自动调度的,我这里使用了三个回调函数:

      on_incoming_call:来电处理函数。来具体看一下代码:

void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
					 pjsip_rx_data *rdata)
{
	
	pjsua_call_info ci;
	
	PJ_UNUSED_ARG(acc_id);	
	PJ_UNUSED_ARG(rdata);
		
	pjsua_call_get_info(call_id, &ci);
		
	PJ_LOG(3,(THIS_FILE, "Incoming call from %.*s!!",	
				 (int)ci.remote_info.slen,	
				 ci.remote_info.ptr));
	
	/* Automatically answer incoming calls with 200/OK */	
	pjsua_call_answer(call_id, 200, NULL, NULL);	
}

我这里使用了来电自动接听的功能,所以使用了pjsua_call_answer(call_id, 200, NULL, NULL)这个函数。

      on_call_media_state:更改媒体状态回调函数。来具体看一下代码:

void on_call_media_state(pjsua_call_id call_id)
{
	pjsua_call_info ci;

	pjsua_call_get_info(call_id, &ci);
	
	if (ci.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
		// When media is active, connect call to sound device.
		pjsua_conf_connect(ci.conf_slot, 0);
		pjsua_conf_connect(0, ci.conf_slot);
	}
}

这个函数主要是在建立连接时使用。

      on_call_state:调用当前呼叫状态回调函数,主要是当前呼叫状态发生状态时,库会自动调用该函数,来具体看一下代码:

void on_call_state(pjsua_call_id call_id, pjsip_event *e)	
{
	pjsua_call_info ci;
	
	PJ_UNUSED_ARG(e);
	pjsua_call_get_info(call_id, &ci);
	PJ_LOG(3,(THIS_FILE, "Call %d state=%.*s", call_id,
				 (int)ci.state_text.slen,
				 ci.state_text.ptr));
}

从代码中可以看出来,主要是打印当前呼叫的状态。

由于网关没有考虑自己主动挂断当前呼叫,所以没考虑其他回调函数。

接着来看一下日志打印配置,我这里配置的打印级别是4,这个数越大,级别越低,来看一下代码:

pjsua_logging_config_default(&log_cfg);
log_cfg.console_level = 4;

打印级别太高的话,控制台输出信息太多,挺讨厌的。

接着来看一下媒体配置信息,来看一下代码:

pjsua_media_config_default(&media_cfg);
media_cfg.clock_rate = 8000;
media_cfg.snd_clock_rate = 8000;
media_cfg.no_vad = PJ_TRUE;
media_cfg.ec_options = 2;
media_cfg.quality = 5;
media_cfg.channel_count = 2;

从代码上可以看出来,时钟和声音时钟都为8Khz,VAD(有效语音侦测功能)关闭,AEC算法选择的是webtrc,语音质量是5,最高10,双声道。这里重点说一下AEC算法和语音质量,NUC972性能有限,AEC算法的选择和语音质量的选择要慎重,不然的话会出现声音卡的现象,如果是其他高性能处理器,这个就不用考虑了。

到此为止,这三个配置基本上完成,就可以调用函数pjsua_init初始化了,如下代码所示:

 status = pjsua_init(&cfg, &log_cfg, &media_cfg);
 if (status != PJ_SUCCESS) 
    error_exit("Error in pjsua_init()", status);

虽然现在基本的配置已经完成了,但是在NUC972上,还需要对编解码算法的支持做一下限制,因为NUC972是一个ARM9,工作频率只有300MHz,在运行speex算法和G722算法时,性能是不够的,会出现非常严重的卡顿现象,所以需要将这两种算法给禁止了,当然,如果使用其他平台可以根据自己的实际情况设置,具体的配置看一下下面的代码:

    pj_str_t host = pj_str("speex");
	status = pjsua_codec_set_priority(&host, 0 );
	if (status != PJ_SUCCESS) 
		error_exit("pjsua_codec_set_priority()", status);
	host = pj_str("G722");
	status = pjsua_codec_set_priority(&host, 0 );
	if (status != PJ_SUCCESS) 
		error_exit("pjsua_codec_set_priority()", status);

将speex和G722算法的优先级都设置为0,这两种算法就被禁止了。

接下来说一下关于数据传输的配置,如下面的代码所示:


		pjsua_transport_config cfg;

		pjsua_transport_config_default(&cfg);
		cfg.port = 8060;
		status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg, NULL);
		if (status != PJ_SUCCESS) 
			error_exit("Error creating transport", status);

从上面的代码中可以很容易的看出来,我设置的传输模式是UDP模式,使用的端口是8060。到这里,基本的配置就算完了,下面就可以启动sip的用户代理了,如下面的代码所示:

/* Initialization is done, now start pjsua */
    status = pjsua_start();
    if (status != PJ_SUCCESS) 
		error_exit("Error starting pjsua", status);
	
    /* Register to SIP server by creating SIP account. */
    {
		pjsua_acc_config cfg;
		pjsua_acc_config_default(&cfg);
		cfg.id = pj_str("sip:"SIP_USER"@"SIP_DOMAIN);
		cfg.reg_uri = pj_str("sip:"SIP_DOMAIN);
		cfg.cred_count = 1;
		cfg.cred_info[0].realm = pj_str("*");//域名最好设置成*,要不然很容易注册不上
		cfg.cred_info[0].scheme = pj_str("Digest");
		cfg.cred_info[0].username = pj_str(SIP_USER);
		cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
		cfg.cred_info[0].data = pj_str(SIP_PASSWD);

		status = pjsua_acc_add(&cfg, PJ_TRUE, &acc_id);
		if (status != PJ_SUCCESS) 
			error_exit("Error adding account", status);
    }

库函数pjsua_start()是启动服务的函数,启动服务以后,要向sip服务器注册,如果目标服务器有当前的账号,就可以注册成功。

最后说一下在主函数中怎么使用pthread_create创建线程并调用pjsip相关的子函数。

一般情况下,系统需要实现多线程,这就牵扯到了pjsip线程与原有线程兼容性的问题,要想解决这一问题,就需要将使用pthread_create创建的线程注册到pjsip线程中,具体的做法看如下代码:

    pthread_t thread20;	
	pj_thread_desc desc;
	pj_bzero(desc, sizeof(desc));
	int iRet20;
	iRet20 = pthread_create(&thread20, PTHREAD_CREATE_JOINABLE,
						(void *)wait_for_hangup, NULL);
	if(iRet20)
	{
		perror("pthread_create:thread20. \n");
		return 0;
	}
	pj_thread_t *thread = &thread20;
	pj_thread_register(NULL, desc, &thread);

我用pthread_create创建了一个线程以后,再使用pjsip库函数pj_thread_register想pjsip库注册一下就OK了!好了,说到这里,用NUC972实现的VOIP网关就可以工作了,下面看一下具体的工作效果:

1、开机运行,打印了一大堆东西,主要看最后几行:

出现这种提示就说明已经注册成功,本机的账户是104,sip服务器的URL是192.168.0.200:8060,设置的是来电自动接听。

2、来电自动接听,又是打印了一堆东西,乱七八糟的,具体的就先不贴了

3、对方挂断,依然是打印了一堆东西。

好了,nuc972上实现简单的自动接听的VOIP网关的功能已经完成。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值