2
votes

I have a problem with a voip app. I'm using PJSIP 2.4.5 (which is the latest version) and I have a problem when my app goes in background. Everything is ok for 30-40 seconds, then I'm not able to receive incoming calls until the app come back to foreground. I've searched a lot around but i didn't find a solution for my problem.

This is my code, where I initialize 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;

}

Those are the first lines printed by pjsip after the activation

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:[email protected]
15:51:51.561    pjsua_acc.c  .Account sip:[email protected] 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:[email protected]: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

I'm sure that i'm using TCP for the socket and I have just tried to remove the udp socket but nothing changed. Searching around I saw that someone has my same problem but after some minutes while my problem happens after half a minute or a little more.

I have tried even to set the keepalive interval of pjsip as you can see from these lines:

pjsip_cfg_t *mysipcfg = pjsip_cfg();
mysipcfg->tcp.keep_alive_interval = 20;

I set it to 20 seconds but nothing changed. Voip and audio capabilities are added to background modes.

I saw through the network monitor that there is an active connection with the voip server while the app is in foreground. When the app goes in background this connection disappear after 3-4 seconds. Anyway i'm still able to get incoming calls for 30-40 seconds.

Any idea on what i'm doing wrong ?

4
I am facing an issue while the application is in background. Issue is that the application is able to get the call back method on_pager in iPhone5s, with iOS8 but in iPhone6 & 6Plus where os version is iOS9 call backs are not getting fired. Any idea on this kind of isseKannan Prasad

4 Answers

1
votes

In general the PJSIP will connect through the UDP transport only. If you want the incoming calls in background also, You need connect through TCP transport only, the TCP transport only support real time data transfer. It follows request-acknowledgment mechanism, so if the app is in background state the call will hits the app and shows local notification or other what ever you implemented for background. In UDP it won't check whether the packet is delivered or not, in TCP it checks. So, to get Incoming call while in background also, force fully set the connection to TCP only, don't enable UDP.

1
votes

Just found the solution myself. The problem was the reg_timeout of the pjsua config. I used the value passed from the server (60 seconds). This value was right when the app was in the foreground. When my app was in the background, instead of the registration expiring after 60 seconds and pjsip was not able to renew it because an iOS app has a minimum keep alive time of 600 seconds to wake up from the background.

The solution was to change the reg timeout when the app switched to background.

You need a function like this:

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

Simply you call this function when the app switches to the background and change the reg_timeout to 600. Then you register again. When the app comes back to the foreground you do the same thing with the original timeout.

Note that this should have changed with Xcode 8 and iOS 10 enforcing VOIP push and disabling VOIP background mode. As of Jan 2016, if the app is offline, you will need to request a VOIP push from your server.

0
votes

For VOIP backgrounding use this functions in 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);
    }
}


}

it should work..

0
votes

When your app was in background instead the registration expired after 60 seconds and pjsip couldn't renew it because an iOS app has a minimum keep alive time of 600 seconds to wake up from background.