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.
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):
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); }
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.
ffprobe -v error -of compact \ -show_entries stream=codec_name,profile,sample_rate,channels,duration out.m4a
stream|codec_name=aac|profile=LC|sample_rate=44100|channels=2|duration=3.000000The 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.
ffprobe -v trace out.m4a 2>&1 | grep -o "type:'....' parent:'root'"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 %”.
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
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 measures both cases.
Settings
.m4a,.mp4and.m4ball write an M4A file (AudioFileFormat.Aac);.aacwrites ADTS (AudioFileFormat.AacAdts). A stream has no extension, so setTranscodeOptions.Format.AacEncoderSettings.Bitrateis for the whole stream: the default, 64,000 per channel, is 128 kbit/s in stereo.RateControlpicksCbr(the default),Abr(block sizes follow the music, averaging the target) orVbr, withVbrQualityfrom 0 to 1. More in Encode AAC in C#.TranscodeOptions.SampleRateandChannelsconvert on the way, as for any other output.TranscodeOptions.FastStart(default true) putsmoovbefore the audio in.m4aand.mp4output; 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.
Limits
- AAC-LC only, mono or stereo, at 8,000 to 48,000 Hz. Anything else throws
ArgumentExceptionnaming the option to set, for example:AAC-LC output takes one or two channels, not 6; set TranscodeOptions.Channels.A 96 kHz source needsSampleRate = 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. Other outputs: WAV to FLAC and WAV to Opus.