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.
2. Two Direct Download Links for .AMR Files
- https://hitokageproduction.com/files/audioSamples/amr.amr (mono AMR sample audio)
- https://filesamples.com/samples/audio/amr/sample1.amr (sample AMR audio file)
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.)
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;
// }