Random ChannelStolen errors

Hello support,

using Unity 6000.1.14f1
using FMOD Plugin Version 2.02.20

Sometimes when playing the game I am working on I get errors like this when I control my character:

[FMOD] ChannelControl::isPlaying(000000EB8A57EFA0:false) returned ERR_CHANNEL_STOLEN for CHANNELCONTROL (0x7FE0005).
UnityEngine.Debug:LogError (object)
FMODUnity.RuntimeUtils:DebugLogError (string) (at Assets/Plugins/FMOD/src/RuntimeUtils.cs:580)
FMODUnity.RuntimeManager:ERROR_CALLBACK (intptr,FMOD.SYSTEM_CALLBACK_TYPE,intptr,intptr,intptr) (at Assets/Plugins/FMOD/src/RuntimeManager.cs:142)

[FMOD] ChannelControl::isPlaying(000000EB8A57F000:false) returned ERR_CHANNEL_STOLEN for CHANNELCONTROL (0x7FE0005).
UnityEngine.Debug:LogError (object)
FMODUnity.RuntimeUtils:DebugLogError (string) (at Assets/Plugins/FMOD/src/RuntimeUtils.cs:580)
FMODUnity.RuntimeManager:ERROR_CALLBACK (intptr,FMOD.SYSTEM_CALLBACK_TYPE,intptr,intptr,intptr) (at Assets/Plugins/FMOD/src/RuntimeManager.cs:142)

[FMOD] ChannelControl::stop() returned ERR_CHANNEL_STOLEN for CHANNELCONTROL (0x7FE0005).
UnityEngine.Debug:LogError (object)
FMODUnity.RuntimeUtils:DebugLogError (string) (at Assets/Plugins/FMOD/src/RuntimeUtils.cs:580)
FMODUnity.RuntimeManager:ERROR_CALLBACK (intptr,FMOD.SYSTEM_CALLBACK_TYPE,intptr,intptr,intptr) (at Assets/Plugins/FMOD/src/RuntimeManager.cs:142)

[FMOD] ChannelControl::setDelay(99328, 119457, true) returned ERR_CHANNEL_STOLEN for CHANNELCONTROL (0x7FC0011).
UnityEngine.Debug:LogError (object)
FMODUnity.RuntimeUtils:DebugLogError (string) (at Assets/Plugins/FMOD/src/RuntimeUtils.cs:580)
FMODUnity.RuntimeManager:ERROR_CALLBACK (intptr,FMOD.SYSTEM_CALLBACK_TYPE,intptr,intptr,intptr) (at Assets/Plugins/FMOD/src/RuntimeManager.cs:142)

[FMOD] ChannelControl::setFadePointRamp(119457, 0) returned ERR_CHANNEL_STOLEN for CHANNELCONTROL (0x7FC0011).
UnityEngine.Debug:LogError (object)
FMODUnity.RuntimeUtils:DebugLogError (string) (at Assets/Plugins/FMOD/src/RuntimeUtils.cs:580)
FMODUnity.RuntimeManager:ERROR_CALLBACK (intptr,FMOD.SYSTEM_CALLBACK_TYPE,intptr,intptr,intptr) (at Assets/Plugins/FMOD/src/RuntimeManager.cs:142)

[FMOD] Channel::getLoopCount(000000EB8A57EB40:0) returned ERR_CHANNEL_STOLEN for CHANNEL (0x7FC0011).
UnityEngine.Debug:LogError (object)
FMODUnity.RuntimeUtils:DebugLogError (string) (at Assets/Plugins/FMOD/src/RuntimeUtils.cs:580)
FMODUnity.RuntimeManager:ERROR_CALLBACK (intptr,FMOD.SYSTEM_CALLBACK_TYPE,intptr,intptr,intptr) (at Assets/Plugins/FMOD/src/RuntimeManager.cs:142)

[FMOD] ChannelControl::isPlaying(000000EB8A57EFA0:false) returned ERR_CHANNEL_STOLEN for CHANNELCONTROL (0x7FC0011).
UnityEngine.Debug:LogError (object)
FMODUnity.RuntimeUtils:DebugLogError (string) (at Assets/Plugins/FMOD/src/RuntimeUtils.cs:580)
FMODUnity.RuntimeManager:ERROR_CALLBACK (intptr,FMOD.SYSTEM_CALLBACK_TYPE,intptr,intptr,intptr) (at Assets/Plugins/FMOD/src/RuntimeManager.cs:142)

[FMOD] ChannelControl::isPlaying(000000EB8A57F000:false) returned ERR_CHANNEL_STOLEN for CHANNELCONTROL (0x7FC0011).
UnityEngine.Debug:LogError (object)
FMODUnity.RuntimeUtils:DebugLogError (string) (at Assets/Plugins/FMOD/src/RuntimeUtils.cs:580)
FMODUnity.RuntimeManager:ERROR_CALLBACK (intptr,FMOD.SYSTEM_CALLBACK_TYPE,intptr,intptr,intptr) (at Assets/Plugins/FMOD/src/RuntimeManager.cs:142)

[FMOD] ChannelControl::stop() returned ERR_CHANNEL_STOLEN for CHANNELCONTROL (0x7FC0011).
UnityEngine.Debug:LogError (object)
FMODUnity.RuntimeUtils:DebugLogError (string) (at Assets/Plugins/FMOD/src/RuntimeUtils.cs:580)
FMODUnity.RuntimeManager:ERROR_CALLBACK (intptr,FMOD.SYSTEM_CALLBACK_TYPE,intptr,intptr,intptr) (at Assets/Plugins/FMOD/src/RuntimeManager.cs:142)

Now I play audio in centralised way. I have this script which basically handles registering audio events:

using System.Collections.Generic;
using FMOD.Studio;
using FMODUnity;
using Sirenix.OdinInspector;
using UnityEngine;
using EventReference = FMODUnity.EventReference;
using STOP_MODE = FMOD.Studio.STOP_MODE;

namespace BTSMOH.Game.Audio
{
    public class AudioRegistry : MonoBehaviour
    {
        [ReadOnly]
        [ShowInInspector]
        private Dictionary<EventReference, AudioEventInfo> registry = new Dictionary<EventReference, AudioEventInfo>();

        private void Awake()
        {
            registry = new Dictionary<EventReference, AudioEventInfo>();
        }

        public void Register(EventReference reference, EventReleaseType releaseType, bool canBePaused)
        {
            EventInstance  instance = RuntimeManager.CreateInstance(reference.Guid);
            AudioEventInfo info     = new AudioEventInfo(instance, releaseType, canBePaused);
            registry.TryAdd(reference, info);
        }

        public void Play(EventReference reference, bool stopBeforePlaying)
        {
            if (IsSoundEventValid(reference, out AudioEventInfo info))
            {
                if (IsEventInstanceValid(info.EventInstance))
                {
                    if (IsInstancePlaying(info.EventInstance) && stopBeforePlaying)
                    {
                        info.EventInstance.stop(STOP_MODE.IMMEDIATE);
                    }

                    info.EventInstance.start();
                    if (info.EventReleaseType == EventReleaseType.AFTERPLAY)
                    {
                        info.EventInstance.release();
                    }
                }
            }
        }

        /// <summary>
        /// PauseState determines if we pause a soundevent
        /// True -> Pause
        /// False -> Resume
        /// </summary>
        /// <param name="reference"></param>
        /// <param name="pauseState"></param>
        public void Pause(EventReference reference, bool pauseState)
        {
            if (IsSoundEventValid(reference, out AudioEventInfo info))
            {
                if (IsEventInstanceValid(info.EventInstance))
                {
                    if (info.CanBePaused)
                    {
                        info.EventInstance.setPaused(pauseState);
                    }
                }
            }
        }

        public void PauseAll()
        {
            foreach (KeyValuePair<EventReference, AudioEventInfo> kvp in registry)
            {
                Pause(kvp.Key, true);
            }
        }

        public void ResumeAll()
        {
            foreach (KeyValuePair<EventReference, AudioEventInfo> kvp in registry)
            {
                Pause(kvp.Key, false);
            }
        }

        public void Stop(EventReference reference, STOP_MODE stopMode)
        {
            if (IsSoundEventValid(reference, out AudioEventInfo info))
            {
                if (IsEventInstanceValid(info.EventInstance))
                {
                    info.EventInstance.stop(stopMode);
                    if (info.EventReleaseType == EventReleaseType.AFTERSTOP)
                    {
                        info.EventInstance.release();
                    }
                }
            }
        }


        private bool IsSoundEventValid(EventReference reference, out AudioEventInfo info)
        {
            info = new AudioEventInfo();
            if (reference.IsNull)
            {
                return false;
            }

            if (registry.TryGetValue(reference, out AudioEventInfo value))
            {
                info = value;
                return true;
            }

            return false;
        }

        private bool IsEventInstanceValid(EventInstance instance)
        {
            return instance.isValid();
        }

        public bool IsPlaying(EventReference reference)
        {
            if (IsSoundEventValid(reference, out AudioEventInfo info))
            {
                return IsInstancePlaying(info.EventInstance);
            }

            return false;
        }

        private bool IsInstancePlaying(EventInstance instance)
        {
            if (IsEventInstanceValid(instance))
            {
                PLAYBACK_STATE playbackState;
                instance.getPlaybackState(out playbackState);
                return playbackState == PLAYBACK_STATE.STARTING || playbackState == PLAYBACK_STATE.PLAYING;
            }

            return false;
        }

        public bool IsFinished(EventReference reference)
        {
            if (IsSoundEventValid(reference, out AudioEventInfo info))
            {
                return IsInstanceFinished(info.EventInstance);
            }

            return false;
        }

        private bool IsInstanceFinished(EventInstance instance)
        {
            if (IsEventInstanceValid(instance))
            {
                PLAYBACK_STATE playbackState;
                instance.getPlaybackState(out playbackState);
                return playbackState == PLAYBACK_STATE.STOPPED;
            }

            return false;
        }


        public void Unregister(EventReference reference)
        {
            if (registry.ContainsKey(reference))
            {
                if (IsSoundEventValid(reference, out AudioEventInfo info))
                {
                    if (IsEventInstanceValid(info.EventInstance))
                    {
                        info.EventInstance.stop(STOP_MODE.IMMEDIATE);
                        if (info.EventReleaseType == EventReleaseType.AFTERUNREGISTER)
                        {
                            info.EventInstance.release();
                        }

                        registry.Remove(reference);
                    }
                }
            }
        }

        public void UnregisterAll()
        {
            List<EventReference> keys = new List<EventReference>(registry.Keys);

            foreach (EventReference reference in keys)
            {
                Unregister(reference);
            }

            registry.Clear();
        }


        public void Dispose()
        {
            UnregisterAll();
        }

        private void OnDisable()
        {
            Dispose();
        }
    }
}

Like for example when I walk around with one of my characters the errors above are thrown.

This is the script that handles character audio:

using System.Collections.Generic;
using BTSMOH.Game.Audio;
using BTSMOH.Game.Entities.Animation;
using FMOD.Studio;
using FMODUnity;
using Sirenix.OdinInspector;
using UnityEngine;
using STOP_MODE = FMOD.Studio.STOP_MODE;

namespace BTSMOH.Game.Entities.Audio
{
    public class CharacterAudible : MonoBehaviour,
                                    IEntityAudible
    {
        [SerializeField]
        private EntitySoundData entitySoundData;

        [ReadOnly]
        [ShowInInspector]
        private EntityAnimationState currentAnimationState;

        private EntityStateSoundInfo currentStateSoundInfo;
        private EventReference       currentSoundEvent;
        private EventInstance        currentEventInstance;

        public EntitySoundData EntitySoundData
        {
            get => entitySoundData;
            set => entitySoundData = value;
        }

        private void Awake()
        {
            currentAnimationState = ScriptableObject.CreateInstance<EntityAnimationState>();
            foreach (KeyValuePair<EntityAnimationState, EntityStateSoundInfo> kvp in entitySoundData.SoundInfo)
            {
                if (!kvp.Value.EventReference.IsNull)
                {
                    GameHandler.AudioRegistry.Register(kvp.Value.EventReference, EventReleaseType.AFTERUNREGISTER, true);
                }
            }
        }

        public void Play(EntityAnimationState state, bool forcePlay)
        {
            // Set entityAnimation state first before play
            if (state == null)
            {
                return;
            }

            if (currentAnimationState != state)
            {
                GameHandler.AudioRegistry.Stop(currentStateSoundInfo.EventReference, STOP_MODE.IMMEDIATE);
                currentAnimationState = state;

                entitySoundData.SoundInfo.TryGetValue(currentAnimationState, out currentStateSoundInfo);
                currentSoundEvent = currentStateSoundInfo.EventReference;
            }


            if (forcePlay)
            {
                GameHandler.AudioRegistry.Play(currentSoundEvent, false);
            }
            else
            {
                if (!GameHandler.AudioRegistry.IsPlaying(currentSoundEvent))
                {
                    GameHandler.AudioRegistry.Play(currentSoundEvent, false);
                }
            }
        }

        public void Pause()
        {
            GameHandler.AudioRegistry.Pause(currentSoundEvent, true);
        }

        public void Resume()
        {
            GameHandler.AudioRegistry.Pause(currentSoundEvent, false);
        }

        public void Stop()
        {
            GameHandler.AudioRegistry.Stop(currentSoundEvent, STOP_MODE.IMMEDIATE);
        }


        private void OnDisable()
        {
            if (GameHandler.AudioRegistry != null)
            {
                foreach (KeyValuePair<EntityAnimationState, EntityStateSoundInfo> kvp in entitySoundData.SoundInfo)
                {
                    GameHandler.AudioRegistry.Unregister(kvp.Value.EventReference);
                }
            }
        }
    }
}

I read that this error specifically “ERR_CHANNEL_STOLEN for CHANNELCONTROL” means a virtual voice has its channel stolen and if there is a voice cutoff I should increase the virtual channel count int the fmod settings. But the count is set to 1024 and I feel this should suffice, especially if like just two characters are just walking around, no?
Any idea or suggestion where I should look in order to further investigate this issue?

Hi,

Thank you for sharing the version number and code!

Just to clarify, are these warnings showing up in the editor or in the built game?

Also, could you please double-check your FMOD settings for the real channel count? While having a virtual channel count of 1024 is generous, what actually limits how many sounds can play simultaneously is the real channel count.

By default, this is usually 32 or 64, depending on your target platform.

If you’re playing lots of short sounds like footsteps and they aren’t released immediately after playing, it’s easy to run out of real voices, even with just a few characters moving around.

Hey there,

so the errors are showing up in the editor, I didnt test around in build yet to be honest.

As for the Real Channel Count:
In “Default” it is set to: 32 (in default the virtual channel count is also set to 128 FYI)
In “Editor” it is set to: 256

Thank you for the extra info!

Since I wasn’t able to reproduce the issue on my end, would it be possible for you to capture a Profiler session during a moment when the errors occur and upload to your FMOD profiler?

I also noticed in your code that EventInstance.release() is used conditionally. Could you please confirm that one-shot sounds like footsteps are set to use EventReleaseType.AFTERPLAY and that they’re not lingering in the registry longer than needed? Holding onto finished instances without releasing them could lead to real voice exhaustion over time.

Oh! Maybe this is the issue right there. I just double checked, my characters setup the audio data like this:

private void Awake()
        {
            currentAnimationState = ScriptableObject.CreateInstance<EntityAnimationState>();
            foreach (KeyValuePair<EntityAnimationState, EntityStateSoundInfo> kvp in entitySoundData.SoundInfo)
            {
                if (!kvp.Value.EventReference.IsNull)
                {
                    GameHandler.AudioRegistry.Register(kvp.Value.EventReference, EventReleaseType.AFTERUNREGISTER, true);
                }
            }
        

and this is ALL the audio data, I think I’ll adjust that for each sound seperately, so one shot sounds are released immediately after playing.
I’ll do that and monitor if the issue is still happening!

1 Like