AudioMix wakelock in Android Monogame

Hi,

I think I found a bug (and a possible fix for it) in Android MonoGame, related to wakelocks in the audio subsystem.

We have a game made with MonoGame (Snake Rewind) in the Google Play store. A lot of people are complaining that it is draining their battery while in background. And true enough, seems like it keeps a wakelock to prevent the device from going to sleep. This can be checked eg. with the following command when the device is connected to the PC:

adb shell dumpsys power

It shows a lot of info, but the interesting part was that the line below still present when the game was put to background (while the game is active and in the foreground there’s two of these wakelocks, but only one is released when going to background).

Wake Locks: size=1
  PARTIAL_WAKE_LOCK              'AudioMix' (uid=1013, pid=0, ws=WorkSource{10216})

I tried disabling all SoundEffect and MediaPlayer calls from the game but the wakelock still persisted in the background. So I started stepping through the initialization code and found out that the wake lock is reserved when this line

OpenALSoundController.GetInstance.Update();

is called in SoundEffectInstancePool.Update(). Apparently the wakelock is reserved when an instance of OpenALSoundController is created by the GetInstance() call. The fix I implemented was to override the OnStop() method in the AndroidGameActivity class and put the following code in it:

protected override void OnStop()
{
    base.OnStop();
    Microsoft.Xna.Framework.Audio.OpenALSoundController.DestroyInstance();
}

This is called when the game is put to background, and destroying the OpenALSoundController at this point seems to release the wakelock. Looks like the DestroyInstance() call could equally well just be added to the OnPause() method too. I tested both ways and didn’t find any harmful side effects. All sounds played correctly when resuming the game again.

But I have to admit that I’m not 100% sure of what I’m doing, so it would be great to get some comment from someone who has more experience from the MonoGame audio subsystem. Is this fix safe or can it cause some bad side effects (memory leaks, unreleased sources/buffers, etc.)? If the fix is not good, what would be a better way to deal with this problem?

The version of openal-soft we are using does not suspend the mixer thread when the app is backgrounded. This was not added to the official openal-soft codebase until fairly recently. It adds two new functions to the API to suspend and resume the mixer thread.

If your work-around works for your case, great. I do not see any major issues with it, though it would be nicer to not have to reinitialise the audio system on wake. A better solution would be to update our version of openal-soft to the latest, though building it from source for Android has always been a difficult process. The several forks of openal-soft for Android that I am aware of are way out of date.

Thanks for the fast reply! I’ll test it a bit more and if I don’t see any harmful side effects I’ll add the workaround to our game as a fast fix.

Thank you @TomRumilus for your fix. I had the same problem using CocosSharp (based on MonoGame).
Since the OpenALSoundController is an internal class I couldn’t use your solution. But, bases on the MonoGame source I found out that OpenTK caused the problem. Adding CleanUpOpenAL function to OnStop() solved the problem.

protected override void OnStop()
{
    base.OnStop();
    CleanUpOpenAL();
}
private void CleanUpOpenAL()
{
    try
    {
        var context = OpenTK.Audio.OpenAL.Alc.GetCurrentContext();

        if (context != OpenTK.ContextHandle.Zero)
            {
                var device = OpenTK.Audio.OpenAL.Alc.GetContextsDevice(context);

                OpenTK.Audio.OpenAL.Alc.DestroyContext(context);
                context = OpenTK.ContextHandle.Zero;

                if (device != IntPtr.Zero)
                {
                    OpenTK.Audio.OpenAL.Alc.CloseDevice(device);
                    device = IntPtr.Zero;
                }
            }
        }
        catch (Exception e)
        {
            _logger.LogMessage(LogLevel.Error, "CleanUpOpenAL FAIL msg: " + e.Message);
        }
}