DSP_READ_CALLBACK use 6 in/out channels instead of 1

Hello,
i have a problem with DSP_READ_CALLBACK, i have 6 in/out channels from mic and 4 of them is zeros, but and the other two are just copies of each other. What does this duplicate data mean? to get total data from a mono channel, do I need to sum all 6 channels? I need this data to use in a voice recognizer, so I only need one channel.

Image from inspector with raw data: first is mic data, second is copy of first and other 4 is zeros
image

public void EnableRecording()
{
    if (_isRecording == false)
    {
        RESULT res = RuntimeManager.CoreSystem.getRecordNumDrivers(out int numofDrivers, out int numOfDriversConnected);

        RuntimeManager.CoreSystem.getRecordDriverInfo(RecordDeviceId, out _, 0,
            out Guid micGUID, out _sampleRate, out SPEAKERMODE speakerMode, out int captureNumChannels, out DRIVER_STATE driverState);

        _driftThreshold = (uint)(_sampleRate * DRIFT_MS) / 1000;       /* The point where we start compensating for drift */
        _desiredLatency = (uint)(_sampleRate * LATENSY_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) */

        // create sound where capture is recorded
        _exinfo.cbsize = Marshal.SizeOf(typeof(CREATESOUNDEXINFO));
        _exinfo.numchannels = captureNumChannels;
        _exinfo.format = SOUND_FORMAT.PCM16;
        _exinfo.defaultfrequency = _sampleRate;
        _exinfo.length = (uint)_sampleRate * sizeof(short) * (uint)captureNumChannels;

        UnityEngine.Debug.Log($"speakerMode: {speakerMode}");
        UnityEngine.Debug.Log($"_exinfo.cbsize: {_exinfo.cbsize}");
        UnityEngine.Debug.Log($"_exinfo.numchannels: {_exinfo.numchannels}");
        UnityEngine.Debug.Log($"_exinfo.format: {_exinfo.format}");
        UnityEngine.Debug.Log($"_exinfo.defaultfrequency: {_exinfo.defaultfrequency}");
        UnityEngine.Debug.Log($"_exinfo.length: {_exinfo.length}");
        UnityEngine.Debug.Log($"_captureRate: {_sampleRate}");

        RuntimeManager.CoreSystem.createSound(
            _exinfo.userdata, 
            MODE.LOOP_NORMAL | MODE.OPENUSER, 
            ref _exinfo, out _sound);
        RuntimeManager.CoreSystem.recordStart(RecordDeviceId, _sound, true);

        _sound.getLength(out soundLength, TIMEUNIT.PCM);
        UnityEngine.Debug.Log($"soundLength: {soundLength}");

        // play sound on dedicated channel in master channel group
        if (RuntimeManager.CoreSystem.getMasterChannelGroup(out _masterCG) != RESULT.OK)
            UnityEngine.Debug.LogWarningFormat("FMOD: Unable to create a master channel group: masterCG");

        RuntimeManager.CoreSystem.getMasterChannelGroup(out _masterCG);
        RuntimeManager.CoreSystem.playSound(_sound, _masterCG, true, out _channel);
        _channel.setPaused(true);

        // Assign the callback to a member variable to avoid garbage collection
        _readCallback = CaptureDSPReadCallback;

        // Allocate a data buffer large enough for 8 channels, pin the memory to avoid garbage collection
        RuntimeManager.CoreSystem.getDSPBufferSize(out uint bufferLength, out int numBuffers);
        _8channelsSampleBuffer = new float[bufferLength * 8];
        _sampleFloatBuffer = new float[bufferLength * captureNumChannels];
        _sampleInt16Buffer = new short[bufferLength * captureNumChannels];

        // Get a handle to this object to pass into the callback
        _objHandle = GCHandle.Alloc(this);
        if (_objHandle != null)
        {
            // Define a basic DSP that receives a callback each mix to capture audio
            DSP_DESCRIPTION desc = new DSP_DESCRIPTION();
            desc.numinputbuffers = 1;
            desc.numoutputbuffers = 1;
            desc.read = _readCallback;
            desc.userdata = GCHandle.ToIntPtr(_objHandle);

            // Create an instance of the capture DSP and attach it to the master channel group to capture all audio            
            if (RuntimeManager.CoreSystem.createDSP(ref desc, out _captureDSP) == RESULT.OK)
            {
                if (_masterCG.addDSP(0, _captureDSP) != RESULT.OK)
                {
                    UnityEngine.Debug.LogWarningFormat("FMOD: Unable to add mCaptureDSP to the master channel group");
                }
            }
            else
            {
                UnityEngine.Debug.LogWarningFormat("FMOD: Unable to create a DSP: mCaptureDSP");
            }
        }
        else
        {
            UnityEngine.Debug.LogWarningFormat("FMOD: Unable to create a GCHandle: mObjHandle");
        }
    }
}

public void DisableRecording()
{
    if (_isRecording)
    {
        if (_objHandle != null)
        {
            if (RuntimeManager.CoreSystem.getMasterChannelGroup(out ChannelGroup masterCG) == RESULT.OK)
            {
                if (_captureDSP.hasHandle())
                {
                    masterCG.removeDSP(_captureDSP);

                    _captureDSP.release();
                }
            }

            if (_objHandle.IsAllocated)
            {
                _objHandle.Free();
            }
        }

        _lastPlayPos = 0;
        _samplesPlayed = 0;
        _lastRecordPos = 0;
        _samplesRecorded = 0;
        _channel.setPaused(true);
        _isRecording = false;
        _sound.release();
        RuntimeManager.CoreSystem.recordStop(RecordDeviceId);
    }
}

[AOT.MonoPInvokeCallback(typeof(DSP_READ_CALLBACK))]
private RESULT CaptureDSPReadCallback(ref DSP_STATE dsp_state, IntPtr inbuffer, IntPtr outbuffer, uint length, int inchannels, ref int outchannels)
{
    DSP_STATE_FUNCTIONS functions = (DSP_STATE_FUNCTIONS)Marshal.PtrToStructure(dsp_state.functions, typeof(DSP_STATE_FUNCTIONS));

    functions.getuserdata(ref dsp_state, out IntPtr userData);

    GCHandle objHandle = GCHandle.FromIntPtr(userData);
    FMODVoiceProcessor obj = objHandle.Target as FMODVoiceProcessor;

    // Copy the incoming buffer to process later
    int lengthElements = (int)length * inchannels;
    Marshal.Copy(inbuffer, obj._8channelsSampleBuffer, 0, lengthElements);

    // Copy the inbuffer to the outbuffer so we can still hear it
    Marshal.Copy(obj._8channelsSampleBuffer, 0, outbuffer, lengthElements);

    for (int i = 0; i < _sampleFloatBuffer.Length; i++)
    {
        _sampleFloatBuffer[i] = _8channelsSampleBuffer[i * inchannels];
    }

    for (int i = 0; i < _sampleFloatBuffer.Length; i++)
    {
        _sampleInt16Buffer[i] = (short)Math.Floor(_sampleFloatBuffer[i] * short.MaxValue);
    }

    FrameCaptured?.Invoke(_sampleInt16Buffer);

    return RESULT.OK;
}

private void OnEnable()
{
    EnableRecording();
}

private void OnDisable()
{
    DisableRecording();
}

Hi,

As noted in our glossary entry on Sample Data, multi-channel PCM sample data is interleaved. As a result, what you’re seeing likely means two things:

  • Your FMOD system is, at the point that you’re retrieving sample data, running with 6 channels, presumably in 5.1. This may be a result of your output driver/device, your microphone and/or its interface, or you may have set up your project like this in Studio or your Unity settings.
  • Outputting mono to two channels from your recording likely means that your microphone is actually recording mono to two channels, but could also mean that the mono signal is being duplicated to another channel as a result of something in mixer like an effect.

Either way, if you’re expecting mono data and the first two channels are identical, you probably only need to be concerned with every 6th sample.

1 Like