Record from non default microphone

Hi everyone.

i’m working on a research project where i need to record multiple microphones simultaneousely in unity. I’ve decided to use FMOD for this. Now i’m running into the problem that, while the windows default microphone records fine, as soon as i select a mic with an index other than 0 (default recording device) it wont record.

i’m getting FMOD OK results all the way but the current record position of the sound i record into doesn’t move (stays at 0).

Has anyone had this Problem before?

i will add some of my code here

 void StartRecording()
    {

        FMOD.RESULT result = RuntimeManager.CoreSystem.getRecordNumDrivers(out numofDrivers, out numOfDriversConnected);

        if (result != FMOD.RESULT.OK)
        {
            UnityEngine.Debug.LogError("Failed to detect connected Microphones: " + result);
            return;
        }
        if (numOfDriversConnected == 0)
        {
            UnityEngine.Debug.Log("No microphone detected!");
            return;
        }
        else
            // UnityEngine.Debug.Log($"{numOfDriversConnected} microphones available.");

        result = RuntimeManager.CoreSystem.getRecordDriverInfo(recordingDeviceIndex, out recordingDeviceName, 50,
            out micGUID, out micSampleRate, out FMODSpeakerMode, out micNumOfChannels, out FMODmicDriverState);
        

        UnityEngine.Debug.Log($"Mic selected: micName {recordingDeviceName} micSampleRate {micSampleRate} FMODSpeakerMode: {FMODSpeakerMode} micNumOfChannels: {micNumOfChannels} micDriverState: {FMODmicDriverState}");

        UnityEngine.Debug.Log("Starting FMOD recording...");

        soundInfo.cbsize = Marshal.SizeOf(typeof(FMOD.CREATESOUNDEXINFO));
        // Set the number of channels (1 for mono, 2 for stereo, etc.)
        soundInfo.numchannels = channels; 
        // Set desired sample rate here
        soundInfo.defaultfrequency = samplingFrequency; 
        // Set Format for the samples (16bit/short here)
        soundInfo.format = FMOD.SOUND_FORMAT.PCM16;
        // Calculate the buffer length in bytes (5 seconds)
        soundInfo.length = (uint)(samplingFrequency * sizeof(short) * soundInfo.numchannels * FMODrollingBufferLength); 

        result = FMODUnity.RuntimeManager.CoreSystem.createSound("", FMOD.MODE.OPENUSER | FMOD.MODE.LOOP_NORMAL, ref soundInfo, out recSound);
        if (result != FMOD.RESULT.OK)
        {
            UnityEngine.Debug.LogError("Failed to create sound for recording: " + result);
            return;
        }

        result = FMODUnity.RuntimeManager.CoreSystem.recordStart(recordingDeviceIndex, recSound, true);
        if (result != FMOD.RESULT.OK)
        {
            UnityEngine.Debug.LogError("Failed to start recording: " + result);
            return;
        }
        else
        {
            UnityEngine.Debug.Log($"recording start result: {result}");
        }

        result = recSound.getLength(out recSoundLength, FMOD.TIMEUNIT.PCM);
        if (result != FMOD.RESULT.OK)
        {
            UnityEngine.Debug.LogError("Failed to start recording: " + result);
            return;
        }

        // recSoundLengthSamples = (uint)(recSoundLength / (sizeof(short) * channels));
        UnityEngine.Debug.Log($"Recording started. Buffer Length: {recSoundLength} samples");
        FMODUnity.RuntimeManager.CoreSystem.getRecordPosition(0, out lastReadPos);
    }

    public byte[] FetchAudioData(int bufferSize)
    {
        // Calculate the size in bytes (16-bit PCM = 2 bytes per sample)
        // Prepare a buffer to hold the fetched audio data
        byte[] audioData = new byte[bufferSize * 2];

        uint currentRecordPos = 0;
        FMOD.RESULT result = FMODUnity.RuntimeManager.CoreSystem.getRecordPosition(0, out currentRecordPos);
        if (result != FMOD.RESULT.OK)
        {
            UnityEngine.Debug.LogError("Failed to get recording position: " + result);
            return audioData;
        }

        // Calculate the amount of new data available since the last read position
        uint availableSamples;
        if (currentRecordPos >= lastReadPos) {
            availableSamples = currentRecordPos - lastReadPos;
        } else {
            // When currentRecordPos has wrapped around and is less than lastReadPos,
            // add recSoundLength to currentRecordPos before subtracting to get the correct distance
            availableSamples = (currentRecordPos + recSoundLength) - lastReadPos;
        }

        // If there's enough data to read
        if (availableSamples >= bufferSize) { // Example buffer size
            // UnityEngine.Debug.Log($"Fetching audio data. Available Samples: {availableSamples}, Buffer Size: {bufferSize}, currentRecordPos: {currentRecordPos}, lastReadPos: {lastReadPos}, recSoundLength: {recSoundLength}");
            
            //skip one buffer to catch up if needed
            if (availableSamples > 2*bufferSize) {
                buffersSkipped ++;
                lastReadPos = (uint)((lastReadPos + bufferSize) % recSoundLength); // Update last read position
                UnityEngine.Debug.LogWarning($"Skipping buffer to catch up with recorded real-time audio data. Sent: {buffersProcessed}, Skipped: {buffersSkipped}");
            }

            uint byteOffset = (uint)(lastReadPos * sizeof(short) * channels);
            IntPtr dataPtr1, dataPtr2;
            uint length1, length2;
            uint lengthInBytes = (uint)(bufferSize * sizeof(short) * channels);
            
            if (byteOffset > recSoundLength * sizeof(short))
            {
                UnityEngine.Debug.LogError($"byteOffset out of bounds: {byteOffset}");
                return audioData;
            }

            result = recSound.@lock(byteOffset, lengthInBytes, out dataPtr1, out dataPtr2, out length1, out length2);
            if (result != FMOD.RESULT.OK)
            {
                UnityEngine.Debug.LogError("Failed to lock recording buffer: " + result);
                return audioData;
            }

            // Copy from the first block of data
            if (dataPtr1 != IntPtr.Zero && length1 > 0)
            {
                Marshal.Copy(dataPtr1, audioData, 0, (int)length1);
            }

            // If there's a second block of data due to wrap-around, copy it immediately after the first block
            if (dataPtr2 != IntPtr.Zero && length2 > 0)
            {
                Marshal.Copy(dataPtr2, audioData, (int)length1, (int)length2); // Notice how we start copying at the end of the first block
            }
            // Unlock the sound object
            recSound.unlock(dataPtr1, dataPtr2, length1, length2);

            buffersProcessed ++;

            lastReadPos = (uint)((lastReadPos + bufferSize) % recSoundLength); // Update last read position
            // audioData contains the left channel audio data in 16-bit PCM format
            return audioData;
        }
        else
        {
            return null;
        }
    }

I would greatly appreciate any help i can get.

Fyi, i’ve messed around with the mic recording code example and simply exposed the mic index so i could change it from 0 and it also doesn’t work with non-default microphones.
only if i start a recording with the default mic first i also get a signal on the non-default microphone with a second script instance.
Is this a known issue and if so is it a windows thing or an FMOD thing?

for now i can carry on with my project since i can always just record the default mic to get the signal from non-default mics :slight_smile:

1 Like

Hi,

Thank you for sharing the workaround.

What version of the FMOD integration are you using?

Please could I get the full script?

here is the full script.
MicHandler.txt (10.8 KB)
had to rename it to .txt to upload :smiley:
but the content is the same
it hopefully isn’t an issue with my script, since the same problem occurs when using the example script.

i’m using version 2.02.21 fo the FMOD integration

1 Like

Thank you for the code!

Could I please confirm whether the issue lies in retrieving the System::getRecordPosition from a non-default input device, or if it involves capturing audio from multiple microphones simultaneously?

I was able to update the record position by passing the recording device index to the functions as shown below:

void Start()
{
    Init(micIndexNumber);
}

void Update()
{
    FMODUnity.RuntimeManager.CoreSystem.getRecordPosition(micIndexNumber, out uint pos);
    UnityEngine.Debug.Log(pos.ToString());
}

Please let me know if I’m misunderstanding the issue.

My issue was that i could not get the record position of the non-default mic if the default mic wasn’t also recording (the record pos is always 0 in that case) but i always get an OK result.
i can reproduce this bug in the recording example aswell.
i could not confirm if data was actually recorded.
maybe its just windows being weird or something.

1 Like

Thank you for the information.

Unfortunately, I am still unable to reproduce this behavior.

Would it be possible to get a copy of your project or a stripped-down version displaying the issue uploaded to your profile? Please note you must register a project with us before uploading files.

Hey, sorry for the late reply.
I’m using the RuntimeManager.CoreSystem instead of creating my own.
the RuntimeManager.CoreSystem is already initialized with the default recording device when starting the recording. so when i try to use it with something else than the default device it wont work properly.
however when i call .setDriver(driverId) the RuntimeManager.CoreSystem is reinitialized with the new recording driver.
This should work even when using a non default mic (haven’t tested it yet).
Hope this helps someone.

Maybe you could add the line

FMODUnity.RuntimeManager.CoreSystem.setDriver(0);

to the recording example (even though its redundant for driverId = 0) so people dont trip on this like i did :slight_smile:

1 Like

Thanks for sharing your solution.

The setDriver() function is for selecting the output device: FMOD Engine | Core API Reference - System. Interesting that it has solved the record issue. It may be using the provided system rather than creating your own? Thank you for the information.