Do I have to reset Audio Session Properties after interruption? - ios

I am implementing a C listener to Audio Session Interruption. When it is called for interruption, I would deactivate my audio session. Then when my app resumes, I would activate the audio session again. I have set a number of properties and category for my audio session, do I have to reset everything after re-activation?
Thanks in advance.
Some code for reference:
Initialization, setting category:
OSStatus error = AudioSessionInitialize(NULL, NULL, interuptListenerCallBack, (__bridge void *)(self));
UInt32 category = kAudioSessionCategory_PlayAndRecord;
error = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category);
if (error) printf("couldn't set audio category!");
//use speaker as default
UInt32 doChangeDefaultOutput = 1;
AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryDefaultToSpeaker, sizeof(doChangeDefaultOutput), &doChangeDefaultOutput);
//allow bluethoothInput
UInt32 allowBluetoothInput = 1;
AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryEnableBluetoothInput, sizeof(allowBluetoothInput), &allowBluetoothInput);
The interuptListenerCallBack is where I deactivate and reactive the Audio Session because of the interruption, using
OSStatus error = AudioSessionSetActive(false);
if (error) printf("couldn't deactivate audio session!");
Or
OSStatus error = AudioSessionSetActive(true);
if (error) printf("AudioSessionSetActive (true) failed");

If you are correctly using the Audio session interruption listener, then no, you should not have to reset the properties. You just need to make sure that you actually call kAudioSessionBeginInterruption and kAudioSessionEndInterruption. I am not sure what your listener looks like, but if you are doing something like this:
if (inInterruptionState == kAudioSessionBeginInterruption) {
AudioSessionSetActive(NO);
}
if (inInterruptionState == kAudioSessionEndInterruption) {
AudioSessionSetActive(YES);
}
And are following the rules of Audio Session, Then theoretically, you should not have to reset your properties.
I don't know what you are using the Audio Session for, but you could also pause and resume playback by using the:
kAudioSessionInterruptionType_ShouldResume
and
kAudioSessionInterruptionType_ShouldNotResume.
You can use these as stated in the Docs:
kAudioSessionInterruptionType_ShouldResume
Indicates that the interruption that has just ended was one for
which it is appropriate to immediately resume playback; for example,
an incoming phone call was rejected by the user.
Available in iOS 4.0 and later.
Declared in AudioSession.h.
kAudioSessionInterruptionType_ShouldNotResume
Indicates that the interruption that has just ended was one for which it is not appropriate to resume playback; for example, your app
had been interrupted by iPod playback.
Available in iOS 4.0 and later.
Declared in AudioSession.h.
You should read the docs because there is a lot of info in there about pausing, resuming, and handling interruptions for the AudioSession.
NOTE:
AudioSession has been deprecated since iOS7. Use AVAudioSession methods instead, or set Pause and Resume option by setting the constant AVAudioSessionInterruptionOptions or AVAudioSessionInterruptionType.
(Available since iOS 6)

Related

iOS app audio stops when screen auto-locks after upgrading app to xcode 8

I have an app that has been published on the iTunes App Store, and it has background mode enabled for audio.
After updating to XCode 8, I published an update for my app, after which I've found that the app stops playing whenever the screen locks. I had not made any changes to background play otherwise. Not sure if the behavior or coding requirements changed for iOS 9+
Here's what my code does:
App plist file:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>remote-notification</string>
</array>
AudioController.m
-(void)setBackgroundPlay:(bool)backgroundPlay
{
NSLog(#"setBackgroundPlay %d", backgroundPlay);
AVAudioSession *mySession = [AVAudioSession sharedInstance];
NSError *audioSessionError = nil;
if (backgroundPlay) {
// Assign the Playback category to the audio session.
[mySession setCategory: AVAudioSessionCategoryPlayback
error: &audioSessionError];
OSStatus propertySetError = 0;
UInt32 allowMixing = true;
propertySetError = AudioSessionSetProperty (
kAudioSessionProperty_OverrideCategoryMixWithOthers, // 1
sizeof (allowMixing), // 2
&allowMixing // 3
);
if (propertySetError != 0) {
NSLog (#"Error setting audio property MixWithOthers");
}
} else {
// Assign the Playback category to the audio session.
[mySession setCategory: AVAudioSessionCategoryPlayback
error: &audioSessionError];
}
if (audioSessionError != nil) {
NSLog (#"Error setting audio session category.");
}
}
The audio does continue playing when I minimize the app, and it continues playing until the screen auto-locks. Whenever the screen turns on (like when a notification is received), audio resumes, and then shuts off when the screen goes black.
As mentioned, this stuff used to work, and seems to have changed behavior after update to Xcode 8/iOS 9.
I've tried searching the forum and other places for people experiences similar issues, but haven't been able to locate anything.
Any suggestions, or a fresh pair of eyes looking at this would be appreciated!
Thanks,
Sridhar
Ok, I found the problem! Everything was ok with regard to how I had setup background audio.
The key giveaway was looking at the console of the device when the screen lock had turned on:
Jan 17 11:03:59 My-iPad Talanome[1179] : kAudioUnitErr_TooManyFramesToProcess : inFramesToProcess=4096, mMaxFramesPerSlice=1156
A little searching led me to this Technical note - https://developer.apple.com/library/content/qa/qa1606/_index.html
The key is this --
// set the mixer unit to handle 4096 samples per slice since we want to keep rendering during screen lock
UInt32 maxFPS = 4096;
AudioUnitSetProperty(mMixer, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0,
&maxFPS, sizeof(maxFPS));
I had not set my maxFramesPerSlice, and so it was defaulting to 1156, which was too small for when the auto-lock is on (which is 4096). Setting the maxFramesPerSlice to 4096 in my audio initialization ensured that I have enough for when the screen locks.
Hope this helps others who may face similar issues!
-Sridhar

Can VToolBox run background on IOS?

I want to compress some data with VToolBox. When I run my app in the foreground it runs well, but when I run my app in the background it gives no compressed data anymore...
I added logs when the encoding starts:
- (void) encode1:(CMSampleBufferRef )sampleBuffer wrapTs:(UInt64)ts;
{
dispatch_sync(aQueue, ^{
frameCount++;
// Get the CV Image buffer
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// Create properties
CMTime presentationTimeStamp = CMTimeMake(frameCount, 1000);
//CMTime duration = CMTimeMake(1, DURATION);
VTEncodeInfoFlags flags;
//NSLog(#"encode sessino status:%d", EncodingSession==nil? 0:1);
// Pass it to the encoder
OSStatus statusCode = VTCompressionSessionEncodeFrame(EncodingSession,
imageBuffer,
presentationTimeStamp,
kCMTimeInvalid,
NULL, (__bridge void*)#(ts), &flags);
NSLog(#"hardware compress result: %d", (int)statusCode);
// Check for error
if (statusCode != noErr) {
and in the compress callback:
void didCompressH264(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags,
CMSampleBufferRef sampleBuffer ){
//get outside stamp
UInt64 pp = [((__bridge NSNumber*)sourceFrameRefCon) longLongValue];
NSLog(#"didCompressH264 status:%d", status);
if (status != 0) return;
if (!CMSampleBufferDataIsReady(sampleBuffer))
{
NSLog(#"didCompressH264 data is not ready ");
return;
}
When I run the app in the background, I can see the log "hardware compress result: 0" which means put data into VToolBox well, but I can't get log "didCompressH264 status".
It seems it never reaches the didCompressH264 function.
So, I wonder if VToolBox can run in the background? If so, how? Any answer is appreciated!
iOS won't let you app run in the background forever. By default, it will give you just a few seconds of running time after the app moves to the background. Beyond that, it will either prevent the app from running or kill it.
You can ask for more time using beginBackgroundTaskWithName:expirationHandler: in your AppDelegate's applicationDidEnterBackground: method. You can check with backgroundTimeRemaining how long iOS gives you. Beyond that, your app will be killed.
Note that quite a few things don't work the same in the background. Not sure what resources the library uses, but I would be surprised if you got full access to the GPU while in the background, for instance.
VideoToolbox stops decompressing frames as soon as your app enters background. Hardware acceleration is only permitted on the foreground application to provide the best experience.

iOS8 AVAudioSession setActive error

I'm testing my app in XCode 6 and find an issue with AVAudioSession in iOS8.
When I call
[[AVAudioSession sharedInstance] setActive:NO error:nil];
I get the following error message:
AVAudioSession.mm:623: -[AVAudioSession setActive:withOptions:error:]:
Deactivating an audio session that has running I/O. All I/O should be
stopped or paused prior to deactivating the audio session.
In AVAudioSession.h, it says
Note that this method will throw an exception in apps linked on or after iOS 8 if the session is set inactive while it has running or paused I/O (e.g. audio queues, players, recorders, converters, remote
I/Os, etc.).
But I'm not sure how can I check if there's running I/O and how can I dispose all when I need to reset the audio session.
I solved this problem, inserting this code in the method of AVAudioPlayerDelegate.
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
[audioPlayer stop];
[audioPlayer prepareToPlay];
}
where audioPlayer is a variable declared in the header of my class.
the instruction prepareToPlay should resolve your problem!
Be warned. If you are playing a movie (even with no sound and the audio track removed) then it will still create an I/O thread which will always cause the above error to be thrown.
I'm using AVAudioEngine and AVAudioPlayerNode to play sounds. I meet the same problem. My environment is iOS 10.
I use AVAudioSession.sharedInstance() to handle AVAudioSession. When I run my app in simulator everything is fine, but when I switch it to my device. It failed.
I suppose that I'm using the same session to handle recording and playing. So it may cause some strange issues. In my case, I record the sound and delay 1s to play it. So when I use stopRecordingAudio(), I add this piece of code :
if audioEngine.running {
audioEngine = AVAudioEngine() // var audioEngine = AVAudioEngine()
try! AVAudioSession.sharedInstance().setActive(false)
}
After redefining audioEngine, audioEngine had been forced to stop and session was released. Crash is never happened again.
Hope this message could help you.
I pasued My AVplayer before AVAudioSession setActive NOï¼›
[self.audioPlayer pause];
[[AVAudioSession sharedInstance] setActive:NO
withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
error:nil];
Swift 5, IOS 12
I use AVPlayer, it does not have a prepareToPlay method. Therefore, Alessio Campanelli's answer does not solve my problem.
But I noticed the following
player.pause()
print("currentTime:", player.currentTime().seconds) //currentTime: 4.258164744
print("status:", player.status.rawValue) //status: 1 (readyToPlay)
print("timeControlStatus:", player.timeControlStatus.rawValue) //timeControlStatus: 0 (paused)
print("rate:", player.rate) //rate: 0.0
sleep(1)
print("currentTime:", player.currentTime().seconds) //currentTime: 4.261325767
print("status:", player.status.rawValue) //status: 1 (readyToPlay)
print("timeControlStatus:", player.timeControlStatus.rawValue) //timeControlStatus: 0 (paused)
print("rate:", player.rate) //rate: 0.0
After you call the pause method, the player plays the audio file for a while. Although other properties say that it is in a pause.
So the only solution I found is
player.pause()
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
do {
try AVAudioSession.sharedInstance().setActive(false)
} catch let error {
print(error)
}
}
In fact, even 0.1 seconds is sufficient. This is a bad way, but I could not find anything better. If anyone knows how to check when the player stops playing the audio file, let me know.

AudioToolbox MusicPlayer change program has no effect

MIDI noob in training here...
I have been using MusicPlayer/MusicSequence/MusicTrack to play MIDI notes on devices running iOS. The notes are playing fine. I am struggling to change the instrument being played. As far as I can figure this is how to do it:
-(void) setInstrument:(MIDIInstruments) program channel:(int) channel MusicTrack:(MusicTrack*) track time:(float) time {
if(channel < 0 || channel > 15 || program >=MIDI_INSTRUMENT_COUNT || time < 0) {
return;
}
MIDIChannelMessage programChange = { ((UInt8)0xC) << 4 | ((UInt8)channel), ((UInt8)program), 0, 0};
OSStatus result = MusicTrackNewMIDIChannelEvent(*track, time, &programChange);
if(result != noErr) {
[NSException raise:#"Set Instrument" format:#"Failed to set instrument error: %#", [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil]];
}
}
In this case channel is 0 or 1, I tried several instruments through out the range of valid instrument enumerations, the time is 0.0, and the MusicTrack is valid, and has ~30 seconds of note events. The call to set the channel event passes back noErr. I am stumped...Anyone?
I had read in other posts that I would be able to generate midi using Music Player and friends. It provides for program changes. So, I had figured it was supported. After exhausting all theories, I turned to AUGraph. I added a *.sf2 file that I found online, instantiated the AUGraph, two AudioUnits, a MidiEndpointRef, and a MidiClientRef; according to this tutorial.
It was in the endpoint callback that I had to turn notes on and off using MusicDeviceMIDIEvent on the samplerUnit that seemed to allow for the program change. Whereas before I was just loading note events into a MusicTrack and playing/stoping the MusicPlayer.

iPhone playback AudioQueue stops/fails to continue after pausing at a breakpoint during debugging

For some reason, it seems that stopping at a breakpoint during debugging will kill my audio queue playback.
AudioQueue will be playing audio
output.
Trigger a breakpoint to
pause my iPhone app.
Subsequent
resume, audio no longer gets played.
( However, AudioQueue callback
functions are still getting called.)
( No AudioSession or AudioQueue
errors are found.)
Since the debugger pauses the application (rather than an incoming phone call, for example) , it's not a typical iPhone interruption, so AudioSession interruption callbacks do not get triggered like in this solution.
I am using three AudioQueue buffers at 4096 samples at 22kHz and filling them in a circular manner.
Problem occurs for both multi-threaded and single-threaded mode.
Is there some known problem that you can't pause and resume AudioSessions or AudioQueues during a debugging session?
Is it running out of "queued buffers" and it's destroying/killing the AudioQueue object (but then my AQ callback shouldn't trigger).
Anyone have insight into inner workings of iPhone AudioQueues?
After playing around with it for the last several days, before posting to StackOverflow, I figured out the answer just today. Go figure!
Just recreate the AudioQueue again by calling my "preparation functions"
SetupNewQueue(mDataFormat.mSampleRate, mDataFormat.mChannelsPerFrame);
StartQueue(true);
So detect when your AudioQueue may have "died". In my case, I would be writing data into an input buffer to be "pulled" by AudioQueue callback. If it doesn't occur in a certain time, or after X number of bytes of input buffer have been filled, I then recreate the AudioQueue.
This seems to solve the issue where "halts/fails" audio when you hit a debugging breakpoint.
The simplified versions of these functions are the following:
void AQPlayer::SetupNewQueue(double inSampleRate, UInt32 inChannelsPerFrame)
{
//Prep AudioStreamBasicDescription
mDataFormat.mSampleRate = inSampleRate;
mDataFormat.SetCanonical(inChannelsPerFrame, YES);
XThrowIfError(AudioQueueNewOutput(&mDataFormat, AQPlayer::AQBufferCallback, this,
NULL, kCFRunLoopCommonModes, 0, &mQueue), "AudioQueueNew failed");
// adjust buffer size to represent about a half second of audio based on this format
CalculateBytesForTime(mDataFormat, kBufferDurationSeconds, &mBufferByteSize, &mNumPacketsToRead);
ctl->cmsg(CMSG_INFO, VERB_NOISY, "AQPlayer Buffer Byte Size: %d, Num Packets to Read: %d\n", (int)mBufferByteSize, (int)mNumPacketsToRead);
mBufferWaitTime = mNumPacketsToRead / mDataFormat.mSampleRate * 0.9;
XThrowIfError(AudioQueueAddPropertyListener(mQueue, kAudioQueueProperty_IsRunning, isRunningProc, this), "adding property listener");
//Allocate AQ buffers (assume we are using CBR (constant bitrate)
for (int i = 0; i < kNumberBuffers; ++i) {
XThrowIfError(AudioQueueAllocateBuffer(mQueue, mBufferByteSize, &mBuffers[i]), "AudioQueueAllocateBuffer failed");
}
...
}
OSStatus AQPlayer::StartQueue(BOOL inResume)
{
// if we are not resuming, we also should restart the file read index
if (!inResume)
mCurrentPacket = 0;
// prime the queue with some data before starting
for (int i = 0; i < kNumberBuffers; ++i) {
mBuffers[i]->mAudioDataByteSize = mBuffers[i]->mAudioDataBytesCapacity;
memset( mBuffers[i]->mAudioData, 0, mBuffers[i]->mAudioDataByteSize );
XThrowIfError(AudioQueueEnqueueBuffer( mQueue,
mBuffers[i],
0,
NULL ),"AudioQueueEnqueueBuffer failed");
}
OSStatus status;
status = AudioSessionSetActive( true );
XThrowIfError(status, "\n\n*** AudioSession failed to become active *** \n\n");
status = AudioQueueStart(mQueue, NULL);
XThrowIfError(status, "\n\n*** AudioQueue failed to start *** \n\n");
return status;
}

Resources