Task 046: .AU File Type

Task 046: .AU File Type

.AU File Format Specifications

The .AU file format, also known as the Sun audio format, is a simple audio container introduced by Sun Microsystems. It is primarily associated with Unix-based systems and supports various audio encodings, with a focus on big-endian byte order. The format consists of a fixed 24-byte header, followed by an optional variable-length information (annotation) chunk, and then the audio data. All integer values in the header are 32-bit unsigned integers stored in big-endian format.

1. List of Properties Intrinsic to the .AU File Format

The properties refer to the structural elements defined in the file header and associated metadata, which are essential to interpreting the file's content. These include:

  • Magic Number: A 4-byte identifier, always set to the ASCII string ".snd" (hexadecimal: 2E 73 6E 64), confirming the file type.
  • Data Offset: A 4-byte unsigned integer indicating the byte offset from the start of the file to the beginning of the audio data section. This value is at least 24 (the minimum header size) and accounts for the optional information chunk.
  • Data Size: A 4-byte unsigned integer specifying the length of the audio data in bytes. If unknown, this is set to 0xFFFFFFFF (all bits set).
  • Encoding: A 4-byte unsigned integer defining the audio data encoding format. Common values include:
  • 1: 8-bit μ-law (ISDN).
  • 2: 8-bit linear PCM.
  • 3: 16-bit linear PCM.
  • 4: 24-bit linear PCM.
  • 5: 32-bit linear PCM.
  • 6: 32-bit IEEE floating-point.
  • 7: 64-bit IEEE floating-point.
  • Other values may include ADPCM variants (e.g., 23 for G.722 ADPCM).
  • Sample Rate: A 4-byte unsigned integer representing the sampling frequency in Hertz (samples per second), such as 8000, 11025, or 44100.
  • Channels: A 4-byte unsigned integer indicating the number of audio channels (e.g., 1 for mono, 2 for stereo).
  • Information Chunk (Annotation): A variable-length string (length = Data Offset - 24 bytes), typically a NULL-terminated ASCII description or metadata about the audio content. This is optional but always present if the data offset exceeds 24.

These properties are derived directly from the file's binary structure and are independent of external file system attributes.

These links provide sample .AU audio files suitable for testing.

3. HTML/JavaScript for Drag-and-Drop .AU File Property Dump

The following is a self-contained HTML page with embedded JavaScript that enables a user to drag and drop a .AU file. Upon dropping, it reads the file, decodes the header, and displays the properties on the screen.

.AU File Property Dumper
Drag and drop a .AU file here

4. Python Class for .AU File Handling

The following Python class can open a .AU file, decode its header, read and print the properties, and write a new .AU file with specified properties and dummy audio data (for demonstration).

import struct
import os

class AUFileHandler:
    def __init__(self, filename=None):
        self.filename = filename
        self.magic = b''
        self.data_offset = 0
        self.data_size = 0
        self.encoding = 0
        self.sample_rate = 0
        self.channels = 0
        self.info = b''

        if filename and os.path.exists(filename):
            self.read()

    def read(self):
        with open(self.filename, 'rb') as f:
            # Read header (24 bytes)
            header = f.read(24)
            if len(header) < 24:
                raise ValueError("Invalid .AU file: Header too short")
            
            self.magic, self.data_offset, self.data_size, self.encoding, self.sample_rate, self.channels = struct.unpack('>4sIIIII', header)
            if self.magic != b'.snd':
                raise ValueError("Invalid .AU file: Magic number mismatch")
            
            # Read info chunk
            info_length = self.data_offset - 24
            self.info = f.read(info_length).split(b'\x00', 1)[0].decode('ascii', errors='ignore')

    def print_properties(self):
        print(f"Magic Number: {self.magic.decode('ascii')}")
        print(f"Data Offset: {self.data_offset}")
        print(f"Data Size: {self.data_size if self.data_size != 0xFFFFFFFF else 'Unknown (0xFFFFFFFF)'}")
        print(f"Encoding: {self.encoding}")
        print(f"Sample Rate: {self.sample_rate} Hz")
        print(f"Channels: {self.channels}")
        print(f"Information Chunk: {self.info or 'None'}")

    def write(self, output_filename, audio_data=b'', info='Sample AU File', data_size=0xFFFFFFFF):
        info_bytes = info.encode('ascii') + b'\x00'  # NULL-terminated
        info_length = len(info_bytes)
        self.data_offset = 24 + info_length
        self.data_size = len(audio_data) if data_size == 0xFFFFFFFF else data_size
        
        header = struct.pack('>4sIIIII', b'.snd', self.data_offset, self.data_size, self.encoding, self.sample_rate, self.channels)
        
        with open(output_filename, 'wb') as f:
            f.write(header)
            f.write(info_bytes)
            f.write(audio_data)

# Example usage:
# handler = AUFileHandler('sample.au')
# handler.print_properties()
# handler.write('output.au', audio_data=b'\x00' * 1024, encoding=3, sample_rate=44100, channels=2)

5. Java Class for .AU File Handling

The following Java class can open a .AU file, decode its header, read and print the properties to the console, and write a new .AU file with specified properties and dummy audio data.

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;

public class AUFileHandler {
    private String filename;
    private String magic;
    private int dataOffset;
    private int dataSize;
    private int encoding;
    private int sampleRate;
    private int channels;
    private String info;

    public AUFileHandler(String filename) {
        this.filename = filename;
        if (filename != null && new File(filename).exists()) {
            read();
        }
    }

    public void read() {
        try (FileInputStream fis = new FileInputStream(filename);
             DataInputStream dis = new DataInputStream(fis)) {
            byte[] magicBytes = new byte[4];
            dis.readFully(magicBytes);
            magic = new String(magicBytes, StandardCharsets.US_ASCII);
            if (!magic.equals(".snd")) {
                throw new IOException("Invalid .AU file: Magic number mismatch");
            }
            dataOffset = dis.readInt();
            dataSize = dis.readInt();
            encoding = dis.readInt();
            sampleRate = dis.readInt();
            channels = dis.readInt();

            int infoLength = dataOffset - 24;
            byte[] infoBytes = new byte[infoLength];
            dis.readFully(infoBytes);
            int nullIndex = 0;
            while (nullIndex < infoLength && infoBytes[nullIndex] != 0) nullIndex++;
            info = new String(infoBytes, 0, nullIndex, StandardCharsets.US_ASCII);
        } catch (IOException e) {
            System.err.println("Error reading .AU file: " + e.getMessage());
        }
    }

    public void printProperties() {
        System.out.println("Magic Number: " + magic);
        System.out.println("Data Offset: " + dataOffset);
        System.out.println("Data Size: " + (dataSize == -1 ? "Unknown (0xFFFFFFFF)" : dataSize));
        System.out.println("Encoding: " + encoding);
        System.out.println("Sample Rate: " + sampleRate + " Hz");
        System.out.println("Channels: " + channels);
        System.out.println("Information Chunk: " + (info.isEmpty() ? "None" : info));
    }

    public void write(String outputFilename, byte[] audioData, String infoStr, int dataSizeParam) throws IOException {
        byte[] infoBytes = (infoStr + "\0").getBytes(StandardCharsets.US_ASCII);
        int infoLength = infoBytes.length;
        this.dataOffset = 24 + infoLength;
        this.dataSize = (dataSizeParam == -1) ? audioData.length : dataSizeParam;

        try (FileOutputStream fos = new FileOutputStream(outputFilename);
             DataOutputStream dos = new DataOutputStream(fos)) {
            dos.writeBytes(".snd");
            dos.writeInt(dataOffset);
            dos.writeInt(this.dataSize);
            dos.writeInt(encoding);
            dos.writeInt(sampleRate);
            dos.writeInt(channels);
            dos.write(infoBytes);
            dos.write(audioData);
        }
    }

    // Example usage:
    // public static void main(String[] args) {
    //     AUFileHandler handler = new AUFileHandler("sample.au");
    //     handler.printProperties();
    //     byte[] dummyData = new byte[1024];
    //     handler.write("output.au", dummyData, "Sample AU File", -1);
    // }
}

6. JavaScript Class for .AU File Handling

The following JavaScript class (for Node.js) can open a .AU file, decode its header, read and print the properties to the console, and write a new .AU file with specified properties and dummy audio data.

const fs = require('fs');

class AUFileHandler {
    constructor(filename = null) {
        this.filename = filename;
        this.magic = '';
        this.dataOffset = 0;
        this.dataSize = 0;
        this.encoding = 0;
        this.sampleRate = 0;
        this.channels = 0;
        this.info = '';

        if (filename && fs.existsSync(filename)) {
            this.read();
        }
    }

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

        this.magic = String.fromCharCode(dataView.getUint8(0), dataView.getUint8(1), dataView.getUint8(2), dataView.getUint8(3));
        if (this.magic !== '.snd') {
            throw new Error('Invalid .AU file: Magic number mismatch');
        }
        this.dataOffset = dataView.getUint32(4, false); // Big-endian
        this.dataSize = dataView.getUint32(8, false);
        this.encoding = dataView.getUint32(12, false);
        this.sampleRate = dataView.getUint32(16, false);
        this.channels = dataView.getUint32(20, false);

        const infoLength = this.dataOffset - 24;
        let infoBuffer = buffer.slice(24, 24 + infoLength);
        const nullIndex = infoBuffer.indexOf(0);
        this.info = infoBuffer.slice(0, nullIndex >= 0 ? nullIndex : infoLength).toString('ascii');
    }

    printProperties() {
        console.log(`Magic Number: ${this.magic}`);
        console.log(`Data Offset: ${this.dataOffset}`);
        console.log(`Data Size: ${this.dataSize === 0xFFFFFFFF ? 'Unknown (0xFFFFFFFF)' : this.dataSize}`);
        console.log(`Encoding: ${this.encoding}`);
        console.log(`Sample Rate: ${this.sampleRate} Hz`);
        console.log(`Channels: ${this.channels}`);
        console.log(`Information Chunk: ${this.info || 'None'}`);
    }

    write(outputFilename, audioData = Buffer.alloc(0), info = 'Sample AU File', dataSize = 0xFFFFFFFF) {
        const infoBuffer = Buffer.from(info + '\0', 'ascii');
        const infoLength = infoBuffer.length;
        this.dataOffset = 24 + infoLength;
        this.dataSize = (dataSize === 0xFFFFFFFF) ? audioData.length : dataSize;

        const header = Buffer.alloc(24);
        header.write('.snd', 0, 4);
        header.writeUInt32BE(this.dataOffset, 4);
        header.writeUInt32BE(this.dataSize, 8);
        header.writeUInt32BE(this.encoding, 12);
        header.writeUInt32BE(this.sampleRate, 16);
        header.writeUInt32BE(this.channels, 20);

        const outputBuffer = Buffer.concat([header, infoBuffer, audioData]);
        fs.writeFileSync(outputFilename, outputBuffer);
    }
}

// Example usage:
// const handler = new AUFileHandler('sample.au');
// handler.printProperties();
// handler.write('output.au', Buffer.alloc(1024), 'Sample AU File', 0xFFFFFFFF);

7. C++ Class for .AU File Handling

The following C++ class can open a .AU file, decode its header, read and print the properties to the console, and write a new .AU file with specified properties and dummy audio data.

#include <iostream>
#include <fstream>
#include <vector>
#include <cstring>
#include <cstdint>
#include <algorithm>

class AUFileHandler {
private:
    std::string filename;
    std::string magic;
    uint32_t dataOffset;
    uint32_t dataSize;
    uint32_t encoding;
    uint32_t sampleRate;
    uint32_t channels;
    std::string info;

    uint32_t readBigEndianUInt32(std::ifstream& file) {
        uint32_t value;
        file.read(reinterpret_cast<char*>(&value), sizeof(value));
        return __builtin_bswap32(value); // Assuming little-endian host
    }

public:
    AUFileHandler(const std::string& fname = "") : filename(fname), dataOffset(0), dataSize(0), encoding(0), sampleRate(0), channels(0) {
        if (!fname.empty()) {
            read();
        }
    }

    void read() {
        std::ifstream file(filename, std::ios::binary);
        if (!file) {
            std::cerr << "Error opening file: " << filename << std::endl;
            return;
        }

        char magicBytes[4];
        file.read(magicBytes, 4);
        magic = std::string(magicBytes, 4);
        if (magic != ".snd") {
            throw std::runtime_error("Invalid .AU file: Magic number mismatch");
        }

        dataOffset = readBigEndianUInt32(file);
        dataSize = readBigEndianUInt32(file);
        encoding = readBigEndianUInt32(file);
        sampleRate = readBigEndianUInt32(file);
        channels = readBigEndianUInt32(file);

        uint32_t infoLength = dataOffset - 24;
        std::vector<char> infoBytes(infoLength);
        file.read(infoBytes.data(), infoLength);
        auto nullIt = std::find(infoBytes.begin(), infoBytes.end(), '\0');
        info = std::string(infoBytes.begin(), nullIt);
    }

    void printProperties() const {
        std::cout << "Magic Number: " << magic << std::endl;
        std::cout << "Data Offset: " << dataOffset << std::endl;
        std::cout << "Data Size: " << (dataSize == 0xFFFFFFFF ? "Unknown (0xFFFFFFFF)" : std::to_string(dataSize)) << std::endl;
        std::cout << "Encoding: " << encoding << std::endl;
        std::cout << "Sample Rate: " << sampleRate << " Hz" << std::endl;
        std::cout << "Channels: " << channels << std::endl;
        std::cout << "Information Chunk: " << (info.empty() ? "None" : info) << std::endl;
    }

    void write(const std::string& outputFilename, const std::vector<uint8_t>& audioData, const std::string& infoStr, uint32_t dataSizeParam = 0xFFFFFFFF) {
        std::string infoWithNull = infoStr + '\0';
        uint32_t infoLength = infoWithNull.size();
        dataOffset = 24 + infoLength;
        dataSize = (dataSizeParam == 0xFFFFFFFF) ? audioData.size() : dataSizeParam;

        std::ofstream file(outputFilename, std::ios::binary);
        if (!file) {
            std::cerr << "Error opening output file: " << outputFilename << std::endl;
            return;
        }

        file.write(".snd", 4);
        uint32_t beDataOffset = __builtin_bswap32(dataOffset);
        file.write(reinterpret_cast<const char*>(&beDataOffset), sizeof(beDataOffset));
        uint32_t beDataSize = __builtin_bswap32(dataSize);
        file.write(reinterpret_cast<const char*>(&beDataSize), sizeof(beDataSize));
        uint32_t beEncoding = __builtin_bswap32(encoding);
        file.write(reinterpret_cast<const char*>(&beEncoding), sizeof(beEncoding));
        uint32_t beSampleRate = __builtin_bswap32(sampleRate);
        file.write(reinterpret_cast<const char*>(&beSampleRate), sizeof(beSampleRate));
        uint32_t beChannels = __builtin_bswap32(channels);
        file.write(reinterpret_cast<const char*>(&beChannels), sizeof(beChannels));
        file.write(infoWithNull.c_str(), infoLength);
        file.write(reinterpret_cast<const char*>(audioData.data()), audioData.size());
    }
};

// Example usage:
// int main() {
//     AUFileHandler handler("sample.au");
//     handler.printProperties();
//     std::vector<uint8_t> dummyData(1024, 0);
//     handler.write("output.au", dummyData, "Sample AU File");
//     return 0;
// }