Recursive AI

Decode AMR in C#: .amr, .awb and 3GP to WAV

All oneaudio recipes

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.

Terminal
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:

C#
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");
Text
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. .amr and .awb files (RFC 4867 storage format) and 3GP files with AMR or AMR-WB tracks open like any other format.
  • Raw frames go through AmrNbDecoder or AmrWbDecoder, one frame per call, as files store them. AmrFrameTable gives a frame’s size from its type. Decode writes 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.