---
title: "Getting started with oneaudio"
url: https://recursiveai.org/oneaudio/docs/getting-started/
description: "Install oneaudio for .NET 10, decode MP3, Ogg, Opus, FLAC and WAV files, then resample, mix, encode, normalize loudness, and play and record on audio…"
version: "1.2.1"
---

# Getting started with oneaudio

Install oneaudio for .NET 10, decode MP3, Ogg, Opus, FLAC and WAV files, then resample, mix, encode, normalize loudness, and play and record on audio devices, all in C# with no native binaries.

oneaudio is an audio library written in C# with no dependency beyond .NET itself. Every sample on this page was compiled and run against `RecursiveAI.OneAudio` 1.2.1 from NuGet (the device sample with `RecursiveAI.OneAudio.Devices` 1.2.1), and the output shown is from that run.

## Install

oneaudio targets .NET 10 (`net10.0`), so you need the .NET 10 SDK.

```sh
dotnet new console -o AudioStart
cd AudioStart
dotnet add package RecursiveAI.OneAudio --version 1.2.1
```

To play or record on an audio device, add the device package too:

```sh
dotnet add package RecursiveAI.OneAudio.Devices --version 1.2.1
```

Everything is in the `OneAudio` namespace, with the codec-specific types in `OneAudio.Flac`, `OneAudio.Opus`, `OneAudio.Vorbis` and `OneAudio.Mp3`, and the meter in `OneAudio.Loudness`. The device types are in `OneAudio.Devices`.

The samples read and write files in the current directory. The ones used here are a 32-second stereo test piece (`music.wav`, 44.1 kHz 16-bit, with the same audio as `music.mp3`, `music.ogg`, `music.opus` and `music.flac`), a six-second spoken line (`voice.wav`, 16 kHz mono) and a short click (`hit.wav`, 48 kHz mono). Put your own files there under those names.

## Decode a file

`Audio.Load` decodes a whole file to 32-bit float samples:

```csharp
using OneAudio;

// Decode a whole file to 32-bit floats. The format is found from the bytes,
// not the extension.
AudioClip clip = Audio.Load("music.mp3");
AudioFormat format = clip.Format;
double seconds = clip.Frames / (double)format.SampleRate;
Console.WriteLine($"{format.SampleRate} Hz, {format.Channels} channels, " +
                  $"{clip.Frames} frames, {seconds:F3} s");

// Samples are interleaved: frame i of channel c is at [i * channels + c].
ReadOnlySpan<float> samples = clip.Samples.Span;
float peak = 0;
foreach (float s in samples)
    peak = Math.Max(peak, Math.Abs(s));
Console.WriteLine($"{samples.Length} samples, sample peak {20 * Math.Log10(peak):F1} dBFS");

// Convert on the way in: 48 kHz mono, whatever the source was.
var options = new LoadOptions { SampleRate = 48000, Channels = 1 };
AudioClip mono48 = Audio.Load("music.ogg", options);
Console.WriteLine($"music.ogg as {mono48.Format.SampleRate} Hz, " +
                  $"{mono48.Format.Channels} channel: {mono48.Frames} frames");
```

```text
44100 Hz, 2 channels, 1411200 frames, 32.000 s
2822400 samples, sample peak -12.1 dBFS
music.ogg as 48000 Hz, 1 channel: 1536000 frames
```

- The format is detected from the first bytes of the file, so the extension doesn’t matter. WAV and RF64, Ogg Vorbis, Ogg Opus, FLAC (native and in Ogg) and MP3 (MPEG-1, 2 and 2.5 Layer III) all come back the same way.
- A frame is one sample per channel. `Samples` is interleaved, so a stereo clip of N frames holds 2N floats. Full scale is ±1, and decoded samples are never clipped.
- The MP3 is exactly 32 seconds, the length of the source. A LAME or Xing/Info header tells the decoder how much padding the encoder added, and it’s trimmed. See [Decode MP3 in C#](https://recursiveai.org/oneaudio/cookbook/decode-mp3/).
- `LoadOptions` converts the sample rate and channel count on the way. `Load` refuses more than 46,080,000 frames (16 minutes at 48 kHz) unless you raise `MaxFrames`. Longer files belong in a stream.

To keep memory flat, open the file as a stream instead and read it in blocks:

```csharp
using OneAudio;

// Stream a file block by block instead of holding it in memory.
using IAudioDecoder decoder = Audio.Open("music.flac");
AudioFormat format = decoder.Format;
Console.WriteLine($"{format.SampleRate} Hz, {format.Channels} channels, " +
                  $"{format.NativeType}, {decoder.LengthFrames} frames");

float[] buffer = new float[4096 * format.Channels];   // room for 4096 frames
long frames = 0;
int read;
while ((read = decoder.Read(buffer)) > 0)   // frames, not samples; 0 is the end
    frames += read;
Console.WriteLine($"read {frames} frames");

// Seeking is sample-accurate.
decoder.Seek(format.SampleRate * 10L);                // 10 seconds in
read = decoder.Read(buffer);
Console.WriteLine($"after the seek: position {decoder.PositionFrames}, {read} frames read");
```

```text
44100 Hz, 2 channels, PcmSigned16, 1411200 frames
read 1411200 frames
after the seek: position 445096, 4096 frames read
```

`Audio.Open` returns an `IAudioDecoder`. `Read` fills the span you give it and returns the number of frames, which can be fewer than asked for; 0 means the end. Decoders allocate nothing per read, and every one of them seeks to the exact frame when the underlying stream can seek. Dispose the decoder when you’re done. More in [Decode audio from a stream](https://recursiveai.org/oneaudio/cookbook/streaming-decode/).

## Convert and resample

```csharp
using OneAudio;
using OneAudio.Opus;

// Convert between formats. The output format comes from the extension:
// .wav, .flac, or .opus/.ogg (Ogg Opus).
TranscodeResult flac = Audio.Transcode("music.wav", "music-copy.flac");
long flacBytes = new FileInfo("music-copy.flac").Length;
Console.WriteLine($"FLAC: {flac.Frames} frames at {flac.Input.SampleRate} Hz, {flacBytes:N0} bytes");

var opusOptions = new TranscodeOptions
{
    Opus = new OpusFileWriterSettings
    {
        Encoder = new OpusEncoderSettings { BitrateBps = 96_000 },
    },
};
TranscodeResult opus = Audio.Transcode("music.wav", "music-96k.opus", opusOptions);
long opusBytes = new FileInfo("music-96k.opus").Length;
Console.WriteLine($"Opus: {opus.Frames} frames, {opusBytes:N0} bytes");

// Change the rate and channel count while converting.
var toMono48 = new TranscodeOptions { SampleRate = 48000, Channels = 1 };
Audio.Transcode("music.mp3", "music-48k-mono.wav", toMono48);

// Resample in memory. The result has exactly round(frames x ratio) frames,
// with the filter delay removed.
AudioClip clip = Audio.Load("music.wav");
AudioClip at48 = Audio.Resample(clip, 48000);
Console.WriteLine($"resampled {clip.Frames} frames at {clip.Format.SampleRate} Hz " +
                  $"to {at48.Frames} at {at48.Format.SampleRate} Hz");

float[] voice = Audio.Load("voice.wav").Samples.ToArray();       // 16 kHz mono
float[] voice44 = Audio.Resample(voice, channels: 1, fromRate: 16000, toRate: 44100,
                                 ResamplerQuality.PolyphaseFast);
Console.WriteLine($"voice: {voice.Length} samples at 16 kHz, {voice44.Length} at 44.1 kHz");
```

```text
FLAC: 1411200 frames at 44100 Hz, 813,193 bytes
Opus: 1411200 frames, 392,386 bytes
resampled 1411200 frames at 44100 Hz to 1536000 at 48000 Hz
voice: 96400 samples at 16 kHz, 265703 at 44.1 kHz
```

- `Audio.Transcode` streams block by block, so memory use doesn’t depend on the length. A lossless source written to WAV or FLAC with no conversion is bit-exact: the FLAC above decodes to the same samples as `music.wav`.
- `.ogg` as an output extension writes Ogg Opus, the only Ogg format oneaudio encodes. There is no MP3, Vorbis or AAC encoder. A path with another extension throws `ArgumentException`.
- `Audio.Resample` returns exactly round(frames × ratio) frames with the filter’s delay removed, so the result lines up with the source. The default quality is `Polyphase` (144 taps, at least 100 dB of alias rejection). `PolyphaseFast` and `Linear` are cheaper. See [Resample audio](https://recursiveai.org/oneaudio/cookbook/resample-audio/).

## Mix

`AudioMixer` mixes any number of voices on buses into a buffer you supply. In a game, the buffer goes to your audio output. Here it goes into a WAV file:

```csharp
using OneAudio;

// The mixer renders into a buffer you supply. Here the buffer goes into a
// WAV file; in a game it goes to your audio output instead.
AudioMixer mixer = Audio.CreateMixer(sampleRate: 48000, channels: 2);

const int MusicBus = 0, VoiceBus = 1;
mixer.SetDucking(MusicBus, VoiceBus);   // music down while the voice bus is loud

// A file at 44.1 kHz: the voice converts it to the mixer's rate.
mixer.Play("music.wav", new PlayOptions { Bus = MusicBus, Gain = 1.5f });

// PlayParams can start a voice at an exact frame of the mixer's timeline.
var voice = Audio.Load("voice.wav");
var hit = Audio.Load("hit.wav");
mixer.Play(new PlayParams(voice) { Bus = VoiceBus, StartAtFrame = 48000 * 2 });
mixer.Play(new PlayParams(hit) { Bus = VoiceBus, StartAtFrame = 48000 * 10, Pan = 0.5f });

var format = new AudioFormat(48000, 2, AudioFormat.DefaultMask(2),
                             NativeSampleType.PcmSigned16, 16);
using var wav = new WavEncoder(File.Create("mix.wav"), format,
                               new WavEncoderSettings(), leaveOpen: false);

float[] block = new float[1024 * 2];    // 1024 frames, stereo, interleaved
for (int i = 0; i < 48000 * 12 / 1024; i++)
{
    mixer.Render(block);                // allocates nothing, takes no locks
    wav.Write(block);
}
wav.Finish();
Console.WriteLine($"mix.wav: {wav.FramesWritten} frames, {mixer.ActiveVoices} voice still playing");
```

```text
mix.wav: 575488 frames, 1 voice still playing
```

- `Audio.CreateMixer` builds a mixer with 64 voices and a limiter on the master output at −1 dB. Every voice feeds a bus, and each bus feeds the master output or another bus.
- `SetDucking(MusicBus, VoiceBus)` turns the music bus down while the voice bus is loud: by 12 dB by default, once the voice bus passes −30 dB.
- Sources at another sample rate are converted per voice. `StartAtFrame` places a voice on the mixer’s timeline to the frame.
- `Render` allocates nothing, takes no locks and never blocks, so it can run on an audio thread. In a real-time app, call `Play` and the other control methods from another thread. They go through a lock-free queue that `Render` drains. Offline, as here, one thread does both.
- The voice still playing is the music, which is 32 seconds long.

To hear the mix instead, hand the mixer to an audio device (next section), or pass `Render`’s buffer to an output API you already use. Buses, filters, reverb and meters are in [Mix audio files](https://recursiveai.org/oneaudio/cookbook/mix-audio/).

## Measure and normalize loudness

```csharp
using OneAudio;
using OneAudio.Loudness;

// Integrated loudness, loudness range and true peak (BS.1770-5, EBU R 128).
LoudnessResult before = Audio.Measure("music.flac");
Console.WriteLine($"music.flac: {before.IntegratedLufs:F1} LUFS, " +
                  $"range {before.LoudnessRange:F1} LU, true peak {before.TruePeakDbtp:F1} dBTP");

// A copy at -14 LUFS with the true peak at most -1 dBTP (the defaults).
NormalizeResult result = Audio.Normalize("music.flac", "music-14.flac");
Console.WriteLine($"gain {result.GainDb:+0.0;-0.0} dB, " +
                  $"limited by the peak ceiling: {result.LimitedByPeak}");

LoudnessResult after = Audio.Measure("music-14.flac");
Console.WriteLine($"music-14.flac: {after.IntegratedLufs:F1} LUFS, " +
                  $"true peak {after.TruePeakDbtp:F1} dBTP");
```

```text
music.flac: -23.0 LUFS, range 0.2 LU, true peak -11.8 dBTP
gain +9.0 dB, limited by the peak ceiling: False
music-14.flac: -14.0 LUFS, true peak -2.8 dBTP
```

- Loudness follows ITU-R BS.1770-5 and EBU R 128: integrated loudness in LUFS, loudness range in LU, and the true peak in dBTP, found with 4× oversampling.
- `Audio.Normalize` writes a copy at −14 LUFS by default, with the true peak held to −1 dBTP. If the ceiling would be crossed, the gain stops short of the target and `LimitedByPeak` is true. It decodes the file twice instead of holding it in memory. Silence is left alone.
- ffmpeg’s `ebur128` filter reads the normalized file as −14.0 LUFS too.

For broadcast at −23 LUFS, or to normalize audio in memory, see [Normalize loudness](https://recursiveai.org/oneaudio/cookbook/normalize-loudness/). For a live meter, see [Measure loudness](https://recursiveai.org/oneaudio/cookbook/measure-loudness/).

## Play and record

The `RecursiveAI.OneAudio.Devices` package opens the sound card. `Audio.Play` hands a mixer to the default output device and starts it:

```csharp
using OneAudio;
using OneAudio.Devices;

var mixer = Audio.CreateMixer();          // 48 kHz stereo
using var output = Audio.Play(mixer);     // default device, follows changes
mixer.Play("hit.wav");                    // every voice the mixer plays is heard

Thread.Sleep(1000);
DeviceCounters c = output.Counters;       // what the hardware really did
Console.WriteLine($"{output.BackendName} '{output.Device?.Id}' {output.State}: " +
                  $"{c.FramesPlayed / (double)mixer.SampleRate:F1} s played, " +
                  $"peak {20 * Math.Log10(c.PeakPlayed):F1} dBFS, " +
                  $"{c.Underruns} underruns");
```

```text
alsa 'default' Running: 1.0 s played, peak -5.0 dBFS, 0 underruns
```

- `Audio.Play(mixer)`, `Audio.CreateOutput(...)` and `Audio.CreateInput(...)` come with the package, as extension members on `Audio`. Each opens the device and starts it.
- The backend is the operating system’s own: WASAPI on Windows, Core Audio on macOS and iOS, ALSA on Linux (PipeWire and PulseAudio through their ALSA plug-ins). This run was on Linux through PipeWire.
- `Counters` come from the device’s own clock: frames written and played, the played peak, underruns, lost devices and reopens.
- A device that can’t be opened isn’t an exception. The output keeps rendering silently at real-time pace, retries twice a second and says why in `LastError`.
- The output follows the system default when it changes, and reopens after the device is lost.

Play a file, record from the microphone and pick a device in [Play an audio file](https://recursiveai.org/oneaudio/cookbook/play-audio-file/), [Record from the microphone](https://recursiveai.org/oneaudio/cookbook/record-microphone/) and [List audio devices](https://recursiveai.org/oneaudio/cookbook/list-audio-devices/). The changelog lists which backends have been checked on real hardware.

## The packages

- **Packages:** `RecursiveAI.OneAudio` 1.2.1, namespace `OneAudio`, for .NET 10 (`net10.0`). `RecursiveAI.OneAudio.Devices` 1.2.1 adds playback and recording, namespace `OneAudio.Devices`, and versions in step with the core package.
- **Dependencies:** none. No native binaries and no other NuGet packages. The device package calls the operating system’s own audio libraries.
- **License:** MIT or Apache-2.0, at your choice.
- **NativeAOT and trimming:** supported, with no warnings.

| Format | Support |
|---|---|
| WAV, RF64 | Read and write |
| FLAC | Read (bit-exact) and write, levels 0–8 |
| Ogg FLAC | Read |
| Ogg Vorbis | Read |
| Ogg Opus | Read and write |
| MP3 | Read, gapless |

WAV covers PCM at 8, 16, 24 and 32 bits, float at 32 and 64 bits, and `WAVE_FORMAT_EXTENSIBLE`. FLAC covers 4 to 32 bits and 1 to 8 channels. MP3 means MPEG-1, 2 and 2.5 Layer III. Chained Ogg files play as one stream.

The one-call `Audio` class registers every codec, so an app that uses it carries all of them. To keep only the codecs you need in a published app, see [Keep only the codecs you use](https://recursiveai.org/oneaudio/cookbook/trim-codecs/).

## What oneaudio doesn’t do

- **MP3, Vorbis and AAC encoding.** It writes WAV, FLAC and Ogg Opus only.
- **AAC and M4A, MP3 Layers I and II, MIDI, spatial audio.** None of these are in 1.2.1. Layer I and II files throw `UnsupportedAudioFormatException`.

## Where next

Cookbook recipes, each a complete program:

- Decode: [MP3](https://recursiveai.org/oneaudio/cookbook/decode-mp3/), [Ogg Vorbis](https://recursiveai.org/oneaudio/cookbook/decode-ogg-vorbis/), [Opus](https://recursiveai.org/oneaudio/cookbook/decode-opus/), [from a stream or untrusted input](https://recursiveai.org/oneaudio/cookbook/streaming-decode/), and [read format and tags](https://recursiveai.org/oneaudio/cookbook/read-audio-metadata/)
- Read and write [FLAC](https://recursiveai.org/oneaudio/cookbook/flac/) and [WAV](https://recursiveai.org/oneaudio/cookbook/wav/), [encode Opus](https://recursiveai.org/oneaudio/cookbook/encode-opus/), [convert between formats](https://recursiveai.org/oneaudio/cookbook/convert-audio-files/)
- [Resample audio](https://recursiveai.org/oneaudio/cookbook/resample-audio/) and [mix audio files](https://recursiveai.org/oneaudio/cookbook/mix-audio/)
- [Play an audio file](https://recursiveai.org/oneaudio/cookbook/play-audio-file/), [record from the microphone](https://recursiveai.org/oneaudio/cookbook/record-microphone/) and [list audio devices](https://recursiveai.org/oneaudio/cookbook/list-audio-devices/)
- [Measure loudness](https://recursiveai.org/oneaudio/cookbook/measure-loudness/) and [normalize loudness](https://recursiveai.org/oneaudio/cookbook/normalize-loudness/)
- [Keep only the codecs you use](https://recursiveai.org/oneaudio/cookbook/trim-codecs/), for small NativeAOT apps

The reference: the [API overview](https://recursiveai.org/oneaudio/docs/api/) and the [changelog](https://recursiveai.org/oneaudio/docs/changelog/).

Guides: [Decode MP3 in C#](https://recursiveai.org/oneaudio/articles/decode-mp3-csharp/), [Resample audio in C#](https://recursiveai.org/oneaudio/articles/resample-audio-csharp/), [Loudness normalization in C#](https://recursiveai.org/oneaudio/articles/loudness-normalization-csharp/), [Choosing a C# audio library](https://recursiveai.org/oneaudio/articles/csharp-audio-library/) and [Migrate from NAudio](https://recursiveai.org/oneaudio/articles/migrate-from-naudio/).
