Problem with loading multiple audio files to FMOD on demand

Sorry for the delayed response!

The main issue here is that you’re calling handle.Free() at the end of the CREATE_PROGRAMMER_SOUND callback, which for me appears to be causing an ArgumentException: GCHandle value belongs to a different domain error. This usually occurs when the GCHandle is attempted to be retrieved from an IntPtr after the handle has been freed.

The recommended way to handle this would be to encapsulate each callback type as we do in our Unity Programmer Sounds scripting example so that you can free the handle at the appropriate time - I’ve edited provided ProgrammerSoundPlayer to follow this:

using System;
using UnityEngine;
using FMOD.Studio;
using FMODUnity;
using System.Runtime.InteropServices;

public class ProgrammerSoundPlayer : MonoBehaviour
{
    [SerializeField] private EventReference programmerSoundEvent;
    private EventInstance soundEvent;
    private FMOD.Studio.EVENT_CALLBACK soundCallback;

    private void Start()
    {
        soundEvent = RuntimeManager.CreateInstance(programmerSoundEvent);
        soundEvent.set3DAttributes(this.transform.position.To3DAttributes());

        // Explicitly create the delegate object and assign it to a member so it doesn't get freed
        // by the garbage collected while it's being used
        soundCallback = new FMOD.Studio.EVENT_CALLBACK(ProgrammerSoundCallback);
    }

    public void PlaySound(string filePath)
    {
        soundEvent.start();
        soundEvent.setCallback(soundCallback);
        soundEvent.setUserData(GCHandle.ToIntPtr(GCHandle.Alloc(filePath)));
    }

    [AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
    private static FMOD.RESULT ProgrammerSoundCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
    {
        FMOD.Studio.EventInstance instance = new FMOD.Studio.EventInstance(instancePtr);

        // Retrieve the user data
        IntPtr stringPtr;
        instance.getUserData(out stringPtr);

        // Get the string object
        GCHandle stringHandle = GCHandle.FromIntPtr(stringPtr);
        String key = stringHandle.Target as String;

        switch (type)
        {
            case FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
                {
                    FMOD.MODE soundMode = FMOD.MODE.LOOP_NORMAL | FMOD.MODE.CREATESTREAM;
                    var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));

                    FMOD.Sound dialogueSound;
                    var soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(key, soundMode, out dialogueSound);
                    if (soundResult == FMOD.RESULT.OK)
                    {
                        parameter.sound = dialogueSound.handle;
                        parameter.subsoundIndex = -1;
                        Marshal.StructureToPtr(parameter, parameterPtr, false);
                    }

                    break;
                }
            case FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND:
                {
                    var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
                    var sound = new FMOD.Sound(parameter.sound);
                    sound.release();

                    break;
                }
            case FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROYED:
                {
                    // Now the event has been destroyed, unpin the string memory so it can be garbage collected
                    stringHandle.Free();

                    break;
                }
        }
        return FMOD.RESULT.OK;
    }

    private void OnDestroy()
    {
        soundEvent.release();
    }
}

You’ll also note a couple of other changes i.e. the callback is static, with the [MonoPInvokeCallback] attribute, and the callback delegate is explicitly assigned to a local member with new - these ensure that the callback will function correctly on AOT platforms without crashing. You can read more info on this in the Callbacks section of our Unity docs.

1 Like