21
votes

I am getting a VOIP socket to run in the background in an iOS application.

My connection works fine, but it won't wake up when my app goes into the background. If I open the app back up, though, it responds to any messages it got while it was asleep.

I set up my stream like this:

CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,
                                   (CFStringRef) @"test.iusealocaltestserver.com",
                                   5060,
                                   &myReadStream,
                                   &myWriteStream);
CFReadStreamSetProperty (    myReadStream,
                             kCFStreamNetworkServiceType,
                             kCFStreamNetworkServiceTypeVoIP
                             );

CFSocketNativeHandle native;
CFDataRef nativeProp = CFReadStreamCopyProperty(myReadStream, kCFStreamPropertySocketNativeHandle);

CFDataGetBytes(nativeProp, CFRangeMake(0, CFDataGetLength(nativeProp)), (UInt8 *)&native);
CFRelease(nativeProp);

CFSocketRef theSocket = CFSocketCreateWithNative(kCFAllocatorDefault, native, 0, NULL, NULL);

CFSocketGetContext(theSocket,&theContext);    


CFOptionFlags readStreamEvents = kCFStreamEventHasBytesAvailable | 
kCFStreamEventErrorOccurred     |
kCFStreamEventEndEncountered    |
kCFStreamEventOpenCompleted;

CFReadStreamSetClient(myReadStream,
                           readStreamEvents,
                           (CFReadStreamClientCallBack)&MyCFReadStreamCallback,
                      (CFStreamClientContext *)(&theContext));

CFReadStreamScheduleWithRunLoop(myReadStream, CFRunLoopGetCurrent(),
                                kCFRunLoopCommonModes);

Then my callback is set up like this:

static void MyCFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void *pInfo);

static void MyCFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo)
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSLog(@"Callback Happened");

   [pool release];
}

"Callback Happened" is getting called when I receive data and the app is open, but it doesn't if the app is minimized. When the app comes back up, though, it processes any data it received while minimized.

I added the voip tag to the info.plist. My CFReadStreamSetProperty returns true. I am running on a device not a simulator. It still doesn't work though, so I dont know what my problem could be. I probably just did something silly, but there's almost nothing online to check my code against.

EDIT: I can't test any of the answers because I am no longer working on this project and don't have access to a mac/iOs sdk. If someone with a similar problem found one of the below answers useful, let me know and I will vote it best answer.

5
+1: did you also add the "audio" tag in the plist? i'm just reading from the application programming guide about multitasking but that seems like its something thats irregular. Hope you get an answer. good question.Jesse Naugher
I'm not playing any audio (yet) so that shouldn't make a difference. But I tried anyway and it didn't work. Thanks for atleast giving me something to try, I've been just smashing my head for hours.Joel
can you post the code to your keep alive timeout handler?Jesse Naugher
@Joel Hi Joel,if you are able to solve the problem please update here.User97693321
Again I no longer am working on this project and don't have access to the code anymore. Sorry :(Joel

5 Answers

29
votes

If you want to let your VOIP application run in background , except those base settings in plist file, you need a TCP socket who's property is set to VOIP, than the iOS system will take care this socket for you, when your application enter background , every thing was 'sleep' except that tcp socket. and if VOIP server send some data thought that TCP socket, your application will be awake up for 10 secs. during this time, you can post a local notification.

Only Tcp socket can be set as VOIP Socket. But from i know , mostly VOIP application are based on UDP socket. if you do not want to separate the control socket from the data socket. you should create another tcp socket which is focus on 'awake' your application , and from my personal experience , it's very hard to keep this 'awake' signal and the real sip control signal synchronize, the application always miss the sip invite request.

So,the best way is separating the sip control single from the UDP data socket , make it as a tcp socket , this is the best solution , but never use tcp socket to transfer voice data.

Another dirty way: keep the application awake all the time. As i said , each TCP single the application received thought that 'VOIP' tcp socket , will keep application awake for 10 seconds, so at the end of this duration(after 9 secs) , you can send a response to the server to ask for another signal , when the next signal arrived, the application will be awake again,after 9 secs , send response again. keep doing this, your application will awake forever.

5
votes

I was stuck with the exact same scenario.
My problem was that i've configured more than one socket as a Voip socket.

you can see at Apple's documentations about voip that they say:
"Configure one of the application’s sockets for VoIP usage"

I guess they are only waking your app according to a single socket.

all other stuff mentioned is still correct:

  • kCFStreamNetworkServiceTypeVoIP
  • 'info.plist' UIBackgroundModes: voip, audio
  • 'info.plist' UIRequiresPersistentWifi key
  • will NOT work over simulator
3
votes

I'm also facing same problem. but in my case everything is working fine, unless network changes happen. I used apple "reachability" class to detect network changes. if the app is in background upto sometime my socket is working even if i manually switched my network for the following.

  1. wifi -> 3g
  2. 3g -> wifi

After sometime, let say again im trying network switch manually. nothing is happ it seems my app is not detecting network changes. I read the below apple doc. i'm sure doing wrong(or) disunderstood step 3 and 6.

There are several requirements for implementing a VoIP app:

1. Add the UIBackgroundModes key to your app’s Info.plist file. Set the value of this key to an array that includes the voip string.

  1. Configure one of the app’s sockets for VoIP usage.

  2. Before moving to the background, call the setKeepAliveTimeout:handler: method to install a handler to be executed periodically. Your app can use this handler to maintain its service connection.

  3. Configure your audio session to handle transitions to and from active use.

5. To ensure a better user experience on iPhone, use the Core Telephony framework to adjust your behavior in relation to cell-based phone calls; see Core Telephony Framework Reference.

  1. To ensure good performance for your VoIP app, use the System Configuration framework to detect network changes and allow your app to sleep as much as possible.
0
votes

You might need to set <key>UIBackgroundModes</key><array><string>audio</string></array> in Info.plist, and you need to make sure that the audio session is active/running/whatever before you switch apps (the assumption is that you won't suddenly start recording/playing music/whatever when your app is in the background).

The docs say that "audio" lets you play audio in the background, but presumably this also applies to recording audio. If it doesn't work, there are a few things you could try:

  • Set both "voip" and "audio".
  • Play silence (this might be easiest to do with the Audio Queue API).
-1
votes

Do you have a 'applicationDidEnterBackground:' in your application delegate. I'm pretty sure that I've read somewhere (that I can't find), that you need to have it defined for ios to recognize that you support background modes. You don't need to implement anything in it.

e.g.

- (void)applicationDidEnterBackground:(UIApplication *)application
{
}