oneaudio decodes AMR (narrowband, 8 kHz) and AMR-WB (wideband, 16 kHz) from .amr, .awb and 3GP files, the formats of phone voice memos and MMS, in managed code. The files here are three-second synthetic test files from the oneaudio repository. Their frames hold pseudo-random bytes rather than speech, so they decode to noise, but they are valid AMR.
dotnet new console -o DecodeAmr && cd DecodeAmr dotnet add package RecursiveAI.OneAudio --version 1.4.0 t=https://raw.githubusercontent.com/recursive-ai-oss/oneaudio-csharp/v1.4.0/testdata curl -L -o memo.amr $t/containers/synthetic-nb.amr curl -L -o memo.awb $t/containers/synthetic-wb.awb curl -L -o memo.3gp $t/containers/amrnb.3gp
Put this in Program.cs and run dotnet run:
using OneAudio; using OneAudio.Amr; // A kit that checks for MP4 and AMR before MP3 (see "If Audio.Open fails"). var kit = new AudioKit(new AudioCodecRegistry() .AddWav().AddMp4().AddAmr().AddAac().AddMp3()); foreach (string path in new[] { "memo.amr", "memo.awb", "memo.3gp" }) { using IAudioDecoder decoder = kit.Open(path); AudioFormat f = decoder.Format; Console.WriteLine($"{path}: {f.SampleRate} Hz, {f.Channels} ch, " + $"{decoder.LengthFrames / (double)f.SampleRate:F2} s"); } // AMR to WAV. TranscodeResult wav = kit.Transcode("memo.amr", "memo.wav"); Console.WriteLine($"memo.wav: {wav.Frames} frames, " + $"{new FileInfo("memo.wav").Length:N0} bytes"); // At the rate and channels your app mixes at. var at48k = new LoadOptions { SampleRate = 48000, Channels = 2 }; AudioClip memo = kit.Load("memo.awb", at48k); Console.WriteLine($"memo.awb at 48 kHz stereo: {memo.Frames} frames"); // Frames from a network stream go straight to AmrNbDecoder: a table-of-contents // byte plus the speech bits, 20 ms each. Here they come from memo.amr. byte[] file = File.ReadAllBytes("memo.amr"); var nb = new AmrNbDecoder(); short[] pcm = new short[AmrNbDecoder.SamplesPerFrame]; int offset = 6, decoded = 0; // skip the "#!AMR\n" magic while (offset < file.Length) { int type = AmrFrameTable.FrameType(file[offset]); int size = 1 + AmrFrameTable.PayloadBytes(false, type); if (decoded == 50) nb.DecodeLost(pcm); // a lost packet, concealed else nb.Decode(file.AsSpan(offset, size), pcm); offset += size; decoded++; } Console.WriteLine($"AmrNbDecoder: {decoded} frames, one concealed");
memo.amr: 8000 Hz, 1 ch, 3.00 s memo.awb: 16000 Hz, 1 ch, 3.00 s memo.3gp: 8000 Hz, 1 ch, 3.00 s memo.wav: 24000 frames, 48,080 bytes memo.awb at 48 kHz stereo: 144000 frames AmrNbDecoder: 150 frames, one concealed
If Audio.Open fails
In 1.4.0, Audio.Open and Audio.Load check for MP3 before MP4 and AMR, and the MP3 check can claim some .amr and .3gp files: they throw AudioFormatException: No MPEG audio frame found. The kit in the program checks MP4 and AMR first, and has the same Open, Load and Transcode. A speech-only app can register just these formats, which also keeps the other decoders out of a trimmed build.
Files and frames
- AMR decodes to 8 kHz mono and AMR-WB to 16 kHz mono.
.amrand.awbfiles (RFC 4867 storage format) and 3GP files with AMR or AMR-WB tracks open like any other format. - Raw frames go through
AmrNbDecoderorAmrWbDecoder, one frame per call, as files store them.AmrFrameTablegives a frame’s size from its type.Decodewrites 16-bit or float samples. - For a lost frame call
DecodeLost. A frame marked damaged is concealed too, and “no data” frames are bridged with comfort noise.
Limits
- All eight AMR modes and all nine AMR-WB modes decode, bit-exact against the 3GPP reference decoders on all 573 TS 26.074 and 221 TS 26.174 test sequences.
- Seeking is exact in position, but the samples after a seek only come close to a decode from the start. For bit-exact output, read forward from the start.
- oneaudio doesn’t write AMR.
Music in M4A and AAC files: Decode AAC and M4A.