---
title: "Decode MP3 in C#"
url: https://recursiveai.org/oneaudio/articles/decode-mp3-csharp/
description: "Decode MP3 files in C# on Windows, Linux and macOS with pure .NET code: float samples, MP3 to WAV on disk or in memory, streaming, seeking and gapless length."
date: 2026-10-26
author: OneAudio team
---

# Decode MP3 in C#

Decode MP3 files in C# on Windows, Linux and macOS with pure .NET code: float samples, MP3 to WAV on disk or in memory, streaming, seeking and gapless length.

October 26, 2026 · OneAudio team · Updated · October 27, 2026

To decode an MP3 in C#, add the `RecursiveAI.OneAudio` package and call `Audio.Load("song.mp3")`: you get the whole file as 32-bit float samples with its sample rate and channel count. `Audio.Transcode("song.mp3", "song.wav")` converts it to WAV, and `Audio.Open` streams it block by block with sample-accurate seeking. OneAudio is written in C# with no native code and no dependency beyond .NET, so the same code runs on Windows, Linux and macOS. It reads the LAME/Xing header, so the decoded length matches the original recording, without the encoder’s padding. Every sample below was compiled and run against OneAudio 1.2.1 on .NET 10, and the outputs shown are from those runs.

## Before you start

*Terminal · NuGet*

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

OneAudio targets .NET 10 (`net10.0`) and is licensed MIT OR Apache-2.0. The samples use `music.mp3`, a 30-second stereo 44.1 kHz test piece encoded with LAME at `-V 2`. Put any MP3 of your own next to the program under that name.

## Decode an MP3 to float samples

*Program.cs · OneAudio 1.2.1 · .NET 10*

```
using OneAudio;
 
// Decode the whole MP3 to 32-bit float samples, interleaved by channel.
AudioClip clip = Audio.Load("music.mp3");
 
AudioFormat format = clip.Format;
Console.WriteLine($"{format.SampleRate} Hz, {format.Channels} channels, {format.NativeType}");
Console.WriteLine($"{clip.Frames} frames = {clip.Frames / (double)format.SampleRate:F3} s");
 
// Frame i of channel c is at [i * channels + c]. Full scale is -1 to +1.
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, peak {20 * Math.Log10(peak):F1} dBFS");
```

*Output · Console*

```
44100 Hz, 2 channels, Lossy
1323000 frames = 30.000 s
2646000 samples, peak -5.1 dBFS
```

- The format is detected from the bytes, not the extension. The same call reads WAV, Ogg Vorbis, Opus and FLAC, and they all come back as the same `AudioClip`.
- A frame is one sample per channel. `Samples` is interleaved, so a stereo clip of N frames holds 2N floats, with full scale at ±1. Decoded samples are never clipped.
- `NativeType` is `Lossy` for MP3: there are no original integer samples to recover, only floats.
- `Audio.Load` holds the whole file in memory and refuses more than 16 minutes at 48 kHz unless you raise `LoadOptions.MaxFrames`. For long files, stream instead (below).

## Convert MP3 to WAV, on disk or in memory

Converting MP3 to WAV in memory is a common need when a speech or analysis service wants WAV bytes and you don’t want temporary files. `Audio.Transcode` works on paths and on streams, and can change the sample rate and channel count on the way:

*Program.cs · OneAudio 1.2.1 · .NET 10*

```
using OneAudio;
 
// MP3 file to WAV file. The output format comes from the extension.
TranscodeResult result = Audio.Transcode("music.mp3", "music-decoded.wav");
Console.WriteLine($"{result.Frames} frames at {result.Input.SampleRate} Hz -> " +
                  $"{new FileInfo("music-decoded.wav").Length:N0} bytes");
 
// MP3 bytes to WAV bytes in memory, e.g. for an upload or a speech API.
// Converted to 16 kHz mono on the way.
byte[] mp3 = File.ReadAllBytes("music.mp3");
using var input = new MemoryStream(mp3);
using var wav = new MemoryStream();
Audio.Transcode(input, wav, new TranscodeOptions
{
    Format = AudioFileFormat.Wav,
    SampleRate = 16000,
    Channels = 1,
});
byte[] wavBytes = wav.ToArray();
Console.WriteLine($"in memory: {mp3.Length:N0} MP3 bytes -> {wavBytes.Length:N0} WAV bytes");
```

*Output · Console*

```
1323000 frames at 44100 Hz -> 5,292,080 bytes
in memory: 526,113 MP3 bytes -> 960,080 WAV bytes
```

- With a path, the output format comes from the extension: `.wav`, `.flac`, or `.opus` and `.ogg` for Ogg Opus. With streams, set `TranscodeOptions.Format`.
- A lossy source is written as 16-bit PCM by default. Set `Wav = new WavEncoderSettings { SampleType = NativeSampleType.PcmSigned24 }` (or `IeeeFloat32`) for another sample type.
- The 16 kHz mono WAV is exactly 30 seconds: 480,000 frames of 2 bytes plus an 80-byte header. The conversion streams block by block, so memory use doesn’t grow with the length of the file.
- If a file conversion fails, the partial output file is deleted.

## Stream a long MP3 and seek

For long files, or when you process audio as it arrives, open a decoder and read it in blocks. This version registers only the MP3 decoder, so an app published trimmed or with NativeAOT doesn’t carry the other codecs:

*Program.cs · OneAudio 1.2.1 · .NET 10*

```
using OneAudio;
 
// Register only the MP3 decoder, so a trimmed or NativeAOT app carries no other codec.
var registry = new AudioCodecRegistry().AddMp3();
using IAudioDecoder decoder = AudioDecoder.OpenFile("music.mp3", registry);
 
AudioFormat format = decoder.Format;
Console.WriteLine($"{format.SampleRate} Hz, {format.Channels} ch, length {decoder.LengthFrames} frames");
 
// Read in blocks of 4096 frames: memory stays flat however long the file is.
float[] block = new float[4096 * format.Channels];
long total = 0;
int frames;
while ((frames = decoder.Read(block)) > 0)   // frames, not samples; 0 means the end
{
    // block[..(frames * format.Channels)] holds this block's interleaved samples.
    total += frames;
}
Console.WriteLine($"decoded {total} frames");
 
// Seek to exactly 12.5 seconds and read from there.
decoder.Seek((long)(12.5 * format.SampleRate));
frames = decoder.Read(block);
Console.WriteLine($"after seek: position {decoder.PositionFrames}, read {frames} frames");
```

*Output · Console*

```
44100 Hz, 2 ch, length 1323000 frames
decoded 1323000 frames
after seek: position 555346, read 4096 frames
```

- `Read` fills the span you pass and returns a number of frames, which can be fewer than you asked for. 0 means the end. Decoders allocate nothing per read.
- `Seek` is exact: 12.5 seconds is frame 551,250, and after reading 4,096 frames the position is 555,346. The decoder resumes up to 48 MP3 frames before the target to rebuild the bit reservoir, so the samples after a seek are identical to a straight decode.
- `AudioDecoder.Open` also takes a `Stream`, including one that can’t seek, such as a network response. Only `Seek` needs a seekable stream.
- `Audio.Open(path)` does the same as `AudioDecoder.OpenFile` with every codec registered. The [streaming recipe](https://recursiveai.org/oneaudio/cookbook/streaming-decode/) has more.

## Gapless: why the length is exactly 30 seconds

An MP3 encoder adds silence at the start (the encoder delay) and pads the end to a whole frame. LAME, and ffmpeg when it writes MP3, record both numbers in the Xing/Info header with the LAME extension. OneAudio reads them, skips the delay and drops the padding, so `LengthFrames` and the decoded audio match the source: 1,323,000 frames for this 30-second file. Files without that header are decoded in full, with nothing trimmed.

That matters when you loop music, splice tracks or line decoded audio up with a timeline. Decoding the same file through the NLayer route shown below gives 1,323,648 frames (30.015 s), 648 more than the source.

## Decoding MP3 with NAudio on Linux and macOS

NAudio is no longer Windows-only. NAudio 3 made `NAudio.Core` and `NAudio.Midi` cross-platform and added `NAudio.Alsa` for playback and recording on Linux, as Mark Heath wrote in his [NAudio 3 release post](https://markheath.net/post/2026/8/15/naudio-3-release) (August 15, 2026). MP3 decoding, though, still depends on the platform. In NAudio 3.3.0, the current release, the [package README](https://www.nuget.org/packages/NAudio/3.3.0) says `AudioFileReader` “reads PCM/float WAV and AIFF everywhere, and uses Media Foundation (MP3, AAC, WMA, FLAC…) and ACM (compressed WAV) when running on Windows”. On Linux it says so at run time:

*Program.cs · NAudio 3.3.0 · Linux*

```
using NAudio.Wave;
 
try
{
    using var reader = new AudioFileReader("music.mp3");
    Console.WriteLine($"{reader.WaveFormat}, {reader.TotalTime}");
}
catch (Exception ex)
{
    Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
}
```

*Output · Console*

```
NotSupportedException: MP3 is only supported by AudioFileReader on Windows, where it is decoded by Media Foundation or ACM. On other platforms use SoundFileReader from the NAudio.SoundFile package, or the Extended Audio File Services readers in NAudio.MacOS.
```

`Mp3FileReader` uses “the ACM MP3 codec that is present on most versions of Windows”, according to NAudio’s [MP3 to WAV guide](https://github.com/naudio/NAudio/blob/main/Docs/ConvertMp3ToWav.md). On Linux the compiler flags it as Windows-only (warning CA1416), and at run time it fails to load `Msacm32.dll`. That leaves two cross-platform routes in NAudio:

- **NAudio.SoundFile**, which calls a system installation of libsndfile. Its [guide](https://github.com/naudio/NAudio/blob/main/Docs/CrossPlatformAudioFilesWithSoundFile.md) says no binary is shipped, so you install libsndfile on each machine, and the codecs available depend on how that libsndfile was built.
- **NLayer**, a managed MP3 decoder (MIT), plugged into `Mp3FileReaderBase` through the `NLayer.NAudioSupport` package, as in the same MP3 guide. This runs on Linux with NAudio.Core 3.3.0 and NLayer.NAudioSupport 3.0.0:

*Program.cs · NAudio.Core 3.3.0 + NLayer.NAudioSupport 3.0.0*

```
using NAudio.Wave;
using NLayer.NAudioSupport;
 
var infile = "music.mp3";
var outfile = "converted.wav";
 
using (var reader = new Mp3FileReaderBase(infile, wf => new Mp3FrameDecompressor(wf)))
{
    WaveFileWriter.CreateWaveFile(outfile, reader);
}
 
using var check = new WaveFileReader(outfile);
Console.WriteLine($"{check.WaveFormat}, {check.SampleCount} frames, {check.TotalTime.TotalSeconds:F3} s");
```

*Output · Console*

```
32 bit IEEEFloat: 44100Hz 2 channels, 1323648 frames, 30.015 s
```

If NAudio is already your playback stack, the NLayer route keeps everything in one pipeline. If you only need samples or files, OneAudio gives you MP3 plus WAV, Ogg Vorbis, Opus and FLAC decoding, and WAV, FLAC and Opus encoding, in one managed package. To hear the result, its companion package `RecursiveAI.OneAudio.Devices` plays it on the default output device: see [play audio in C#](https://recursiveai.org/oneaudio/articles/play-audio-csharp/). Moving file code across is covered in [migrating from NAudio](https://recursiveai.org/oneaudio/articles/migrate-from-naudio/).

## Speed and memory

In the project’s [published decode benchmark](https://github.com/recursive-ai-oss/oneaudio-csharp/blob/v1.2.1/README.md#decode), OneAudio decoded a 30-second 128 kbit/s stereo MP3 in 37.0 ms, against 107 ms for NLayer 1.16.0, and allocated 132 KB against NLayer’s 36 MB. Both were measured with BenchmarkDotNet 0.15.8 on an AMD Ryzen 9 7950X3D under Linux with .NET 10.0.13. The README also lists the native `mpg123` decoder, run as a separate process, for comparison. Decoding allocates a fixed few hundred kilobytes, whatever the length of the file.

The decoder was also checked against the ISO/IEC 11172-4 compliance streams: all 11 decode within the standard’s full-accuracy limits, per the README’s conformance table.

## What the MP3 decoder covers

- **Formats:** MPEG-1, MPEG-2 and MPEG-2.5 Layer III at constant, variable and free-format bitrates; mono, stereo, joint stereo and dual channel. MP3 files with Layer I or II audio raise `UnsupportedAudioFormatException`.
- **Tags:** ID3v2 tags in front of the audio are skipped. ID3v1, APE and Lyrics3 tags at the end are ignored.
- **Damaged files:** frames that can’t be decoded come out as silence and decoding continues, so a cut or corrupted download still plays to the end.
- **No MP3 encoder:** OneAudio writes WAV, FLAC and Opus. For a smaller lossy file, encode to Opus with `Audio.Transcode("in.wav", "out.opus")`.

## Where next

- [Getting started with OneAudio](https://recursiveai.org/oneaudio/docs/getting-started/): decode, convert, resample, mix and normalize.
- [Decode MP3](https://recursiveai.org/oneaudio/cookbook/decode-mp3/) and [convert audio files](https://recursiveai.org/oneaudio/cookbook/convert-audio-files/), the cookbook recipes.
- [Resample audio in C#](https://recursiveai.org/oneaudio/articles/resample-audio-csharp/), for example to 16 kHz for speech recognition.
- [Play an MP3 in C#](https://recursiveai.org/oneaudio/articles/play-audio-csharp/) on Windows, Linux and macOS.
- The [API overview](https://recursiveai.org/oneaudio/docs/api/#mp3) has the MP3 details: seeking, gapless trimming and damaged data.
