Test 010: .AAC File Format
Test 010: .AAC File Format
1. List of Properties Intrinsic to the .AAC File Format
The .AAC file format typically refers to audio files containing Advanced Audio Coding (AAC) data encapsulated in the Audio Data Transport Stream (ADTS) format. This is a streaming-oriented container for AAC audio, as defined in MPEG-2 Part 7 (ISO/IEC 13818-7) and related standards. The file consists of a sequence of ADTS frames, each comprising a header followed by raw AAC data blocks. There is no global file header; properties are derived from the per-frame ADTS headers.
The intrinsic properties (fields) of the ADTS header, which define the file's structure and audio characteristics, are as follows. These are bit-level fields, with the header totaling 56 bits (7 bytes) without CRC or 72 bits (9 bytes) with CRC. Fields marked as "fixed" are expected to remain constant across frames in a valid file, while "variable" fields may change.
- Syncword: 12 bits, fixed. Value: 0xFFF (all 1s). Serves as the frame synchronization marker.
- MPEG Version: 1 bit, fixed. Value: 0 for MPEG-4 AAC, 1 for MPEG-2 AAC.
- Layer: 2 bits, fixed. Value: Always 00 (reserved for compatibility).
- Protection Absence: 1 bit, fixed. Value: 1 if no CRC is present, 0 if CRC is included.
- Profile: 2 bits, fixed. Value: MPEG-4 Audio Object Type minus 1 (e.g., 00 for AAC Main, 01 for AAC LC).
- Sampling Frequency Index: 4 bits, fixed. Value: Index to sampling rate (e.g., 0x3 for 44.1 kHz, 0x4 for 48 kHz; 0xF forbidden).
- Private Bit: 1 bit, fixed. Value: Typically 0; ignored in decoding.
- Channel Configuration: 3 bits, fixed. Value: Number of channels (e.g., 1 for mono, 2 for stereo; 0 indicates in-band Program Config Element).
- Originality: 1 bit, fixed. Value: 1 if audio is original, 0 otherwise (often ignored).
- Home: 1 bit, fixed. Value: 1 for home use, 0 otherwise (often ignored).
- Copyright ID Bit: 1 bit, variable. Part of a copyright identifier stream.
- Copyright ID Start: 1 bit, variable. Value: 1 if this is the start of a new copyright ID, 0 otherwise.
- Frame Length: 13 bits, variable. Total length of the ADTS frame in bytes, including header and CRC (if present).
- Buffer Fullness: 11 bits, variable. Indicates bit reservoir state; 0x7FF signals variable bitrate (VBR).
- Number of AAC Frames (Raw Data Blocks): 2 bits, variable. Number of raw data blocks minus 1 (typically 0 for one block per frame).
- CRC Check: 16 bits, optional (present if Protection Absence is 0). Cyclic redundancy check for error detection.
These properties are intrinsic to the format's structure within the file system, enabling frame-by-frame parsing without additional metadata containers like MP4.
2. Two Direct Download Links for .AAC Files
- https://getsamplefiles.com/download/aac/sample-1.aac (Sample audio file, approximately 15 seconds, stereo, 44.1 kHz)
- https://getsamplefiles.com/download/aac/sample-2.aac (Sample audio file, approximately 30 seconds, stereo, 48 kHz)
3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .AAC File Analysis
The following is a self-contained HTML page with embedded JavaScript that allows users to drag and drop a .AAC file. Upon dropping, it parses the ADTS headers of all frames in the file and displays the properties listed in section 1 on the screen. It uses the File API and DataView for binary parsing. For display, it outputs properties in a structured text format within a <pre> element. Note: This is intended for browser use; large files may impact performance.
Drag and Drop .AAC File
4. Python Class for .AAC File Handling
The following Python class, AACFileHandler, can open a .AAC file, decode (parse) the ADTS headers, read and print the properties to the console, and write a new .AAC file (e.g., copying the original or modifying properties if needed). It uses struct for binary unpacking. For writing, it supports recreating the file from parsed frames. Usage example: handler = AACFileHandler('input.aac'); handler.print_properties(); handler.write('output.aac').
import struct
import os
class AACFileHandler:
def __init__(self, filepath):
self.filepath = filepath
self.frames = []
self._parse_file()
def _parse_file(self):
with open(self.filepath, 'rb') as f:
data = f.read()
offset = 0
while offset < len(data):
if len(data) - offset < 7 or struct.unpack_from('>H', data, offset)[0] & 0xFFF0 != 0xFFF0:
raise ValueError(f"Invalid syncword at offset {offset}")
# Unpack header (7 bytes minimum)
header = struct.unpack_from('>7B', data, offset)
mpeg_version = (header[1] >> 3) & 0x01
layer = (header[1] >> 1) & 0x03
protection_absent = header[1] & 0x01
profile = header[2] >> 6
sampling_freq_index = (header[2] >> 2) & 0x0F
private_bit = (header[2] >> 1) & 0x01
channel_config = ((header[2] & 0x01) << 2) | (header[3] >> 6)
originality = (header[3] >> 5) & 0x01
home = (header[3] >> 4) & 0x01
copyright_id_bit = (header[3] >> 3) & 0x01
copyright_id_start = (header[3] >> 2) & 0x01
frame_length = ((header[3] & 0x03) << 11) | (header[4] << 3) | (header[5] >> 5)
buffer_fullness = ((header[5] & 0x1F) << 6) | (header[6] >> 2)
num_aac_frames = (header[6] & 0x03) + 1
header_size = 7 if protection_absent else 9
crc = struct.unpack_from('>H', data, offset + 7)[0] if not protection_absent else None
if frame_length < header_size or offset + frame_length > len(data):
raise ValueError("Invalid frame length")
raw_data = data[offset + header_size:offset + frame_length]
self.frames.append({
'syncword': 0xFFF,
'mpeg_version': mpeg_version,
'layer': layer,
'protection_absent': protection_absent,
'profile': profile,
'sampling_freq_index': sampling_freq_index,
'private_bit': private_bit,
'channel_config': channel_config,
'originality': originality,
'home': home,
'copyright_id_bit': copyright_id_bit,
'copyright_id_start': copyright_id_start,
'frame_length': frame_length,
'buffer_fullness': buffer_fullness,
'num_aac_frames': num_aac_frames,
'crc': crc,
'raw_data': raw_data
})
offset += frame_length
def print_properties(self):
for i, frame in enumerate(self.frames, 1):
print(f"Frame {i}:")
for key, value in frame.items():
if key != 'raw_data':
print(f" {key}: {value}")
print()
def write(self, output_path):
with open(output_path, 'wb') as f:
for frame in self.frames:
header = bytearray(7)
header[0] = 0xFF
header[1] = 0xF0 | (frame['mpeg_version'] << 3) | (frame['layer'] << 1) | frame['protection_absent']
header[2] = (frame['profile'] << 6) | (frame['sampling_freq_index'] << 2) | (frame['private_bit'] << 1) | (frame['channel_config'] >> 2)
header[3] = (frame['channel_config'] << 6) | (frame['originality'] << 5) | (frame['home'] << 4) | (frame['copyright_id_bit'] << 3) | (frame['copyright_id_start'] << 2) | ((frame['frame_length'] >> 11) & 0x03)
header[4] = (frame['frame_length'] >> 3) & 0xFF
header[5] = ((frame['frame_length'] & 0x07) << 5) | ((frame['buffer_fullness'] >> 6) & 0x1F)
header[6] = ((frame['buffer_fullness'] & 0x3F) << 2) | (frame['num_aac_frames'] - 1)
f.write(header)
if not frame['protection_absent']:
f.write(struct.pack('>H', frame['crc']))
f.write(frame['raw_data'])
5. Java Class for .AAC File Handling
The following Java class, AACFileHandler, can open a .AAC file, decode the ADTS headers, read and print the properties to the console, and write a new .AAC file. It uses ByteBuffer for parsing. Usage example: AACFileHandler handler = new AACFileHandler("input.aac"); handler.printProperties(); handler.write("output.aac");.
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AACFileHandler {
private String filepath;
private List<Map<String, Object>> frames = new ArrayList<>();
public AACFileHandler(String filepath) throws IOException {
this.filepath = filepath;
parseFile();
}
private void parseFile() throws IOException {
byte[] data = Files.readAllBytes(new File(filepath).toPath());
ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
int offset = 0;
while (offset < data.length) {
if (buffer.getShort(offset) != (short) 0xFFF0) { // Mask for 12 bits
throw new IOException("Invalid syncword at offset " + offset);
}
Map<String, Object> frame = new HashMap<>();
int byte1 = Byte.toUnsignedInt(buffer.get(offset + 1));
int byte2 = Byte.toUnsignedInt(buffer.get(offset + 2));
int byte3 = Byte.toUnsignedInt(buffer.get(offset + 3));
int byte4 = Byte.toUnsignedInt(buffer.get(offset + 4));
int byte5 = Byte.toUnsignedInt(buffer.get(offset + 5));
int byte6 = Byte.toUnsignedInt(buffer.get(offset + 6));
frame.put("syncword", 0xFFF);
frame.put("mpeg_version", (byte1 >> 3) & 0x01);
frame.put("layer", (byte1 >> 1) & 0x03);
int protectionAbsent = byte1 & 0x01;
frame.put("protection_absent", protectionAbsent);
frame.put("profile", byte2 >> 6);
frame.put("sampling_freq_index", (byte2 >> 2) & 0x0F);
frame.put("private_bit", (byte2 >> 1) & 0x01);
frame.put("channel_config", ((byte2 & 0x01) << 2) | (byte3 >> 6));
frame.put("originality", (byte3 >> 5) & 0x01);
frame.put("home", (byte3 >> 4) & 0x01);
frame.put("copyright_id_bit", (byte3 >> 3) & 0x01);
frame.put("copyright_id_start", (byte3 >> 2) & 0x01);
int frameLength = ((byte3 & 0x03) << 11) | (byte4 << 3) | (byte5 >> 5);
frame.put("frame_length", frameLength);
frame.put("buffer_fullness", ((byte5 & 0x1F) << 6) | (byte6 >> 2));
frame.put("num_aac_frames", (byte6 & 0x03) + 1);
int headerSize = protectionAbsent == 1 ? 7 : 9;
Integer crc = protectionAbsent == 1 ? null : (int) buffer.getShort(offset + 7);
frame.put("crc", crc);
if (frameLength < headerSize || offset + frameLength > data.length) {
throw new IOException("Invalid frame length");
}
byte[] rawData = new byte[frameLength - headerSize];
System.arraycopy(data, offset + headerSize, rawData, 0, rawData.length);
frame.put("raw_data", rawData);
frames.add(frame);
offset += frameLength;
}
}
public void printProperties() {
for (int i = 0; i < frames.size(); i++) {
System.out.println("Frame " + (i + 1) + ":");
Map<String, Object> frame = frames.get(i);
for (Map.Entry<String, Object> entry : frame.entrySet()) {
if (!entry.getKey().equals("raw_data")) {
System.out.println(" " + entry.getKey() + ": " + entry.getValue());
}
}
System.out.println();
}
}
public void write(String outputPath) throws IOException {
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
for (Map<String, Object> frame : frames) {
ByteBuffer header = ByteBuffer.allocate(7).order(ByteOrder.BIG_ENDIAN);
header.put((byte) 0xFF);
int byte1 = ((Integer) frame.get("mpeg_version") << 3) | ((Integer) frame.get("layer") << 1) | (Integer) frame.get("protection_absent");
header.put((byte) byte1);
int byte2 = ((Integer) frame.get("profile") << 6) | ((Integer) frame.get("sampling_freq_index") << 2) | ((Integer) frame.get("private_bit") << 1) | ((Integer) frame.get("channel_config") >> 2);
header.put((byte) byte2);
int byte3 = (((Integer) frame.get("channel_config") & 0x03) << 6) | ((Integer) frame.get("originality") << 5) | ((Integer) frame.get("home") << 4) | ((Integer) frame.get("copyright_id_bit") << 3) | ((Integer) frame.get("copyright_id_start") << 2) | (((Integer) frame.get("frame_length") >> 11) & 0x03);
header.put((byte) byte3);
header.put((byte) (((Integer) frame.get("frame_length") >> 3) & 0xFF));
header.put((byte) ((((Integer) frame.get("frame_length") & 0x07) << 5) | (((Integer) frame.get("buffer_fullness") >> 6) & 0x1F)));
header.put((byte) ((((Integer) frame.get("buffer_fullness") & 0x3F) << 2) | ((Integer) frame.get("num_aac_frames") - 1)));
fos.write(header.array());
if ((Integer) frame.get("protection_absent") == 0) {
ByteBuffer crcBuf = ByteBuffer.allocate(2).order(ByteOrder.BIG_ENDIAN);
crcBuf.putShort(((Integer) frame.get("crc")).shortValue());
fos.write(crcBuf.array());
}
fos.write((byte[]) frame.get("raw_data"));
}
}
}
}
6. JavaScript Class for .AAC File Handling
The following JavaScript class, AACFileHandler, is designed for Node.js (requires fs module). It can open a .AAC file, decode the ADTS headers, read and print the properties to the console, and write a new .AAC file. Usage example: const handler = new AACFileHandler('input.aac'); handler.printProperties(); handler.write('output.aac');.
const fs = require('fs');
class AACFileHandler {
constructor(filepath) {
this.filepath = filepath;
this.frames = [];
this.parseFile();
}
parseFile() {
const data = fs.readFileSync(this.filepath);
const view = new DataView(data.buffer);
let offset = 0;
while (offset < data.length) {
if (view.getUint16(offset) !== 0xFFF0) {
throw new Error(`Invalid syncword at offset ${offset}`);
}
const frame = {};
const byte1 = view.getUint8(offset + 1);
const byte2 = view.getUint8(offset + 2);
const byte3 = view.getUint8(offset + 3);
const byte4 = view.getUint8(offset + 4);
const byte5 = view.getUint8(offset + 5);
const byte6 = view.getUint8(offset + 6);
frame.syncword = 0xFFF;
frame.mpeg_version = (byte1 >> 3) & 0x01;
frame.layer = (byte1 >> 1) & 0x03;
const protectionAbsent = byte1 & 0x01;
frame.protection_absent = protectionAbsent;
frame.profile = byte2 >> 6;
frame.sampling_freq_index = (byte2 >> 2) & 0x0F;
frame.private_bit = (byte2 >> 1) & 0x01;
frame.channel_config = ((byte2 & 0x01) << 2) | (byte3 >> 6);
frame.originality = (byte3 >> 5) & 0x01;
frame.home = (byte3 >> 4) & 0x01;
frame.copyright_id_bit = (byte3 >> 3) & 0x01;
frame.copyright_id_start = (byte3 >> 2) & 0x01;
const frameLength = ((byte3 & 0x03) << 11) | (byte4 << 3) | (byte5 >> 5);
frame.frame_length = frameLength;
frame.buffer_fullness = ((byte5 & 0x1F) << 6) | (byte6 >> 2);
frame.num_aac_frames = (byte6 & 0x03) + 1;
const headerSize = protectionAbsent ? 7 : 9;
frame.crc = protectionAbsent ? null : view.getUint16(offset + 7);
if (frameLength < headerSize || offset + frameLength > data.length) {
throw new Error('Invalid frame length');
}
frame.raw_data = data.slice(offset + headerSize, offset + frameLength);
this.frames.push(frame);
offset += frameLength;
}
}
printProperties() {
this.frames.forEach((frame, index) => {
console.log(`Frame ${index + 1}:`);
for (const [key, value] of Object.entries(frame)) {
if (key !== 'raw_data') {
console.log(` ${key}: ${value}`);
}
}
console.log();
});
}
write(outputPath) {
const output = [];
this.frames.forEach(frame => {
const header = new Uint8Array(7);
header[0] = 0xFF;
header[1] = 0xF0 | (frame.mpeg_version << 3) | (frame.layer << 1) | frame.protection_absent;
header[2] = (frame.profile << 6) | (frame.sampling_freq_index << 2) | (frame.private_bit << 1) | (frame.channel_config >> 2);
header[3] = (frame.channel_config << 6) | (frame.originality << 5) | (frame.home << 4) | (frame.copyright_id_bit << 3) | (frame.copyright_id_start << 2) | ((frame.frame_length >> 11) & 0x03);
header[4] = (frame.frame_length >> 3) & 0xFF;
header[5] = ((frame.frame_length & 0x07) << 5) | ((frame.buffer_fullness >> 6) & 0x1F);
header[6] = ((frame.buffer_fullness & 0x3F) << 2) | (frame.num_aac_frames - 1);
output.push(header);
if (!frame.protection_absent) {
const crcBuf = new Uint8Array(2);
const crcView = new DataView(crcBuf.buffer);
crcView.setUint16(0, frame.crc);
output.push(crcBuf);
}
output.push(frame.raw_data);
});
fs.writeFileSync(outputPath, Buffer.concat(output));
}
}
7. C++ Class for .AAC File Handling
The following C++ class, AACFileHandler, can open a .AAC file, decode the ADTS headers, read and print the properties to the console, and write a new .AAC file. It uses standard file I/O and bit manipulation. Compile with g++ -std=c++11. Usage example: AACFileHandler handler("input.aac"); handler.printProperties(); handler.write("output.aac");.
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <cstdint>
#include <stdexcept>
#include <cstring>
class AACFileHandler {
private:
std::string filepath;
struct Frame {
uint16_t syncword = 0xFFF;
uint8_t mpeg_version;
uint8_t layer;
uint8_t protection_absent;
uint8_t profile;
uint8_t sampling_freq_index;
uint8_t private_bit;
uint8_t channel_config;
uint8_t originality;
uint8_t home;
uint8_t copyright_id_bit;
uint8_t copyright_id_start;
uint16_t frame_length;
uint16_t buffer_fullness;
uint8_t num_aac_frames;
uint16_t crc; // Only if protection_absent == 0
std::vector<uint8_t> raw_data;
};
std::vector<Frame> frames;
void parseFile() {
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) throw std::runtime_error("Cannot open file");
size_t size = file.tellg();
file.seekg(0);
std::vector<uint8_t> data(size);
file.read(reinterpret_cast<char*>(data.data()), size);
size_t offset = 0;
while (offset < size) {
if (offset + 7 > size || (data[offset] != 0xFF || (data[offset + 1] & 0xF0) != 0xF0)) {
throw std::runtime_error("Invalid syncword at offset " + std::to_string(offset));
}
Frame frame;
uint8_t byte1 = data[offset + 1];
uint8_t byte2 = data[offset + 2];
uint8_t byte3 = data[offset + 3];
uint8_t byte4 = data[offset + 4];
uint8_t byte5 = data[offset + 5];
uint8_t byte6 = data[offset + 6];
frame.mpeg_version = (byte1 >> 3) & 0x01;
frame.layer = (byte1 >> 1) & 0x03;
frame.protection_absent = byte1 & 0x01;
frame.profile = byte2 >> 6;
frame.sampling_freq_index = (byte2 >> 2) & 0x0F;
frame.private_bit = (byte2 >> 1) & 0x01;
frame.channel_config = ((byte2 & 0x01) << 2) | (byte3 >> 6);
frame.originality = (byte3 >> 5) & 0x01;
frame.home = (byte3 >> 4) & 0x01;
frame.copyright_id_bit = (byte3 >> 3) & 0x01;
frame.copyright_id_start = (byte3 >> 2) & 0x01;
frame.frame_length = ((byte3 & 0x03) << 11) | (byte4 << 3) | (byte5 >> 5);
frame.buffer_fullness = ((byte5 & 0x1F) << 6) | (byte6 >> 2);
frame.num_aac_frames = (byte6 & 0x03) + 1;
size_t header_size = frame.protection_absent ? 7 : 9;
if (frame.protection_absent == 0) {
frame.crc = (data[offset + 7] << 8) | data[offset + 8];
}
if (frame.frame_length < header_size || offset + frame.frame_length > size) {
throw std::runtime_error("Invalid frame length");
}
frame.raw_data.assign(data.begin() + offset + header_size, data.begin() + offset + frame.frame_length);
frames.push_back(frame);
offset += frame.frame_length;
}
}
public:
AACFileHandler(const std::string& filepath) : filepath(filepath) {
parseFile();
}
void printProperties() const {
for (size_t i = 0; i < frames.size(); ++i) {
const auto& frame = frames[i];
std::cout << "Frame " << (i + 1) << ":" << std::endl;
std::cout << " syncword: " << frame.syncword << std::endl;
std::cout << " mpeg_version: " << static_cast<int>(frame.mpeg_version) << std::endl;
std::cout << " layer: " << static_cast<int>(frame.layer) << std::endl;
std::cout << " protection_absent: " << static_cast<int>(frame.protection_absent) << std::endl;
std::cout << " profile: " << static_cast<int>(frame.profile) << std::endl;
std::cout << " sampling_freq_index: " << static_cast<int>(frame.sampling_freq_index) << std::endl;
std::cout << " private_bit: " << static_cast<int>(frame.private_bit) << std::endl;
std::cout << " channel_config: " << static_cast<int>(frame.channel_config) << std::endl;
std::cout << " originality: " << static_cast<int>(frame.originality) << std::endl;
std::cout << " home: " << static_cast<int>(frame.home) << std::endl;
std::cout << " copyright_id_bit: " << static_cast<int>(frame.copyright_id_bit) << std::endl;
std::cout << " copyright_id_start: " << static_cast<int>(frame.copyright_id_start) << std::endl;
std::cout << " frame_length: " << frame.frame_length << std::endl;
std::cout << " buffer_fullness: " << frame.buffer_fullness << std::endl;
std::cout << " num_aac_frames: " << static_cast<int>(frame.num_aac_frames) << std::endl;
std::cout << " crc: " << (frame.protection_absent ? "N/A" : std::to_string(frame.crc)) << std::endl;
std::cout << std::endl;
}
}
void write(const std::string& outputPath) const {
std::ofstream file(outputPath, std::ios::binary);
if (!file) throw std::runtime_error("Cannot open output file");
for (const auto& frame : frames) {
uint8_t header[7];
header[0] = 0xFF;
header[1] = 0xF0 | (frame.mpeg_version << 3) | (frame.layer << 1) | frame.protection_absent;
header[2] = (frame.profile << 6) | (frame.sampling_freq_index << 2) | (frame.private_bit << 1) | (frame.channel_config >> 2);
header[3] = (frame.channel_config << 6) | (frame.originality << 5) | (frame.home << 4) | (frame.copyright_id_bit << 3) | (frame.copyright_id_start << 2) | ((frame.frame_length >> 11) & 0x03);
header[4] = (frame.frame_length >> 3) & 0xFF;
header[5] = ((frame.frame_length & 0x07) << 5) | ((frame.buffer_fullness >> 6) & 0x1F);
header[6] = ((frame.buffer_fullness & 0x3F) << 2) | (frame.num_aac_frames - 1);
file.write(reinterpret_cast<const char*>(header), 7);
if (frame.protection_absent == 0) {
uint8_t crc_bytes[2] = {(frame.crc >> 8) & 0xFF, frame.crc & 0xFF};
file.write(reinterpret_cast<const char*>(crc_bytes), 2);
}
file.write(reinterpret_cast<const char*>(frame.raw_data.data()), frame.raw_data.size());
}
}
};