FMOD mixerResume causes unwanted audio to play after pausing an EventInstance

I have my app setup where when its audio is interrupted by another app (like a phone call), my app will automatically pause any audio.

In my app I have an FMOD::Studio::System instance, and FMOD::System instance, and an FMOD::Studio::EventInstance instance:

    FMOD::Studio::System *studio_system_;
    FMOD::Studio::EventInstance *event_instance_;
    FMOD::System *low_level_system_;

On an audio interruption in iOS I am making the following calls:

    event_instance_->setPaused(true);
    studio_system_->update();
    low_level_system_->mixerSuspend()

When the user wants to resume the audio, the app makes these calls:

    event_instance_->setPaused(false);
    studio_system_->update();
    low_level_system_->mixerResume()

Everything is almost working perfectly except that when the audio is resumed there appears to be a stutter of the audio. It sounds as if the audio is resumed, paused for a split second, and then resumed again.

I don’t know exactly what’s causing the stutter, but the documentation for mixerSuspend said that it saves the current internal state. I’m wondering if maybe the state of the audio being paused is not being processed before mixerSuspend caches the state. So, I tried replacing calls to studio_system_->update() with studio_system_->flushCommands() but that also doesn’t work.

What am I doing wrong? Why am I getting this audio stuttering?

Have you tried without pausing at all? mixerSuspend will affectively pause everything for you.

You’re interruption code should look similar to this.

Thanks for the response!

I have tried that. The problem I had with that approach was that when the audio in my app gets interrupted, I want to intentionally pause the audio and keep it paused even when the app regains audio focus and is foregrounded. I only want to resume the app when the user presses resume in the app.

It sounds you have two events there, interruption ends, then some time later the user presses “resume” to unpause the audio. I’d separate out your logic in the same way.

Interruption start:

  • EventInstance::setPaused(true)
  • Studio::System::flushCommands
  • System::mixerSuspend

Interruption end:

  • [activate audio session]
  • System::mixerResume

User “resume” action:

  • EventInstance::setPaused(false)

I’ve tried implementing your suggestion but am experiencing another issue:

If my app is playing audio and I switch over to another app that can play audio (YouTube for example), and I start to play audio in YouTube, then my app will receive the Interruption start callback and I make the calls the you suggested. When I return back to my app, I receive the Interruption end callback, but once mixerResume is called I can hear my audio playing for a split second. So, I appear to be running into a similar issue still.

Thanks for the details, I can see what the issue is now.

All audio produced by FMOD goes into a ring buffer defined by System::setDSPBufferSize. This ring buffer is not currently reset when mixerSuspend occurs (it should be). So what you are hearing on mixerResume is the buffered audio from before the suspend happened.

This is something we will need to fix on our side, the only way to clear that ring buffer from the API side is to shutdown the FMOD System, which isn’t ideal.

If you’re okay with all audio being effectively paused until the user presses resume, then remove the setPaused calls and the flushCommands call, then only mixerSuspend in interriptionStart and mixerResume when they press the resume button. interruptionEnd would then only call activate on the audio session, nothing else.

Thanks for your response. The work around that you’ve recommended is sufficient.

The c++ code that we use is meant to be used on both iOS and Android. I’ve had to make some adjustments in the code so that Android still works properly.

In particular one issue I ran into when I made the recommended change was that on Android I had to make sure to call mixerResume before calling org.fmod.FMOD.close(); otherwise my app would freeze (most likely a result of a deadlock that I read about in the FMOD documentation). If I do call mixerResume though I would again hear the little bit of audio before the audio would stop completely. To get around this I’m using the preprocessor to perform slightly different actions depending on the platform and it appears to be working fine now.

Thanks for your help. And if you resolve the issue on your end, please let me know what version of FMOD I can expect to see the change in.

One other thing that maybe you could help me with is a case in my app where I need to allow the user to pause the audio but then start the audio again from the beginning (so the audio restarts). The implementation of this restart behaviour currently makes calls to release the event instance and then construct a new event instance. So, the chain of calls looks something like this:

// User pauses the audio
low_level_system_->mixerSuspend();

// User chooses to restart the audio
event_instance_->release();
event_instance_->stop(FMOD_STUDIO_STOP_IMMEDIATE);
studio_system_->update();
// series of steps to create new event instance ...
event_instance_->start();
low_level_system_->mixerResume();

When the audio restarts I am hearing the short blip of audio. This is most likely being caused by the same issue with mixerResume that you’ve discovered. Do you know of any work arounds that I could implement for this case?

Generally mixerSuspend / mixerResume is only used to handle platform specific audio interface requirements such as disposing the audio interface during interruptions. If you just want the ability to pause sounds at the users request I recommend using EventInstance::setPause or Bus::setPause (for the master bus to pause everything). Restarting an EventInstance doesn’t require releasing, just call EventInstance::start on an already started instance and it will restart.

Regarding the previously discussed fix, I’ve scheduled the work for 2.00.11, there are no guarantees, but if everything goes smoothly the fix will be out in ~1 month.

1 Like