iOS: PJSIP后台30-40秒后没有来电
iOS: PJSIP background no incoming calls after 30-40 seconds
我的 voip 应用程序有问题。我正在使用 PJSIP 2.4.5(这是最新版本),当我的应用程序进入后台时我遇到了问题。在 30-40 秒内一切正常,然后在应用程序返回前台之前我无法接听来电。我搜索了很多,但没有找到解决问题的方法。
这是我的代码,我在其中初始化 PJSIP
static pjsua_acc_id acc_id;
static pjsua_call_id incoming_call_id;
static pjsua_call_id current_call_id;
int imcalling=1; //this variable tell the program if I'm currently calling
const size_t MAX_SIP_ID_LENGTH = 50;
const size_t MAX_SIP_REG_URI_LENGTH = 50;
static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata);
static void on_call_state(pjsua_call_id call_id, pjsip_event *e);
static void on_call_media_state(pjsua_call_id call_id);
static void error_exit(const char *title, pj_status_t status);
//pointers for the callbacks to objective-c
void (*callee_response)();
void (*callee_hangup)(int);
void (*call_incoming)(char*);
void (*call_incoming_canceled)(char*);
void (*call_get_statistics)(char*);
int startPjsip(const char *sipUser, const char *sipPassword, const char* sipDomain, const char* realm, unsigned int reg_timeout, int transport_protocol, void(*incoming_call)(char*), void(*incoming_call_cancel)(char*), void(*get_call_statistics)(char*))
{
pj_status_t status;
pjsip_cfg_t *mysipcfg = pjsip_cfg();
mysipcfg->tcp.keep_alive_interval = 20;
// Create pjsua first
status = pjsua_create();
if (status != PJ_SUCCESS) error_exit("Error in pjsua_create()", status);
// Init pjsua
{
// Init the config structure
pjsua_config 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;
// Init the logging config structure
pjsua_logging_config log_cfg;
pjsua_logging_config_default(&log_cfg);
log_cfg.console_level = 4;
// Init the pjsua
status = pjsua_init(&cfg, &log_cfg, NULL);
if (status != PJ_SUCCESS) error_exit("Error in pjsua_init()", status);
}
// Add UDP transport.
{
// Init transport config structure
pjsua_transport_config cfg;
pjsua_transport_config_default(&cfg);
cfg.port = 5060;
// Add UDP transport.
status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg, NULL);
if (status != PJ_SUCCESS) error_exit("Error creating transport", status);
}
// Add TCP transport.
{
// Init transport config structure
pjsua_transport_config cfg;
pjsua_transport_config_default(&cfg);
cfg.port = 5060;
// Add TCP transport.
status = pjsua_transport_create(PJSIP_TRANSPORT_TCP, &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);
call_incoming=incoming_call; //pointer to the function that will tell to the app that a call is incoming
call_incoming_canceled=incoming_call_cancel; //pointer to the function that will tell to the app that a call is canceled
call_get_statistics=get_call_statistics; //pointer to the function that send the call statistics to the app
// Register the account on sip server
{
pjsua_acc_config cfg;
pjsua_acc_config_default(&cfg);
char sipId[MAX_SIP_ID_LENGTH];
sprintf(sipId, "sip:%s@%s", sipUser, sipDomain);
cfg.id = pj_str(sipId);
char regUri[MAX_SIP_REG_URI_LENGTH];
if(transport_protocol==1) sprintf(regUri, "sip:%s;transport=tcp", sipDomain);
else sprintf(regUri, "sip:%s", sipDomain);
cfg.reg_uri = pj_str(regUri);
cfg.reg_timeout = reg_timeout;
//cfg.ka_interval = 30;
cfg.cred_count = 1;
cfg.cred_info[0].realm = pj_str((char*)realm);
cfg.cred_info[0].scheme = pj_str("digest");
cfg.cred_info[0].username = pj_str((char*)sipUser);
cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
cfg.cred_info[0].data = pj_str((char*)sipPassword);
cfg.reg_retry_interval = 30;
//pj_log_set_level(6);
status = pjsua_acc_add(&cfg, PJ_TRUE, &acc_id);
if (status != PJ_SUCCESS) error_exit("Error adding account", status);
}
return 0;
}
激活后 pjsip 打印的第一行
15:51:51.442 os_core_unix.c !pjlib 2.4.5 for POSIX initialized
15:51:51.444 sip_endpoint.c .Creating endpoint instance...
15:51:51.444 pjlib .select() I/O Queue created (0x16295940)
15:51:51.444 sip_endpoint.c .Module "mod-msg-print" registered
15:51:51.445 sip_transport. .Transport manager created.
15:51:51.445 pjsua_core.c .PJSUA state changed: NULL --> CREATED
15:51:51.447 sip_endpoint.c .Module "mod-pjsua-log" registered
15:51:51.447 sip_endpoint.c .Module "mod-tsx-layer" registered
15:51:51.447 sip_endpoint.c .Module "mod-stateful-util" registered
15:51:51.447 sip_endpoint.c .Module "mod-ua" registered
15:51:51.448 sip_endpoint.c .Module "mod-100rel" registered
15:51:51.448 sip_endpoint.c .Module "mod-pjsua" registered
15:51:51.448 sip_endpoint.c .Module "mod-invite" registered
15:51:51.490 coreaudio_dev. .. dev_id 0: iPhone IO device (in=1, out=1) 8000Hz
15:51:51.508 coreaudio_dev. ..core audio initialized
15:51:51.509 pjlib ..select() I/O Queue created (0x16a63e14)
15:51:51.547 sip_endpoint.c .Module "mod-evsub" registered
15:51:51.547 sip_endpoint.c .Module "mod-presence" registered
15:51:51.547 sip_endpoint.c .Module "mod-mwi" registered
15:51:51.548 sip_endpoint.c .Module "mod-refer" registered
15:51:51.548 sip_endpoint.c .Module "mod-pjsua-pres" registered
15:51:51.548 sip_endpoint.c .Module "mod-pjsua-im" registered
15:51:51.548 sip_endpoint.c .Module "mod-pjsua-options" registered
15:51:51.549 pjsua_core.c .1 SIP worker threads created
15:51:51.549 pjsua_core.c .pjsua version 2.4.5 for iPhone OS-9.2/arm-iPhone4,1/iOS-SDK-9.2 initialized
15:51:51.549 pjsua_core.c .PJSUA state changed: CREATED --> INIT
15:51:51.554 pjsua_core.c SIP UDP socket reachable at 192.168.100.174:5060
15:51:51.556 udp0x16ab8400 SIP UDP transport started, published address is 192.168.100.174:5060
15:51:51.559 tcptp:5060 SIP TCP listener ready for incoming connections at 192.168.100.174:5060
15:51:51.559 pjsua_core.c PJSUA state changed: INIT --> STARTING
15:51:51.559 sip_endpoint.c .Module "mod-unsolicited-mwi" registered
15:51:51.559 pjsua_core.c .PJSUA state changed: STARTING --> RUNNING
15:51:51.560 pjsua_acc.c Adding account: id=sip:2011@37.187.161.173
15:51:51.561 pjsua_acc.c .Account sip:2011@37.187.161.173 added with id 0
15:51:51.561 pjsua_acc.c .Acc 0: setting registration..
15:51:51.563 tcpc0x1629d414 ..TCP client transport created
15:51:51.564 tcpc0x1629d414 ..TCP transport 192.168.100.174:49474 is connecting to 37.187.161.173:5060...
15:51:51.564 pjsua_acc.c ..Contact for acc 0 updated for SIP outbound: <sip:2011@192.168.100.174:49474;transport=TCP;ob>;reg-id=1;+sip.instance="<urn:uuid:00000000-0000-0000-0000-0000d3a3575f>"
15:51:51.566 pjsua_core.c ...TX 629 bytes Request msg REGISTER/cseq=17104 (tdta0x16abce00) to TCP 37.187.161.173:5060:
REGISTER sip:37.187.161.173;transport=tcp SIP/2.0
我确定我正在为套接字使用 TCP,我刚刚尝试删除 udp 套接字,但没有任何改变。四处搜索,我看到有人遇到了同样的问题,但几分钟后,我的问题发生在半分钟或更长时间后。
我什至尝试过设置 pjsip 的 keepalive 间隔,正如您从这些行中看到的那样:
pjsip_cfg_t *mysipcfg = pjsip_cfg();
mysipcfg->tcp.keep_alive_interval = 20;
我将它设置为 20 秒,但没有任何改变。 Voip 和音频功能已添加到后台模式。
我通过网络监视器看到,当应用程序处于前台时,与 voip 服务器的连接处于活动状态。当应用程序进入后台时,此连接会在 3-4 秒后消失。无论如何,我仍然能够在 30-40 秒内接到来电。
知道我做错了什么吗?
对于 VOIP 后台使用 AppDelegate.m
中的此功能
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self performSelectorOnMainThread:@selector(keepAlive) withObject:nil waitUntilDone:YES];
[[UIApplication sharedApplication] setKeepAliveTimeout:600 handler: ^{
[self performSelectorOnMainThread:@selector(keepAlive) withObject:nil waitUntilDone:YES];
}];
}
static pj_thread_desc a_thread_desc;
static pj_thread_t *a_thread;
- (void)keepAlive {
int i;
NSLog(@"Keep alive starts");
if (!pj_thread_is_registered())
{
pj_thread_register("ipjsua", a_thread_desc, &a_thread);
}
/* Since iOS requires that the minimum keep alive interval is 600s,
* application needs to make sure that the account's registration
* timeout is long enough.
*/
for (i = 0; i < (int)pjsua_acc_get_count(); ++i) {
if (pjsua_acc_is_valid(i)) {
pjsua_acc_set_registration(i, PJ_TRUE);
}
}
}
它应该可以工作..
刚刚自己找到了解决方案。问题是 pjsua 配置的 reg_timeout。我使用从服务器传递的值(60 秒)。当应用程序处于前台时,此值是正确的。当我的应用程序在后台时,而不是注册在 60 秒后过期并且 pjsip 无法更新它,因为 iOS 应用程序的最小保持时间为 600 秒才能从后台唤醒。
解决方案是更改应用程序切换到后台时的注册超时。
您需要这样的功能:
void change timeout() {
pjsua_acc_config = cfg;
pjsua_acc_get_config(acc_id, pool, cfg);
cgf.reg_timeout = 600;
pjsua_acc_modify_config(acc_id, cfg)
}
只要你在app切换到后台的时候调用这个函数,把reg_timeout改成600,然后你再注册就可以了。当应用程序返回前台时,您对原始超时执行相同的操作。
注意 这应该随着 Xcode 8 和 iOS 10 强制 VOIP 推送和禁用 VOIP 后台模式而改变。自 2016 年 1 月起,如果该应用程序处于离线状态,您将需要从您的服务器请求 VOIP 推送。
一般来说,PJSIP 只会通过 UDP 传输进行连接。如果你也想在后台接听电话,你只需要通过 TCP 传输连接,TCP 传输只支持实时数据传输。它遵循请求确认机制,因此如果应用程序处于后台状态,调用将命中应用程序并显示本地通知或您为后台实现的其他内容。在 UDP 中它不会检查数据包是否已传送,在 TCP 中它会检查。因此,要在后台也收到来电,强制将连接完全设置为 TCP,不要启用 UDP。
当您的应用程序处于后台时,注册在 60 秒后过期并且 pjsip 无法续订它,因为 iOS 应用程序的最小保持活动时间为 600 秒才能从后台唤醒。
我的 voip 应用程序有问题。我正在使用 PJSIP 2.4.5(这是最新版本),当我的应用程序进入后台时我遇到了问题。在 30-40 秒内一切正常,然后在应用程序返回前台之前我无法接听来电。我搜索了很多,但没有找到解决问题的方法。
这是我的代码,我在其中初始化 PJSIP
static pjsua_acc_id acc_id;
static pjsua_call_id incoming_call_id;
static pjsua_call_id current_call_id;
int imcalling=1; //this variable tell the program if I'm currently calling
const size_t MAX_SIP_ID_LENGTH = 50;
const size_t MAX_SIP_REG_URI_LENGTH = 50;
static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata);
static void on_call_state(pjsua_call_id call_id, pjsip_event *e);
static void on_call_media_state(pjsua_call_id call_id);
static void error_exit(const char *title, pj_status_t status);
//pointers for the callbacks to objective-c
void (*callee_response)();
void (*callee_hangup)(int);
void (*call_incoming)(char*);
void (*call_incoming_canceled)(char*);
void (*call_get_statistics)(char*);
int startPjsip(const char *sipUser, const char *sipPassword, const char* sipDomain, const char* realm, unsigned int reg_timeout, int transport_protocol, void(*incoming_call)(char*), void(*incoming_call_cancel)(char*), void(*get_call_statistics)(char*))
{
pj_status_t status;
pjsip_cfg_t *mysipcfg = pjsip_cfg();
mysipcfg->tcp.keep_alive_interval = 20;
// Create pjsua first
status = pjsua_create();
if (status != PJ_SUCCESS) error_exit("Error in pjsua_create()", status);
// Init pjsua
{
// Init the config structure
pjsua_config 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;
// Init the logging config structure
pjsua_logging_config log_cfg;
pjsua_logging_config_default(&log_cfg);
log_cfg.console_level = 4;
// Init the pjsua
status = pjsua_init(&cfg, &log_cfg, NULL);
if (status != PJ_SUCCESS) error_exit("Error in pjsua_init()", status);
}
// Add UDP transport.
{
// Init transport config structure
pjsua_transport_config cfg;
pjsua_transport_config_default(&cfg);
cfg.port = 5060;
// Add UDP transport.
status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg, NULL);
if (status != PJ_SUCCESS) error_exit("Error creating transport", status);
}
// Add TCP transport.
{
// Init transport config structure
pjsua_transport_config cfg;
pjsua_transport_config_default(&cfg);
cfg.port = 5060;
// Add TCP transport.
status = pjsua_transport_create(PJSIP_TRANSPORT_TCP, &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);
call_incoming=incoming_call; //pointer to the function that will tell to the app that a call is incoming
call_incoming_canceled=incoming_call_cancel; //pointer to the function that will tell to the app that a call is canceled
call_get_statistics=get_call_statistics; //pointer to the function that send the call statistics to the app
// Register the account on sip server
{
pjsua_acc_config cfg;
pjsua_acc_config_default(&cfg);
char sipId[MAX_SIP_ID_LENGTH];
sprintf(sipId, "sip:%s@%s", sipUser, sipDomain);
cfg.id = pj_str(sipId);
char regUri[MAX_SIP_REG_URI_LENGTH];
if(transport_protocol==1) sprintf(regUri, "sip:%s;transport=tcp", sipDomain);
else sprintf(regUri, "sip:%s", sipDomain);
cfg.reg_uri = pj_str(regUri);
cfg.reg_timeout = reg_timeout;
//cfg.ka_interval = 30;
cfg.cred_count = 1;
cfg.cred_info[0].realm = pj_str((char*)realm);
cfg.cred_info[0].scheme = pj_str("digest");
cfg.cred_info[0].username = pj_str((char*)sipUser);
cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
cfg.cred_info[0].data = pj_str((char*)sipPassword);
cfg.reg_retry_interval = 30;
//pj_log_set_level(6);
status = pjsua_acc_add(&cfg, PJ_TRUE, &acc_id);
if (status != PJ_SUCCESS) error_exit("Error adding account", status);
}
return 0;
}
激活后 pjsip 打印的第一行
15:51:51.442 os_core_unix.c !pjlib 2.4.5 for POSIX initialized
15:51:51.444 sip_endpoint.c .Creating endpoint instance...
15:51:51.444 pjlib .select() I/O Queue created (0x16295940)
15:51:51.444 sip_endpoint.c .Module "mod-msg-print" registered
15:51:51.445 sip_transport. .Transport manager created.
15:51:51.445 pjsua_core.c .PJSUA state changed: NULL --> CREATED
15:51:51.447 sip_endpoint.c .Module "mod-pjsua-log" registered
15:51:51.447 sip_endpoint.c .Module "mod-tsx-layer" registered
15:51:51.447 sip_endpoint.c .Module "mod-stateful-util" registered
15:51:51.447 sip_endpoint.c .Module "mod-ua" registered
15:51:51.448 sip_endpoint.c .Module "mod-100rel" registered
15:51:51.448 sip_endpoint.c .Module "mod-pjsua" registered
15:51:51.448 sip_endpoint.c .Module "mod-invite" registered
15:51:51.490 coreaudio_dev. .. dev_id 0: iPhone IO device (in=1, out=1) 8000Hz
15:51:51.508 coreaudio_dev. ..core audio initialized
15:51:51.509 pjlib ..select() I/O Queue created (0x16a63e14)
15:51:51.547 sip_endpoint.c .Module "mod-evsub" registered
15:51:51.547 sip_endpoint.c .Module "mod-presence" registered
15:51:51.547 sip_endpoint.c .Module "mod-mwi" registered
15:51:51.548 sip_endpoint.c .Module "mod-refer" registered
15:51:51.548 sip_endpoint.c .Module "mod-pjsua-pres" registered
15:51:51.548 sip_endpoint.c .Module "mod-pjsua-im" registered
15:51:51.548 sip_endpoint.c .Module "mod-pjsua-options" registered
15:51:51.549 pjsua_core.c .1 SIP worker threads created
15:51:51.549 pjsua_core.c .pjsua version 2.4.5 for iPhone OS-9.2/arm-iPhone4,1/iOS-SDK-9.2 initialized
15:51:51.549 pjsua_core.c .PJSUA state changed: CREATED --> INIT
15:51:51.554 pjsua_core.c SIP UDP socket reachable at 192.168.100.174:5060
15:51:51.556 udp0x16ab8400 SIP UDP transport started, published address is 192.168.100.174:5060
15:51:51.559 tcptp:5060 SIP TCP listener ready for incoming connections at 192.168.100.174:5060
15:51:51.559 pjsua_core.c PJSUA state changed: INIT --> STARTING
15:51:51.559 sip_endpoint.c .Module "mod-unsolicited-mwi" registered
15:51:51.559 pjsua_core.c .PJSUA state changed: STARTING --> RUNNING
15:51:51.560 pjsua_acc.c Adding account: id=sip:2011@37.187.161.173
15:51:51.561 pjsua_acc.c .Account sip:2011@37.187.161.173 added with id 0
15:51:51.561 pjsua_acc.c .Acc 0: setting registration..
15:51:51.563 tcpc0x1629d414 ..TCP client transport created
15:51:51.564 tcpc0x1629d414 ..TCP transport 192.168.100.174:49474 is connecting to 37.187.161.173:5060...
15:51:51.564 pjsua_acc.c ..Contact for acc 0 updated for SIP outbound: <sip:2011@192.168.100.174:49474;transport=TCP;ob>;reg-id=1;+sip.instance="<urn:uuid:00000000-0000-0000-0000-0000d3a3575f>"
15:51:51.566 pjsua_core.c ...TX 629 bytes Request msg REGISTER/cseq=17104 (tdta0x16abce00) to TCP 37.187.161.173:5060:
REGISTER sip:37.187.161.173;transport=tcp SIP/2.0
我确定我正在为套接字使用 TCP,我刚刚尝试删除 udp 套接字,但没有任何改变。四处搜索,我看到有人遇到了同样的问题,但几分钟后,我的问题发生在半分钟或更长时间后。
我什至尝试过设置 pjsip 的 keepalive 间隔,正如您从这些行中看到的那样:
pjsip_cfg_t *mysipcfg = pjsip_cfg();
mysipcfg->tcp.keep_alive_interval = 20;
我将它设置为 20 秒,但没有任何改变。 Voip 和音频功能已添加到后台模式。
我通过网络监视器看到,当应用程序处于前台时,与 voip 服务器的连接处于活动状态。当应用程序进入后台时,此连接会在 3-4 秒后消失。无论如何,我仍然能够在 30-40 秒内接到来电。
知道我做错了什么吗?
对于 VOIP 后台使用 AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self performSelectorOnMainThread:@selector(keepAlive) withObject:nil waitUntilDone:YES];
[[UIApplication sharedApplication] setKeepAliveTimeout:600 handler: ^{
[self performSelectorOnMainThread:@selector(keepAlive) withObject:nil waitUntilDone:YES];
}];
}
static pj_thread_desc a_thread_desc;
static pj_thread_t *a_thread;
- (void)keepAlive {
int i;
NSLog(@"Keep alive starts");
if (!pj_thread_is_registered())
{
pj_thread_register("ipjsua", a_thread_desc, &a_thread);
}
/* Since iOS requires that the minimum keep alive interval is 600s,
* application needs to make sure that the account's registration
* timeout is long enough.
*/
for (i = 0; i < (int)pjsua_acc_get_count(); ++i) {
if (pjsua_acc_is_valid(i)) {
pjsua_acc_set_registration(i, PJ_TRUE);
}
}
}
它应该可以工作..
刚刚自己找到了解决方案。问题是 pjsua 配置的 reg_timeout。我使用从服务器传递的值(60 秒)。当应用程序处于前台时,此值是正确的。当我的应用程序在后台时,而不是注册在 60 秒后过期并且 pjsip 无法更新它,因为 iOS 应用程序的最小保持时间为 600 秒才能从后台唤醒。
解决方案是更改应用程序切换到后台时的注册超时。
您需要这样的功能:
void change timeout() {
pjsua_acc_config = cfg;
pjsua_acc_get_config(acc_id, pool, cfg);
cgf.reg_timeout = 600;
pjsua_acc_modify_config(acc_id, cfg)
}
只要你在app切换到后台的时候调用这个函数,把reg_timeout改成600,然后你再注册就可以了。当应用程序返回前台时,您对原始超时执行相同的操作。
注意 这应该随着 Xcode 8 和 iOS 10 强制 VOIP 推送和禁用 VOIP 后台模式而改变。自 2016 年 1 月起,如果该应用程序处于离线状态,您将需要从您的服务器请求 VOIP 推送。
一般来说,PJSIP 只会通过 UDP 传输进行连接。如果你也想在后台接听电话,你只需要通过 TCP 传输连接,TCP 传输只支持实时数据传输。它遵循请求确认机制,因此如果应用程序处于后台状态,调用将命中应用程序并显示本地通知或您为后台实现的其他内容。在 UDP 中它不会检查数据包是否已传送,在 TCP 中它会检查。因此,要在后台也收到来电,强制将连接完全设置为 TCP,不要启用 UDP。
当您的应用程序处于后台时,注册在 60 秒后过期并且 pjsip 无法续订它,因为 iOS 应用程序的最小保持活动时间为 600 秒才能从后台唤醒。