Get instance from ChannelControl in SYSTEM_CALLBACK_TYPE.ERROR

I have registered a callback to get errors that looks like this and im trying to get the name of the thing to debug it but CHANNELGROUP is an interface and i cant figure out a way to determine the underlying type since all i have is the raw pointer.

        errorCallback = new FMOD.SYSTEM_CALLBACK(ERROR_CALLBACK);
        var result = system.setCallback(errorCallback, FMOD.SYSTEM_CALLBACK_TYPE.ERROR);

        private static FMOD.RESULT ERROR_CALLBACK(IntPtr system, FMOD.SYSTEM_CALLBACK_TYPE type, IntPtr commanddata1, IntPtr commanddata2, IntPtr userdata)
        {
            FMOD.ERRORCALLBACK_INFO callbackInfo = Marshal.PtrToStructure<FMOD.ERRORCALLBACK_INFO>(commanddata1);

            if(callbackInfo.instancetype == ERRORCALLBACK_INSTANCETYPE.CHANNELGROUP)
            {
                // get the name or other identifier of the callbackInfo.instance
            }
        }

We provide parameterized constructors which you can use to wrap an IntPtr in its corresponding type. i.e

if (callbackInfo.instancetype == ERRORCALLBACK_INSTANCETYPE.CHANNELGROUP)
{
    FMOD.ChannelGroup cg = new FMOD.ChannelGroup(callbackInfo.instance);
    cg.getName(out string name, 256);
}
else if (callbackInfo.instancetype == ERRORCALLBACK_INSTANCETYPE.CHANNEL)
{
    FMOD.Channel c = new FMOD.Channel(callbackInfo.instance);
    channel.getName(out string name, 256);
}

Ok but what do i do if it is an CHANNELCONTROL which is an IChannelControl interface in c#?

I am not sure what an IChannelControl is, it doesn’t appear to be part of the FMOD Unity Integration that we ship, but I think I see what you mean- how do you take the instance pointer for a ChannelControl and convert it into either the Channel or ChannelGroup.
If that is what you are after, you can do a similar thing to what I mentioned above but test the conversion from IntrPtr to the corresponding type with a successful API call:

if (callbackInfo.instancetype == FMOD.ERRORCALLBACK_INSTANCETYPE.CHANNELCONTROL)
{
    FMOD.Channel c = new FMOD.Channel(callbackInfo.instance);
    if (c.getCurrentSound(out FMOD.Sound sound) == FMOD.RESULT.OK)
    {
        sound.getName(out string soundName, 256);
        Debug.Log("soundName: " + soundName);
    }
    FMOD.ChannelGroup cg = new FMOD.ChannelGroup(callbackInfo.instance);
    if (cg.getName(out string cgName, 256) == FMOD.RESULT.OK)
    {
        Debug.Log("cgName: " + cgName);
    }
}

I tried doing that but all i get out is garbage like a “�” or some random characters such as “xzK7”. Also it seems like both often return the same string.

The IChannelControl is in
Assets/Plugins/FMOD/src/fmod.cs:2064
and it is implemented by Channel and ChannelGroup.
Im guessing its just a straight port of the abstract base class thats used in c++ which is not really useful in c# since there seem to be no way to check the pointer type.

There is also a channelcontrol_type but that only seems to be used for a CHANNELCONTROL_CALLBACK.

Also seems strange to me that the callback returns the abstract type instead of the actual type.

Thanks, I have no idea why I couldn’t find this when searching previously.
Not sure why we have IChannelControl either, outside of maybe for composite structures- but yes it’s probably just mirroring the C++ API.

Can you please tell me the value of each callbackInfo member during one of these garbage name logs, and send me an updated snippet of your callback?

Here are two examples of results and the code I use. I remove the execution paths that arent taken for brevity.

I dont get any ok results so it makes sense that I get garbage out but if I cant get the sound of the ChannelGroup because it was stolen, its not a very useful instance. What I actually want is both the stolen and the thief as that would make it easier to debug.

 FMOD.ERRORCALLBACK_INFO
functionname "ChannelControl::stop" nativeUtf8Ptr 0x7ffe21a5ce88
functionparams "" nativeUtf8Ptr 0xf4323f030
instance 0x203c0041
instancetype CHANNELCONTROL
result ERR_CHANNEL_STOLEN

channel.getCurrentSound ERR_CHANNEL_STOLEN
sound.getName ERR_INVALID_PARAM
sound description "c:\game\x.sourcegen.componentaccessgenerator\x.sourcegen.componentaccessgenerator.componentmanagergeneration.generator\temp\generatedcode\y.shared\gameplayconditionchecker__componentmanagergeneration_sls_795.g.cs"

channelGroup.getName ERR_INVALID_HANDLE
channelGroup description "event:/AMB/Local/Amb_Local/Fire/AMB_Local_Fire_Torch_Generic_Loop_FILTER"

-------------
 FMOD.ERRORCALLBACK_INFO
functionname "ChannelControl::stop" nativeUtf8Ptr 0x7ffe21a5cc40
functionparams "" nativeUtf8Ptr 0xf4323efe0
instance 0x203c0041
instancetype CHANNELCONTROL
result ERR_CHANNEL_STOLEN

channel.getCurrentSound ERR_CHANNEL_STOLEN
sound.getName ERR_INVALID_PARAM 
sound description "" <-- square is ETX

channelGroup.getName ERR_INVALID_HANDLE 
channelGroup description "xq\t��" <-- square is SOH

--------------

The code for getting the data above

[AOT.MonoPInvokeCallback(typeof(FMOD.SYSTEM_CALLBACK))]
private static FMOD.RESULT ERROR_CALLBACK(IntPtr system, FMOD.SYSTEM_CALLBACK_TYPE type, IntPtr commanddata1, IntPtr commanddata2, IntPtr userdata)
{
    FMOD.ERRORCALLBACK_INFO callbackInfo = Marshal.PtrToStructure<FMOD.ERRORCALLBACK_INFO>(commanddata1);

    var func = (string)callbackInfo.functionname;
    var param = (string)callbackInfo.functionparams;
    var instancetype = callbackInfo.instancetype;
    var instance = callbackInfo.instance;

    var description = "";
    const int NAME_MAX_LENGTH = 256;
    switch (instancetype)
    {
        case FMOD.ERRORCALLBACK_INSTANCETYPE.CHANNELCONTROL:
        {
            FMOD.Channel channel = new FMOD.Channel(instance);
            var result1 = channel.getCurrentSound(out FMOD.Sound sound);
            var result2 = sound.getName(out description, NAME_MAX_LENGTH);

            FMOD.ChannelGroup channelGroup = new FMOD.ChannelGroup(instance);
            var result3 = channelGroup.getName(out description, NAME_MAX_LENGTH);
            break;
        }
    }
}

My actual code looks like this

switch (instancetype)
{
    case FMOD.ERRORCALLBACK_INSTANCETYPE.CHANNELCONTROL:
    {
        var channel = new FMOD.Channel(instance);
        if (channel.getCurrentSound(out FMOD.Sound sound) == FMOD.RESULT.OK)
        {
            var result = sound.getName(out description, NAME_MAX_LENGTH);
            return result == FMOD.RESULT.OK;
        }
        else
        {
            FMOD.ChannelGroup channelGroup = new FMOD.ChannelGroup(instance);
            var result = channelGroup.getName(out description, NAME_MAX_LENGTH);
            return result == FMOD.RESULT.OK;
        }
    }
}

While we are on the topic of stolen audio logs, we also have another debug log thats set up like this

var debugCallback = new FMOD.DEBUG_CALLBACK(DEBUG_CALLBACK);
var result = FMOD.Debug.Initialize(fmodSettings.LoggingLevel, FMOD.DEBUG_MODE.CALLBACK, debugCallback, null);

[AOT.MonoPInvokeCallback(typeof(FMOD.DEBUG_CALLBACK))]
private static FMOD.RESULT DEBUG_CALLBACK(FMOD.DEBUG_FLAGS flags, IntPtr filePtr, int line, IntPtr funcPtr, IntPtr messagePtr)
{
    FMOD.StringWrapper funcWrapper = new FMOD.StringWrapper(funcPtr);
    FMOD.StringWrapper messageWrapper = new FMOD.StringWrapper(messagePtr);

    string func = (string)funcWrapper;
    string message = (string)messageWrapper;

    RuntimeUtils.DebugLog($"[FMOD] {func} : {message}", flags);

    return FMOD.RESULT.OK;
}

And the message looks like this
"[FMOD] ChannelI::updateVirtualState : Channel 0x23EC002D Stolen with audibility 0.180135"

Is there any way to get that pointer and instancetype without having to parse the string?

Okay in the context of channel stealing I can see why you wouldn’t be getting a workable Channel from the instance pointer.

We don’t have a way to get the pointer or instance type during a DEBUG_CALLBACK, that callback is really just intended for passing you a formatted string message to give to your own logging system.

If you want more details on the now destroyed Channel, you could potentially retrieve it ahead of time and store it in a dictionary, using the instance pointer to look up the corresponding sound name in the error callback. I acknowledge though that this is an unreasonable amount of effort just to figure out which sound has disappeared, and this is pretty clearly a limitation with our logging- I will suggest to the Dev team that we add some information on the stolen Sound name either in that updateVirtualState log or in the ERRORCALLBACK_INFO.

In the meantime, the recommended way to debug voice stealing is to use the FMOD Studio Profiler. You might not be able to easily figure out which Channel stole a voice from which other Channel, but with the Lifespans graph you’ll be able to see which voices are dropping in and out, which is probably the next best things.

:+1:

Thats really useful, i had done some profiling but i missed the lifetime option.
However some of what im seeing is not really explained well in lifetime link you provided.


Im guessing the bright line is the playing event and the faded lines are virtualized?

image
Whats the difference between a solid line and dotted line (referenced?)

image
Also im guessing the left and right “cap” of the line is just there to show start/stop?

image
And that this is just the dotted version of the start/stop “caps”?

I tried this but i cant manage to connect the two ends.

When i send the event i have the event instance and from that i can get a ChannelGroup that has 0 getNumChannels and then when i get the error i get a Channel that i cant get the group or sounds from since it has been stolen.