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;
}
}
}