Trying to identify problem with core audio vs iPhone background execution - iphone

I have an audio app in the store. It works fine with versions of the iPhone OS before iOS 4.0. I am working on a new version, starting by bringing the app into compliance with the new iOS multitasking capabilities. (I have iOS 4.0 on my iPhone, and have upgraded to the latest xCode and SDK.)
The result has been a show-stopper.
o When I install the app on the iPhone and run in the debugger, or start the app after the iPhone was powered down, the app acts just fine - playing audio as I push buttons, or restarting audio from "state" that was saved the previous time the app was run.
o If I try to push the Home button and then restart the app from the background, the audio - a and mix of 1-n audio streams - comes out with a stuttering sound that only I could recognize.
o If - while the audio is cleanly - I call the iPhone, I can either answer or decline the call. In both cases the app comes back cleanly, playing as it should.
Note that I am not trying to play anything while in background mode. I simply want to shut things off and bring them back when asked.
Technology: All of the audio logic uses RemoteIO. (I have plans for someday doing some fairly sophisticated signal processing.)
Please excuse the pseudo code narrative - some of the real code is pretty extensive.
MainViewController:
o viewDidLoad - nothing special
o didBecomeActive
set self as audio session delegate;
set the AudioSessionCategory to MediaPlayback;
set the AudioSessionActive;
create a Player (see below);
restart any sounds that were previously playing;
o willResignActive
save the state of what was playing;
stopAllSounds;
stop the player;
set the audio session inactive;
o didEnterBackground - nothing (method is registered, but no content.)
o willEnterForeground - nothing (method is registered, but no content.)
Overview of Player - my class:
The Player is a singleton. I make sure that the singleton instance is destroyed, and a new one created, in didBecomeActive. Instantiating the singleton actually starts the AudioUnit.
constructor
setupRemoteIO;
start
AudioOutputUnitStart;
stop
AudioOutputUnitStop;
player callback
the usual stuff;
>
Here are some methods from my MainViewController:
(void)willResignActive:(NSNotification *)note {
[ self saveState ];
[ [ NaturePlayer getInstance ] stopAllSounds ];
[ [ NaturePlayer getInstance ] stop ];
NSError *activationError = nil;
[[AVAudioSession sharedInstance] setActive: NO error: &activationError];
[NaturePlayer destroy];
}
- (void)didBecomeActive:(NSNotification *)note {
[ self setupAudioSession ];
NSError *activationError = nil;
[[AVAudioSession sharedInstance] setActive: YES error: &activationError];
[ self restartSounds ];
[ self setViewState ];
}
- (void)didEnterBackground:(NSNotification *)note {
int xxx = 1;
}
- (void)willEnterForeground:(NSNotification *)note {
int xxx = 1;
}
- (void) applicationWillTerminate: (NSNotification*) notification {
int xxx = 1;
}
Here is a method from the NaturePlayer:
/**
* Stop the playback process.
*/
void NaturePlayerDelegate::stop() {
if (isPlaying_) {
AudioOutputUnitStop(outputUnit_);
isPlaying_ = FALSE ;
}
}
I checked: stop() is being called, and in turn calls AudioOutputUnitStop();
Also checked: The doc on AudioOutputUnitStop says it blocks until all activity has completed.

Hard to say without more details on the audio code. But I suggest you might want to examine your Player object teardown code to make sure the audio unit and the audio session are completely stopped (buffer callback thread drained) before releasing stuff.

Are you initializing and un-initializing AudioUnits within your code? I have recently found an issue that AudioUnits being created and destroyed then leaving and returning to your App will cause stuttering.
I was able to fix this in my application by never uninitializing the AudioUnits.
More info on my problem and my AudioUnit code.

I had the same problem in a project of mine. I found that if I called AudioOutputUnitStop() from a button click before suspending the app, then called AudioOutputUnitStart() when the app resumed, I had no problems. But if I called AudioOutputUnitStop() automatically from the viewDidUnload handler or as a response to the UIApplicationWillResignActiveNotification, I had the stuttering, feedback-like sound described above.
I also noticed that if I stopped the audio unit manually, then suspended the app, the interruption listener was called. But if I suspended the app while the audio unit was still running, the interruption listener was not called. The interruption listener is supposed to stop the audio unit, so that's not doing me much good.
After several hours of tracing this, I found a way to finish my task without audio units, and took them out of my app. So I'm afraid I don't have a complete answer. But hopefully these clues will help someone. I may need to do this again in the future, so I'll check back here to see if someone else has figured it out.

Related

Alarm clock with answer-to-disable on iOS

Apple resources contain a lot of informations but there's one thing which I can't clearly understand reading about audio and notification.
Is it possible to make an app, running in background which produce sound (even if phone is locked and/or silenced) and when it's happend user must solve eg. equation to turn it off?
p.s. For now I mostly use Cordova framework but Obj-C tip will also be nice.
Yes it is posssible.
You can use UILocalNotification for this.
Also apple allows apps that are playing music in background.
Please check these links for the background task feature:
ManagingYourApplicationsFlow
ios multitasking background tasks
How to handle background audio playing while ios device is locked or on another
You can change Local Notifications for NSTimers (keeping them alive in inactive mode with https://github.com/mruegenberg/MMPDeepSleepPreventer) and calculate the time interval for each alarm. That way you can then play an audio even with the screen locked and the sound off pasting this in your - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions:
// Let the sound run with the screen blocked
NSError *setCategoryErr = nil;
NSError *activationErr = nil;
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error:&setCategoryErr];
[[AVAudioSession sharedInstance] setActive:YES error:&activationErr];
But you will have some problems:
The app must be playing an audio file each 10 seconds so it doesn´t deep sleep and kills all NSTimers.
Apple could reject your app for doing so.
You can´t close the app with the home button, otherwise, it won´t work.
You must open the app every time you need to use the alarm (you can´t schedule and forget).
When the alarm fires, you only have the lock screen of the iPhone and need to unlock it first and then stop the alarm from inside the app.
In Apple they don´t want competitors for their alarm clock app, that's for sure! Almost all the alarm clock apps you see in the App Store use this poor approach.

Music not stopping when moving an app to the background

I am optimizing a game so that when there is an incoming call or if it is moved to the background for any other reason, it should stop the music, timers and pause correctly.
It works great other than the fact that for some reason the music doesn't stop playing even though I issued a command for it to stop. What's weirder is that when the game returns to the foreground there's 2 background musics playing instead of one.
Here is my some of my code, nothing too complicated or out of the ordinary:
...
NativeApplication.nativeApplication.systemIdleMode = SystemIdleMode.KEEP_AWAKE;
NativeApplication.nativeApplication.addEventListener(Event.ACTIVATE, handleActivate);
NativeApplication.nativeApplication.addEventListener(Event.DEACTIVATE, handleDeactivate);
...
private function handleActivate(event:Event):void
{
stage.frameRate = previousFrameRate;
//if the music was on when the game is moved to the background, then replay it when its moved back to the foreground
if(musicOn)
{
BGMusic.play();
}
}
private function handleDeactivate(event:Event):void
{
BGMusic.stop();
stage.frameRate = 0;
}
It is worth noting that if I don't replay the music when then game returns to the foreground (i.e. when I don't use BGMuusic.play() in handleActivate), there won't be any music as expected. It is only when I stop the music AND resume it later when moving to the foreground that the background music doesn't stop and I get two playing tracks.
Any ideas? As I said, everything else works fine, and the game correctly pauses. I am testing this on Flash builder Emulator since I don't have the necessarily certificates to test it directly on the iphone. So maybe it is a problem with the emulator itself rather than the code.
Thanks in advance
EDIT: Am writing this with flash and adobe air
Well that was stupid of me. Everything is working fine I just forgot to add another condition to the if statement. So instead of:
if(musicOn)
{
BGMusic.play();
}
It should be
if(musicOn && !BGMusic.isPlaying())
{
BGMusic.play();
}
Everything works fine now...I will change the variable name of musicOn to something clearer like musicButtonOn.
Anyways thanks for help. If you have any tips or comments feel free to tell me =D
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
NSLog(#"Playing stoped");
[player stop];
}

MPMoviePlayerController background audio issue in iOS5

I have an App that does the pretty standard operation:
It plays audio (streamed or in filesystem) when the app is in 1) Foreground mode, 2) Screen locked state 3)Background mode.
This was working fine in all iOS prior to iOS5.
I have been using MPMoviePlayerController (Because it can play streamed and local file system audio)
I have the following setup:
info.plist has Background Mode set to "Audio"
I have Audiosession setup as shown at http://developer.apple.com/library/ios/#qa/qa1668/_index.html
NSError *activationError = nil;
AVAudioSession *mySession = [AVAudioSession sharedInstance];
[mySession setCategory: AVAudioSessionCategoryPlayback error: &activationError];
if (activationError) { /* handle the error condition */ }
[mySession setActive: YES error: &activationError];
if (activationError) { /* handle the error condition */ }
I have background timer enabled that gets stopped at the end of audio playback
UIBackgroundTaskIdentifier newId = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:NULL];
I have the Moveplayer's useApplicationAudioSession = NO
I have subscribed to the following events to detect and handle various playback state and to start a new audio file at the end of current file.
MPMoviePlayerLoadStateDidChangeNotification
MPMoviePlayerPlaybackDidFinishNotification
MPMoviePlayerPlaybackStateDidChangeNotification
MPMoviePlayerNowPlayingMovieDidChangeNotification
Problem:
With this the audio starts to play and when the application is put to background state or if the phone is locked, the audio continues to play. But, after when I start another audio file,
I start getting PlaybackDidFinishNotification immediately with the state set to Playback ended (But the file was never played)
The same code plays audio files in foreground mode (After the current audio file ends, the next file is started without any problem)
Is there anything new in iOS5 I should be doing to get this to work? I read through the MPMoviePlayerController class reference and I couldn't see anything specific for iOS5.
Thanks in advance.
Finally figured out the issue. This is solved in this post in apple dev forums (needs login to see). That post was applicable to AVPlayer but also fixes the problem with MPMoviePlayerController as well.
Basically, this is an excerpt from that post:
your app must support remote control events! These are the audio
controller interface prex/nex/play/pause on the left of the multitask
switcher taskbar (not sure about the proper name of the thing). You
to this ensuring your view becomes First Controller and then calling
> [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
in viewDidLoad. Once you do this, your Player will no longer return
NO!!
My situation was different and I'm only answering here (and in the other SO question) to help future searchers on this error message. This does not answer the original question.
My app plays a sound OR a song but when I first coded it could play both. And in testing I always tested with a song. I played the song in the usual way:
self.musicQuery = [MPMediaQuery songsQuery];
[_musicQuery addFilterPredicate:[MPMediaPropertyPredicate predicateWithValue:selectedSongID forProperty:MPMediaItemPropertyPersistentID comparisonType:MPMediaPredicateComparisonEqualTo]];
[_musicQuery setGroupingType:MPMediaGroupingTitle];
[_myPlayer setQueueWithQuery:_musicQuery];
[_myPlayer play];
Weeks passed and I started testing with the sound, played with AVAudioPlayer. My app started freezing for 5 seconds and I'd get the MediaPlayer: Message playbackState timed out message in the Console.
It turns out that passing a query that was empty was causing the freeze and the message. Changing my app's logic to only play a song when there was a song to play fixed it.

Distinguish between an iPhone App crashing and being killed

Hello I am planning to develop a simple iPhone game. I would like to be able to distinguish between a genuine crash, and the user killing the app ( by double tapping home screen and long-clicking it to kill it) .
Can someone please shed some light on what exactly happens when the user kill the app through the multitasking bar.
If your app is in the background and suspended when the user kills it, it will receive no notification. This accounts for the majority of cases.
If your app is currently running in the background (there are only very specific categories of apps that can do that), then it receives applicationWillTerminate.
Indeed, Apple is very clear as to the fact that you should save any relevant data before entering the background. Have a look at this (chapter "Responding to Application Termination"):
Even if you develop your application using iOS SDK 4 and later, you must still be prepared for your application to be killed without any notification. The user can kill applications explicitly using the multitasking UI. In addition, if memory becomes constrained, the system might remove applications from memory to make more room. If your application is currently suspended, the system kills your application and removes it from memory without any notice. However, if your application is currently running in the background state (in other words, not suspended), the system calls the applicationWillTerminate: method of your application delegate. Your application cannot request additional background execution time from this method.
EDIT:
about the "saying sorry" thing...
you can certainly do that on the next launch. simply store a key in NSUserDefaults and remove it when the app enters the background (I hope all this sounds familiar to you, otherwise look into the UIApplicationDelegate protocol).
when the app starts up, you check the key; if it is there, then the app was not closed by the user; if the app is not there, then the user at least moved the app to the background and did not experience any sudden termination...
For iOS6 and later there is a way to do this. A side effect of State Restoration is that it will delete the state when there is either a crash during restore or a user manually kills the app. You can use this to your advantage to detect a user manually killing the app.
From the documentation:
Be aware that the system automatically deletes an app’s preserved state when the user force quits the app. Deleting the preserved state information when the app is killed is a safety precaution. (The system also deletes preserved state if the app crashes at launch time as a similar safety precaution.) If you want to test your app’s ability to restore its state, you should not use the multitasking bar to kill the app during debugging. Instead, use Xcode to kill the app or kill the app programmatically by installing a temporary command or gesture to call exit on demand.
The following code assumes that you already have a BOOL for crash detection called _didCrashInLastSession. There are different approaches for getting this value such as this 3rd party library. In your code call the method [self getLaunchType] to see which type of launch you are dealing with and act on that accordingly. Put the following in your AppDelegate.m:
typedef NS_ENUM(NSInteger, LaunchType) {
LaunchTypeUnknown,
LaunchTypeNewInstall,
LaunchTypeNormalLaunch,
LaunchTypeCrashedLastSession,
LaunchTypeUserManualQuit,
};
static BOOL hadStateToRestore = NO;
static NSString * const kAppHasEverRunKey = #"appHasEverRun";
- (BOOL)application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder
{
// Called when going into the background
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kAppHasEverRunKey];
[[NSUserDefaults standardUserDefaults] synchronize];
return YES;
}
- (BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder
{
// Called on start up
hadStateToRestore = YES;
return YES;
}
- (LaunchType)getLaunchType
{
if (_didCrashInLastSession) {
return LaunchTypeCrashedLastSession;
}
if (![[NSUserDefaults standardUserDefaults] boolForKey:kAppHasEverRunKey]) {
return LaunchTypeNewInstall;
}
if (!hadStateToRestore) {
return LaunchTypeUserManualQuit;
}
return LaunchTypeNormalLaunch;
}
Update: At least one 3rd party SDK breaks this technique: Urban Airship.
You can do it through your device also.
Connect your device to your machine.
Run xcode and go to organizer.
There select your device and device logs.
There you can also see crash logs of your app or game.

What happens to an iPhone app when iPhone goes into stand-by mode?

My app uses NSTimer and it appears that NSTimer doesn't fire when the iPhone goes into the stand-by mode (either by pressing the hardware button or by the idle timer).
When I activate the iPhone again, my app is still in the foreground. What happens to third party apps when the iPhone is the stand-by mode?
Although it's not evident here, I believe the original poster did find an answer to his question by starting a thread (available here) in the iPhone Developer Forums (which I eventually had to find myself because the information wasn't shared here).
In case someone else has the same question and finds the page in the future, here's a helpful response that was posted by someone on the Apple forum called "eskimo1" (which I have edited slightly such that it is easier to read without having the context provided by the entire original thread):
Regarding iPhone app status terminology, "active" does not mean "awake", it means "attached to the GUI". Think of it being analogous to "frontmost" in Mac OS X. When you lock the device your app deactivates but the device may or may not go to sleep
iPhone OS rarely sleeps if the device is connected to main power (i.e., via USB). It can sleep if running on battery, however.
A short time after the screen is locked (20 seconds according to Oliver Drobnik), the device sleeps. This is like closing the lid on your laptop; all activity on the main CPU halts.
This does not happen if the device is playing audio in the right audio session. See DTS Q&A QA1626 "Audio Session - Ensuring audio playback continues when screen is locked" for details.
Note that the idleTimerDisabled property (which can be turned on to prevent the screen from turning off while the app is running) is about locking the screen after user inactivity. It's not directly related to system sleep (it's indirectly related in that the system may sleep shortly after it's locked).
See Application Interruptions in the iPhone OS Programming Guide, especially the applicationWillResignActive and applicationDidBecomeActive events. (The whole guide is certainly worth reading.) When You ignore the events, the timer seems to go on for a while and then stops. Sounds logical, the application could easily drain the battery if kept running. And what exactly happens to the application? I guess it simply does not get any CPU time – it freezes and only thaws when You turn the machine back “on.”
My first advice is do not disable the idle timer, that is just a hack. If you want to keep a timer alive during UI events run the timer on the current run loop using NSCommonModes:
// create timer and add it to the current run loop using common modes
self.timer = [NSTimer timerWithTimeInterval:.1 target:self selector:#selector(handleTimer) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
I used the information on this post for a small sample that I was building. This is the code that I used when I initiated the playback to prevent the audio from stopping:
AudioSession.Category = AudioSessionCategory.MediaPlayback;
And when the application is done with the playback to reset to the original value:
AudioSession.Category = AudioSessionCategory.SoloAmbientSound;
The full sample is here:
http://github.com/migueldeicaza/monotouch-samples/tree/master/StreamingAudio/
I was faced with this issue recently in an app I am working on that uses several timers and plays some audio prompts and made two relatively simple changes:
In the AppDelegate I implemented the following methods and there mere presence allows the app to continue when the screen is locked
// this receives the notification when the device is locked
- (void)applicationWillResignActive:(UIApplication *)application
{
}
// this receives the notification that the application is about to become active again
- (void)applicationWillBecomeActive:(NSNotification *)aNotification
{
}
references: UIApplicationDelegate Protocol Reference & NSApplication Class Reference in the API doc (accessible via Xcode, just search for applicationWillBecomeActive).
Made the main viewcontroller class an AVAudioPlayerDelegate and used this code from Apple's "AddMusic" sample to make the audio alerts the app played mix nicely into the iPod audio etc...
I just dropped this code into a method that is called during viewDidLoad. If this interests you, you fall into the "who should read this doc" category for this: Audio Session Programming Guide
// Registers this class as the delegate of the audio session.
[[AVAudioSession sharedInstance] setDelegate: self];
// The AmbientSound category allows application audio to mix with Media Player
// audio. The category also indicates that application audio should stop playing
// if the Ring/Siilent switch is set to "silent" or the screen locks.
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryAmbient error: nil];
// Activates the audio session.
NSError *activationError = nil;
[[AVAudioSession sharedInstance] setActive: YES error: &activationError];
I believe your application should run normally when suspended. (think Pandora Radio)
Check to see if your timer is being deallocated due to your view being hidden or some other event occurring.