To decode an Ogg Vorbis file in C#, add the RecursiveAI.OneAudio package and call Audio.Open("music.ogg"): you get a streaming decoder that fills a float buffer block by block, with sample-accurate Seek. Audio.Load decodes the whole file into memory, and Audio.Transcode("music.ogg", "music.wav") converts OGG to WAV. The decoder is managed C# checked against libvorbis on the Xiph test vectors, so the same code runs on Windows, Linux and macOS. Every sample below was compiled and run against OneAudio 1.2.1 on .NET 10.
Before you start
dotnet new console -o OggVorbis cd OggVorbis dotnet add package RecursiveAI.OneAudio --version 1.2.1
OneAudio targets .NET 10 and is licensed MIT OR Apache-2.0. The samples use music.ogg, 30 seconds of stereo music at 44.1 kHz encoded with libvorbis at quality 5, and chained.ogg, that file followed by a second, 10-second Vorbis stream.
Decode, seek and convert an OGG file
using OneAudio; // Stream an Ogg Vorbis file: interleaved floats, block by block. using IAudioDecoder vorbis = Audio.Open("music.ogg"); Console.WriteLine($"{vorbis.Format.SampleRate} Hz, {vorbis.Format.Channels} ch, {vorbis.LengthFrames} frames " + $"({vorbis.LengthFrames / (double)vorbis.Format.SampleRate:F3} s)"); float[] block = new float[4096 * vorbis.Format.Channels]; long frames = 0; int n; while ((n = vorbis.Read(block)) > 0) frames += n; Console.WriteLine($"decoded {frames} frames"); // Seek to an exact frame, e.g. a loop point at 10 s. vorbis.Seek(441000); n = vorbis.Read(block); Console.WriteLine($"after seek: position {vorbis.PositionFrames}"); // Two Ogg Vorbis streams joined end to end (a chained file, here 30 s + 10 s) play as one. using IAudioDecoder chained = Audio.Open("chained.ogg"); Console.WriteLine($"chained.ogg: {chained.LengthFrames} frames"); // OGG to WAV, or to FLAC or Opus by changing the extension. Audio.Transcode("music.ogg", "music-from-ogg.wav"); Console.WriteLine($"music-from-ogg.wav: {new FileInfo("music-from-ogg.wav").Length:N0} bytes");
44100 Hz, 2 ch, 1323000 frames (30.000 s) decoded 1323000 frames after seek: position 445096 chained.ogg: 1764000 frames music-from-ogg.wav: 5,292,080 bytes
Readfills the span you pass with interleaved floats and returns a count of frames (one sample per channel), which can be less than you asked for; 0 means the end. Samples are never clipped, so a loud Vorbis file can exceed ±1 slightly and nothing is lost.- The length is exact before you decode: 1,323,000 frames is exactly 30 seconds, because the decoder applies the stream’s granule positions to trim the first and last packets.
Seeklands on the frame you ask for. Reading a block of 4,096 frames from frame 441,000 leaves the position at 445,096. That makes music loops with a loop point in the middle of a file sample-exact.- A chained file, two or more Vorbis streams one after another in one file, plays as one stream: 30 s + 10 s is 1,764,000 frames, and seeking works across the join.
- Decoding allocates nothing per read. For a game, play the file with
mixer.PlayStream("music.ogg", loop: true)and it is decoded ahead on a background thread. See the game audio mixer article.
NVorbis
NVorbis is the managed Vorbis decoder most C# answers point to, MIT licensed. Its latest stable release is 0.10.5, from October 16, 2022; 1.0.0-rc.2 is a prerelease. Following its README:
using NVorbis; using (var vorbis = new VorbisReader("music.ogg")) { var channels = vorbis.Channels; var sampleRate = vorbis.SampleRate; Console.WriteLine($"{sampleRate} Hz, {channels} ch, {vorbis.TotalTime}"); var readBuffer = new float[channels * sampleRate / 5]; // 200 ms long samples = 0; int cnt; while ((cnt = vorbis.ReadSamples(readBuffer, 0, readBuffer.Length)) > 0) samples += cnt; // samples, not frames Console.WriteLine($"decoded {samples / channels} frames"); }
44100 Hz, 2 ch, 00:00:30 decoded 1323000 frames
- Both decoders give the same 1,323,000 frames. NVorbis’s
ReadSamplescounts samples, OneAudio’sReadcounts frames: divide or multiply by the channel count when you move code across. - The NVorbis README says the “sample value range is -0.99999994f to 0.99999994f unless vorbis.ClipSamples == false”, so it clips by default. OneAudio never clips decoded samples.
- NVorbis decodes Vorbis only. OneAudio reads Vorbis, Opus, FLAC, MP3 and WAV through the same call, writes WAV, FLAC and Opus, and adds resampling, a mixer and loudness measurement.
In the project’s published decode benchmark, OneAudio decoded three Xiph test files 1.20 to 1.25 times as fast as NVorbis 0.10.5. On the 186-second stereo file it took 196 ms against 240 ms and allocated 686 KB against NVorbis’s 77.9 MB. Measured with BenchmarkDotNet 0.15.8 on an AMD Ryzen 9 7950X3D under Linux with .NET 10.0.13. The decoder’s accuracy is checked against libvorbis 1.3.7: the same frame counts on every test vector, and floor-1 streams within 2e-6 of libvorbis’s float output.
Writing a compressed file
OneAudio decodes Vorbis; for lossy output it encodes Opus, which is also stored in Ogg files. Audio.Transcode("music.wav", "music.opus") writes an Ogg Opus file, covered in encode and decode Opus in C#. For lossless output, write FLAC (read and write FLAC in C#).
Where next
- Decode Ogg Vorbis and streaming decode, the cookbook recipes.
- Play audio in C#, to hear the file.
- The decoding section of the API overview: chained streams, seeking and limits.