iOS8 AVAudioSession setActive error - avaudiosession

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.

Related

AudioKit: Red Mic Icon Always Appears When App In Background

I’m having difficulty finding a way to get rid of the red “mic in use” icon at the top of the iPhone when my app goes to background (while not recording). Thus it gives the appearance that the app is always recording even when in the background.
Here’s how I’m initializing the mic in my AudioKitManager class:
// FYI: need to set to playAndRecord here otherwise crashes with “required condition is false: IsFormatSampleRateAndChannelCountValid(format)”
do {
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: .allowBluetoothA2DP)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print(error)
}
mic = AKMicrophone()
let inputBooster = AKBooster(mic)
// boost helps with pitch tracking on some iPads
inputBooster.gain = 5
tracker = AKFrequencyTracker(inputBooster)
tracker.stop() // turned off until it's needed - startPitchTracking() called
micMixer = AKMixer(tracker)
micBooster = AKBooster(micMixer)
micBooster.gain = 0.0
micBooster >>> mixer
print("Setting AudioKit.output = mixer")
AudioKit.output = mixer
AKSettings.playbackWhileMuted = true
AKSettings.defaultToSpeaker = true
AKSettings.sampleRate = 44100
do {
print("Attempting to start AudioKit")
try AudioKit.start()
} catch {
AKLog("AudioKit did not start!")
}
Things I’ve tried:
“Stopping” and “starting” the mic when the recording vc appears/disappears. e.g. mic.stop() - Red icon still shows even when mic stopped.
Setting the AVAudioSessionCatagory to and from playAndRecord and .play as recording vc appears/disappears. Really thought this would work!… but the Red Icon of Terror still stares back at me and into my soul.
Calling: AKSettings.audioInputEnabled = false. - Red icon still indicated mic input is enabled.
Only initializing the mic when needed. (Tried this a while ago: seem to remember it was a total no-go)
Dressing as David Bowie and singing “Starman” loudly into mic before app goes to background. Probably should have been the first thing to try, but still to no avail.
Various combinations of above
Any help much appreciated! Thx!
Kudos to AudioKit - it’s an amazing framework! :^)
AudioKit: 4.5.4
iOS: 12.1
Xcode: 10.1
Aha. The solution is to call audioKit.stop() when the app goes to background. Then audioKit.start() before it's used again!! Goodbye red mic icon. Goodbye.
I'd overlooked this previously because I'd been experiencing a lot of issues whenever stopping and starting audio kit. However (as I mentioned in this post Continuous Sine Wave From AKMIDISampler when AKMicrophone is Present ) the main problem was fixed by reloading the samples into all samplers AFTER Audio Kit is started again.
Wheew.

Keep AVAudioPlayer sound in the memory

I use AVAudioPlayer to play a click sound if the user taps on a button.
Because there is a delay between the tap and the sound, I play the sound once in viewDidAppear with volume = 0
I found that if the user taps on the button within a time period the sound plays immediately, but after a certain time there is a delay between the tap and the sound in this case also.
It seems like in the first case the sound comes from cache of the initial play, and in the second case the app has to load the sound again.
Therefore now I play the sound every 2 seconds with volume = 0 and when the user actually taps on the button the sound comes right away.
My question is there a better approach for this?
My goal would be to keep the sound in cache within the whole lifetime of the app.
Thank you,
To avoid audio lag, use the .prepareToPlay() method of AVAudioPlayer.
Apple's Documentation on Prepare To Play
Calling this method preloads buffers and acquires the audio hardware
needed for playback, which minimizes the lag between calling the
play() method and the start of sound output.
If player is declared as an AVAudioPlayer then player.prepareToPlay() can be called to avoid the audio lag. Example code:
struct AudioPlayerManager {
var player: AVAudioPlayer? = AVAudioPlayer()
mutating func setupPlayer(soundName: String, soundType: SoundType) {
if let soundURL = Bundle.main.url(forResource: soundName, withExtension: soundType.rawValue) {
do {
player = try AVAudioPlayer(contentsOf: soundURL)
player?.prepareToPlay()
}
catch {
print(error.localizedDescription)
}
} else {
print("Sound file was missing, name is misspelled or wrong case.")
}
}
Then play() can be called with minimal lag:
player?.play()
If you save the pointer to AVAudioPlayer then your sound remains in memory and no other lag will occur.
First delay is caused by sound loading, so your 1st playback in viewDidAppear is right.

Using Spotify/background music with camera open

I have an app that needs to have:
Background music playing while using the app (eg. spotify)
Background music playing while watching movie from AVPlayer
Stop the music when recording a video
Like Snapchat, the camera-viewcontroller is part of a "swipeview" and therefore always on.
However, when opening and closing the app, the music makes a short "crack" noise/sound that ruins the music.
I recorded it here:
https://soundcloud.com/morten-stulen/hacky-sound-ios
(3 occurrences)
I use these settings for changing the AVAudiosession in the appdelegate didFinishLaunchingWithOptions:
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord,withOptions:
[AVAudioSessionCategoryOptions.MixWithOthers,
AVAudioSessionCategoryOptions.DefaultToSpeaker])
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print("error")
}
I use the LLSimpleCamera control for video recording and I've set the session there to:
_session.automaticallyConfiguresApplicationAudioSession = NO;
It seems others have the same problem with other camera libraries as well:
https://github.com/rFlex/SCRecorder/issues/127
https://github.com/rFlex/SCRecorder/issues/224
This guy removed the audioDeviceInput, but I kinda need that for recording video.
https://github.com/omergul123/LLSimpleCamera/issues/48
I also tried with Apple's code "AvCam", and I still have the same issue. How does Snapchat do this?!
Any help would be greatly appreciated, and I'll gladly provide more info or code!
I do something similar to what you're wanting, but without the camera aspect, but I think this will do what you want. My app allows background audio that will mix with non-fullscreen video/audio. When the user plays an audio file or a full screen video file, I stop the background audio completely.
The reason I do SoloAmbient then Playback is because I allow my audio to be played in the background when the device is locked. Going SoloAmbient will stop all background music playing and then switching to Playback lets my audio play in the app as well as in the background.
This is why you see a call to a method that sets the lock screen information in the Unload method. In this case, it is nulling it out so that there is no lock screen info.
In AppDelegate.swift
//MARK: Audio Session Mixing
func allowBackgroundAudio()
{
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: .MixWithOthers)
} catch {
NSLog("AVAudioSession SetCategory - Playback:MixWithOthers failed")
}
}
func preventBackgroundAudio()
{
do {
//Ask for Solo Ambient to prevent any background audio playing, then change to normal Playback so we can play while locked
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategorySoloAmbient)
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
} catch {
NSLog("AVAudioSession SetCategory - SoloAmbient failed")
}
}
When I want to stop background audio, for example when playing an audio track that should be alone, I do the following:
In MyAudioPlayer.swift
func playUrl(url: NSURL?, backgroundImageUrl: NSURL?, title: String, subtitle: String)
{
ForgeHelper.appDelegate().preventBackgroundAudio()
if _mediaPlayer == nil {
self._mediaPlayer = MediaPlayer()
_mediaPlayer!.delegate = self
}
//... Code removed for brevity
}
And when I'm done with my media playing, I do this:
private func unloadMediaPlayer()
{
if _mediaPlayer != nil {
_mediaPlayer!.unload()
self._mediaPlayer = nil
}
_controlView.updateForProgress(0, duration: 0, animate: false)
ForgeHelper.appDelegate().allowBackgroundAudio()
setLockScreenInfo()
}
Hope this helps you out!

Do I have to reset Audio Session Properties after interruption?

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)

How to detect if AVAudioRecorder is paused?

By looking at reference, there is no explicit solution - some property or a delegate callback...
But maybe there could be some trick, how to reliably ask the recorder to tell me whether it is paused. Sometimes the required information can be derived from the state of other properties..etc..
Of course I can store that "paused" information myself throughout the management of the whole recording session. But it's less reliable and I want to be sure, that its not possible as described above.
You could simply check for isRecording to be false, that would either mean that it is stopped or paused.
In SWIFT 2 you just check Booll property called recording:
if audioRecorder.recording{
print("recording")
}else{
print("not recording")
}
You can check a boolean value isRecording that indicates whether the audio recorder is recording.
if audioRecorder.isRecording {
// AudioRecorder is recording.
} else {
// AudioRecorder is not recording.
}

Resources