NAudio icon indicating copy to clipboard operation
NAudio copied to clipboard

WasapiOut cuts sounds off prematurely

Open jonjonsson opened this issue 2 years ago • 7 comments

Thanks for a great library! I read the latest blogpost on https://markheath.net/ about Wasapi so I switched over to that.

I think there is an issue though. My apologies if this is known, I couldn't find anything about it.

The problem is that the latency value in WasapiOut affects how much of the sound is played. For example the default value of 200 latency there is a slight cutoff. If you increase it to 1000 the cutoff is longer and a latency of 0 there is practically no cut off (there still might be a millisecond or two though, see comment in code below).


string file = "C:\\temp\\test.wav"; // Test file from https://github.com/jonjonsson/SoundMonster/raw/main/test.wav

Console.WriteLine("The test sound is 4 tones of equal length. Depending on latency the last tone is cut out.");
Console.WriteLine("First test with all default value. The last tone is cut prematurely.");

using (var audioFile = new AudioFileReader(file))
using (var outputDevice = new WasapiOut()) // 200 ms latency default
{
    outputDevice.Init(audioFile);
    outputDevice.Play();
    while (outputDevice.PlaybackState == PlaybackState.Playing)
    {
        Thread.Sleep(2000);
    }
}

Console.WriteLine("Next test with increased latency to 1000 ms. The last tone is completely cut out in this case");

using (var audioFile = new AudioFileReader(file))
using (var outputDevice = new WasapiOut(new MMDeviceEnumerator().GetDefaultAudioEndpoint(DataFlow.Render, Role.Console), AudioClientShareMode.Shared, useEventSync: true, 1000))
{
    outputDevice.Init(audioFile);
    outputDevice.Play();
    while (outputDevice.PlaybackState == PlaybackState.Playing)
    {
        Thread.Sleep(4000);
    }
}

Console.WriteLine("Next test with latency set to 0. All of the sound plays now. That being said the sound ends with a little glitch like it is not cut on a zero crossing (it is perfectly cut).");

using (var audioFile = new AudioFileReader(file))
using (var outputDevice = new WasapiOut(new MMDeviceEnumerator().GetDefaultAudioEndpoint(DataFlow.Render, Role.Console), AudioClientShareMode.Shared, useEventSync: true, 0))
{
    outputDevice.Init(audioFile);
    outputDevice.Play();
    while (outputDevice.PlaybackState == PlaybackState.Playing)
    {
        Thread.Sleep(5000);
    }
}

Console.WriteLine("Final test using WaveOutEvent instead of WasapiOut, works normally, no glitch at the end of sound");
using (var audioFile = new AudioFileReader(file))
using (var outputDevice = new WaveOutEvent())
{
    outputDevice.Init(audioFile);
    outputDevice.Play();
    while (outputDevice.PlaybackState == PlaybackState.Playing)
    {
        Thread.Sleep(2000);
    }
}

jonjonsson avatar Nov 15 '22 14:11 jonjonsson

thanks, sounds like a bug. The WasapiOut code is loosely based on this sample which includes a sleep to ensure the final bit of audio gets played, but maybe the duration calculation isn't quite right. Having said that, could you also try putting a short sleep in your test before outputDevice gets Disposed. It might be because PlaybackState is getting set to Stopped before the playback thread ends.

Also, I recommend not using 1 second as latency - 200ms is more than long enough. And 0 is also not a good idea - probably should be disallowed, as it will result in the playback thread using more CPU than needed

markheath avatar Nov 15 '22 20:11 markheath

Thanks Mark!

When I was troubleshooting I tried delaying the dispose but that did not change anything.

I am just using the default 200ms and noticed the problem when trying to loop sounds properly.

jonjonsson avatar Nov 15 '22 22:11 jonjonsson

What version of NAudio are you using? I've struggled to reproduce this issue with the NAudio demo app - I can hear all four tones (except on DirectSoundOut which does cut them off).

By the way, you mentioned looping - if you want to loop with NAudio, avoid closing and reopening an output device. Instead play an IWaveProvider that implements looping itself (there's various articles showing how to do this with a LoopStream)

markheath avatar Nov 16 '22 08:11 markheath

Actually I have reproduced it now (needed to be using Event sync), and think I have a fix for it

markheath avatar Nov 16 '22 10:11 markheath

Great, thanks again!

Yep, I'm using LoopsSream. Come to think of it the problem was not during the looping. I just noticed it when I was editing sounds for looping and playing them without looping.

jonjonsson avatar Nov 16 '22 11:11 jonjonsson

the change I've just pushed should resolve the issue and I also improved a few other issues with WASAPI playback. Feel free to test with the latest code. Not sure when I'll get round to pushing another version

markheath avatar Nov 16 '22 11:11 markheath

Sounds good! I've only ever used the NuGet package, I'll see how I get a long with the source.

jonjonsson avatar Nov 16 '22 12:11 jonjonsson