Thanks for sharing, with a few modifications to your code i finally got the result. Put this script somewhere in a root or a start scene, fmod audio will be captured into audio source, but only when Unity Recorder is on (Time.captureFramerate != 0).
But you need to toggle “Allow unsafe” in the Player settings for this to be compiled, or use jeff_fmod’s version of fmod read dsp callback, those changes are just for some performance to avoid array allocations.
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using FMOD;
using FMODUnity;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace Recorder
{
[RequireComponent(typeof(AudioSource))]
public sealed class FmodRecorderHelper : MonoBehaviour
{
private readonly List<float> _buffer = new();
private DSP_DESCRIPTION _dspDesc;
private DSP _dsp;
private bool _isRunning = false;
private bool _isCreatedDsp = false;
private static bool _isInitialized = false;
private void Awake()
{
if (_isInitialized)
{
Destroy(this);
return;
}
_isInitialized = true;
DontDestroyOnLoad(this);
}
private void Reset()
{
var audioSource = GetComponent<AudioSource>();
audioSource.spatialBlend = 0f;
audioSource.reverbZoneMix = 0f;
}
private void OnDestroy()
{
RemoveDsp();
}
private void OnAudioFilterRead(float[] data, int channels)
{
if (!_isRunning) return;
if (_buffer.Count >= data.Length)
{
// Copy from intermediate buffer into Unity's audio buffer
for (int i = 0; i < data.Length; i++)
{
data[i] = _buffer[i];
}
_buffer.RemoveRange(0, data.Length);
}
}
private void Update()
{
if (Time.captureFramerate == 0)
{
if (_isRunning) RemoveDsp();
return;
}
if (_isRunning) return;
CreateDsp();
RuntimeManager.CoreSystem.getMasterChannelGroup(out var group);
if (group.hasHandle())
{
_isRunning = true;
CHECK_RESULT(group.addDSP(0, _dsp));
}
}
private void CreateDsp()
{
if (_isCreatedDsp) return;
_isCreatedDsp = true;
_dspDesc = new DSP_DESCRIPTION
{
numinputbuffers = 1,
numoutputbuffers = 1,
read = (ref DSP_STATE dsp_state, IntPtr inbuffer, IntPtr outbuffer, uint length, int inchannels, ref int outchannels) =>
{
if (length > 0)
{
unsafe
{
// Copy to buffer
var tmp = new Span<float>(inbuffer.ToPointer(), (int) length * inchannels);
for (int i = 0; i < tmp.Length; i++)
{
_buffer.Add(tmp[i]);
}
// Silence FMOD output
tmp = new Span<float>(outbuffer.ToPointer(), (int) length * inchannels);
for (int i = 0; i < tmp.Length; i++)
{
tmp[i] = 0f;
}
}
}
return RESULT.OK;
},
};
var result = RuntimeManager.CoreSystem.createDSP(ref _dspDesc, out _dsp);
var unitySpeakerMode = AudioSettings.GetConfiguration().speakerMode;
var speakerMode = unitySpeakerMode switch
{
AudioSpeakerMode.Mono => SPEAKERMODE.MONO,
AudioSpeakerMode.Stereo => SPEAKERMODE.STEREO,
AudioSpeakerMode.Quad => SPEAKERMODE.QUAD,
AudioSpeakerMode.Surround => SPEAKERMODE.SURROUND,
AudioSpeakerMode.Mode5point1 => SPEAKERMODE._5POINT1,
AudioSpeakerMode.Mode7point1 => SPEAKERMODE._7POINT1,
AudioSpeakerMode.Prologic => SPEAKERMODE.DEFAULT,
_ => SPEAKERMODE.DEFAULT,
};
int numChannels = unitySpeakerMode switch
{
AudioSpeakerMode.Mono => 1,
AudioSpeakerMode.Stereo => 2,
AudioSpeakerMode.Quad => 4,
AudioSpeakerMode.Surround => 5,
AudioSpeakerMode.Mode5point1 => 6,
AudioSpeakerMode.Mode7point1 => 7,
AudioSpeakerMode.Prologic => 2,
_ => 2,
};
_dsp.setChannelFormat(CHANNELMASK.STEREO, numChannels, speakerMode);
CHECK_RESULT(result);
}
private void RemoveDsp()
{
_isRunning = false;
if (!_isCreatedDsp) return;
_isCreatedDsp = false;
CHECK_RESULT(RuntimeManager.CoreSystem.getMasterChannelGroup(out var group));
CHECK_RESULT(group.removeDSP(_dsp));
CHECK_RESULT(_dsp.release());
}
private static void CHECK_RESULT(RESULT result, [CallerLineNumber] int sourceLineNumber = 0)
{
if (result == RESULT.OK) return;
Debug.LogError($"Call to FMOD API failed with result: \"{Error.String(result)}\" line: {sourceLineNumber}");
}
}
}