Skip to content

Latest commit

 

History

History
98 lines (71 loc) · 4.38 KB

File metadata and controls

98 lines (71 loc) · 4.38 KB

Playing Audio with WasapiPlayer

WasapiPlayer is NAudio 3's modern WASAPI playback device. It is the recommended way to play audio through WASAPI, superseding the older WasapiOut. It still implements IWavePlayer, so it works with the rest of the NAudio playback pipeline, but adds:

  • Zero-copy buffering — audio is read directly into the WASAPI render buffer via Span<byte>, avoiding an intermediate copy.
  • MMCSS thread priority — the playback thread can be registered with the Multimedia Class Scheduler Service for glitch-resistant low-latency playback.
  • IAudioClient3 low-latency shared mode — when the OS and driver support it, shared-mode playback can run at the engine's minimum period.
  • A fluent builder (WasapiPlayerBuilder) instead of a long constructor.
  • IAsyncDisposable for non-blocking teardown in async/UI code.

Creating a WasapiPlayer

You create a WasapiPlayer through WasapiPlayerBuilder. With no configuration, it uses the default render device in shared mode with event synchronization:

using NAudio.Wave;

var player = new WasapiPlayerBuilder().Build();

The builder methods are chainable, so you can configure exactly what you need:

var player = new WasapiPlayerBuilder()
    .WithDevice(someMMDevice)        // default: system default render device
    .WithExclusiveMode()             // default: shared mode
    .WithEventSync()                 // default: event sync (vs WithPollingSync)
    .WithLatency(50)                 // default: 200ms
    .WithLowLatency()                // try IAudioClient3 shared-mode low latency
    .WithMmcssThreadPriority("Pro Audio")
    .WithCategory(AudioStreamCategory.Media)
    .Build();

To select a specific device, enumerate render endpoints with MMDeviceEnumerator (see enumerating output devices) and pass the MMDevice to WithDevice.

Share modes

WithSharedMode() (the default) mixes your audio with other applications. In shared mode the engine resamples/converts your audio to the device mix format automatically, so any reasonable WaveFormat will play.

WithExclusiveMode() takes sole ownership of the device, allowing the exact sample rate and lower latency, but no other application can play through it while you hold it. In exclusive mode the format must be natively supported by the device — check first with IsFormatSupported or discover one with GetSupportedExclusiveFormat:

var preferred = new WaveFormat(48000, 24, 2);
var format = player.GetSupportedExclusiveFormat(preferred);
if (format == null)
{
    // no supported exclusive format found for this device
}

Playing audio

Usage mirrors any other IWavePlayer: call Init with your source, Play to start, Stop to stop, and subscribe to PlaybackStopped to know when playback ends. If a SynchronizationContext is present when the player is constructed (e.g. on a UI thread), PlaybackStopped is raised on that context.

using NAudio.Wave;

using var audioFile = new AudioFileReader("example.mp3");
using var player = new WasapiPlayerBuilder().Build();

player.Init(audioFile);
player.Play();
while (player.PlaybackState == PlaybackState.Playing)
{
    Thread.Sleep(500);
}

Volume control

WasapiPlayer exposes several levels of volume control:

  • Volume / IsMuted — your application's slider in the Windows volume mixer (delegates to SessionVolume). This is the one most apps want.
  • SessionVolume — the full SimpleAudioVolume for the session.
  • StreamVolume — per-channel volume for this stream (shared mode only; throws in exclusive mode).
  • DeviceVolume — the device endpoint master volume, affecting all applications on that device. Use with care.
player.Volume = 0.5f;   // 50% for this application only
player.IsMuted = true;  // mute just this application

Async disposal

In async or UI code, prefer DisposeAsync over Dispose so the calling thread isn't blocked while the playback thread is joined:

await using var player = new WasapiPlayerBuilder().Build();
player.Init(reader);
player.Play();
// ...
// disposal happens asynchronously at end of scope

Getting playback position

WasapiPlayer implements IWavePosition. GetPosition() returns the number of bytes the device has actually rendered (driven by the audio clock), which is not the same as the read position of your source stream.