Adding signal DSP causes starvation

I’m getting the starvation warning from the following code run in unity specifically on ending playmode. No actual audio needs to be played.


using System;
using FMOD;
using FMODUnity;
using UnityEngine;

class ExactExample : MonoBehaviour
{
  private DSP mCaptureDSP;
  private uint mBufferLength;
  private int mChannels = 0;
  
  void Start()
  {
    
    DSP_DESCRIPTION desc = new DSP_DESCRIPTION
    {
      numinputbuffers  = 1,
      numoutputbuffers = 1,
      // most other callbacks will not produce the starvation warning
      // although process callback fires a raw exception even when empty
      read             = CaptureDSPReadCallback 
    };

    RuntimeManager.CoreSystem.getMasterChannelGroup( out var masterCG );
    RuntimeManager.CoreSystem.createDSP( ref desc, out mCaptureDSP );
    masterCG.addDSP( 0, mCaptureDSP );
  }

  [AOT.MonoPInvokeCallback( typeof(DSP_READ_CALLBACK) )]
  static RESULT CaptureDSPReadCallback( ref DSP_STATE dsp_state, IntPtr inbuffer, IntPtr outbuffer, uint length, int inchannels, ref int outchannels )
  {
    return RESULT.OK;
  }

  private void OnDisable()
  {
    if( mCaptureDSP.hasHandle() )
    {
      if( RuntimeManager.CoreSystem.getMasterChannelGroup( out var channelGroup ) == RESULT.OK ) 
        channelGroup.removeDSP( mCaptureDSP );
      
      mCaptureDSP.release();
      mCaptureDSP.clearHandle();
    }
  }
}

If the read callback is set, the warning will fire when exiting playmode.

I’ve tried a bunch of variations. None will prevent the warning on shutdown.

Worth noting that assigning the process callback will throw a marshal type exception and lock the editor:


  [AOT.MonoPInvokeCallback( typeof(DSP_PROCESS_CALLBACK) )]
  private RESULT ProcessCallback( ref DSP_STATE dsp_state, uint length, ref DSP_BUFFER_ARRAY inbufferarray, ref DSP_BUFFER_ARRAY outbufferarray, bool inputsidle, DSP_PROCESS_OPERATION op )
  {
    return RESULT.OK;
  }

.....
MarshalDirectiveException: Structure field of type Int32[] can't be marshalled as LPArray
  at (wrapper native-to-managed) ExactExample.ProcessCallback(FMOD.DSP_STATE&,uint,FMOD.DSP_BUFFER_ARRAY&,FMOD.DSP_BUFFER_ARRAY&,int,FMOD.DSP_PROCESS_OPERATION)

Super annoying.

Am I doing something incredibly wrong here or is this just a bug in fmod’s unity api?

NOTE: Tested with an older project and the starvation warning does not happen with 2.02.15 it does happen with 2.02.22.

This is a crosspost in the hopes of getting an actual response.

Hi,

I’ve been able to reproduce both issues on my end: the starvation warning is likely benign and safe to ignore, but the process callback throwing MarshalDirectiveException is more concerning. Unfortunately I don’t have a workaround for the latter at present, but I’ve passed on both issues to the development team to investigate further.

thank you for checking in on this, is there a public bug tracker I can follow to know when this has been resolved?

No problem! Unfortunately we don’t have a public-facing bug tracker. If you’d like to keep up to date with the latest release and fixes, you can subscribe to our newsletter, which you can do from the footer of any of our webpages besides the forum. Our FMOD Engine revision history doc should contain a note about the fix when it ships as well.

I can confirm that the MarshalDirectiveException issue has just been fixed, pending the release of 2.02.25. The DSP_BUFFER_ARRAY struct was not implemented correctly for C# and should look like the following:

[StructLayout(LayoutKind.Sequential)]
public struct DSP_BUFFER_ARRAY
{
    public int              numbuffers;
    public IntPtr           buffernumchannels;
    public IntPtr           bufferchannelmask;
    public IntPtr           buffers;
    public SPEAKERMODE      speakermode;

    /*
        These properties take advantage of the fact that numbuffers is always zero or one
    */

    public int numchannels
    {
        get 
        {
            if (buffernumchannels != IntPtr.Zero && numbuffers != 0)
                return Marshal.ReadInt32(buffernumchannels);

            return 0;
        }
        set
        {
            if (buffernumchannels != IntPtr.Zero && numbuffers != 0)
                Marshal.WriteInt32(buffernumchannels, value);
        }
    }

    public IntPtr buffer
    {
        get
        {
            if (buffers != IntPtr.Zero && numbuffers != 0)
                return Marshal.ReadIntPtr(buffers);

            return IntPtr.Zero;
        }
        set
        {
            if (buffers != IntPtr.Zero && numbuffers != 0)
                Marshal.WriteIntPtr(buffers, value);
        }
    }
}

As for the WASAPI starvation error, I haven’t been able to pin down exactly where the stall is coming from, but I do have a theory. The problem only seems to occur once a callback has been registered, and our native mixer thread calls into C# code. When that happens, the thread is associated with the managed garbage collector. This happens automatically, without any input from us. When exiting play mode, these systems are torn down, which I believe is where the managed/native association is torn down, but this cases a stall. During that moment the platform audio engine gets starved of audio and complains thusly.

It’s important to note that the stall is not coming from the add/remove/release of the DSP, anyone experiencing that would signify a much more serious stall at runtime. A stall at shutdown should be inaudible, and while annoying that it’s a warning, it should be benign.