Search Unity

Audio Is OnFilterRead paused by the Garbage Collector?

Discussion in 'Audio & Video' started by sebj, Jul 1, 2020.

  1. sebj

    sebj

    Joined:
    Dec 4, 2013
    Posts:
    70
    Unity's GC uses stop-the-world, wherein all threads are paused at the same time for garbage collection.

    Is the audio thread that calls OnFilterRead known to mono/stopped this way (i.e. has mono_thread_attach() called on it?)

    Sj
     
  2. Jon_Olive

    Jon_Olive

    Joined:
    Sep 26, 2017
    Posts:
    23
    In my experience excess garbage collection can cause a bunch of problems with OnAudioFilterRead - so it certainly has an effect on it somehow. If it isn't causing a problem in the build - but only in the editor - check any custom inspector scripts for unneccesary garbage - that was the culprit in my case.
     
  3. nzhangaudio

    nzhangaudio

    Joined:
    May 26, 2015
    Posts:
    20
    according to my last communication with Unity devs, when OnAudioFilterRead is used, the mixer thread ends up being attached to the scripting runtime, and once that happens, it is subject to garbage collection pauses forever. There's nothing you can do to detach it. So yeah.

    I also wonder what the alternative is if I want to use a custom filter on a per-source basis. DSPGraph seems not remotely ready, and Native Audio Plugin can be only attached to the mixer?
     
  4. april_4_short

    april_4_short

    Joined:
    Jul 19, 2021
    Posts:
    489
    Does this mean that if
    OnAudioFilterRead
    is NOT used, that the Mixer uses the audio/dsp thread?
     
  5. Tak

    Tak

    Joined:
    Mar 8, 2010
    Posts:
    1,001
    The audio mixer thread is always used for mixing audio, but when you use
    OnAudioFilterRead
    , that thread (permanently) becomes subject to the managed garbage collector.
     
    nzhangaudio likes this.
  6. april_4_short

    april_4_short

    Joined:
    Jul 19, 2021
    Posts:
    489
    THANK YOU! For clarifying and responding. This makes a lot of sense. I'd imagine you'd have to do this, in case some idiot (like me) started creating oodles of garbage in OnAudioFilterRead calls. I will try not to, but sometimes... I am just garbage.

    Does the data array that OnAudioFilterRead uses ... is it the same array every single time it's called?

    eg, if I don't chuck more stuff in it, does it have the same content in it that it had the last time it was called, just 1/48th of a second ago?
     
  7. Tak

    Tak

    Joined:
    Mar 8, 2010
    Posts:
    1,001
    It's more that all threads working with managed memory need to be paused while the garbage collector is running, so that references aren't changing in the middle of its run.

    The data in the array is the input from the mixer for this mix. If your filter is the only thing running, it will always be zeroes - if something else up the filter chain is producing a signal, then it will be the signal buffer for this mix.
     
  8. april_4_short

    april_4_short

    Joined:
    Jul 19, 2021
    Posts:
    489
    You're about to discover how dense I can be. This isn't trolling, as some suggest, I just need things spelt out in very literal and exact terms to have any hope of understanding what's going on. Sorry!

    where is this coming from? I had wrongly presumed that this was from the AudioSource, but coming from a mixer... if I don't have one in my project, where is it coming from? And where is it going to, in terms of a "mix"?

    So populating data, once, doesn't mean that it's then in there again and again, somewhere it's getting flushed, or is a new segment of the data... or in some other way it's fulshed/clear/empty?

    Let's say OnAudioFilterRead is called just once, and I fill data[] with 1's.

    Then turn off the filter with OnAudioFilterRead. That array, that I filled, it's gone to zeros, now?
     
  9. Tak

    Tak

    Joined:
    Mar 8, 2010
    Posts:
    1,001
    I mean the system's low-level mixer, in this case Unity's embedded FMOD.

    So if you have one filter attached to an
    AudioSource
    , the input data will come from the
    AudioSource
    . If you have a bunch of filters chained together, or if your filter is on an
    AudioListener
    , then your input data will be whatever partially-mixed signal is there at that point in the mix.

    The output goes to the next thing in the mix: more filters, an
    AudioListener
    , etc., and then eventually to the audio output device.

    The system appears to use a single managed buffer for all custom audio filters.
    This means that every time a filter runs:
    • The buffer is overwritten with the input data from the mixer
    • The filter is executed
    • The contents of the buffer are written to the output buffer from the mixer
    In your scenario, if there are no other filters, your buffer may still be filled with 1s, just because nothing has overwritten them yet. If there are other filters, your buffer will be overwritten as soon as another one runs.
     
  10. YoghurtForBreakfast

    YoghurtForBreakfast

    Joined:
    Mar 6, 2022
    Posts:
    1
    Hi !
    This thread is the closest discussion to my inquiries that I've seen yet, so I wanted keep the conversation regarding the output of the onAudioFilterRead.
    Now I understand that the output of the filter goes to whatever is next on the gameobject chain but I can't find a way to send the output to a mixer group. I've tried attaching a new AudioSource after the filter but at the moment of assigning a group to the AudioSource output, it doesn't seem to work. What am I missing?
     
  11. cxode

    cxode

    Joined:
    Jun 7, 2017
    Posts:
    268
    S***. Well, that explains the audio glitches I'm getting. If I comment out all the
    OnAudioFilterRead
    s in my project, the audio glitches disappear (but so does all my cool custom audio functionality).

    Is there an alternative way to process audio, besides
    OnAudioFilterRead
    , that doesn't cause the audio thread to pause when GC is collected?
     
  12. cxode

    cxode

    Joined:
    Jun 7, 2017
    Posts:
    268
    I'm not even doing any allocations in the method. This feels so unfair :(
     
  13. kloot

    kloot

    Joined:
    Mar 14, 2018
    Posts:
    78
    Wow... This explains a bug I've been working with for many days (I really wish this would have been mentioned in the OnAudioFilterRead documentation).

    So is there an alternative way of reading runtime sound data from an Audio Mixer (or any way to tell the runtime to go back to the sate it was before OnAudioFilterRead was called)?
     
    cxode likes this.
  14. SeventhString

    SeventhString

    Unity Technologies

    Joined:
    Jan 12, 2023
    Posts:
    410
    Currently, the only "official" way to read and use the data going through the mixer is by writing a custom Native Audio Plugin and interact with it from the C# scripting layer.

    All about creating a NativeAudioPlugin would be >> here <<

    And this is how you could interact with it from your scripts:

    Code (CSharp):
    1. using System.Runtime.InteropServices;
    2.  
    3. public class MyNativePluginInterface : MonoBehaviour
    4. {
    5.     [DllImport("YourPluginName")]
    6.     private static extern int YourPluginFunction();
    7.  
    8.     void Start()
    9.     {
    10.         YourPluginFunction();
    11.     }
    12. }
    13.  
    You could also provide a callback to the NativeAudioPlugin so it triggers or alters states of your script:

    Code (CSharp):
    1. public class MyPluginInterface : MonoBehaviour
    2. {
    3.     [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    4.     public delegate void YourCallbackDelegate(float value);
    5.  
    6.     [DllImport("YourPluginName")]
    7.     public static extern void RegisterCallback(YourCallbackDelegate callback);
    8.  
    9.     void Start()
    10.     {
    11.         RegisterCallback(YourScriptCallbackMethod);
    12.     }
    13.  
    14.     void YourScriptCallbackMethod(float receivedValue)
    15.     {
    16.         Debug.Log("Received value: " + receivedValue);
    17.     }
    18. }
    19.  
    This last method essentially gives the C++ layer a pointer to a C# function. Use this power carefully, and ensure the C++ doesn't invoke the callback after the C# object is destroyed or in contexts where it's not safe to do so. Also be really cautious about any long-running/waiting/blocking operation as you might block the main or the audio thread, spreading doom and despair all around.

    I hope this helps!
     
    cxode likes this.
  15. kloot

    kloot

    Joined:
    Mar 14, 2018
    Posts:
    78
    Thanks for the info @SeventhString. I gotta say though, that writing a plugin in another language just to monitor if a channel has input or not seems (bananas) overkill, especially since OnAudioFilterRead does the trick.

    It would be great if we could either read output from an audio mixer based on an exposed parameter, or somehow undo the overhead caused by OnAudioFilterRead once it's no longer needed.
     
    cxode and SeventhString like this.
  16. SeventhString

    SeventhString

    Unity Technologies

    Joined:
    Jan 12, 2023
    Posts:
    410
    I would honestly love that too... This is in our wishlist, some proof-of-concepts exist, but we got a mountain of things to do in our backlog before getting there :(
     
    kloot likes this.
  17. cxode

    cxode

    Joined:
    Jun 7, 2017
    Posts:
    268
    Oh, dear god. Thanks for the info. I'm glad it's at least POSSIBLE. I hope there's a better way sooner rather than later.
     
    kloot likes this.