Task 044: .AT3 File Format

Task 044: .AT3 File Format

The .AT3 file format is a proprietary audio format developed by Sony, utilizing the Adaptive Transform Acoustic Coding 3 (ATRAC3) compression algorithm. It employs a RIFF (Resource Interchange File Format) container, similar to WAV files, but with specific header configurations for ATRAC3 data. The format supports compressed audio, typically at sample rates of 44.1 kHz or 48 kHz, with bitrates such as 66 kbps, 105 kbps, or 132 kbps for stereo. The structure includes mandatory chunks (fmt, fact, data) and optional ones (e.g., smpl for loops). The audio format code in the fmt chunk is 0x0270, and the fact chunk may extend beyond the standard 4 bytes to include codec-specific information.

List of all the properties of this file format intrinsic to its file system:

  • Audio Format Code: The waveform format identifier (uint16, typically 0x0270 for ATRAC3).
  • Number of Channels: The number of audio channels (uint16, e.g., 1 for mono, 2 for stereo).
  • Sample Rate: The sampling frequency in Hz (uint32, e.g., 44100).
  • Average Bytes per Second: The average data rate (uint32, calculated as sample rate * block align / 1024 for ATRAC3 frames).
  • Block Align: The size of each audio block in bytes (uint16, e.g., 384 for 132 kbps stereo).
  • Bits per Sample: The bit depth per sample (uint16, typically 0 for compressed formats).
  • Extra Param Size: The size of additional format-specific data (uint16, typically 14 for ATRAC3).
  • Extra Params: Format-specific extra data (byte array of size specified above, often including joint stereo flag, redundant channel count, frame size, and padding).
  • Number of Samples: The total number of audio samples (uint32 from fact chunk; may include additional codec data in extended fact chunks).
  • Data Size: The size of the raw compressed audio data chunk (uint32).

Two direct download links for files of format .AT3:

Ghost blog embedded HTML JavaScript that allows a user to drag and drop a file of format .AT3 and dump to screen all these properties:

Drag and drop .AT3 file here
  1. Python class that can open any file of format .AT3 and decode, read, write, and print to console all the properties from the above list:
import struct
import sys

class AT3File:
    def __init__(self, filename):
        self.filename = filename
        self.audio_format = None
        self.channels = None
        self.sample_rate = None
        self.byte_rate = None
        self.block_align = None
        self.bits_per_sample = None
        self.extra_size = None
        self.extra_params = None
        self.num_samples = None
        self.data_size = None
        self.data = None  # For writing back
        self.header_data = b''  # To store header for writing

    def read(self):
        with open(self.filename, 'rb') as f:
            data = f.read()
            offset = 0

            # RIFF header
            riff = data[offset:offset+4].decode('ascii')
            if riff != 'RIFF':
                raise ValueError('Invalid RIFF header')
            offset += 4
            file_size = struct.unpack('<I', data[offset:offset+4])[0] + 8
            offset += 4
            wave = data[offset:offset+4].decode('ascii')
            if wave != 'WAVE':
                raise ValueError('Invalid WAVE format')
            offset += 4

            # fmt chunk
            fmt_id = data[offset:offset+4].decode('ascii')
            if fmt_id != 'fmt ':
                raise ValueError('Invalid fmt chunk')
            offset += 4
            fmt_size = struct.unpack('<I', data[offset:offset+4])[0]
            offset += 4
            self.audio_format = struct.unpack('<H', data[offset:offset+2])[0]
            if self.audio_format != 0x0270:
                raise ValueError('Not ATRAC3 format')
            offset += 2
            self.channels = struct.unpack('<H', data[offset:offset+2])[0]
            offset += 2
            self.sample_rate = struct.unpack('<I', data[offset:offset+4])[0]
            offset += 4
            self.byte_rate = struct.unpack('<I', data[offset:offset+4])[0]
            offset += 4
            self.block_align = struct.unpack('<H', data[offset:offset+2])[0]
            offset += 2
            self.bits_per_sample = struct.unpack('<H', data[offset:offset+2])[0]
            offset += 2
            self.extra_size = struct.unpack('<H', data[offset:offset+2])[0]
            offset += 2
            self.extra_params = data[offset:offset+self.extra_size]
            offset += self.extra_size

            # fact chunk
            fact_id = data[offset:offset+4].decode('ascii')
            if fact_id != 'fact':
                raise ValueError('Invalid fact chunk')
            offset += 4
            fact_size = struct.unpack('<I', data[offset:offset+4])[0]
            offset += 4
            self.num_samples = struct.unpack('<I', data[offset:offset+4])[0]
            offset += 4  # Assume extra if fact_size > 4, skip

            # data chunk
            data_id = data[offset:offset+4].decode('ascii')
            if data_id != 'data':
                raise ValueError('Invalid data chunk')
            offset += 4
            self.data_size = struct.unpack('<I', data[offset:offset+4])[0]
            offset += 4
            self.data = data[offset:offset+self.data_size]

            self.header_data = data[:offset - self.data_size - 4]  # Header up to data size

    def print_properties(self):
        print(f"Audio Format Code: {self.audio_format} (0x{self.audio_format:04x})")
        print(f"Number of Channels: {self.channels}")
        print(f"Sample Rate: {self.sample_rate} Hz")
        print(f"Average Bytes per Second: {self.byte_rate}")
        print(f"Block Align: {self.block_align}")
        print(f"Bits per Sample: {self.bits_per_sample}")
        print(f"Extra Param Size: {self.extra_size}")
        print(f"Extra Params (hex): {' '.join(f'{b:02x}' for b in self.extra_params)}")
        print(f"Number of Samples: {self.num_samples}")
        print(f"Data Size: {self.data_size} bytes")

    def write(self, output_filename):
        if self.data is None:
            raise ValueError('No data to write')
        with open(output_filename, 'wb') as f:
            # Write original header and adjust sizes if needed
            f.write(self.header_data)
            f.write(struct.pack('<I', self.data_size))
            f.write(self.data)

# Example usage: python script.py input.at3 output.at3
if __name__ == '__main__':
    if len(sys.argv) < 3:
        print("Usage: python script.py input.at3 output.at3")
        sys.exit(1)
    at3 = AT3File(sys.argv[1])
    at3.read()
    at3.print_properties()
    at3.write(sys.argv[2])
  1. Java class that can open any file of format .AT3 and decode, read, write, and print to console all the properties from the above list:
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class AT3File {
    private String filename;
    private int audioFormat;
    private int channels;
    private int sampleRate;
    private int byteRate;
    private int blockAlign;
    private int bitsPerSample;
    private int extraSize;
    private byte[] extraParams;
    private int numSamples;
    private int dataSize;
    private byte[] data;
    private byte[] headerData;

    public AT3File(String filename) {
        this.filename = filename;
    }

    public void read() throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile(filename, "r")) {
            byte[] buffer = new byte[(int) raf.length()];
            raf.readFully(buffer);
            ByteBuffer bb = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN);
            int offset = 0;

            // RIFF header
            String riff = new String(buffer, offset, 4);
            if (!riff.equals("RIFF")) throw new IOException("Invalid RIFF header");
            offset += 4;
            int fileSize = bb.getInt(offset) + 8;
            offset += 4;
            String wave = new String(buffer, offset, 4);
            if (!wave.equals("WAVE")) throw new IOException("Invalid WAVE format");
            offset += 4;

            // fmt chunk
            String fmtId = new String(buffer, offset, 4);
            if (!fmtId.equals("fmt ")) throw new IOException("Invalid fmt chunk");
            offset += 4;
            int fmtSize = bb.getInt(offset);
            offset += 4;
            audioFormat = bb.getShort(offset) & 0xFFFF;
            if (audioFormat != 0x0270) throw new IOException("Not ATRAC3 format");
            offset += 2;
            channels = bb.getShort(offset) & 0xFFFF;
            offset += 2;
            sampleRate = bb.getInt(offset);
            offset += 4;
            byteRate = bb.getInt(offset);
            offset += 4;
            blockAlign = bb.getShort(offset) & 0xFFFF;
            offset += 2;
            bitsPerSample = bb.getShort(offset) & 0xFFFF;
            offset += 2;
            extraSize = bb.getShort(offset) & 0xFFFF;
            offset += 2;
            extraParams = new byte[extraSize];
            System.arraycopy(buffer, offset, extraParams, 0, extraSize);
            offset += extraSize;

            // fact chunk
            String factId = new String(buffer, offset, 4);
            if (!factId.equals("fact")) throw new IOException("Invalid fact chunk");
            offset += 4;
            int factSize = bb.getInt(offset);
            offset += 4;
            numSamples = bb.getInt(offset);
            offset += 4; // Skip extra if present

            // data chunk
            String dataId = new String(buffer, offset, 4);
            if (!dataId.equals("data")) throw new IOException("Invalid data chunk");
            offset += 4;
            dataSize = bb.getInt(offset);
            offset += 4;
            data = new byte[dataSize];
            System.arraycopy(buffer, offset, data, 0, dataSize);

            headerData = new byte[offset - dataSize];
            System.arraycopy(buffer, 0, headerData, 0, headerData.length);
        }
    }

    public void printProperties() {
        System.out.printf("Audio Format Code: %d (0x%04X)%n", audioFormat, audioFormat);
        System.out.printf("Number of Channels: %d%n", channels);
        System.out.printf("Sample Rate: %d Hz%n", sampleRate);
        System.out.printf("Average Bytes per Second: %d%n", byteRate);
        System.out.printf("Block Align: %d%n", blockAlign);
        System.out.printf("Bits per Sample: %d%n", bitsPerSample);
        System.out.printf("Extra Param Size: %d%n", extraSize);
        System.out.print("Extra Params (hex): ");
        for (byte b : extraParams) {
            System.out.printf("%02x ", b & 0xFF);
        }
        System.out.println();
        System.out.printf("Number of Samples: %d%n", numSamples);
        System.out.printf("Data Size: %d bytes%n", dataSize);
    }

    public void write(String outputFilename) throws IOException {
        if (data == null) throw new IOException("No data to write");
        try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
            fos.write(headerData);
            ByteBuffer bb = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
            bb.putInt(dataSize);
            fos.write(bb.array());
            fos.write(data);
        }
    }

    // Example usage: java AT3File input.at3 output.at3
    public static void main(String[] args) throws IOException {
        if (args.length < 2) {
            System.out.println("Usage: java AT3File input.at3 output.at3");
            System.exit(1);
        }
        AT3File at3 = new AT3File(args[0]);
        at3.read();
        at3.printProperties();
        at3.write(args[1]);
    }
}
  1. JavaScript class that can open any file of format .AT3 and decode, read, write, and print to console all the properties from the above list:
const fs = require('fs');

class AT3File {
  constructor(filename) {
    this.filename = filename;
    this.audioFormat = null;
    this.channels = null;
    this.sampleRate = null;
    this.byteRate = null;
    this.blockAlign = null;
    this.bitsPerSample = null;
    this.extraSize = null;
    this.extraParams = null;
    this.numSamples = null;
    this.dataSize = null;
    this.data = null;
    this.headerData = null;
  }

  read() {
    const buffer = fs.readFileSync(this.filename);
    const dataView = new DataView(buffer.buffer);
    let offset = 0;

    // RIFF header
    const riff = String.fromCharCode(...buffer.slice(0, 4));
    if (riff !== 'RIFF') throw new Error('Invalid RIFF header');
    offset += 4;
    const fileSize = dataView.getUint32(offset, true) + 8;
    offset += 4;
    const wave = String.fromCharCode(...buffer.slice(offset, offset + 4));
    if (wave !== 'WAVE') throw new Error('Invalid WAVE format');
    offset += 4;

    // fmt chunk
    const fmtId = String.fromCharCode(...buffer.slice(offset, offset + 4));
    if (fmtId !== 'fmt ') throw new Error('Invalid fmt chunk');
    offset += 4;
    const fmtSize = dataView.getUint32(offset, true);
    offset += 4;
    this.audioFormat = dataView.getUint16(offset, true);
    if (this.audioFormat !== 0x0270) throw new Error('Not ATRAC3 format');
    offset += 2;
    this.channels = dataView.getUint16(offset, true);
    offset += 2;
    this.sampleRate = dataView.getUint32(offset, true);
    offset += 4;
    this.byteRate = dataView.getUint32(offset, true);
    offset += 4;
    this.blockAlign = dataView.getUint16(offset, true);
    offset += 2;
    this.bitsPerSample = dataView.getUint16(offset, true);
    offset += 2;
    this.extraSize = dataView.getUint16(offset, true);
    offset += 2;
    this.extraParams = buffer.slice(offset, offset + this.extraSize);
    offset += this.extraSize;

    // fact chunk
    const factId = String.fromCharCode(...buffer.slice(offset, offset + 4));
    if (factId !== 'fact') throw new Error('Invalid fact chunk');
    offset += 4;
    const factSize = dataView.getUint32(offset, true);
    offset += 4;
    this.numSamples = dataView.getUint32(offset, true);
    offset += 4; // Skip extra

    // data chunk
    const dataId = String.fromCharCode(...buffer.slice(offset, offset + 4));
    if (dataId !== 'data') throw new Error('Invalid data chunk');
    offset += 4;
    this.dataSize = dataView.getUint32(offset, true);
    offset += 4;
    this.data = buffer.slice(offset, offset + this.dataSize);

    this.headerData = buffer.slice(0, offset - this.dataSize);
  }

  printProperties() {
    console.log(`Audio Format Code: ${this.audioFormat} (0x${this.audioFormat.toString(16).padStart(4, '0')})`);
    console.log(`Number of Channels: ${this.channels}`);
    console.log(`Sample Rate: ${this.sampleRate} Hz`);
    console.log(`Average Bytes per Second: ${this.byteRate}`);
    console.log(`Block Align: ${this.blockAlign}`);
    console.log(`Bits per Sample: ${this.bitsPerSample}`);
    console.log(`Extra Param Size: ${this.extraSize}`);
    console.log(`Extra Params (hex): ${Array.from(this.extraParams).map(b => b.toString(16).padStart(2, '0')).join(' ')}`);
    console.log(`Number of Samples: ${this.numSamples}`);
    console.log(`Data Size: ${this.dataSize} bytes`);
  }

  write(outputFilename) {
    if (!this.data) throw new Error('No data to write');
    const outputBuffer = Buffer.concat([this.headerData, Buffer.alloc(4), this.data]);
    const dataView = new DataView(outputBuffer.buffer);
    dataView.setUint32(this.headerData.length, this.dataSize, true);
    fs.writeFileSync(outputFilename, outputBuffer);
  }
}

// Example usage: node script.js input.at3 output.at3
if (process.argv.length < 4) {
  console.log('Usage: node script.js input.at3 output.at3');
  process.exit(1);
}
const at3 = new AT3File(process.argv[2]);
at3.read();
at3.printProperties();
at3.write(process.argv[3]);
  1. C class that can open any file of format .AT3 and decode, read, write, and print to console all the properties from the above list:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef struct {
    uint16_t audio_format;
    uint16_t channels;
    uint32_t sample_rate;
    uint32_t byte_rate;
    uint16_t block_align;
    uint16_t bits_per_sample;
    uint16_t extra_size;
    uint8_t *extra_params;
    uint32_t num_samples;
    uint32_t data_size;
    uint8_t *data;
    uint8_t *header_data;
    size_t header_size;
} AT3File;

AT3File* at3_create(const char* filename) {
    AT3File* at3 = malloc(sizeof(AT3File));
    memset(at3, 0, sizeof(AT3File));
    // Initialization if needed
    return at3;
}

void at3_read(AT3File* at3, const char* filename) {
    FILE* fp = fopen(filename, "rb");
    if (!fp) {
        perror("Failed to open file");
        exit(1);
    }
    fseek(fp, 0, SEEK_END);
    size_t file_size = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    uint8_t* buffer = malloc(file_size);
    fread(buffer, 1, file_size, fp);
    fclose(fp);

    size_t offset = 0;

    // RIFF header
    if (memcmp(buffer + offset, "RIFF", 4) != 0) {
        fprintf(stderr, "Invalid RIFF header\n");
        exit(1);
    }
    offset += 4;
    uint32_t riff_size = *(uint32_t*)(buffer + offset) + 8;
    offset += 4;
    if (memcmp(buffer + offset, "WAVE", 4) != 0) {
        fprintf(stderr, "Invalid WAVE format\n");
        exit(1);
    }
    offset += 4;

    // fmt chunk
    if (memcmp(buffer + offset, "fmt ", 4) != 0) {
        fprintf(stderr, "Invalid fmt chunk\n");
        exit(1);
    }
    offset += 4;
    uint32_t fmt_size = *(uint32_t*)(buffer + offset);
    offset += 4;
    at3->audio_format = *(uint16_t*)(buffer + offset);
    if (at3->audio_format != 0x0270) {
        fprintf(stderr, "Not ATRAC3 format\n");
        exit(1);
    }
    offset += 2;
    at3->channels = *(uint16_t*)(buffer + offset);
    offset += 2;
    at3->sample_rate = *(uint32_t*)(buffer + offset);
    offset += 4;
    at3->byte_rate = *(uint32_t*)(buffer + offset);
    offset += 4;
    at3->block_align = *(uint16_t*)(buffer + offset);
    offset += 2;
    at3->bits_per_sample = *(uint16_t*)(buffer + offset);
    offset += 2;
    at3->extra_size = *(uint16_t*)(buffer + offset);
    offset += 2;
    at3->extra_params = malloc(at3->extra_size);
    memcpy(at3->extra_params, buffer + offset, at3->extra_size);
    offset += at3->extra_size;

    // fact chunk
    if (memcmp(buffer + offset, "fact", 4) != 0) {
        fprintf(stderr, "Invalid fact chunk\n");
        exit(1);
    }
    offset += 4;
    uint32_t fact_size = *(uint32_t*)(buffer + offset);
    offset += 4;
    at3->num_samples = *(uint32_t*)(buffer + offset);
    offset += 4; // Skip extra

    // data chunk
    if (memcmp(buffer + offset, "data", 4) != 0) {
        fprintf(stderr, "Invalid data chunk\n");
        exit(1);
    }
    offset += 4;
    at3->data_size = *(uint32_t*)(buffer + offset);
    offset += 4;
    at3->data = malloc(at3->data_size);
    memcpy(at3->data, buffer + offset, at3->data_size);

    at3->header_size = offset - at3->data_size;
    at3->header_data = malloc(at3->header_size);
    memcpy(at3->header_data, buffer, at3->header_size);

    free(buffer);
}

void at3_print_properties(AT3File* at3) {
    printf("Audio Format Code: %u (0x%04X)\n", at3->audio_format, at3->audio_format);
    printf("Number of Channels: %u\n", at3->channels);
    printf("Sample Rate: %u Hz\n", at3->sample_rate);
    printf("Average Bytes per Second: %u\n", at3->byte_rate);
    printf("Block Align: %u\n", at3->block_align);
    printf("Bits per Sample: %u\n", at3->bits_per_sample);
    printf("Extra Param Size: %u\n", at3->extra_size);
    printf("Extra Params (hex): ");
    for (int i = 0; i < at3->extra_size; i++) {
        printf("%02x ", at3->extra_params[i]);
    }
    printf("\n");
    printf("Number of Samples: %u\n", at3->num_samples);
    printf("Data Size: %u bytes\n", at3->data_size);
}

void at3_write(AT3File* at3, const char* output_filename) {
    if (!at3->data) {
        fprintf(stderr, "No data to write\n");
        exit(1);
    }
    FILE* fp = fopen(output_filename, "wb");
    if (!fp) {
        perror("Failed to open output file");
        exit(1);
    }
    fwrite(at3->header_data, 1, at3->header_size, fp);
    uint32_t data_size_le = at3->data_size;
    fwrite(&data_size_le, sizeof(uint32_t), 1, fp);
    fwrite(at3->data, 1, at3->data_size, fp);
    fclose(fp);
}

void at3_destroy(AT3File* at3) {
    free(at3->extra_params);
    free(at3->data);
    free(at3->header_data);
    free(at3);
}

// Example usage: ./program input.at3 output.at3
int main(int argc, char** argv) {
    if (argc < 3) {
        printf("Usage: %s input.at3 output.at3\n", argv[0]);
        return 1;
    }
    AT3File* at3 = at3_create(argv[1]);
    at3_read(at3, argv[1]);
    at3_print_properties(at3);
    at3_write(at3, argv[2]);
    at3_destroy(at3);
    return 0;
}