---
title: "Convert WAV and MP3 to M4A (AAC) in C#"
url: https://recursiveai.org/oneaudio/cookbook/convert-wav-to-m4a/
description: "Convert a WAV or MP3 file to M4A (AAC-LC) in C# with one call, choose the bit rate, or write raw .aac (ADTS), with no FFmpeg or Media Foundation."
version: "1.7.1"
---

# Convert WAV and MP3 to M4A (AAC) in C#

`Audio.Transcode("in.wav", "out.m4a")` encodes a WAV file to AAC in an M4A file in one call, and an MP3, Ogg, FLAC or any other format oneaudio reads converts the same way. The encoder is AAC-LC, written in C#: no FFmpeg, Media Foundation or native library. Pass your own WAV to the program; without one, it first writes a three-second test WAV so that it runs as is. The MP3 is a short synthetic test file from the oneaudio repository.

```shell
dotnet new console -o ToM4a && cd ToM4a
dotnet add package RecursiveAI.OneAudio --version 1.7.1
t=https://raw.githubusercontent.com/recursive-ai-oss/oneaudio-csharp/v1.7.1/testdata
curl -L -o song.mp3 $t/mp3/small_44100_2_joint.mp3
```

Put this in `Program.cs` and run `dotnet run` (or `dotnet run -- yourfile.wav`):

```csharp
using OneAudio;
using OneAudio.Aac;

string wav = args.Length > 0 ? args[0] : "test.wav";
if (!File.Exists(wav))
    WriteTestWav(wav);

// WAV to M4A. The default is constant bit rate, 64 kbit/s per channel.
Audio.Transcode(wav, "out.m4a");

// MP3 to M4A is the same call: the output format comes from the extension.
Audio.Transcode("song.mp3", "song.m4a");

// Choose the bit rate (bits per second for all channels together).
var at96k = new TranscodeOptions { Aac = new AacEncoderSettings { Bitrate = 96_000 } };
Audio.Transcode(wav, "out-96k.m4a", at96k);

// Average bit rate: block sizes follow the music, the file averages the target.
var abr96k = new TranscodeOptions
{
    Aac = new AacEncoderSettings { Bitrate = 96_000, RateControl = AacRateControl.Abr },
};
Audio.Transcode(wav, "abr-96k.m4a", abr96k);

// .aac writes raw AAC with ADTS headers instead of an M4A file.
Audio.Transcode(wav, "out.aac");

string[] files = [wav, "out.m4a", "out-96k.m4a", "abr-96k.m4a", "out.aac",
                  "song.mp3", "song.m4a"];
foreach (string f in files)
{
    using IAudioDecoder d = Audio.Open(f);
    double seconds = d.LengthFrames / (double)d.Format.SampleRate;
    Console.WriteLine($"{f,-12}{new FileInfo(f).Length,9:N0} bytes {seconds,7:F3} s " +
                      $"{d.Format.SampleRate} Hz {d.Format.Channels} ch");
}

// A test WAV: 3 s of a fading C major chord with a little noise, 44.1 kHz stereo.
static void WriteTestWav(string path)
{
    var random = new Random(1);
    float[] s = new float[44100 * 3 * 2];
    for (int i = 0; i < s.Length; i++)
    {
        double t = i / 2 / 44100.0;
        double chord = Math.Sin(2 * Math.PI * 261.63 * t)
                     + Math.Sin(2 * Math.PI * 329.63 * t)
                     + Math.Sin(2 * Math.PI * 392.00 * t);
        double noise = 0.01 * (random.NextDouble() - 0.5);
        s[i] = (float)(0.25 * chord * Math.Exp(-t) + noise);
    }
    var format = new AudioFormat(44100, 2);
    var pcm16 = new WavEncoderSettings { SampleType = NativeSampleType.PcmSigned16 };
    using var wav = new WavEncoder(File.Create(path), format, pcm16, leaveOpen: false);
    wav.Write(s);
}
```

```text
test.wav      529,280 bytes   3.000 s 44100 Hz 2 ch
out.m4a        49,928 bytes   3.000 s 44100 Hz 2 ch
out-96k.m4a    37,796 bytes   3.000 s 44100 Hz 2 ch
abr-96k.m4a    37,172 bytes   3.000 s 44100 Hz 2 ch
out.aac        49,427 bytes   3.042 s 44100 Hz 2 ch
song.mp3       10,448 bytes   0.600 s 44100 Hz 2 ch
song.m4a       10,074 bytes   0.600 s 44100 Hz 2 ch
```

The M4A files decode to exactly the length that went in, 3.000 and 0.600 seconds: the encoder’s 1,024 frames of priming and the padding at the end are recorded in the file’s edit list and in an iTunes `iTunSMPB` tag, which players use for gapless playback. A raw `.aac` file has nowhere to record them, so it decodes a little longer.

## Check the file

FFmpeg 8.0.1’s `ffprobe` reads `out.m4a` as AAC, LC profile, 44,100 Hz, 2 channels, 3.000000 s, and `ffmpeg -v warning -i out.m4a -f null -` decodes it without a warning. Headless Chrome 149 played `out.m4a`, `out-96k.m4a`, `abr-96k.m4a`, `song.m4a` and `out.aac` in an `<audio>` element: `readyState` 4, `currentTime` advancing, and a duration of 3 s for the 3-second M4A files.

```shell
ffprobe -v error -of compact \
  -show_entries stream=codec_name,profile,sample_rate,channels,duration out.m4a
```

```text
stream|codec_name=aac|profile=LC|sample_rate=44100|channels=2|duration=3.000000
```

The M4A files are fast start: `moov`, the box a player needs before it can play, comes ahead of the audio in `mdat`, so playback can begin while the file is still downloading.

```shell
ffprobe -v trace out.m4a 2>&1 | grep -o "type:'....' parent:'root'"
```

```text
type:'ftyp' parent:'root'
type:'moov' parent:'root'
type:'mdat' parent:'root'
```

The constant and average bit rate files at 96 kbit/s differ, 37,796 and 37,172 bytes, and `ffprobe` measures their audio at 95.7 and 94.0 kbit/s. The project’s changelog says an ABR file “still averages the target within 5 %”.

```shell
for f in out-96k.m4a abr-96k.m4a; do
  ffprobe -v error -of compact=p=0 -show_entries format=filename,size:stream=bit_rate $f
done
```

```text
bit_rate=95674
filename=out-96k.m4a|size=37796
bit_rate=94033
filename=abr-96k.m4a|size=37172
```

The project’s README says that “this library’s decode and ffmpeg’s agree to about 1e-7 outside noise-substituted bands, while those bands match in energy but not sample by sample (noise generation is decoder-specific; `NoiseSubstitution = false` makes every band deterministic)”. [Encode AAC](https://recursiveai.org/oneaudio/cookbook/encode-aac/#noise-substitution-and-other-decoders) measures both cases.

## Settings

- `.m4a`, `.mp4` and `.m4b` all write an M4A file (`AudioFileFormat.Aac`); `.aac` writes ADTS (`AudioFileFormat.AacAdts`). A stream has no extension, so set `TranscodeOptions.Format`.
- `AacEncoderSettings.Bitrate` is for the whole stream: the default, 64,000 per channel, is 128 kbit/s in stereo. `RateControl` picks `Cbr` (the default), `Abr` (block sizes follow the music, averaging the target) or `Vbr`, with `VbrQuality` from 0 to 1. More in [Encode AAC in C#](https://recursiveai.org/oneaudio/cookbook/encode-aac/).
- `TranscodeOptions.SampleRate` and `Channels` convert on the way, as for any other output.
- `TranscodeOptions.FastStart` (default true) puts `moov` before the audio in `.m4a` and `.mp4` output; set it false to keep it at the end.
- If a conversion fails, the partial output file is deleted.

On quality, the project’s changelog says that, “scored with ViSQOL v3 (audio mode, MOS-LQO) on 10 SQAM excerpts (5 stereo, 5 mono) not used for tuning, it is on par with ffmpeg 8.0.1’s native `aac` at the same nominal rates”, and that “the only point behind is stereo 192 (by 0.007; worst file 0.015).” The scores are in the [changelog](https://recursiveai.org/oneaudio/docs/changelog/).

## Limits

- AAC-LC only, mono or stereo, at 8,000 to 48,000 Hz. Anything else throws `ArgumentException` naming the option to set, for example: `AAC-LC output takes one or two channels, not 6; set TranscodeOptions.Channels.` A 96 kHz source needs `SampleRate = 48000`.
- No HE-AAC (SBR or parametric stereo) output, and no TNS, intensity stereo or LTP.
- MP3 to M4A decodes and re-encodes, so it’s lossy twice. Start from WAV or FLAC when you have it.
- No title, artist or cover art is written.

To decode M4A files, see [Decode AAC, HE-AAC and M4A](https://recursiveai.org/oneaudio/cookbook/decode-aac/). Other outputs: [WAV to FLAC](https://recursiveai.org/oneaudio/cookbook/convert-wav-to-flac/) and [WAV to Opus](https://recursiveai.org/oneaudio/cookbook/encode-opus/).
