Playback data from input device

Hello.
I am creating application that allows user to capture audio data from input device and send it directly to Unity in realtime (latency value is not critical, but the less the better). Capture need to be occured in the following way: User load some audio file into OBS studio->Sound from OBS goes into VoiceMeeter to the some virtual input->Unity captures sound from VoiceMeeter input device in byte[] form.
At the moment main problem is in last step. I am trying to use FMOD to get audio data from VoiceMeeter virtual input, but I don’t understand in which way I should to this.
For now I tried next solution:

  void Start()
    {
        RuntimeManager.CoreSystem.getDriverInfo(InputDeviceIndex, out InputDeviceName, 50,
            out InputGUID, out SampleRate, out FMODSpeakerMode, out NumOfChannels);

        exinfo.cbsize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(FMOD.CREATESOUNDEXINFO));
        exinfo.numchannels = NumOfChannels;
        exinfo.format = FMOD.SOUND_FORMAT.PCM16;
        exinfo.defaultfrequency = SampleRate;
        exinfo.length = (uint)SampleRate * sizeof(short) * (uint)NumOfChannels;

        RuntimeManager.CoreSystem.createStream("testStream", FMOD.MODE.CREATESTREAM | FMOD.MODE.OPENUSER, ref exinfo ,out sound);
        //RuntimeManager.CoreSystem.createSound(exinfo.userdata, FMOD.MODE.LOOP_NORMAL | FMOD.MODE.OPENUSER, // I also tried to use createSound function but got the same result
    }

    private void GetAudioData()
    {
        IntPtr data = new IntPtr();
        uint read, lenght;
        sound.getLength(out lenght, FMOD.TIMEUNIT.PCM);
        var result = sound.readData(data, lenght, out read);
    }

    void Update()
    {
        if (!recordStarted)
        {
            GetAudioData();
        }
    }

When I try to debug result I get next error:

ERR_INVALID_PARAM

I checked this post but didn’t understand your recommendations about adding DSP and author’s solution with “create a looping sound with a set buffer”. The idea is pretty clear, but I don’t know how it should be implemented in code. Could you please help and provide some information about handling realtime audio from input device? Maybe some steps that I need to do to achieve result? Code examples will be really helpful too, because I’m not familiar with FMOD Api at all

Which line is returning ERR_INVALID_PARAM? You should download the FMOD Engine from our download page and take a look at the core “user_created_sound” example or the “record” example in api\core\examples. It shows you the correct way to handle a real time stream using the FMOD API.
I happen to have a Unity C# version of the record example which should give you a good starting point:

using FMODUnity;
using UnityEngine;

public class RecordExample: MonoBehaviour
{
    public int InputDeviceIndex = 0;
    public string DeviceName = "";

    private FMOD.Sound sound;
    private FMOD.CREATESOUNDEXINFO exinfo;
    private FMOD.Channel channel;

    private int sampleRate = 0;
 
    const int LATENCY_MS = (50); /* Some devices will require higher latency to avoid glitches */
    const int DRIFT_MS = (1);

    uint soundLength;
    static int lastRecordPos = 0;
    static int minRecordDelta = -1;
    static int lastPlayPos = 0;
    int samplesRecorded = 0;
    int samplesPlayed = 0;

    int driftThreshold;     /* The point where we start compensating for drift */
    int desiredLatency;     /* User specified latency */
    int adjustedLatency;    /* User specified latency adjusted for driver update granularity */
    int actualLatency;      /* Latency measured once playback begins (smoothened for jitter) */

    float updateTimeOut = 0.1f;
    float time = 0;

    void Start()
    {
        RuntimeManager.CoreSystem.getRecordDriverInfo(InputDeviceIndex, out DeviceName, 128,
            out System.Guid deviceGuid, out sampleRate, out FMOD.SPEAKERMODE speakerMode, out int numChannels, out FMOD.DRIVER_STATE driverState);

        exinfo.cbsize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(FMOD.CREATESOUNDEXINFO));
        exinfo.numchannels = 1;
        exinfo.format = FMOD.SOUND_FORMAT.PCM16;
        exinfo.defaultfrequency = sampleRate;
        exinfo.length = (uint)sampleRate * sizeof(short);

        var mode = FMOD.MODE.LOOP_NORMAL | FMOD.MODE.OPENUSER;
        RuntimeManager.CoreSystem.createSound(exinfo.userdata, mode, ref exinfo, out sound);
        RuntimeManager.CoreSystem.recordStart(InputDeviceIndex, sound, true);

        sound.getLength(out soundLength, FMOD.TIMEUNIT.PCM); 
        
        driftThreshold = (sampleRate * DRIFT_MS) / 1000;       /* The point where we start compensating for drift */
        desiredLatency = (sampleRate * LATENCY_MS) / 1000;     /* User specified latency */
        adjustedLatency = desiredLatency;                      /* User specified latency adjusted for driver update granularity */
        actualLatency = desiredLatency;                        /* Latency measured once playback begins (smoothened for jitter) */
    }

    void Update()
    {
        time += Time.deltaTime;
        if (time >= updateTimeOut)
        {
            time = 0.0f;
            RuntimeManager.CoreSystem.getRecordPosition(InputDeviceIndex, out uint recordPos);

            int recordDelta = ((int)recordPos >= lastRecordPos) ? ((int)recordPos - lastRecordPos) : ((int)recordPos + (int)soundLength - lastRecordPos);
            lastRecordPos = (int)recordPos;
            samplesRecorded += recordDelta;

            if (recordDelta != 0 && (recordDelta < minRecordDelta))
            {
                minRecordDelta = recordDelta; /* Smallest driver granularity seen so far */
                adjustedLatency = (recordDelta <= desiredLatency) ? desiredLatency : recordDelta; /* Adjust our latency if driver granularity is high */
            }

            /*
                Delay playback until our desired latency is reached.
            */
            channel.isPlaying(out bool playing);
            if (!playing && samplesRecorded >= adjustedLatency)
            {
                FMODUnity.RuntimeManager.CoreSystem.playSound(sound, new FMOD.ChannelGroup(), false, out channel);
            }

            channel.isPlaying(out playing);
            if (playing)
            {
                /*
                    Stop playback if recording stops.
                */
                bool isRecording = false;
                FMODUnity.RuntimeManager.CoreSystem.isRecording(InputDeviceIndex, out isRecording);

                if (!isRecording)
                {
                    channel.setPaused(true);
                }

                /*
                    Determine how much has been played since we last checked.
                */
                uint playPos = 0;
                channel.getPosition(out playPos, FMOD.TIMEUNIT.PCM);

                int playDelta = ((int)playPos >= lastPlayPos) ? ((int)playPos - lastPlayPos) : ((int)playPos + (int)soundLength - lastPlayPos);
                lastPlayPos = (int)playPos;
                samplesPlayed += playDelta;

                /*
                    Compensate for any drift.
                */
                int latency = samplesRecorded - samplesPlayed;
                actualLatency = (int)((0.97f * actualLatency) + (0.03f * latency));

                int playbackRate = (int)sampleRate;
                if (actualLatency < (int)(adjustedLatency - driftThreshold))
                {
                    /* Play position is catching up to the record position, slow playback down by 2% */
                    playbackRate = sampleRate - (sampleRate / 50);
                }
                else if (actualLatency > (int)(adjustedLatency + driftThreshold))
                {
                    /* Play position is falling behind the record position, speed playback up by 2% */
                    playbackRate = sampleRate + (sampleRate / 50);
                }

                channel.setFrequency((float)playbackRate);
            }
        }
    }
}