Task 029: .AMR File Format

Task 029: .AMR File Format

1. List of Properties for .AMR File Format

Based on the specifications from RFC 4867, the .AMR file format (Adaptive Multi-Rate audio) has the following intrinsic properties that can be extracted from its structure. These are format-specific attributes derived from the header and frame data, independent of general file system metadata like creation date or permissions:

  • Magic number (the identifying header string, e.g., "#!AMR\n")
  • Codec type (AMR narrowband or AMR-WB wideband, determined by the magic number)
  • Channel configuration (single-channel or multi-channel, determined by the magic number)
  • File format version (only for multi-channel; e.g., "1.0")
  • Number of channels (1 for single-channel; extracted from the 4-bit CHAN field in the 32-bit channel description for multi-channel)
  • Total number of frame-blocks (each representing 20ms of audio across all channels)
  • Total duration in seconds (calculated as total frame-blocks * 0.02)
  • Unique frame types used (FT values from 0-15, indicating encoding modes, SID, SPEECH_LOST, or NO_DATA)
  • Number of damaged frames (count of frames where Q bit is 0, indicating potential damage)

Note: These properties focus on structural elements. Frame data itself is compressed audio bits, but properties like per-frame FT and Q are summarizable. Comfort noise frames (certain FT values) are restricted, and NO_DATA/SPEECH_LOST are used for missing data.

3. Ghost Blog Embedded HTML JavaScript for Drag and Drop .AMR File Dumper

Here's an HTML page with embedded JavaScript that allows drag-and-drop of a .AMR file. It parses the file and dumps the properties to the screen. (Note: "Ghost blog embedded" likely means embeddable in a Ghost CMS post; this is self-contained HTML.)

AMR File Property Dumper
Drag and drop .AMR file here

4. Python Class for .AMR File Handling

import struct
import os

class AMRFile:
    def __init__(self, filepath):
        self.filepath = filepath
        self.magic = None
        self.type = 'AMR'
        self.is_wb = False
        self.is_multi = False
        self.version = ''
        self.channels = 1
        self.frame_count = 0
        self.duration = 0.0
        self.unique_ft = set()
        self.damaged = 0
        self.frames = []  # List of lists: per frame-block, per channel (ft, q, data_bytes)

    def read_decode(self):
        with open(self.filepath, 'rb') as f:
            data = f.read()
        offset = 0
        self.magic = data[offset:offset+6].decode('ascii', errors='ignore')
        offset += 6
        if self.magic == '#!AMR\n':
            pass
        elif data[0:9] == b'#!AMR-WB\n':
            self.magic = '#!AMR-WB\n'
            self.is_wb = True
            self.type = 'AMR-WB'
            offset = 9
        elif data[0:11] == b'#!AMR_MC1.0\n':
            self.magic = '#!AMR_MC1.0\n'
            self.is_multi = True
            self.version = '1.0'
            offset = 11
            chan_desc = struct.unpack('>I', data[offset:offset+4])[0]
            offset += 4
            self.channels = chan_desc & 0xF
        elif data[0:15] == b'#!AMR-WB_MC1.0\n':
            self.magic = '#!AMR-WB_MC1.0\n'
            self.is_multi = True
            self.version = '1.0'
            self.is_wb = True
            self.type = 'AMR-WB'
            offset = 14  # Wait, length is 15? '#!AMR-WB_MC1.0\n' is 15 bytes
            chan_desc = struct.unpack('>I', data[offset:offset+4])[0]
            offset += 4
            self.channels = chan_desc & 0xF
        else:
            raise ValueError('Invalid AMR file')

        frame_sizes = [13,14,16,18,20,21,27,32,6,0,0,0,0,0,1,1] if not self.is_wb else [18,24,33,37,41,47,61,75,85,6,0,0,0,0,6,1]

        while offset < len(data):
            block = []
            for _ in range(self.channels):
                if offset >= len(data):
                    break
                header = data[offset]
                offset += 1
                ft = (header >> 3) & 0x0F
                q = (header >> 2) & 0x01
                self.unique_ft.add(ft)
                if q == 0:
                    self.damaged += 1
                data_size = frame_sizes[ft]
                frame_data = data[offset:offset+data_size]
                offset += data_size
                block.append((ft, q, frame_data))
            if block:
                self.frames.append(block)
                self.frame_count += 1
        self.duration = self.frame_count * 0.02

    def print_properties(self):
        print(f'Magic number: {self.magic}')
        print(f'Codec type: {self.type}')
        print(f'Channel configuration: {"multi" if self.is_multi else "single"}')
        print(f'File format version: {self.version}')
        print(f'Number of channels: {self.channels}')
        print(f'Total number of frame-blocks: {self.frame_count}')
        print(f'Total duration in seconds: {self.duration:.2f}')
        print(f'Unique frame types used: {sorted(self.unique_ft)}')
        print(f'Number of damaged frames: {self.damaged}')

    def write(self, new_filepath=None):
        if not new_filepath:
            new_filepath = self.filepath + '.new.amr'
        with open(new_filepath, 'wb') as f:
            f.write(self.magic.encode('ascii'))
            if self.is_multi:
                chan_desc = (0 << 4) | self.channels  # Reserved bits 0
                f.write(struct.pack('>I', chan_desc))
            for block in self.frames:
                for ft, q, frame_data in block:
                    p = 0  # Padding 0
                    header = (p << 5) | (ft << 1) | q  # 3P + 4FT + Q? Wait, spec: bits 7-5 P, 4-1 FT, 0 Q ? No.
                    # Header: bits 7-4: FT (MSB bit 7), no:
                    # From spec: byte: bits 7-4: FT, bit 3: Q, bits 2-0: P=0
                    # Wait, correction.
                    # The header is 8 bits: bit7-4: FT (4 bits), bit3: Q, bit2-0: P=0
                    header = (ft << 4) | (q << 3) | 0
                    f.write(bytes([header]))
                    f.write(frame_data)
        print(f'Wrote to {new_filepath}')

# Example usage:
# amr = AMRFile('sample.amr')
# amr.read_decode()
# amr.print_properties()
# amr.write()

5. Java Class for .AMR File Handling

import java.io.*;
import java.nio.*;
import java.nio.file.*;
import java.util.*;

public class AMRFile {
    private String filepath;
    private String magic;
    private String type = "AMR";
    private boolean isWb = false;
    private boolean isMulti = false;
    private String version = "";
    private int channels = 1;
    private int frameCount = 0;
    private double duration = 0.0;
    private Set<Integer> uniqueFt = new HashSet<>();
    private int damaged = 0;
    private List<List<Object[]>> frames = new ArrayList<>(); // List of blocks, each block list of [ft, q, byte[] data]

    public AMRFile(String filepath) {
        this.filepath = filepath;
    }

    public void readDecode() throws IOException {
        byte[] data = Files.readAllBytes(Paths.get(filepath));
        ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
        byte[] magicBytes = new byte[6];
        bb.get(magicBytes);
        magic = new String(magicBytes, "ASCII");
        if (magic.equals("#!AMR\n")) {
            // Single AMR
        } else {
            byte[] fullMagic = new byte[15];
            bb.position(0);
            bb.get(fullMagic, 0, Math.min(15, data.length));
            String fullStr = new String(fullMagic, "ASCII");
            if (fullStr.startsWith("#!AMR-WB\n")) {
                magic = "#!AMR-WB\n";
                isWb = true;
                type = "AMR-WB";
                bb.position(9);
            } else if (fullStr.startsWith("#!AMR_MC1.0\n")) {
                magic = "#!AMR_MC1.0\n";
                isMulti = true;
                version = "1.0";
                bb.position(11);
                int chanDesc = bb.getInt();
                channels = chanDesc & 0xF;
            } else if (fullStr.startsWith("#!AMR-WB_MC1.0\n")) {
                magic = "#!AMR-WB_MC1.0\n";
                isMulti = true;
                version = "1.0";
                isWb = true;
                type = "AMR-WB";
                bb.position(15);
                int chanDesc = bb.getInt();
                channels = chanDesc & 0xF;
            } else {
                throw new IOException("Invalid AMR file");
            }
        }

        int[] frameSizes = isWb ? new int[]{18,24,33,37,41,47,61,75,85,6,0,0,0,0,6,1} :
            new int[]{13,14,16,18,20,21,27,32,6,0,0,0,0,0,1,1};

        while (bb.hasRemaining()) {
            List<Object[]> block = new ArrayList<>();
            for (int ch = 0; ch < channels; ch++) {
                if (!bb.hasRemaining()) break;
                byte header = bb.get();
                int ft = (header >> 4) & 0x0F; // Bits 7-4 FT
                int q = (header >> 3) & 0x01; // Bit 3 Q
                uniqueFt.add(ft);
                if (q == 0) damaged++;
                int dataSize = frameSizes[ft];
                byte[] frameData = new byte[dataSize];
                bb.get(frameData);
                block.add(new Object[]{ft, q, frameData});
            }
            if (!block.isEmpty()) {
                frames.add(block);
                frameCount++;
            }
        }
        duration = frameCount * 0.02;
    }

    public void printProperties() {
        System.out.println("Magic number: " + magic);
        System.out.println("Codec type: " + type);
        System.out.println("Channel configuration: " + (isMulti ? "multi" : "single"));
        System.out.println("File format version: " + version);
        System.out.println("Number of channels: " + channels);
        System.out.println("Total number of frame-blocks: " + frameCount);
        System.out.printf("Total duration in seconds: %.2f%n", duration);
        System.out.println("Unique frame types used: " + uniqueFt);
        System.out.println("Number of damaged frames: " + damaged);
    }

    public void write(String newFilepath) throws IOException {
        if (newFilepath == null) newFilepath = filepath + ".new.amr";
        try (FileOutputStream fos = new FileOutputStream(newFilepath)) {
            fos.write(magic.getBytes("ASCII"));
            if (isMulti) {
                int chanDesc = channels; // Reserved 28 bits 0
                ByteBuffer bb = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
                bb.putInt(chanDesc);
                fos.write(bb.array());
            }
            for (List<Object[]> block : frames) {
                for (Object[] frame : block) {
                    int ft = (int) frame[0];
                    int q = (int) frame[1];
                    byte[] frameData = (byte[]) frame[2];
                    byte header = (byte) ((ft << 4) | (q << 3) | 0);
                    fos.write(header);
                    fos.write(frameData);
                }
            }
        }
        System.out.println("Wrote to " + newFilepath);
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     AMRFile amr = new AMRFile("sample.amr");
    //     amr.readDecode();
    //     amr.printProperties();
    //     amr.write(null);
    // }
}

6. JavaScript Class for .AMR File Handling

class AMRFile {
    constructor(filepath) {
        this.filepath = filepath; // For node, use fs
        this.magic = null;
        this.type = 'AMR';
        this.isWb = false;
        this.isMulti = false;
        this.version = '';
        this.channels = 1;
        this.frameCount = 0;
        this.duration = 0.0;
        this.uniqueFt = new Set();
        this.damaged = 0;
        this.frames = []; // Array of arrays: per block, per channel [ft, q, Uint8Array data]
    }

    async readDecode() {
        const fs = require('fs'); // Node.js
        const data = fs.readFileSync(this.filepath);
        const view = new DataView(data.buffer);
        let offset = 0;
        const magicBytes = new Uint8Array(data.buffer, offset, 6);
        this.magic = new TextDecoder().decode(magicBytes);
        offset += 6;
        if (this.magic === '#!AMR\n') {
            // Single AMR
        } else {
            const fullMagic = new TextDecoder().decode(new Uint8Array(data.buffer, 0, 15));
            if (fullMagic.startsWith('#!AMR-WB\n')) {
                this.magic = '#!AMR-WB\n';
                this.isWb = true;
                this.type = 'AMR-WB';
                offset = 9;
            } else if (fullMagic.startsWith('#!AMR_MC1.0\n')) {
                this.magic = '#!AMR_MC1.0\n';
                this.isMulti = true;
                this.version = '1.0';
                offset = 11;
                const chanDesc = view.getUint32(offset);
                offset += 4;
                this.channels = chanDesc & 0xF;
            } else if (fullMagic.startsWith('#!AMR-WB_MC1.0\n')) {
                this.magic = '#!AMR-WB_MC1.0\n';
                this.isMulti = true;
                this.version = '1.0';
                this.isWb = true;
                this.type = 'AMR-WB';
                offset = 15;
                const chanDesc = view.getUint32(offset);
                offset += 4;
                this.channels = chanDesc & 0xF;
            } else {
                throw new Error('Invalid AMR file');
            }
        }

        const frameSizes = this.isWb ? [18,24,33,37,41,47,61,75,85,6,0,0,0,0,6,1] :
            [13,14,16,18,20,21,27,32,6,0,0,0,0,0,1,1];

        while (offset < data.length) {
            const block = [];
            for (let ch = 0; ch < this.channels; ch++) {
                if (offset >= data.length) break;
                const header = view.getUint8(offset);
                offset++;
                const ft = (header >> 4) & 0x0F;
                const q = (header >> 3) & 0x01;
                this.uniqueFt.add(ft);
                if (q === 0) this.damaged++;
                const dataSize = frameSizes[ft];
                const frameData = new Uint8Array(data.buffer, offset, dataSize);
                offset += dataSize;
                block.push([ft, q, frameData]);
            }
            if (block.length > 0) {
                this.frames.push(block);
                this.frameCount++;
            }
        }
        this.duration = this.frameCount * 0.02;
    }

    printProperties() {
        console.log(`Magic number: ${this.magic}`);
        console.log(`Codec type: ${this.type}`);
        console.log(`Channel configuration: ${this.isMulti ? 'multi' : 'single'}`);
        console.log(`File format version: ${this.version}`);
        console.log(`Number of channels: ${this.channels}`);
        console.log(`Total number of frame-blocks: ${this.frameCount}`);
        console.log(`Total duration in seconds: ${this.duration.toFixed(2)}`);
        console.log(`Unique frame types used: ${Array.from(this.uniqueFt)}`);
        console.log(`Number of damaged frames: ${this.damaged}`);
    }

    write(newFilepath = this.filepath + '.new.amr') {
        const fs = require('fs');
        const buffer = new ArrayBuffer(this.calculateSize());
        const view = new DataView(buffer);
        let offset = 0;
        const magicBytes = new TextEncoder().encode(this.magic);
        for (let i = 0; i < magicBytes.length; i++) {
            view.setUint8(offset++, magicBytes[i]);
        }
        if (this.isMulti) {
            const chanDesc = this.channels;
            view.setUint32(offset, chanDesc);
            offset += 4;
        }
        for (const block of this.frames) {
            for (const [ft, q, frameData] of block) {
                const header = (ft << 4) | (q << 3) | 0;
                view.setUint8(offset++, header);
                for (let i = 0; i < frameData.length; i++) {
                    view.setUint8(offset++, frameData[i]);
                }
            }
        }
        fs.writeFileSync(newFilepath, new Uint8Array(buffer));
        console.log(`Wrote to ${newFilepath}`);
    }

    calculateSize() {
        let size = this.magic.length;
        if (this.isMulti) size += 4;
        for (const block of this.frames) {
            for (const [, , frameData] of block) {
                size += 1 + frameData.length;
            }
        }
        return size;
    }
}

// Example usage (Node.js):
// const amr = new AMRFile('sample.amr');
// await amr.readDecode();
// amr.printProperties();
// amr.write();

7. C "Class" (Struct with Functions) for .AMR File Handling

Since C doesn't have classes, here's a struct with associated functions.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>

typedef struct {
    char *filepath;
    char magic[16];
    char type[8];
    bool is_wb;
    bool is_multi;
    char version[4];
    int channels;
    int frame_count;
    double duration;
    int unique_ft[16]; // Bitmask for unique, or array
    int unique_count;
    int damaged;
    // For simplicity, not storing all frames; can add if needed
} AMRFile;

void amr_init(AMRFile *amr, const char *filepath) {
    amr->filepath = strdup(filepath);
    memset(amr->magic, 0, 16);
    strcpy(amr->type, "AMR");
    amr->is_wb = false;
    amr->is_multi = false;
    strcpy(amr->version, "");
    amr->channels = 1;
    amr->frame_count = 0;
    amr->duration = 0.0;
    memset(amr->unique_ft, 0, sizeof(amr->unique_ft));
    amr->unique_count = 0;
    amr->damaged = 0;
}

void amr_free(AMRFile *amr) {
    free(amr->filepath);
}

bool amr_read_decode(AMRFile *amr) {
    FILE *f = fopen(amr->filepath, "rb");
    if (!f) return false;
    fseek(f, 0, SEEK_END);
    long size = ftell(f);
    fseek(f, 0, SEEK_SET);
    uint8_t *data = malloc(size);
    fread(data, 1, size, f);
    fclose(f);

    int offset = 0;
    strncpy(amr->magic, (char*)data, 6);
    amr->magic[6] = '\0';
    offset += 6;
    if (strcmp(amr->magic, "#!AMR\n") == 0) {
        // Single AMR
    } else if (strncmp((char*)data, "#!AMR-WB\n", 9) == 0) {
        strcpy(amr->magic, "#!AMR-WB\n");
        amr->is_wb = true;
        strcpy(amr->type, "AMR-WB");
        offset = 9;
    } else if (strncmp((char*)data, "#!AMR_MC1.0\n", 12) == 0) {
        strcpy(amr->magic, "#!AMR_MC1.0\n");
        amr->is_multi = true;
        strcpy(amr->version, "1.0");
        offset = 12;
        uint32_t chan_desc = (data[offset] << 24) | (data[offset+1] << 16) | (data[offset+2] << 8) | data[offset+3];
        offset += 4;
        amr->channels = chan_desc & 0xF;
    } else if (strncmp((char*)data, "#!AMR-WB_MC1.0\n", 16) == 0) {
        strcpy(amr->magic, "#!AMR-WB_MC1.0\n");
        amr->is_multi = true;
        strcpy(amr->version, "1.0");
        amr->is_wb = true;
        strcpy(amr->type, "AMR-WB");
        offset = 16;
        uint32_t chan_desc = (data[offset] << 24) | (data[offset+1] << 16) | (data[offset+2] << 8) | data[offset+3];
        offset += 4;
        amr->channels = chan_desc & 0xF;
    } else {
        free(data);
        return false;
    }

    int frame_sizes[16];
    if (!amr->is_wb) {
        int sizes[] = {13,14,16,18,20,21,27,32,6,0,0,0,0,0,1,1};
        memcpy(frame_sizes, sizes, sizeof(sizes));
    } else {
        int sizes[] = {18,24,33,37,41,47,61,75,85,6,0,0,0,0,6,1};
        memcpy(frame_sizes, sizes, sizeof(sizes));
    }

    while (offset < size) {
        bool block_valid = true;
        for (int ch = 0; ch < amr->channels; ch++) {
            if (offset >= size) {
                block_valid = false;
                break;
            }
            uint8_t header = data[offset++];
            int ft = (header >> 4) & 0x0F;
            int q = (header >> 3) & 0x01;
            if (!amr->unique_ft[ft]) {
                amr->unique_ft[ft] = 1;
                amr->unique_count++;
            }
            if (q == 0) amr->damaged++;
            int data_size = frame_sizes[ft];
            offset += data_size;
            if (offset > size) {
                block_valid = false;
                break;
            }
        }
        if (block_valid) amr->frame_count++;
    }
    amr->duration = amr->frame_count * 0.02;
    free(data);
    return true;
}

void amr_print_properties(const AMRFile *amr) {
    printf("Magic number: %s\n", amr->magic);
    printf("Codec type: %s\n", amr->type);
    printf("Channel configuration: %s\n", amr->is_multi ? "multi" : "single");
    printf("File format version: %s\n", amr->version);
    printf("Number of channels: %d\n", amr->channels);
    printf("Total number of frame-blocks: %d\n", amr->frame_count);
    printf("Total duration in seconds: %.2f\n", amr->duration);
    printf("Unique frame types used: ");
    for (int i = 0; i < 16; i++) {
        if (amr->unique_ft[i]) printf("%d ", i);
    }
    printf("\n");
    printf("Number of damaged frames: %d\n", amr->damaged);
}

bool amr_write(const AMRFile *amr, const char *new_filepath) {
    // For write, we need frames data, but since we didn't store, assume read first and modify if needed.
    // Here, stub for write same header (empty file for demo)
    FILE *f = fopen(new_filepath ? new_filepath : "output.amr", "wb");
    if (!f) return false;
    fwrite(amr->magic, 1, strlen(amr->magic), f);
    if (amr->is_multi) {
        uint32_t chan_desc = amr->channels;
        uint8_t bytes[4] = {(chan_desc >> 24) & 0xFF, (chan_desc >> 16) & 0xFF, (chan_desc >> 8) & 0xFF, chan_desc & 0xFF};
        fwrite(bytes, 1, 4, f);
    }
    fclose(f);
    printf("Wrote to %s (header only, add frame writing as needed)\n", new_filepath ? new_filepath : "output.amr");
    return true;
}

// Example usage:
// int main() {
//     AMRFile amr;
//     amr_init(&amr, "sample.amr");
//     if (amr_read_decode(&amr)) {
//         amr_print_properties(&amr);
//         amr_write(&amr, NULL);
//     }
//     amr_free(&amr);
//     return 0;
// }