iPhone Socket gets closed unexpected - iphone

Well at least unexpected for me... This is the situation:
I open a Socket using the following code:
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)ipaddress , 3333,&readStream, &writeStream);
if(!CFReadStreamOpen(readStream) || !CFWriteStreamOpen(writeStream))
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Connection error"
message:#"There has been a connection error, please ensure your internet connection is working"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
return;
}
This all goes fine, next thing I do is setup a callback:
CFStreamClientContext ctxt = {0,(void*)NULL,NULL,NULL,NULL};
static const CFOptionFlags kReadNetworkEvents = kCFStreamEventEndEncountered |
kCFStreamEventErrorOccurred |
kCFStreamEventHasBytesAvailable |
kCFStreamEventOpenCompleted |
kCFStreamEventNone;
CFReadStreamSetClient(readStream, kReadNetworkEvents, ReadStreamClientCallBack, &ctxt);
CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
Also works fine, I'll list the callback method to be complete:
static void ReadStreamClientCallBack( CFReadStreamRef stream, CFStreamEventType type, void *clientCallBackInfo ) {
switch (type)
{
case kCFStreamEventEndEncountered:
{
break;
}
case kCFStreamEventErrorOccurred:
break;
case kCFStreamEventHasBytesAvailable:
{
[this stopListen];
UInt8 buffer[1024];
int count = CFReadStreamRead(stream, buffer, 1024);
CFStreamStatus status = CFReadStreamGetStatus(this.readStream);
CFErrorRef error = CFReadStreamCopyError (this.readStream);
CFStringRef errorCode = CFErrorCopyDescription(error);
// Bunch of other irrelevant code
Now what goes wrong: All this code works perfectly fine as long as I'm staying in the application, even if I exit the application and enter it again it works still fine. If I enter standby while the application is open it also works fine. However if I exit the application, put my phone on standby, get my phone out of standby and reenter the application, the call back immediatly gets called, with eventtype kCFStreamEventHasBytesAvailable, even though I'm 100% sure no bytes have been send. If I then call CFReadStreamRead it returns -1 to me, since this means an error occured I figured out the error code which is 57, this mean that the socket has been closed.
Am I overlooking a certain aspect of socket programming on the iPhone? I must admit I'm new to it. Is it not possible to keep a TCP Socket open while out of the application (and entering standby)?
I have tried to call CFReadStreamOpen again which returned false to me.
I'm lost here, please help!
Thanks in advance

Part of the multi-tasking features in 4.0+ require extra coding to support keeping connections alive and performing tasks in the background. You are probably just a victim of the OS taking back those resources since you didn't "opt-in" for them at the appropriate time.
This section covers the basics of backgrounding:
http://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
Specifically this:
Be prepared to handle connection
failures in your network-based
sockets. The system may tear down
socket connections while your
application is suspended for any
number of reasons. As long as your
socket-based code is prepared for
other types of network failures, such
as a lost signal or network
transition, this should not lead to
any unusual problems. When your
application resumes, if it encounters
a failure upon using a socket, simply
reestablish the connection.
However, there are three very special things that you can do in the background, and if you do you should declare them in your Info.plist to be a good App citizen. These three values are in the UIBackgroundModes property and they are audio location and voip
You can request more time for your task by registering a block (even though it isn't guarunteed you'll get it) like this:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UIApplication* app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do the work associated with the task.
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
See also A Short Practical Guide to Blocks for a quick overview and the Concurrency Programming Guide for more detailed information.

Related

How to use AVCaptureSession to stream live preview video, then take a photo, then return to streaming

I have an application that creates its own live preview prior to taking a still photo. The app needs to run some processing on the image data and thus is not able to rely on AVCaptureVideoPreviewLayer. Getting the initial stream to work is going quite well, using Apple's example code. The problem comes when I try to switch to the higher quality image to take the snapshot. In response to a button press I attempt to reconfigure the session for taking a full resolution photo. I've tried many variations but here is my latest example (which still does not work):
- (void)sessionSetupForPhoto
{
[session beginConfiguration];
session.sessionPreset = AVCaptureSessionPresetPhoto;
AVCaptureStillImageOutput *output = [[[AVCaptureStillImageOutput alloc] init] autorelease];
for (AVCaptureOutput *output in [session outputs]) {
[session removeOutput:output];
}
if ([session canAddOutput:output]){
[session addOutput:output];
} else {
NSLog(#"Not able to add an AVCaptureStillImageOutput");
}
[session commitConfiguration];
}
I am consistently getting an error message just after the commitConfiguration line that looks like this:
(that is to say, I am getting an AVCaptureSessionRuntimeErrorNotification sent to my registered observer)
Received an error:
NSConcreteNotification 0x19d870 {name
= AVCaptureSessionRuntimeErrorNotification;
object = ;
userInfo = {
AVCaptureSessionErrorKey = "Error Domain=AVFoundationErrorDomain
Code=-11800 \"The operation
couldn\U2019t be completed.
(AVFoundationErrorDomain error
-11800.)\" UserInfo=0x19d810 {}";
The documentation in XCode ostensibly provides more information for the error number (-11800), "AVErrorUnknown - Reason for the error is unknown.";
Previously I had also tried calls to stopRunning and startRunning, but no longer do that after watching WWDC Session 409, where it is discouraged. When I was stopping and starting, I was getting a different error message -11819, which corresponds to "AVErrorMediaServicesWereReset - The operation could not be completed because media services became unavailable.", which is much nicer than simply "unknown", but not necessarily any more helpful.
It successfully adds the AVCaptureStillImageOutput (i.e., does NOT emit the log message).
I am testing on an iPhone 3g (w/4.1) and iPhone 4.
This call is happening in the main thread, which is also where my original AVCaptureSession setup took place.
How can I avoid the error? How can I switch to the higher resolution to take the photo?
Thank you!
Since you're processing the video data coming out of the AVCaptureSession, I'm assuming you have an AVCaptureVideoDataOutput connected to it prior to calling sessionSetupForPhoto.
If so, can you elaborate on what you're doing in captureOutput:didOutputSampleBuffer:? Without being able to see more, I'm guessing there may be a problem with removing the old outputs and subsequently setting the photo quality preset.
Also, the output variable you're using as an iterator when you remove your outputs is hiding the still image output. Not a problem, but it makes the code a little harder to read.
There is no need to switch sessions. Just add AVCaptureStillImageOutput to your session on initialization and call the following when you are about to capture the image and use the CMSampleBufferRef accordingly:
captureStillImageAsynchronouslyFromConnection:videoConnection
completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error)
{
}

How to set VoIP mode on a socket in iOS 4

I'm trying to set up a socket in VoIP mode on an iPhone, so that my app can be woken when an event happens. I have a simple server that will write to the socket if and only if the app should wake up and talk to the main web service about something. Calling
CFReadStreamSetProperty()
on the stream attached to the socket always seems to return zero, which if I'm not mistaken is FALSE, meaning the stream did not recognize and/or accept the property value. I read in a previous question that this facility is not available on the simulator, so I tried it on a real phone, with the same result.
How can I figure out why the call is failing?
The code is below:
- (id) init {
NSLog(#"NotificationClient init, host = %#", [self getNotificationHostName]);
CFHostRef notificationHost = CFHostCreateWithName(kCFAllocatorDefault, (CFStringRef)[self getNotificationHostName]);
CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, notificationHost, [self getNotificationPort], &_fromServer, &_toServer);
BOOL status;
status = CFReadStreamOpen(_fromServer);
status = CFReadStreamSetProperty(_fromServer, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
NSLog(#"status from setting VoIP mode on socket is %d", status);
status = CFWriteStreamOpen(_toServer);
[self sendMessage:#"STRT" withArgument:#"iPhone"];
[self startReceivingMessages];
return self;
}
Hmm... it looks like there were two problems. First, you need to set the property before opening the stream. And second, it looks like it only works if you are on the main thread when you do this.

Game Center Multiplayer using GKMatch but seems can't be connected

Hi I'm a new bie in Game Center for iOS. I'm trying to add the multiplayer feature using matches to my game and following the documentation.
So far I reached a point where 2 of my clients can successfully get a match, i.e. the matchmakerViewController:didFindMatch callback is called and a GKMatch object is delivered.
However after that I seems to be stuck there forever, because according to the documentation, I'll have to wait until all the players (2 in my case) are actually connected before starting my game. But it seems the match:player:didChangeState callback is never called to indicate a successful connection. Well, I'm sure my clients are all in the same wifi network ( or is it a must?) Could any one enlighten me on this case? Do I have to do any extra things to make the clients to connect? Thanks a lot for the help!
So I was running into this and the solution (for me) was somewhat embarrasing. I had copied and pasted a bunch of the code from the Apple docs..and they left out an obvious step. They never actually set the match's delegate!
My code now is:
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *)match {
[self dismissModalViewControllerAnimated:YES];
self.myMatch = match; // Use a retaining property to retain the match.
self.myMatch.delegate = self; // THIS LINE WAS MISSING IN THE APPLE DOCS. DOH.
// Start the game using the match.
NSLog(#"Match started! Expected Player Count:%d %#",match.expectedPlayerCount, match.playerIDs);}
Once I actually set the match delegate, the functions get called. Doh.
When you get the GKMatch object, be sure to check the expectedPlayerCount property. It is possible that the other player is already connected, and thus you will not get a match:player:didChangeState on the delegate.
I have had the same problem with a friend. The solution was quite strange but it works afterwards. On all devices you have to enable the Notifications (Sounds/Alerts/Badges) for the Game Center inside the Settings/Notifications options. Afterwards we could establish a connection and did receive a match object
Inside your callback matchmakerViewController:didFindMatch
Add this code then you'll see the callback "match:player:didChangeState" being called by GC
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = 2;
request.maxPlayers = 2;
[[GKMatchmaker sharedMatchmaker] addPlayersToMatch:match matchRequest:request completionHandler:^(NSError* error) {
if(error)
NSLog(#"Error adding player: %#", [error localizedDescription]);
}];
It was working all along. The only difference is that... when you use invites the event "didChangeState" doensn't get called. You're connected without notice and you can start to receive data. I never tried to send/receive data... cuz i was expecting the event first, but i did send something by mistake one time, and it worked. :)
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *) match {
//Dismiss window
[self dismissModalViewControllerAnimated:YES];
//Retain match
self.myMatch = match;
//Delegate
myMatch.delegate = self;
//Flag
matchStarted = TRUE;
//Other stuff
}
- (void)match:(GKMatch *)match player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state {
//This code gets called only on auto-match
}
The above code works as expected.
Make sure that you've set your class as the delegate for GKSession. The class will need to implement the GKSessionDelegate protocol... otherwise, it'll never receive this callback. Here's the protocol reference. Hope this helps!

AudioQueue code from SpeakHere fails on iPad

I've using the SpeakHere audio classes in an App I'm creating that must Play & Record simultaneously.
I'm using the newest SDK with a 3.2 device target in a universal app build (targeting iPad & iPhone).
The app plays streaming movies using MPMoviePlayerController and Records audio simultaneously.
This works 100% perfectly on an iPhone.
However, it fails 100% on my clients iPad. Logs show !act errors that the AudioSession simply is refusing to active! And every log file I've received from him contains numerous Interruptions & Route Changes (namely Category) being returned to the callback functions.
**On an iPhone I do NOT see anything like this at all. The logs show only that teh record was create, and recorded to the specified file. No interruptions, no route changes, no nonsense.
Here's the relevant logs:
Jul 10 07:15:21 iPad mediaserverd[15502] <Error>: [07:15:21.464 <0x1207000>] AudioSessionSetClientPlayState: Error adding running client - session not active
Sat Jul 10 07:15:21 iPad mediaserverd[15502] <Error>: [07:15:21.464 <AudioQueueServer>] AudioQueue: Error '!act' from AudioSessionSetClientPlayState(15642)
I've stubbed out both my callback functions to merely log the occurrences of interruptions and route changes (with reasons). So I won't bother posting the code, since it does literally nothing. I see these logs numerous times during a single attempt to start recording on the iPad though.
I've read virtually every post I can find in the Apple Dev forum and StackOverflow, but cannot seem to find someone with the same problem or any relevant notes in the Apple Docs that explain the difference in iPad behavior.
--Note: The iPad did display some other defective behaviors that were remedied, such as the mismatched Begin Interruption calls that never ended (so I never deactivate the session).
I never receive any logs indicating any failed initialization or activation calls from the AudioQueue or AudioSession code. It simply fails when I attempt to start recording.
--I even attempted forcing AudioSessionSetActive(true); calls before every attempted use of the sound system and I still receive these errors.
Here's the relevant code for the initialization calls:
//Initialize the Sound System
OSStatus error = AudioSessionInitialize(NULL, NULL, interruptionListener, self);
if (error){ printf("ERROR INITIALIZING AUDIO SESSION! %d\n", (int)error); }
else {
//must set the session active first according to devs talking about some defect....
error = AudioSessionSetActive(true);
if (error) NSLog(#"AudioSessionSetActive (true) failed");
UInt32 category = kAudioSessionCategory_PlayAndRecord;
error = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category);
if (error) printf("couldn't set audio category!\n");
error = AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, propListener, self);
if (error) printf("ERROR ADDING AUDIO SESSION PROP LISTENER! %d\n", (int)error);
//Force mixing!
UInt32 allowMixing = true;
error = AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryMixWithOthers, sizeof (allowMixing), &allowMixing );
if (error) printf("ERROR ENABLING MIXING PROPS! %d\n", (int)error);
UInt32 inputAvailable = 0;
UInt32 size = sizeof(inputAvailable);
// we do not want to allow recording if input is not available
error = AudioSessionGetProperty(kAudioSessionProperty_AudioInputAvailable, &size, &inputAvailable);
if (error) printf("ERROR GETTING INPUT AVAILABILITY! %d\n", (int)error);
isInputAvailable = (inputAvailable) ? YES : NO;
//iPad doesn't require the routing changes, branched to help isolate iPad behavioral issues
if(! [Utils GetMainVC].usingiPad){
//redirect to speaker? //this only resets on a category change!
UInt32 doChangeDefaultRoute = 1;
error = AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryDefaultToSpeaker, sizeof (doChangeDefaultRoute), &doChangeDefaultRoute);
if (error) printf("ERROR CHANGING DEFAULT ROUTE PROPS! %d\n", (int)error);
//this resets with interruption and/or route changes
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
error = AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute,sizeof (audioRouteOverride),&audioRouteOverride);
if (error) printf("ERROR SPEAKER ROUTE PROPS! %d\n", (int)error);
}
// we also need to listen to see if input availability changes
error = AudioSessionAddPropertyListener(kAudioSessionProperty_AudioInputAvailable, propListener, self);
if (error) printf("ERROR ADDING AUDIO SESSION PROP LISTENER! %d\n", (int)error);
error = AudioSessionSetActive(true);
if (error) NSLog(#"AudioSessionSetActive (true) failed");
}
// Allocate our singleton instance for the recorder & player object
myRecorder = new AQRecorder();
myPlayer = new AQPlayer();
Later on in the loadstate callback for the video I merely attempt to start the recording to a predetermined filepath:
myRecorder->StartRecord((CFStringRef)myPathStr);
And audio recording completely fails.
Thanks for your time and help on this.
Turns out this is an odd issue.
1) Use only sound recording and play back and the code runs perfectly on iPad.
2) Add the movie playback and DO NOT call any routing changes and things work fine on iPad.
Somehow the presence of the Movie Player playback is enough to change the AudioSession in some way that forcing any route changes (like to use the device speaker instead of headphones) causes the AudioSession to become inactive.

Why does Reachability sample app stall here?

Apple's sample app named Reachability shows how to detect connections. If you have only wifi but not internet, the app stalls for over a minute on the second line below:
SCNetworkReachabilityFlags reachabilityFlags;
BOOL gotFlags = SCNetworkReachabilityGetFlags(reachabilityRef, &reachabilityFlags);
SCNetworkReachabilityGetFlags comes from SystemConfiguration.framework. Any suggestions on how to get around this?
To answer your question directly, no, there doesn't seem to be any way to "get around" SCNetworkReachabilityGetFlags() taking a long time to return under the specific circumstances you described (e.g., checking remote host reachability via WiFi connection to a router with no Internet). A couple of options:
OPTION 1. Make the call in a separate thread so that the rest of your app can keep running. Modify ReachabilityAppDelegate.m as follows for an example:
// Modified version of existing "updateStatus" method
- (void)updateStatus
{
// Query the SystemConfiguration framework for the state of the device's network connections.
//self.remoteHostStatus = [[Reachability sharedReachability] remoteHostStatus];
self.remoteHostStatus = -1;
self.internetConnectionStatus = [[Reachability sharedReachability] internetConnectionStatus];
self.localWiFiConnectionStatus = [[Reachability sharedReachability] localWiFiConnectionStatus];
[tableView reloadData];
// Check remote host status in a separate thread so that the UI won't hang
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSTimer *timer = [NSTimer timerWithTimeInterval:0 target:self selector:#selector(updateRemoteHostStatus) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[pool release];
}
// New method
- (void) updateRemoteHostStatus
{
self.remoteHostStatus = [[Reachability sharedReachability] remoteHostStatus];
[tableView reloadData];
}
OPTION 2. Use a different API/function that uses a timeout value when trying to connect to the remote host. That way your app would only hang for X seconds before it gives up.
Some other things to note:
The specific call to SCNetworkReachabilityGetFlags() that you're asking about (i.e., line ~399 in Reachability.m) is trying to see if www.apple.com is "reachable" to deduce if "the external internet" is "reachable" in general.
In Apple's System Config framework "reachable" might not mean what you think it does. According to the official docs, "reachable" seems to mean that, in theory, your computer could connect to host X if it wanted to, but it might need to actually establish a connection first (e.g., dial a modem first). In other words, SCNetworkReachabilityGetFlags() doesn't actually establish a connection.