MFC项目 | PJSIP使用说明 | 福大嵌入式实时系统课学习 | 音频通话

0. 简介

  • 本人前文针对PJSIP库进行了多维度的阐述(本文内一些基础内容可参考),但是作为SIP客户端,仅仅只是在终端运行是不足以满足需求的,那么就需要做一个带有UI界面的应用程序来实现。这里选择使用的是MFC,编译器为VS2019。

  • 网络上基本搜不到用MFC来做这个的,希望能帮到大家。

  • 现有的应用程序有MicroSIP,功能十分完善,这里我作为一个简单实现,MicroSIP可在官网下载。

  • 该项目基于simple_pjsua.c,但是添加了不少功能,也重写了一些函数。

  • 项目大致功能基本实现,后续可以多作交流。

1. UI界面展示

实现基本功能

2. 拨号

拨号盘的功能主要集中在拨号上。

void CMFCApplication1Dlg::OnBnClickedMakecall()
{
	pj_status_t status;
	m_ctlUri.GetWindowText(sipUri);

	if (sipUri.IsEmpty()) {
		AfxMessageBox(_T("Please enter a SIP URI."));
		return;
	}

	CString tempUri;
	tempUri.Format(_T("sip:%s@%s"), sipUri.GetString(), _T(SIP_DOMAIN));
	sipUri = tempUri;

	// 将 CString 转换为 char*
	char szUri[MAX_URI_LEN] = { 0 };
	CT2CA pszConvertedAnsiString(sipUri);
	strncpy_s(szUri, sizeof(szUri), pszConvertedAnsiString, _TRUNCATE);

	if (CMFCApplication1Dlg::pro == 2) {
		pj_str_t uri = pj_str(szUri);
		if (pj_strcmp(&uri, &self_id) == 0) {
			AfxMessageBox(_T("Call self."));
			return;
		}
		status = pjsua_call_make_call(pjsua_acc_get_default(), &uri, 0, NULL, NULL, NULL);
		if (status != PJ_SUCCESS) {
			AfxMessageBox(_T("Error making call"));
			return;
		}
	}
	else if (CMFCApplication1Dlg::pro == 1)
	{
		add_tcp_transport_suffix(szUri);
		pj_str_t uri = pj_str(szUri);
		if (pj_strcmp(&uri, &self_id) == 0) {
			AfxMessageBox(_T("Call self."));
			return;
		}
		status = pjsua_call_make_call(pjsua_acc_get_default(), &uri, 0, NULL, NULL, NULL);
		if (status != PJ_SUCCESS) {
			AfxMessageBox(_T("Error making call"));
			return;
		}
	}
	else
	{
		AfxMessageBox(_T("Error making call"));
		return;
	}
	AfxMessageBox(_T("Making call"));
}

3. 电话呼入

挂断功能较为简单,不多赘述。

3.1 来电回调函数

void CMFCApplication1Dlg::on_incoming_call1(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data* rdata)
{
	pjsua_call_info ci;

	pjsua_call_get_info(call_id, &ci);

	if (s_instance != nullptr) {

		PJ_UNUSED_ARG(acc_id);
		PJ_UNUSED_ARG(rdata);

		PJ_LOG(3, (THIS_FILE, "Incoming call from %.*s!!",
			(int)ci.remote_info.slen,
			ci.remote_info.ptr));

		CMFCApplication1Dlg::current_call_id = call_id;

		s_instance->current_call_id = call_id;

		// 获取请求方的ID
		CString remoteInfo(ci.remote_info.ptr, ci.remote_info.slen);

		// 将请求方的ID设置到CEdit控件中
		s_instance->m_ctlYLUri.SetWindowText(remoteInfo);

		AfxMessageBox(remoteInfo);
	}
}

3.2 应答

//应答
void CMFCApplication1Dlg::OnBnClickedButton1()
{
	if (current_call_id != PJSUA_INVALID_ID) {
		pjsua_call_answer(current_call_id, 200, NULL, NULL);
		current_call_id = PJSUA_INVALID_ID; // Reset the call ID after answering
	}
	else {
		AfxMessageBox(_T("No incoming call to answer"));
	}
}

4. 添加账户

4.1 TCP

//添加TCP账户
void CMFCApplication1Dlg::OnBnClickedButton4()
{
	pjsua_acc_id acc_id;

	pj_status_t status;

	/* Add TCP transport. */
	if (CMFCApplication1Dlg::pro == 2) {
		pjsua_destroy();
		InitializePJSIP();
	}
	else if (CMFCApplication1Dlg::pro == 1) {
		AfxMessageBox(_T("已有账户"));
		return;
	}
	{
		pjsua_transport_config cfg;

		pjsua_transport_config_default(&cfg);
		cfg.port = 5060;
		status = pjsua_transport_create(PJSIP_TRANSPORT_TCP, &cfg, NULL);
		if (status != PJ_SUCCESS) {
			AfxMessageBox(_T("Error creating transport"));
			return;
		}
		CMFCApplication1Dlg::pro = 1;
	}

	status = pjsua_start(); if (status != PJ_SUCCESS) {
		AfxMessageBox(_T("Error starting pjsua"));
		return;
	}


	/* 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":5060;transport=tcp");
		self_id = cfg.id;
		cfg.reg_uri = pj_str("sip:" SIP_DOMAIN":5060;transport=tcp");
		cfg.cred_count = 1;
		cfg.cred_info[0].realm = pj_str(SIP_REALM);
		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) {
			AfxMessageBox(_T("Error adding account"));
			return;
		}
		// 等待注册完成
		pjsua_acc_info acc_info;
		for (int i = 0; i < 10; ++i) {
			pjsua_acc_get_info(acc_id, &acc_info);
			CString errorMsg;
			if (acc_info.status == 200) {
				if (s_instance1 != nullptr) {
					CString Info("sip:" SIP_USER "@" SIP_DOMAIN":5060;transport=tcp");
					s_instance1->m_ctlAcc.SetWindowText(Info);
				}
				AfxMessageBox(_T("Registration successful"));
				return;
			}
			pj_thread_sleep(500); // 休眠500ms然后重试
		}
		if(acc_info.status == PJSIP_SC_TRYING) AfxMessageBox(_T("Registration timed out or failed, check for your SIPServer"));

	}
}

4.2 UDP

//添加UDP账户
void CMFCApplication1Dlg::OnBnClickedButton3()
{

	pjsua_acc_id acc_id;

	pj_status_t status;

	/* Add UDP transport. */
	if (CMFCApplication1Dlg::pro == 1) {
		pjsua_destroy();
		InitializePJSIP();
	}
	else if(CMFCApplication1Dlg::pro == 2){
		AfxMessageBox(_T("已有账户"));
		return;
	}
	{
		pjsua_transport_config cfg;

		pjsua_transport_config_default(&cfg);
		cfg.port = 5060;
		status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg, NULL);
		if (status != PJ_SUCCESS) {
			AfxMessageBox(_T("Error creating transport"));
			return;
		}
		CMFCApplication1Dlg::pro = 2;
	}

	status = pjsua_start();

	if (status != PJ_SUCCESS) {
		AfxMessageBox(_T("Error starting pjsua"));
		return;
	} 

	/* 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);
		self_id = cfg.id;
		cfg.reg_uri = pj_str("sip:" SIP_DOMAIN);
		cfg.cred_count = 1;
		cfg.cred_info[0].realm = pj_str(SIP_REALM);
		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) {
			AfxMessageBox(_T("Error adding account"));
			return;
		}
		// 等待注册完成
		pjsua_acc_info acc_info;
		for (int i = 0; i < 10; ++i) {
			pjsua_acc_get_info(acc_id, &acc_info);
			CString errorMsg;
			if (acc_info.status == 200) {
				if (s_instance1 != nullptr) {
					CString Info("sip:" SIP_USER "@" SIP_DOMAIN);
					s_instance1->m_ctlAcc.SetWindowText(Info);
				}
				AfxMessageBox(_T("Registration successful"));
				return;
			}
			pj_thread_sleep(500); // 休眠500ms然后重试
		}
		if (acc_info.status == PJSIP_SC_TRYING) AfxMessageBox(_T("Registration timed out or failed, check for your SIPServer"));
	}
}

5. 状态回调函数

void CMFCApplication1Dlg::on_call_state1(pjsua_call_id call_id, pjsip_event* e) {
	PJ_UNUSED_ARG(e);
	pjsua_call_info ci;
	pjsua_call_get_info(call_id, &ci);

	if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
		switch (ci.last_status) {
		case PJSIP_SC_OK:  // 200 OK
			AfxMessageBox(_T("Call ended successfully."));
			break;
		case PJSIP_SC_REQUEST_TERMINATED:
			AfxMessageBox(_T("Terminated."));
			break;
		case PJSIP_SC_BUSY_HERE:  // 486 Busy Here
			AfxMessageBox(_T("The line is busy."));
			break;
		case PJSIP_SC_NOT_FOUND:  // 404 Not Found
			AfxMessageBox(_T("The number you dialed is incorrect."));
			break;
		case PJSIP_SC_REQUEST_TIMEOUT:  // 408 Request Timeout
			AfxMessageBox(_T("Call failed due to timeout."));
			break;
		case PJSIP_SC_SERVICE_UNAVAILABLE:  // 503 Service Unavailable
			AfxMessageBox(_T("The service is unavailable."));
			break;
		case PJSIP_SC_DECLINE:
			AfxMessageBox(_T("Decline."));
			break;
		default:
			AfxMessageBox(_T("Call failed."));
			break;
		}
	}
}
  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: PJSIP是一个嵌入式的SIP协议栈,提供了实现SIP通信的功能。下面是使用PJSIP嵌入式的步骤: 1. 下载和编译:首先,你需要从PJSIP官方网站下载最新的PJSIP源代码。然后,根据目标设备的类型(例如Linux,Windows,Android等),按照PJSIP官方文档提供的指导进行编译。 2. 初始化:在你的应用程序中,需要首先初始化PJSIP库。你可以使用pj_init()函数来初始化PJSIP库,并提供一个回调函数来处理错误信息。 3. 创建SIP用户代理:使用pj_status_t pj_sock_load()函数来创建一个UDP或TCP套接字。然后,使用pj_sock_bind()函数将套接字绑定到IP地址和端口号上。接下来,使用pj_sock_setsockopt()函数设置套接字选项。最后,使用pj_sock_register()函数将套接字注册到PJSIP库中。 4. 创建和注册SIP账号:使用pj_status_t pjsua_acc_create()函数创建一个SIP账号对象。然后,使用pjsua_acc_cfg结构体设置账号的配置参数,如SIP服务器地址、账号信息等。最后,使用pjsua_acc_add()函数将SIP账号添加到PJSIP库中。 5. 呼叫和接听:使用pjsua_call_make_call()函数发起呼叫,提供对方的SIP URI。可以使用pjsua_call_on_answer()函数监听呼叫的接听事件,并执行相关的操作。 6. 处理消息和事件:使用pjsua_call_set_callback()函数设置呼叫的回调函数,以处理通话中的消息和事件。 7. 释放资源:在应用程序退出时,使用pj_shutdown()函数释放PJSIP库占用的资源。 以上是使用PJSIP嵌入式的基本步骤,具体的实现细节和功能可以参考PJSIP官方文档和示例代码。 ### 回答2: PJSIP是一个开源的多媒体通信库,适用于开发语音、视频、实时通信等应用程序。以下是使用PJSIP嵌入式的基本步骤: 1. 下载和安装PJSIP库:从PJSIP的官方网站下载最新版本的库文件,并按照其提供的文档进行安装。 2. 配置和编译:根据自己的需求,通过修改配置文件来配置PJSIP库的功能和选项。然后,使用提供的编译工具编译库文件。可以根据不同的平台和编译工具进行配置和编译。 3. 初始化PJSIP:在应用程序中,首先需要初始化PJSIP库。这可以通过调用相应的初始化函数来完成。初始化过程会创建一些必要的对象和数据结构,准备好使用PJSIP库的环境。 4. 创建和配置用户账号:在使用PJSIP之前,需要创建一个用户账号,并进行相应的配置。账号参数包括用户名、密码、服务器地址等。通过调用库提供的API函数,可以创建和配置用户账号。 5. 发起和接听通话:一旦用户账号创建和配置完成,就可以使用PJSIP库提供的API函数来发起和接听通话。可以通过调用函数来拨打电话、接听电话、挂断电话等。 6. 处理通话事件:在通话过程中,可能会触发一些事件,比如来电、通话状态改变等。应用程序需要注册相应的事件处理函数,以便及时处理这些事件。通过事件处理函数,可以获取通话状态、接听来电、播放音频等。 7. 清理和释放资源:当应用程序不再使用PJSIP库时,应该进行清理和释放资源的操作。可以调用对应的函数来销毁用户账号、释放PJSIP库的资源等。 总的来说,使用PJSIP进行嵌入式开发需要下载安装库文件,配置编译库,初始化PJSIP库,创建和配置用户账号,发起和接听通话,处理通话事件,最后进行清理和释放资源。以上是PJSIP嵌入式的基本使用流程,可以根据实际需求进行相应的扩展和调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值