AudioQueueNewInput decreases playback volume for AVAudioPlayer - iphone

I am using Stephen Celis' SCListener class to record iPhone microphone audio levels. I also am playing audio through the use of AVAudioPlayer. For example, the user presses 'Play' to kick off a sound playing in the background and then has the option to blow into the microphone to play additional, shorter sounds. The code all works fine, playing all the sounds when they should be played, however, the AVAudioPlayer sound volume greatly decreases when you begin listening with the SCListener. I have narrowed down the culprit to this line in the SCListener source code:
AudioQueueNewInput(&format, listeningCallback, self, NULL, NULL, 0, &queue);
I have racked my brain and can not find out how to keep the playback volume at it's highest level once this line has executed. I have spoken with Stephen Celis, too, and he does not know what is happening. It is possible, I suppose, that the iPhone turns down the output volume when the microphone is being used so that feedback isn't introduced, but it seems like there should be a way to disable that.
In summary:
Start playing long audio file with AVAudioPlayer - 100% volume (loud).
Enable SCListener and begin listening (which calls AudioQueueNewInput).
The output volume on the AVAudioPlayer sound greatly decreases
Call [[SCListener sharedListener] stop] to dispose of the queue
AVAudioPlayer sound resumes higher playback volume
Has anyone seen anything like this or have any ideas on how to keep the playback volume higher? I have explicitly set the volume parameter to 1.0f to ensure that the gain is at it's highest level.

you can try this:
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute,sizeof (audioRouteOverride),&audioRouteOverride);

I'm not familiar with Stephen Celis's work, but if tposchel's suggestion does not work, it might be of some value to look at the InputQueue's values versus the OutputQueue's audio values in debug mode (this is tricky, though, since the callbacks for these methods are realtime threads).
This may be informative in that it will tell you what the OS believes it is sending to your output device (headphone, or built-in speaker).
The brute force way to fix this problem is to manually normalize (or scale up, as it were) the values within your OutputQueue's callback. This doesn't address your root problem, perhaps, but may be a hack until you find the answer.

Related

Is AVAudioPlayer able to loop large audio files without an audible gap?

I have large looping background music files of up to 10 minutes length. The sounds are looped perfectly and if the player introduces no delay you would not notice where the loop point is.
Can AVAudioPlayer play them without that the user will hear a gap caused by latency or other problems with looping?
You might be hearing a delay because your music is compressed (mp3 for example). AVAudioPlayer has to decode the mp3 as it streams. If you had the same music in an uncompressed format, it would take significantly more disk space, but would loop seamlessly.
As long as you don't stop/start it at the end and you set numberOfLoops to a negative integer, it should work.
I had problems trying to create seamless loops. I tried using the numberOfLoops property, with no success. I tried switching to uncompressed audio files, with no success. I even tried switching to an AVAudioQueuePlayer, queued with multiple instances of the same asset, with no success. In the end I solved the problem by creating two instances of AVAudioPlayer (player1 & player2) and switching between them. I began playback of player1 and used player2's prepareToPlay method, and the play(atTime time:) method setting the TimeInterval to player1.deviceCurrentTime - player1.currentTime + player1.duration.
I then used the delegate method audioPlayerDidFinishPlaying to prepare player1 to restart playback after player2 was done. To be honest, I don't know why this approach worked and simply setting numberOfLoops to -1 did not. But it may work for you also

AVAudioPlayer lag when calling play

I have set up an AVAudioPlayer object in viewDidLoad, as per the Apple guidelines, calling prepareToPlay as the last line in viewDidLoad (i have tried in awakeFromNib also).
When i press my play button, there is a pause, as it would appear to load the file, then it plays.
In audioPlayerDidFinishPlaying, i reload the player with a different sound, and when clicking play for a second time, the file plays instantly.
What would cause the player to lag on the first play?
Thanks.
The delay is due to AVAudioPlayer being initialised. Please see this answer.
The audio system runs on several asynchronous software processes (audio units, OS drivers, etc.) and hardware systems (DMA, DACs, audio amp power supplies, etc.) that never really all completely finish initialization until some sound is actually played all the way out the speakers or earphones.
Here's one method to do that: Create a sound file containing a half second of silence. On app start up, while your app and view controller are still loading, use AVAudioPlayer to play this file of silence. Now when your view finishes loading, AVAudioPlayer should be ready to play subsequent non-silent sounds much faster, since some audio (silence) has already already gone all the way out to the speakers.
What kind of sound are you playing? Alerts, something longer? If alerts, I did go this way and it's much better with lags ...
create system sound with AudioServicesCreateSystemSoundID
play system sound with AudioServicesPlaySystemSound
dispose system sound with AudioServicesDisposeSystemSoundID
... you only need to store SystemSoundID for each sound you would like to play.

AudioServices (Easy), AVAudioPlayer (Medium), OpenAL (Hard & Overkill?)

I need to play sounds (~5 seconds each) throughout my iphone application. When they're triggered, they need to play immediately.
For the moment I'm using AudioServices and (as you probably know) the first time you play a sound it lags, then every time there after it's perfect. Is there some code available that's clever enough to preload an AudioServices sound (by playing it silently maybe?). I've read adjusting the system volume programmatically will get your app rejected, so that's not an option. Seems AudioServices isn't made for volume correction from what I can see.
I've looked into OpenAL and while feasible seems a little over kill. AVAudioPlayer seems like a little bit of a better option, I'm using that for background music at present. Extending my music player to handle a 'sound board' might be my last resort.
On the topic of OpenAL, does anyone know of a place with a decent (app store friendly) OpenAL wrapper for the iPhone?
Thanks in advance
Finch could be perfect for you. It’s a tiny wrapper around OpenAL with very low latency and simple API. See also all SO questions tagged ‘Finch’.
If you use an AVAudioPlayer, you can call prepareToPlay when you initialize the object to reduce the delay between calling play and having the audio start.

Best way to play different sounds one at a time on iphone?

I have a bunch of sounds I want to play. Right now I'm using AVAudioPlayer. A new sound should start when the user clicks a button. The url property of AVAudioPlayer is read-only. I'd rather not have to release and re-alloc the object every time I want a new sound played. What would be the best way to do this? Is there a different class I need to use?
The absolute easiest way to play sound is Playing UI Sound Effects or Invoking Vibration Using System Sound Services. Essentially you call AudioServicesCreateSystemSoundID() and AudioServicesPlaySystemSound(). Creating a system sound is relatively expensive and playback stops when it's disposed with AudioServicesDisposeSystemSoundID() anyway, so you really want to cache them:
Create them at app launch (or possibly the first time the sound is played). You can keep them in an NSDictionary keyed by sound file name and store the sound ID in an NSValue, or so.
If you can be bothered, dispose them when your app exits, is backgrounded, or on a memory warning.
The biggest caveat is that only a few sound formats are supported for "system sounds"; apple seems to recommend IMA4 (afconvert -f caff -d ima4 input.file output.caf and optionally -c 1 if you're happy with mono).
That said, it's pretty much perfect for playing short sounds provided you don't mind one-at-a-time sounds (it restarts playback if you play a sound while it's already playing). I suspect it's also played directly by the kernel (you can set a flag to make it continue playback when your app exits), which probably means it's more efficient CPU-wise. It might mix with sounds played by AVAudioPlayer.

Is it OK to mix AudioServicesPlaySystemSound and AVAudioPlayer?

We have a game that is very close to being released, but we have one extremely annoying problem. One on of our Beta testers phones, he can't hear any of the in game sound effects. He can, however, hear the background music and the title screen music just fine.
The background and title music are both being played via AVAudioPlayer (they are longer, we need looping and volume control, etc). The sound effects are simply being played with AudioServicesPlaySystemSound (they are very short, we don't need precise control or to know when they end, etc). This works on most iPhones, but not on this one. All of this is being played with an audio session of AVAudioSessionCategorySoloAmbient.
So I have two questions:
- First, is this an acceptable implementation? i.e. is there something I missed that says you can't mix these two frameworks, or a reason why its a bad idea to mix them?
- Second, has anyone seen something like this before? If so, did you find a way around it?
Additional background note: I can pretty conclusively say that on his phone, it is the mixing of the two frameworks. He was able to hear sounds until roughly the same build where we added the title screen music. Also, if I change one of the sounds to work through an AVAudioPlayer, he's able to hear it. Unfortunately, I can't simply move the sounds into AVAudioPlayers because it just doesn't perform well at all, and I need better synchronization.
EDIT 2:
I've determined the cause of the error. The two different audio frameworks play on two different volume settings. There is the main volume (that's what shows up once you are in the app, and has no title on it) and the ringer volume (that's what shows up when you're on the home screen without any app loaded). The AVAudioPlayer calls play with the main volume setting. As I have set the category to AVAudioSessionCategorySoloAmbient (see code below), this is the volume control that will be adjusted if you use the volume rocker inside the app. The SystemSounds, however, are played at the Ringer Volume. This will NOT come up inside the app, and as such does not allow the user to adjust their settings during the game.
It's easy to see the possible confusion for the user: Let's say they have their ringer set to low, or even off. They start playing the game. They hear the title screen music (AVAudioPlayer) and are able to turn the volume up or down and it responds normally. Then they start the game and the sound effects play (SystemSounds), and they hear nothing because the ringer volume is low/off. In an effort to hear the sound effects, they bump up the volume and the background music responds accordingly. So from this point of view, it definitely looks like the sound effects just aren't playing.
If you have this similar situation, have your user make sure that their ringer volume is up before playing the game and their mute switch is on (i.e. not muted). You can also verify all of this by doing the test yourself - drop your ringer volume and bump your in-app volume up. Your SystemSounds should drop out of the mix.
Original answer:
In re-digging through all of the docs, I found the answer. I wasn't properly activating the audio session. Previously I only had this code to set the category:
NSError *setCategoryError = nil;
BOOL categoryWasSet = [[AVAudioSession sharedInstance]
setCategory: AVAudioSessionCategorySoloAmbient
error: &setCategoryError];
But I needed to also add the following code to explicitly activate the audio session:
NSError *activationError = nil;
BOOL activationSuccess = [[AVAudioSession sharedInstance]
setActive: YES
error: &activationError];
I've re-tested with this user, and it has completely fixed the issue. Hope that helps anyone who might have a similar problem.
EDIT: This doesn't seem to have resolved the issue after all. I got a false positive from my tester, and when examined more in detail it seems that he didn't actually hear the SystemSound generated sounds.
I have two apps on the app store that mix those two frameworks. AVAudioPlayer for sounds that require start and stop and volume control and AudioServices toolbox for short little beeps and clicks. The only think I can think of, off the top of my head, is that the volume of AVAudioPlayer seems to be controlled separately from the AudioServices sounds. So check to make sure that the tester doesn't have sounds turned off (with the vibrate mode switch) or the volume turned way down.
If this doesn't help, tell me more about your sounds/code and I can add some code to my app to see if I can get the same behavior. Of course, sounds like most your testers don't have this problem, and I probably won't see it either.