[iOS] Raw PCM data corrupted with distortion/echo on subsequent recording sessions

Hello!

I am writing to report a problem I ran into when implementing voice recording on iOS via a capture DSP. Here are the details of the issue, and how I resolved it (in case someone runs into this problem).

FMOD Version: 2.03.13
Platform: iPhone SE2, iOS 26.5 ((Deployed via Unity IL2CPP)
API Used: FMOD Core API (Bypassing FMOD Studio and Unity RuntimeManager)

Description

When recording on iOS, the first recording session delivers acceptable audio data. However, if the FMOD::System object remains alive between sessions, any subsequent call to System::recordStart results in digital distortion and an additive echo loop that is baked directly into the raw captured PCM bytes. This issue does not occur on Android, high-level code is identical as the same Unity project is built for both iOS and Android.

Context & Implementation Detail

The recording pipeline captures raw microphone data dynamically via an DSP_READ_CALLBACK mapped to a custom DSP on a recording ChannelGroup. The raw PCM float data is pulled directly from this stream.

The corruption and echo are present in the raw bytes immediately upon exiting the DSP callback, before any external encoders or processing layers touch the data.

Only the Core API is used in this pipeline, nothing else. Here is what was added to the UnityAppController.mm’s startUnity method:

[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:nil]; [[AVAudioSession sharedInstance] setActive:YES error:nil];

Removing the DefaultToSpeaker option did not resolve the issue.

Steps to Reproduce

  1. Initialize the FMOD::System Core context on an iOS device.
  2. Call System::recordStart using a designated recording driver.
  3. Read raw PCM floats via the custom DSP callback. Result on Run 1: Audio bytes are clean.
  4. Stop the recording session using System::recordStop.
  5. Start a second recording session using System::recordStart under the same active FMOD::System context.
  6. Read the raw PCM floats again. Result on Run 2+: The raw bytes are immediately corrupted with distortion and a compounding echo loop.

Attempted Fixes That Failed to Resolve the Issue

  1. Using a large, non-looping (loop: false) user sound buffer instead of a looping buffer.
  2. Switching the target recording device index from Driver 0 (Core Audio input) to Driver 1 (Core Audio input (Voice)).
  3. Calling System::mixerSuspend() upon stopping and System::mixerResume() before restarting. This actually caused the distortion to appear on the very first run, signalling that maybe some residual data is not cleaned in a correct way.

Workaround

The only way to get clean raw bytes on subsequent recordings is to completely destroy and rebuild the core system context between recording sessions:

  1. Call System::release() on the active FMOD::System instance.
  2. Instantiate a brand-new system object via FMOD::System_Create().
  3. Re-initialize the Core API context from scratch before calling System::recordStart again.

In order to achieve control over the creation and release of the FMOD::System core in the context of Unity, the RuntimeManager was turned off and instantiation was disabled by editing the source code of the Unity implementation.

Thank you for bringing this to our attention and the detailed post.

Is it possible to check if the issue is present in our record CORE API example:

and let me know if the issue persists? Could I also please grab your Unity version?

Thank you for sharing your workaround.

Hello, Connor!

Thank you for your reply, much appreciated.

I did as you asked and gave it a go with the Core API examples from the website.

I had to change the info.plistand record.ccto make it work on iOS 26.5 and request the correct permissions.

I did get some echo and progressively worse artifacts initially, but it turned out to be due to usingwithOptions:AVAudioSessionCategoryOptionDefaultToSpeakerand the device microphone not filtering out its own output during recording.

Everything worked correctly once I removed it and forwarded sound to the isolated call speaker. No echo, no artifacts, perfectly fine.

Just to be sure, I turned off withOptions:AVAudioSessionCategoryOptionDefaultToSpeakeron the Unity project once again to check if it was the source of the issue. The issue persists on the Unity project, as described in the report, even with the call speaker as output device.

I am using Unity 6000.3.11f1.

I hope this helps!

Regards,

Vasil

Thank you for testing that. Unfortunately, I was not able to reproduce the issue.

In our Unity Integration | Examples Record we playback the sound on a channel rather than reading the PCM data. Could you test if that works without any artifacts? That example uses the Core API so you can still keep some of your current system as well.

Hello, Connor!

Apologies for the delayed reply.

Yes, the example on your website works fine, no issues there.

I did some additional testing and created a barebones example project that uses a capture DSP to replicate the issue. I confirmed the original reported bug, it is still happening, and I managed to reproduce it on Windows in the Unity Editor as well.

On Linux (EndeavourOS with PulseAudio in the Unity Editor) and on Android (11+ with AAudio, on Samsung M51), there are no issues with repeated recording and playback. Works smoothly every time (as reported).

However, repeated recordings on iOS (with CoreAudio on iPhone SE2) and even on Windows 10 (with WASAPI in the Unity Editor) introduce artifacts.

While the iOS problem was expected, the Windows one was not - I tested on two different Windows machines (Dell Latitude 5320 and an Intel NUC 12 Extreme Dragon i9) and repeated recordings did introduce noise and artifacts.

Notably, this happens almost right off the bat on the Dell machine (which is a lot more modest in terms of hardware), and requires 3-4 attempts on the NUC machine until the bug is replicated.

I am guessing some buffer/DSP clean up jobs are dropped on the slower machine, which the faster one picks up somehow. On the same NUC machine, when you run the project under Linux, no such artifacts appear and everything works smoothly.

I can send you the example Unity project I created to replicate the issue. It is just a single scene with a couple of buttons for recording/playback. If you are interested, please let me know what would be the most appropriate way to send you the code.

Regards,

Vasil

Hey Vasil,

A project with a consistent repro would be great! You can upload it to the Uploads tab of your FMOD user profile. Uploads here are only accessible by yourself and FMOD staff.

Hello, Leah!

Thank you for the reply. I just submitted a request to register the project I am working on in order to unlock the option to upload.

I will post a message here, when I have successfully uploaded the project archive.

Have a great day!

Regards,

Vasil

Hello, Leah!

I uploaded an archive of the git repo containing the test project in the Uploadssection of my profile.

The name of the archive is DebugRecordingIssue.zip.

I added a brief README.MD in the archive that explains the structure of the project and some additional information.

Please let me know, if you run into any problems or need more information.

Have a great weekend!

Regards,

Vasil

Sorry for the delayed response.

Thanks for uploading your repro project! I’ve been able to reproduce the issue on both Windows PIE and iOS.

I suspect this is the case, especially since you’re using UniTask. Your FMOD code appears to be sound, and there should be no old samples lingering in the mixer since you’re creating a new recording sound each time, so it’s very likely that either Actor.recordingService or Recording._recordingContainer aren’t being cleared out properly when recording is reinitialized. I’ll continue to investigate and get back to you soon.

Hello, Leah!

Thank you very much for the update. If there is anything I can do to help, please let me know.

It is definitely a head-scratcher - for a long time I thought I was missing something obvious and that my buffer clean-up methods are failing.

At the same time, it is very hard for me to explain why the same Unity code behaves differently on different platforms. I would not rule out some Unity quirk making everyone’s life difficult, but there is also the possibility that the cause for this is somewhere in the middleware layer between Unity and the platform’s native audio manager.

Thanks again for your help, hopefully we can find out what is causing this.

Regards,

Vasil

After some more digging, it seems that the issue is due to the use of ConcurrentQueue for RecordingService._rawPCMS - whether it’s due to the enqueueing, clearing, or both, I’m not sure. That said, I was able to resolve the problem by doing the following:

  • Swap _rawPCMs from ConcurrentQueue to BlockingCllection
  • Change adding the buffer to _rawPCMs to recordingService._rawPCMs.TryAdd(buffer);
  • Change clearing the buffer to while (_rawPCMs.TryTake(out _)) { }

Give that a try and let me know how it goes!

Hello, Leah!

Thank you very much for your message, apologies for the late reply.

I made the changes you suggested - basically swapping out ConcurrentQueuefor BlockingCollection, fixing item addition and collection clear to fit the new API.

However, I still managed to reproduce to issue on both iOS and on Windows. As before, Android and Linux builds remain unaffected and work fine.

If you are testing on Windows, there is a subtle problem with reproducing the bug - if you are on a fast machine, it takes several attempts (5-6 on my NUC12 i9) to get the issue to manifest. You may get some initial false positives.

I was quite optimistic when you said swapping ConcurrentQueue with BlockingCollection would fix the issue, as a race condition was always the most likely culprit. However, looking at the version of the code implemented in my actual project (where I run the same code, but I also dispose and recreate the core system before recording), no additional operations are done on the ConcurrentQueue buffer (compared to the sample project), it is only calls to the FMOD Core API.

So, if the C# buffer logic is untouched and works fine on Android/Linux, but on iOS/Windows you get a bug that only recreating the System::core fixes, it is logical to think some internal FMOD implementation buffers are being polluted on iOS/Windows. This morning, I did some more tests and tried introducing a DSP::reset command on the capture DSP, sandwiched between System::lockDSP and System::unlockDSP in order to clean up the buffers. Unfortunately, this also did not help. Only disposing and recreating System::core helped.

I will keep trying and let you know if I come across another working solution or some clue on what the issue may be.

Regards,

Vasil