Decode MP3 files to float samples or WAV in C# with oneaudio, a managed decoder with no native code: gapless trimming, exact seeking, and MP3 bytes held in memory.
using OneAudio; // The whole file as float samples. AudioClip clip = Audio.Load("music.mp3"); Console.WriteLine($"music.mp3: {clip.Format.SampleRate} Hz, {clip.Format.Channels} ch, " + $"{clip.Frames} frames ({clip.Frames / 44100.0:F3} s)"); // The same audio encoded without the LAME/Xing header: nothing tells the // decoder how much padding the encoder added. using (IAudioDecoder plain = Audio.Open("music-noheader.mp3")) Console.WriteLine($"music-noheader.mp3: {plain.LengthFrames} frames " + $"({plain.LengthFrames / 44100.0:F3} s)"); // MP3 to WAV, streamed block by block. A lossy source is written as 16-bit // PCM unless you choose otherwise. TranscodeResult wav = Audio.Transcode("music.mp3", "music-decoded.wav"); long bytes = new FileInfo("music-decoded.wav").Length; Console.WriteLine($"music-decoded.wav: {wav.Frames} frames, {bytes:N0} bytes"); // MP3 bytes in memory, for example from a database or an HTTP response. byte[] mp3 = File.ReadAllBytes("music.mp3"); using IAudioDecoder decoder = Audio.Open(new MemoryStream(mp3)); float[] block = new float[1152 * decoder.Format.Channels]; decoder.Seek(44100 * 20); // exact: the same samples as decoding from the start int frames = decoder.Read(block); Console.WriteLine($"{decoder.GetType().Name}: read {frames} frames " + $"at {decoder.PositionFrames - frames}");
music.mp3: 44100 Hz, 2 ch, 1411200 frames (32.000 s) music-noheader.mp3: 1412352 frames (32.026 s) music-decoded.wav: 1411200 frames, 5,644,880 bytes Mp3Decoder: read 1152 frames at 882000
Gapless length
An MP3 encoder adds silence at the start and pads the end to a whole frame. LAME, and ffmpeg through LAME, records both amounts in a Xing/Info header with the LAME extension. oneaudio reads that header and trims both, so music.mp3 decodes to exactly the 1,411,200 frames of the WAV it was made from. That’s what lets a loop or a track sequence play without a gap.
music-noheader.mp3 is the same audio encoded without the header. Nothing says how much padding there is, so the whole decoded signal is kept: 1,152 frames longer, with the encoder’s delay at the start. A VBRI header, or a Xing header without the LAME extension, gives only the frame count.
Good to know
- MPEG-1, MPEG-2 and MPEG-2.5 Layer III are supported, at fixed, variable or free-format bitrates, in mono, stereo, joint stereo and dual channel. Layer I and Layer II files throw
UnsupportedAudioFormatException. - An ID3v2 tag at the start is skipped, and ID3v1, APE and Lyrics3 tags at the end are ignored. oneaudio 1.2.1 doesn’t return their contents.
- Seeking is exact and gives the same samples as decoding from the start. The first seek in a file makes one pass over the frame headers. The decoder then starts up to 48 frames before the target, to rebuild the bit reservoir.
- A frame that can’t be decoded, for example after a cut in the file, comes out as silence and decoding goes on.
- Writing MP3 isn’t supported. To compress, write Ogg Opus or FLAC.
- The decoder matches all 11 ISO/IEC 11172-4 compliance streams within the full-accuracy limits.
The rest of the MP3 rules are in the API overview.
A longer guide, with NAudio on Linux compared: Decode MP3 in C#.