Play a WAV, MP3, Ogg or FLAC file on the default sound card in C#, with oneaudio’s mixer and the RecursiveAI.OneAudio.Devices package, and check from the device’s own counters that it played.
dotnet add package RecursiveAI.OneAudio --version 1.2.1 dotnet add package RecursiveAI.OneAudio.Devices --version 1.2.1
using OneAudio; using OneAudio.Devices; // A mixer is the source; Audio.Play opens the default output and starts it. AudioMixer mixer = Audio.CreateMixer(); // 48 kHz stereo using AudioOutputDevice output = Audio.Play(mixer); AudioClip clip = Audio.Load("voice.wav"); // 16 kHz mono; the mixer converts mixer.Play(clip); long frames = clip.Frames * mixer.SampleRate / clip.Format.SampleRate; // Wait until the device's own clock says the whole clip has played. var timeout = DateTime.UtcNow.AddSeconds(15); while (output.Counters.FramesPlayed < frames && DateTime.UtcNow < timeout) Thread.Sleep(50); DeviceCounters c = output.Counters; AudioDeviceFormat device = output.DeviceFormat; double rate = mixer.SampleRate; Console.WriteLine($"{output.BackendName}: {output.Device?.Id ?? "no device"}, " + $"{output.State}"); Console.WriteLine($"device format {device.SampleRate} Hz, {device.Channels} ch, " + $"latency {c.LatencyMilliseconds:F0} ms"); Console.WriteLine($"clip {frames / rate:F2} s, " + $"device played {c.FramesPlayed / rate:F2} s, " + $"peak {20 * Math.Log10(c.PeakPlayed):F1} dBFS, " + $"underruns {c.Underruns}"); if (output.LastError is not null) Console.WriteLine($"last error: {output.LastError}");
alsa: default, Running device format 48000 Hz, 2 ch, latency 25 ms clip 6.03 s, device played 6.06 s, peak -7.5 dBFS, underruns 0
This ran on Linux, through ALSA’s default device to PipeWire, with the PipeWire sink muted so the run made no sound. The stream itself ran as usual.
Audio.Play(mixer)opens the default output at the mixer’s rate and channel count and starts pulling fromRenderon its own thread. Any voice the mixer plays is heard, so a game keeps one output and callsmixer.Playfor each sound.FramesPlayedcomes from the device’s clock, not from what was handed over, so it’s the right thing to wait on. It runs a little past the clip because the device keeps playing silence.PeakPlayedis the loudest sample among frames the device has finished: 0 means it only played silence.DeviceFormatis what the hardware runs at. When it differs from the mixer’s rate, the package converts with the library’s polyphase resampler.- The output limiter leaves samples up to 0.9 alone and bends anything louder smoothly under 1, so a hot mix doesn’t clip.
- When no device can be opened,
StateisNoDevice, the output renders silently at real-time pace, retries twice a second, andLastErrorsays why. The program above then stops at its timeout and prints the error. usingdisposes the output, which stops and closes the device.AudioMixerisn’t disposable.
For a long file, stream it instead of loading it: mixer.PlayStream("music.ogg") decodes ahead on a background thread. To play on a device other than the default, see List audio devices.
MP3, WAV and Ogg playback on Windows, Linux and macOS, compared with SoundPlayer and NAudio: Play audio in C#.