I’m working on what will eventually be a tool to stream user audio input in realtime between servers. So my program is reading in mic data into one byte array (using @lock/unlock), and then another FMOD::Sound will playback audio from that array.
My understanding is that to read in raw PCM bytes, FMOD.MODE.CREATESTREAM is needed, which relies on buffering before playback. This is creating latency that I can not get rid of. I’ve tried waiting before calling playSound(), similar to the get_user_sound sample. Along with adjusting decodebuffersize. This helped a little, but not enough. And making it too small lead to stuttering.
How can play back the PCM bytes in real time without buffering delay? Without adjusting decodebuffersize, it’s ~1000ms delay. With adjustments to decodebuffersize, I got it down to ~300ms, but this is too slow.
Input mic code snippets:
void Start()
{
// Store relevant information into FMOD.CREATESOUNDEXINFO variable.
exinfo.cbsize = 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);
var res = RuntimeManager.CoreSystem.createSound(exinfo.userdata, FMOD.MODE.LOOP_NORMAL | FMOD.MODE.OPENUSER,
ref exinfo, out sound);
FMOD_ERRCHECK(res);
// Start recording
FMOD_ERRCHECK(RuntimeManager.CoreSystem.recordStart(RecordingDeviceIndex, sound, true));
FMOD_ERRCHECK(sound.getLength(out soundLength, FMOD.TIMEUNIT.PCM));
}
void Update()
{
FMOD_ERRCHECK(RuntimeManager.CoreSystem.getRecordPosition(RecordingDeviceIndex, out recordpos));
if (recordpos != lastrecordpos)
{
int blocklength;
uint len1, len2;
blocklength = (int)recordpos - (int)lastrecordpos;
if (blocklength < 0)
{
blocklength += (int)soundLength;
}
FMOD_ERRCHECK(sound.@lock(lastrecordpos * (uint)exinfo.numchannels * sizeof(short), (uint)blocklength * (uint)exinfo.numchannels * sizeof(short), out ptr1, out ptr2, out len1, out len2));
if (len1 > 0)
{
if (len1 + samplePos > soundData.Length)
{
if (len1 > samplePos)
{
samplePos = 0;
}
else
{
// Remove oldest sound first
shiftArrayStart(ref soundData, len1);
samplePos -= (int)len1;
}
}
//Debug.Log("Copying " + len1 + " bytes to position " + samplePos);
Marshal.Copy(ptr1, soundData, samplePos, (int)len1);
samplePos += (int)len1;
}
if (len2 > 0)
{
// not handled...
}
FMOD_ERRCHECK(sound.unlock(ptr1, ptr2, len1, len2));
}
lastrecordpos = recordpos;
}
Playback sound code snippets:
void Start()
{
//Setup receive stream to playback sound
exinfo2.cbsize = Marshal.SizeOf(typeof(FMOD.CREATESOUNDEXINFO));
exinfo2.numchannels = NumOfChannels;
exinfo2.format = FMOD.SOUND_FORMAT.PCM16;
exinfo2.defaultfrequency = SampleRate;
exinfo2.pcmreadcallback = PCMREADCALLBACK;
exinfo2.length = exinfo.length;
exinfo2.decodebuffersize = 4096;
FMOD_ERRCHECK(RuntimeManager.CoreSystem.createSound(exinfo2.userdata, FMOD.MODE.LOOP_NORMAL | FMOD.MODE.OPENUSER |FMOD.MODE.OPENONLY | FMOD.MODE.CREATESTREAM, ref exinfo2, out recvSound));
FMOD_ERRCHECK(RuntimeManager.CoreSystem.playSound(recvSound, channelGroup, true, out channel));
}
private FMOD.RESULT PCMREADCALLBACK(IntPtr soundraw, IntPtr data, uint sizebytes)
{
if (samplePos == 0)
{
// nothing recorded yet
return FMOD.RESULT.OK;
}
if (sizebytes >= samplePos)
{
// Copy everything
Marshal.Copy(soundData, 0, data, samplePos);
samplePos = 0;
}
else
{
// Only copy what fits
Marshal.Copy(soundData, 0, data, (int)sizebytes);
// shift whatevers left to the start
shiftArrayStart(ref soundData, sizebytes);
samplePos -= (int)sizebytes;
}
return FMOD.RESULT.OK;
}