naudio / NAudio

Audio and MIDI library for .NET
MIT License
5.37k stars 1.09k forks source link

Upgrading from v1.8.4 to v2.2.1 causes missing samples (choppy playback) with high channel count `AsioDevice`. #1161

Open nitz opened 1 month ago

nitz commented 1 month ago

Hello all!

I have a project that has been running on v1.8.4 for many years now wonderfully. As part of looking to the future, I've been attempting to upgrade to v2.2.1 to hopefully soon be able to move away from .NET Framework. However, I've run into a confusing issue:

When playing back audio to an ASIO device with higher channel counts, I end up with missed samples (and therefore "crunchy" or "choppy" audio.)

I've witnessed this behavior before on v1.8.4, but only with specific scenarios (e.g.: 64 channel playback with a woefully underpowered processor.) However, when I'm trying the newer version of NAudio, I'm running into the issue starting at 17 channels and up, even on my development machine.

At 17 channels, the gaps/choppiness is almost completely unnoticeable. At 24 channels, the effect is more pronounced. At channel counts of 48, or 64, the output is completely unrecognizable. The result feels similar to when you have a sample provider that fills buffers with an incorrect stride.

The effect can occur more pronounced at lower channel counts if my application is built in debug. It's this fact that makes me believe it's due to timing (or the inability to fill out samples fast enough), but I'm not sure what would have changed with that between NAudio versions, as I see nothing in the changelogs.

My reproduction setup includes a 64 channel ASIO device, with it expecting 512 samples at 32 bits per sample, with a latency of 5 ms. I am providing samples with a rate of 48,000.

Initializing looks like this:

_asioDevice = new AsioOut(DanteName);
int desiredChannels = 32; // actual value set from elsewhere
_sampleProvider = new MyCustomSampleProvider(desiredChannels);
_waveProvider = new SampleToWaveProvider(_sampleProvider);
_asioDevice.Init(_waveProvider);
_asioDevice.Play();

Rolling back and forth changing nothing but which NAudio packages are referenced is enough to see (or hide) the issue for me.

I did notice that the latest patch notes specified WASAPI uses background threads, but I was unable to determine if a similar thing is true about ASIO devices; as I would think perhaps a background thread priority could be the root cause for what I'm experiencing.

Any thoughts or guidance would be greatly appreciated!

markheath commented 1 month ago

hmm, strange, I don't think anything has really changed with the ASIO support in a while, and the threading with ASIO is not under .NET control. I wondered if perhaps I might have accidentally released a debug build but as far as I can see that doesn't seem to be the case.

Obviously when dealing with high channel counts and low latency you need to be sure that your sample provider can keep up. And if the .NET garbage collector were to kick in, that would likely disrupt filling a short buffer and cause an audible dropout

nitz commented 1 month ago

Hi Mark! Thanks for the incredibly quick reply :)

I've done a bit more digging, and I think you've definitely echoed my findings so far.

I'm sure it's out of the scope of what your fantastic project is here to provide, but I figured it's worth asking someone with far greater knowledge than I: Is there a way I can tell the driver/device to be more lenient on how long it will wait for me to fill a buffer? It's nice in scenarios like with DVS where I can control the expected latency manually, but I'd like to be able to plan ahead for devices with less configurability.

Cheers and thanks so much again for the reply, and for all that you do!

markheath commented 1 month ago

the time you get to fill a buffer is simply your ASIO latency - so 5ms, which to be honest I'm impressed you're managing at all. In an ideal world, I'd rework NAudio to use Span rather than byte arrays which would allow for more efficient of passing audio through pipelines. I prototyped it a while back, but sadly I don't really have the time to do any major NAudio work these days.

nitz commented 4 weeks ago

I prototyped it a while back, but sadly I don't really have the time to do any major NAudio work these days.

I feel this in my soul 😂

the time you get to fill a buffer is simply your ASIO latency - so 5ms, which to be honest I'm impressed you're managing at all.

Yeah, it's been tricky at some points, but most of the time the data is from some pretty quick signal generation or duplication to multiple channels. With as performant as NAudio already is, it's made so much already such a breeze!

I'll let you know if I come across any concrete examples for something that might make good "low hanging fruit" improvements, but until then I'm just gonna up the hardware requirements for when we need larger channel counts!

Again, thanks so much!