Test 010: .AAC File Format
Test 010: .AAC File Format
1. Properties of the .AAC File Format Intrinsic to Its File System
The Advanced Audio Coding (AAC) file format is a lossy audio compression format defined in MPEG-2 Part 7 (ISO/IEC 13818-7) and MPEG-4 Part 3 (ISO/IEC 14496-3). AAC data is typically encapsulated in container formats like ADTS (Audio Data Transport Stream) or ADIF (Audio Data Interchange Format) for .AAC files, or MP4 containers for .m4a files. Below is a list of intrinsic properties of the .AAC file format, focusing on ADTS and ADIF containers, as these are commonly associated with the .AAC extension:
- File Extension: .aac
- Media Type: audio/aac, audio/aacp
- Container Format: ADTS (Audio Data Transport Stream) or ADIF (Audio Data Interchange Format)
- Compression Type: Lossy perceptual audio compression
- Sample Rate: Supports a wide range (8 kHz to 96 kHz)
- Channels: Up to 48 audio channels
- Bit Rate: Variable (VBR) or constant (CBR), typically 8–320 kbps
- Frame Size: Each frame decodes to 1024 time-domain samples (fixed block size)
- Profile: AAC-LC (Low Complexity), Main, SSR (Scalable Sample Rate), or others like HE-AAC
- Header Structure:
- ADTS: Contains a 7-byte or 9-byte header per frame, including syncword, profile, sample rate, and channel configuration
- ADIF: Single header at the file’s start, containing metadata like profile, sample rate, and bitrate
- Syncword: 12-bit sequence (0xFFF) in ADTS headers for frame synchronization
- Bitstream Encoding: Binary data, not further compressible with lossless methods (e.g., zip)
- Metadata Support: Limited in ADTS/ADIF compared to MP4; includes basic tags like profile and channel info
- Frame Independence: Each frame is independently decodable (no inter-frame dependencies in most profiles)
- File Size: Smaller than MP3 at similar quality due to efficient compression
Sources: Information compiled from web sources, including Wikipedia (), MultimediaWiki (), and Library of Congress (,,).
Notes:
- The full specifications (ISO/IEC 13818-7 and 14496-3) are not freely available, limiting detailed header parsing in the code below.
- The following implementations focus on reading ADTS headers, as they are more common for .AAC files and feasible to parse without proprietary specs.
- Writing functionality is limited to copying or re-encoding existing AAC data, as full encoding requires proprietary libraries or complex codec implementations.
2. Python Class for .AAC File Handling
Below is a Python class that opens, reads, and decodes an .AAC file (ADTS format), extracts properties, and prints them to the console. Writing is implemented as copying the file to preserve the AAC bitstream.
import os
import struct
class AACFile:
def __init__(self, filepath):
self.filepath = filepath
self.properties = {
'file_extension': '.aac',
'media_type': 'audio/aac',
'container_format': 'ADTS', # Assuming ADTS for .aac
'sample_rate': None,
'channels': None,
'bit_rate': None,
'profile': None,
'frame_size': 1024, # Fixed for AAC
'syncword': None
}
def _parse_adts_header(self, header):
"""Parse ADTS header (7 bytes) to extract properties."""
if len(header) < 7:
return False
syncword = (header[0] << 4) | (header[1] >> 4)
if syncword != 0xFFF:
return False
profile = (header[1] & 0xC0) >> 6 # 2 bits
sample_rate_idx = (header[2] & 0x3C) >> 2 # 4 bits
channels_idx = ((header[2] & 0x01) << 2) | ((header[3] & 0xC0) >> 6) # 3 bits
frame_length = ((header[3] & 0x03) << 11) | (header[4] << 3) | ((header[5] & 0xE0) >> 5) # 13 bits
# Map indices to actual values
sample_rates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000]
channels_map = [0, 1, 2, 3, 4, 5, 6, 7, 8]
profiles = ['Main', 'LC', 'SSR', 'Reserved']
self.properties['syncword'] = syncword
self.properties['profile'] = profiles[profile] if profile < len(profiles) else 'Unknown'
self.properties['sample_rate'] = sample_rates[sample_rate_idx] if sample_rate_idx < len(sample_rates) else None
self.properties['channels'] = channels_map[channels_idx] if channels_idx < len(channels_map) else None
self.properties['bit_rate'] = (frame_length * 8 * self.properties['sample_rate']) // 1024 # Approximate
return True
def read(self):
"""Read and decode AAC file properties."""
try:
with open(self.filepath, 'rb') as f:
header = f.read(7)
if self._parse_adts_header(header):
print("Successfully parsed AAC file properties.")
else:
print("Invalid ADTS header or not an AAC file.")
except FileNotFoundError:
print(f"File {self.filepath} not found.")
except Exception as e:
print(f"Error reading AAC file: {e}")
def write(self, output_path):
"""Write (copy) the AAC file to a new location."""
try:
with open(self.filepath, 'rb') as f_in, open(output_path, 'wb') as f_out:
f_out.write(f_in.read())
print(f"AAC file written to {output_path}")
except Exception as e:
print(f"Error writing AAC file: {e}")
def print_properties(self):
"""Print all properties to console."""
print("AAC File Properties:")
for key, value in self.properties.items():
print(f"{key.replace('_', ' ').title()}: {value}")
# Example usage
if __name__ == "__main__":
aac = AACFile("sample.aac")
aac.read()
aac.print_properties()
aac.write("output.aac")
Notes:
- Requires an ADTS .aac file for testing.
- Uses
struct
for binary parsing of the ADTS header. - Bit rate is approximated based on frame length and sample rate.
- Writing is a simple file copy to avoid complex encoding.
3. Java Class for .AAC File Handling
import java.io.*;
public class AACFile {
private String filepath;
private String fileExtension = ".aac";
private String mediaType = "audio/aac";
private String containerFormat = "ADTS";
private Integer sampleRate;
private Integer channels;
private Integer bitRate;
private String profile;
private final int frameSize = 1024;
private Integer syncword;
public AACFile(String filepath) {
this.filepath = filepath;
}
private boolean parseADTSHeader(byte[] header) {
if (header.length < 7) return false;
syncword = ((header[0] & 0xFF) << 4) | ((header[1] & 0xF0) >> 4);
if (syncword != 0xFFF) return false;
int profileIdx = (header[1] & 0xC0) >> 6;
int sampleRateIdx = (header[2] & 0x3C) >> 2;
int channelsIdx = ((header[2] & 0x01) << 2) | ((header[3] & 0xC0) >> 6);
int frameLength = ((header[3] & 0x03) << 11) | ((header[4] & 0xFF) << 3) | ((header[5] & 0xE0) >> 5);
int[] sampleRates = {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000};
int[] channelsMap = {0, 1, 2, 3, 4, 5, 6, 7, 8};
String[] profiles = {"Main", "LC", "SSR", "Reserved"};
this.profile = profileIdx < profiles.length ? profiles[profileIdx] : "Unknown";
this.sampleRate = sampleRateIdx < sampleRates.length ? sampleRates[sampleRateIdx] : null;
this.channels = channelsIdx < channelsMap.length ? channelsMap[channelsIdx] : null;
this.bitRate = this.sampleRate != null ? (frameLength * 8 * this.sampleRate) / 1024 : null;
return true;
}
public void read() {
try (FileInputStream fis = new FileInputStream(filepath)) {
byte[] header = new byte[7];
if (fis.read(header) == 7 && parseADTSHeader(header)) {
System.out.println("Successfully parsed AAC file properties.");
} else {
System.out.println("Invalid ADTS header or not an AAC file.");
}
} catch (FileNotFoundException e) {
System.out.println("File " + filepath + " not found.");
} catch (IOException e) {
System.out.println("Error reading AAC file: " + e.getMessage());
}
}
public void write(String outputPath) {
try (FileInputStream fis = new FileInputStream(filepath);
FileOutputStream fos = new FileOutputStream(outputPath)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
System.out.println("AAC file written to " + outputPath);
} catch (IOException e) {
System.out.println("Error writing AAC file: " + e.getMessage());
}
}
public void printProperties() {
System.out.println("AAC File Properties:");
System.out.println("File Extension: " + fileExtension);
System.out.println("Media Type: " + mediaType);
System.out.println("Container Format: " + containerFormat);
System.out.println("Sample Rate: " + sampleRate);
System.out.println("Channels: " + channels);
System.out.println("Bit Rate: " + bitRate);
System.out.println("Profile: " + profile);
System.out.println("Frame Size: " + frameSize);
System.out.println("Syncword: " + syncword);
}
public static void main(String[] args) {
AACFile aac = new AACFile("sample.aac");
aac.read();
aac.printProperties();
aac.write("output.aac");
}
}
Notes:
- Parses ADTS headers similarly to the Python implementation.
- Writing is implemented as a file copy.
- Requires a valid .aac file for testing.
4. JavaScript Class for .AAC File Handling
const fs = require('fs').promises;
class AACFile {
constructor(filepath) {
this.filepath = filepath;
this.properties = {
fileExtension: '.aac',
mediaType: 'audio/aac',
containerFormat: 'ADTS',
sampleRate: null,
channels: null,
bitRate: null,
profile: null,
frameSize: 1024,
syncword: null
};
}
async parseADTSHeader(header) {
if (header.length < 7) return false;
const syncword = (header[0] << 4) | (header[1] >> 4);
if (syncword !== 0xFFF) return false;
const profileIdx = (header[1] & 0xC0) >> 6;
const sampleRateIdx = (header[2] & 0x3C) >> 2;
const channelsIdx = ((header[2] & 0x01) << 2) | ((header[3] & 0xC0) >> 6);
const frameLength = ((header[3] & 0x03) << 11) | (header[4] << 3) | ((header[5] & 0xE0) >> 5);
const sampleRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000];
const channelsMap = [0, 1, 2, 3, 4, 5, 6, 7, 8];
const profiles = ['Main', 'LC', 'SSR', 'Reserved'];
this.properties.syncword = syncword;
this.properties.profile = profileIdx < profiles.length ? profiles[profileIdx] : 'Unknown';
this.properties.sampleRate = sampleRateIdx < sampleRates.length ? sampleRates[sampleRateIdx] : null;
this.properties.channels = channelsIdx < channelsMap.length ? channelsMap[channelsIdx] : null;
this.properties.bitRate = this.properties.sampleRate ? Math.floor((frameLength * 8 * this.properties.sampleRate) / 1024) : null;
return true;
}
async read() {
try {
const buffer = await fs.readFile(this.filepath);
const header = buffer.slice(0, 7);
if (await this.parseADTSHeader(header)) {
console.log('Successfully parsed AAC file properties.');
} else {
console.log('Invalid ADTS header or not an AAC file.');
}
} catch (error) {
if (error.code === 'ENOENT') {
console.log(`File ${this.filepath} not found.`);
} else {
console.log(`Error reading AAC file: ${error.message}`);
}
}
}
async write(outputPath) {
try {
const data = await fs.readFile(this.filepath);
await fs.writeFile(outputPath, data);
console.log(`AAC file written to ${outputPath}`);
} catch (error) {
console.log(`Error writing AAC file: ${error.message}`);
}
}
printProperties() {
console.log('AAC File Properties:');
for (const [key, value] of Object.entries(this.properties)) {
console.log(`${key.replace(/([A-Z])/g, ' $1').trim()}: ${value}`);
}
}
}
// Example usage
(async () => {
const aac = new AACFile('sample.aac');
await aac.read();
aac.printProperties();
await aac.write('output.aac');
})();
Notes:
- Uses Node.js
fs.promises
for asynchronous file operations. - ADTS header parsing mirrors Python and Java implementations.
- Writing is a file copy operation.
- Run in a Node.js environment with a valid .aac file.
5. C Class for .AAC File Handling
C does not have classes, so we use a struct and functions to achieve similar functionality.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
typedef struct {
const char *filepath;
const char *file_extension;
const char *media_type;
const char *container_format;
uint32_t sample_rate;
uint32_t channels;
uint32_t bit_rate;
const char *profile;
const int frame_size;
uint32_t syncword;
} AACFile;
void init_aac_file(AACFile *aac, const char *filepath) {
aac->filepath = filepath;
aac->file_extension = ".aac";
aac->media_type = "audio/aac";
aac->container_format = "ADTS";
aac->sample_rate = 0;
aac->channels = 0;
aac->bit_rate = 0;
aac->profile = NULL;
aac->frame_size = 1024;
aac->syncword = 0;
}
int parse_adts_header(AACFile *aac, uint8_t *header) {
if (!header) return 0;
aac->syncword = ((header[0] << 4) | (header[1] >> 4));
if (aac->syncword != 0xFFF) return 0;
int profile_idx = (header[1] & 0xC0) >> 6;
int sample_rate_idx = (header[2] & 0x3C) >> 2;
int channels_idx = ((header[2] & 0x01) << 2) | ((header[3] & 0xC0) >> 6);
int frame_length = ((header[3] & 0x03) << 11) | (header[4] << 3) | ((header[5] & 0xE0) >> 5);
uint32_t sample_rates[] = {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000};
uint32_t channels_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
const char *profiles[] = {"Main", "LC", "SSR", "Reserved"};
aac->profile = profile_idx < 4 ? profiles[profile_idx] : "Unknown";
aac->sample_rate = sample_rate_idx < 12 ? sample_rates[sample_rate_idx] : 0;
aac->channels = channels_idx < 9 ? channels_map[channels_idx] : 0;
aac->bit_rate = aac->sample_rate ? (frame_length * 8 * aac->sample_rate) / 1024 : 0;
return 1;
}
void read_aac_file(AACFile *aac) {
FILE *file = fopen(aac->filepath, "rb");
if (!file) {
printf("File %s not found.\n", aac->filepath);
return;
}
uint8_t header[7];
if (fread(header, 1, 7, file) == 7 && parse_adts_header(aac, header)) {
printf("Successfully parsed AAC file properties.\n");
} else {
printf("Invalid ADTS header or not an AAC file.\n");
}
fclose(file);
}
void write_aac_file(AACFile *aac, const char *output_path) {
FILE *in_file = fopen(aac->filepath, "rb");
FILE *out_file = fopen(output_path, "wb");
if (!in_file || !out_file) {
printf("Error opening files for writing.\n");
if (in_file) fclose(in_file);
if (out_file) fclose(out_file);
return;
}
uint8_t buffer[1024];
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, 1024, in_file)) > 0) {
fwrite(buffer, 1, bytes_read, out_file);
}
printf("AAC file written to %s\n", output_path);
fclose(in_file);
fclose(out_file);
}
void print_properties(AACFile *aac) {
printf("AAC File Properties:\n");
printf("File Extension: %s\n", aac->file_extension);
printf("Media Type: %s\n", aac->media_type);
printf("Container Format: %s\n", aac->container_format);
printf("Sample Rate: %u\n", aac->sample_rate);
printf("Channels: %u\n", aac->channels);
printf("Bit Rate: %u\n", aac->bit_rate);
printf("Profile: %s\n", aac->profile ? aac->profile : "Unknown");
printf("Frame Size: %d\n", aac->frame_size);
printf("Syncword: %u\n", aac->syncword);
}
int main() {
AACFile aac;
init_aac_file(&aac, "sample.aac");
read_aac_file(&aac);
print_properties(&aac);
write_aac_file(&aac, "output.aac");
return 0;
}
Notes:
- Uses standard C I/O for file operations.
- ADTS header parsing is consistent with other implementations.
- Writing is a file copy operation.
- Compile with a C compiler (e.g.,
gcc
) and test with a valid .aac file.
General Notes
- Decoding Limitation: Full decoding to raw audio requires libraries like
libfaad
orffmpeg
, as the AAC codec is complex and proprietary. The above implementations parse the ADTS header to extract metadata but do not decode audio samples. - Writing Limitation: Writing is implemented as a file copy to preserve the AAC bitstream, as encoding new AAC data requires proprietary tools or libraries.
- Testing: You’ll need a valid .aac file (ADTS format) for testing. Sample files can be found at standards.iso.org ().
- ADTS vs. ADIF: The code assumes ADTS, as it’s more common for .aac files. ADIF parsing would require a different header structure (single header at file start).
- Dependencies: The Python, Java, and C implementations have no external dependencies. The JavaScript implementation requires Node.js.
If you need further assistance or want to extend the code (e.g., to handle ADIF or decode audio), please let me know!
1. List of Properties Intrinsic to the .AAC File Format
The .AAC file format uses the ADTS (Audio Data Transport Stream) container for AAC audio data. It consists of a concatenation of ADTS frames, with no global file header or footer. Each frame has its own header (7 or 9 bytes) followed by raw AAC data. The properties are per-frame and include the following fields from the ADTS header (bit sizes and meanings provided for clarity). These are the core structural elements that define the format:
- Syncword (12 bits): Fixed value 0xFFF for frame synchronization.
- ID (1 bit): MPEG identifier (0 for MPEG-4, 1 for MPEG-2).
- Layer (2 bits): Always 0.
- Protection absent (1 bit): 1 if no CRC, 0 if CRC is present.
- Profile (2 bits): AAC profile (MPEG-4 Audio Object Type minus 1).
- Sampling frequency index (4 bits): Index to sampling rate (e.g., 3 = 48000 Hz).
- Private bit (1 bit): Unused by MPEG; typically ignored.
- Channel configuration (3 bits): Number of audio channels (0 indicates in-band configuration).
- Original/copy (1 bit): 1 if original audio, 0 otherwise.
- Home (1 bit): 1 for home use, 0 otherwise.
- Copyright identification bit (1 bit): Part of a copyright identifier stream.
- Copyright identification start (1 bit): 1 if this is the start of a copyright identifier.
- AAC frame length (13 bits): Total length of the frame (header + CRC if present + raw data).
- ADTS buffer fullness (11 bits): Bit reservoir state (0x7FF for variable bitrate).
- Number of raw data blocks in frame (2 bits): Number of AAC data blocks minus 1 (typically 0 for 1 block).
- CRC (16 bits): Cyclic redundancy check for error detection (present only if protection absent == 0).
Note: For simplicity, the implementations below assume 1 raw data block per frame (number of raw data blocks in frame == 0), as recommended for maximum compatibility. Files with multiple blocks per frame would require AAC bitstream parsing to separate blocks, which is beyond the scope here.
2. Python Class
import os
class AACFile:
def __init__(self, filepath):
self.filepath = filepath
self.frames = [] # List of dicts, each with properties and raw_data
self.load()
def load(self):
with open(self.filepath, 'rb') as f:
data = f.read()
offset = 0
while offset < len(data):
if len(data) - offset < 7:
break
header = data[offset:offset + 9]
# Check syncword
if (header[0] != 0xFF) or ((header[1] & 0xF0) != 0xF0):
offset += 1
continue
# Extract fields
id_bit = (header[1] >> 3) & 0x01
layer = (header[1] >> 1) & 0x03
protection_absent = header[1] & 0x01
profile = (header[2] >> 6) & 0x03
sampling_freq_index = (header[2] >> 2) & 0x0F
private_bit = (header[2] >> 1) & 0x01
channel_config = ((header[2] & 0x01) << 2) | ((header[3] >> 6) & 0x03)
original_copy = (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) & 0x07)
buffer_fullness = ((header[5] & 0x1F) << 6) | ((header[6] >> 2) & 0x3F)
num_raw_blocks = (header[6] & 0x03) + 1
if num_raw_blocks != 1:
raise ValueError("Only single raw data block per frame supported")
header_size = 7 if protection_absent else 9
crc = None if protection_absent else ((header[7] << 8) | header[8])
if offset + frame_length > len(data):
break
raw_data = data[offset + header_size: offset + frame_length]
frame_props = {
'syncword': 0xFFF,
'id': id_bit,
'layer': layer,
'protection_absent': protection_absent,
'profile': profile,
'sampling_frequency_index': sampling_freq_index,
'private_bit': private_bit,
'channel_configuration': channel_config,
'original_copy': original_copy,
'home': home,
'copyright_identification_bit': copyright_id_bit,
'copyright_identification_start': copyright_id_start,
'aac_frame_length': frame_length,
'adts_buffer_fullness': buffer_fullness,
'number_of_raw_data_blocks_in_frame': num_raw_blocks - 1,
'crc': crc
}
self.frames.append({'properties': frame_props, 'raw_data': raw_data})
offset += frame_length
def write(self, output_path=None):
if output_path is None:
output_path = self.filepath
with open(output_path, 'wb') as f:
for frame in self.frames:
props = frame['properties']
h0 = 0xFF
h1 = 0xF0 | (props['id'] << 3) | (props['layer'] << 1) | props['protection_absent']
h2 = (props['profile'] << 6) | (props['sampling_frequency_index'] << 2) | (props['private_bit'] << 1) | (props['channel_configuration'] >> 2)
h3 = ((props['channel_configuration'] & 0x03) << 6) | (props['original_copy'] << 5) | (props['home'] << 4) | (props['copyright_identification_bit'] << 3) | (props['copyright_identification_start'] << 2) | ((props['aac_frame_length'] >> 11) & 0x03)
h4 = (props['aac_frame_length'] >> 3) & 0xFF
h5 = ((props['aac_frame_length'] & 0x07) << 5) | ((props['adts_buffer_fullness'] >> 6) & 0x1F)
h6 = ((props['adts_buffer_fullness'] & 0x3F) << 2) | props['number_of_raw_data_blocks_in_frame']
header = bytes([h0, h1, h2, h3, h4, h5, h6])
if not props['protection_absent']:
crc_high = (props['crc'] >> 8) & 0xFF
crc_low = props['crc'] & 0xFF
header += bytes([crc_high, crc_low])
f.write(header)
f.write(frame['raw_data'])
3. Java Class
import java.io.*;
import java.nio.file.*;
import java.util.*;
public class AACFile {
private String filepath;
private List<Map<String, Object>> frames = new ArrayList<>(); // Each map has properties and byte[] raw_data
public AACFile(String filepath) {
this.filepath = filepath;
load();
}
private void load() {
try {
byte[] data = Files.readAllBytes(Paths.get(filepath));
int offset = 0;
while (offset < data.length) {
if (data.length - offset < 7) break;
byte[] header = Arrays.copyOfRange(data, offset, offset + 9);
// Check syncword
if ((header[0] & 0xFF) != 0xFF || (header[1] & 0xF0) != 0xF0) {
offset++;
continue;
}
int id = (header[1] >> 3) & 0x01;
int layer = (header[1] >> 1) & 0x03;
int protectionAbsent = header[1] & 0x01;
int profile = (header[2] >> 6) & 0x03;
int samplingFreqIndex = (header[2] >> 2) & 0x0F;
int privateBit = (header[2] >> 1) & 0x01;
int channelConfig = ((header[2] & 0x01) << 2) | ((header[3] >> 6) & 0x03);
int originalCopy = (header[3] >> 5) & 0x01;
int home = (header[3] >> 4) & 0x01;
int copyrightIdBit = (header[3] >> 3) & 0x01;
int copyrightIdStart = (header[3] >> 2) & 0x01;
int frameLength = ((header[3] & 0x03) << 11) | ((header[4] & 0xFF) << 3) | ((header[5] >> 5) & 0x07);
int bufferFullness = ((header[5] & 0x1F) << 6) | ((header[6] >> 2) & 0x3F);
int numRawBlocks = (header[6] & 0x03) + 1;
if (numRawBlocks != 1) {
throw new IllegalArgumentException("Only single raw data block per frame supported");
}
int headerSize = protectionAbsent == 1 ? 7 : 9;
Integer crc = protectionAbsent == 1 ? null : (((header[7] & 0xFF) << 8) | (header[8] & 0xFF));
if (offset + frameLength > data.length) break;
byte[] rawData = Arrays.copyOfRange(data, offset + headerSize, offset + frameLength);
Map<String, Object> frameProps = new HashMap<>();
frameProps.put("syncword", 0xFFF);
frameProps.put("id", id);
frameProps.put("layer", layer);
frameProps.put("protection_absent", protectionAbsent);
frameProps.put("profile", profile);
frameProps.put("sampling_frequency_index", samplingFreqIndex);
frameProps.put("private_bit", privateBit);
frameProps.put("channel_configuration", channelConfig);
frameProps.put("original_copy", originalCopy);
frameProps.put("home", home);
frameProps.put("copyright_identification_bit", copyrightIdBit);
frameProps.put("copyright_identification_start", copyrightIdStart);
frameProps.put("aac_frame_length", frameLength);
frameProps.put("adts_buffer_fullness", bufferFullness);
frameProps.put("number_of_raw_data_blocks_in_frame", numRawBlocks - 1);
frameProps.put("crc", crc);
Map<String, Object> frame = new HashMap<>();
frame.put("properties", frameProps);
frame.put("raw_data", rawData);
frames.add(frame);
offset += frameLength;
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void write(String outputPath) throws IOException {
if (outputPath == null) outputPath = filepath;
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
for (Map<String, Object> frame : frames) {
@SuppressWarnings("unchecked")
Map<String, Integer> props = (Map<String, Integer>) frame.get("properties");
int h0 = 0xFF;
int h1 = 0xF0 | (props.get("id") << 3) | (props.get("layer") << 1) | props.get("protection_absent");
int h2 = (props.get("profile") << 6) | (props.get("sampling_frequency_index") << 2) | (props.get("private_bit") << 1) | (props.get("channel_configuration") >> 2);
int h3 = ((props.get("channel_configuration") & 0x03) << 6) | (props.get("original_copy") << 5) | (props.get("home") << 4) | (props.get("copyright_identification_bit") << 3) | (props.get("copyright_identification_start") << 2) | ((props.get("aac_frame_length") >> 11) & 0x03);
int h4 = (props.get("aac_frame_length") >> 3) & 0xFF;
int h5 = ((props.get("aac_frame_length") & 0x07) << 5) | ((props.get("adts_buffer_fullness") >> 6) & 0x1F);
int h6 = ((props.get("adts_buffer_fullness") & 0x3F) << 2) | props.get("number_of_raw_data_blocks_in_frame");
byte[] header = new byte[] {(byte) h0, (byte) h1, (byte) h2, (byte) h3, (byte) h4, (byte) h5, (byte) h6};
if (props.get("protection_absent") == 0) {
int crcVal = props.get("crc");
byte[] crcBytes = new byte[] {(byte) ((crcVal >> 8) & 0xFF), (byte) (crcVal & 0xFF)};
byte[] fullHeader = new byte[header.length + crcBytes.length];
System.arraycopy(header, 0, fullHeader, 0, header.length);
System.arraycopy(crcBytes, 0, fullHeader, header.length, crcBytes.length);
fos.write(fullHeader);
} else {
fos.write(header);
}
fos.write((byte[]) frame.get("raw_data"));
}
}
}
}
4. JavaScript Class
const fs = require('fs');
class AACFile {
constructor(filepath) {
this.filepath = filepath;
this.frames = []; // Array of objects, each with properties and raw_data Buffer
this.load();
}
load() {
const data = fs.readFileSync(this.filepath);
let offset = 0;
while (offset < data.length) {
if (data.length - offset < 7) break;
const header = data.subarray(offset, offset + 9);
// Check syncword
if (header[0] !== 0xFF || (header[1] & 0xF0) !== 0xF0) {
offset++;
continue;
}
const id = (header[1] >> 3) & 0x01;
const layer = (header[1] >> 1) & 0x03;
const protectionAbsent = header[1] & 0x01;
const profile = (header[2] >> 6) & 0x03;
const samplingFreqIndex = (header[2] >> 2) & 0x0F;
const privateBit = (header[2] >> 1) & 0x01;
const channelConfig = ((header[2] & 0x01) << 2) | ((header[3] >> 6) & 0x03);
const originalCopy = (header[3] >> 5) & 0x01;
const home = (header[3] >> 4) & 0x01;
const copyrightIdBit = (header[3] >> 3) & 0x01;
const copyrightIdStart = (header[3] >> 2) & 0x01;
const frameLength = ((header[3] & 0x03) << 11) | (header[4] << 3) | ((header[5] >> 5) & 0x07);
const bufferFullness = ((header[5] & 0x1F) << 6) | ((header[6] >> 2) & 0x3F);
const numRawBlocks = (header[6] & 0x03) + 1;
if (numRawBlocks !== 1) {
throw new Error('Only single raw data block per frame supported');
}
const headerSize = protectionAbsent ? 7 : 9;
const crc = protectionAbsent ? null : ((header[7] << 8) | header[8]);
if (offset + frameLength > data.length) break;
const rawData = data.subarray(offset + headerSize, offset + frameLength);
const frameProps = {
syncword: 0xFFF,
id,
layer,
protection_absent: protectionAbsent,
profile,
sampling_frequency_index: samplingFreqIndex,
private_bit: privateBit,
channel_configuration: channelConfig,
original_copy: originalCopy,
home,
copyright_identification_bit: copyrightIdBit,
copyright_identification_start: copyrightIdStart,
aac_frame_length: frameLength,
adts_buffer_fullness: bufferFullness,
number_of_raw_data_blocks_in_frame: numRawBlocks - 1,
crc
};
this.frames.push({ properties: frameProps, raw_data: rawData });
offset += frameLength;
}
}
write(outputPath = null) {
if (!outputPath) outputPath = this.filepath;
let outputBuffer = Buffer.alloc(0);
for (const frame of this.frames) {
const props = frame.properties;
const h0 = 0xFF;
const h1 = 0xF0 | (props.id << 3) | (props.layer << 1) | props.protection_absent;
const h2 = (props.profile << 6) | (props.sampling_frequency_index << 2) | (props.private_bit << 1) | (props.channel_configuration >> 2);
const h3 = ((props.channel_configuration & 0x03) << 6) | (props.original_copy << 5) | (props.home << 4) | (props.copyright_identification_bit << 3) | (props.copyright_identification_start << 2) | ((props.aac_frame_length >> 11) & 0x03);
const h4 = (props.aac_frame_length >> 3) & 0xFF;
const h5 = ((props.aac_frame_length & 0x07) << 5) | ((props.adts_buffer_fullness >> 6) & 0x1F);
const h6 = ((props.adts_buffer_fullness & 0x3F) << 2) | props.number_of_raw_data_blocks_in_frame;
let header = Buffer.from([h0, h1, h2, h3, h4, h5, h6]);
if (!props.protection_absent) {
const crcHigh = (props.crc >> 8) & 0xFF;
const crcLow = props.crc & 0xFF;
header = Buffer.concat([header, Buffer.from([crcHigh, crcLow])]);
}
outputBuffer = Buffer.concat([outputBuffer, header, frame.raw_data]);
}
fs.writeFileSync(outputPath, outputBuffer);
}
}
5. C++ Class
#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <stdexcept>
#include <cstring>
class AACFile {
private:
std::string filepath;
struct Frame {
std::map<std::string, int> properties;
std::vector<unsigned char> raw_data;
};
std::vector<Frame> frames;
public:
AACFile(const std::string& fp) : filepath(fp) {
load();
}
void load() {
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) return;
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<unsigned char> data(size);
file.read(reinterpret_cast<char*>(data.data()), size);
size_t offset = 0;
while (offset < data.size()) {
if (data.size() - offset < 7) break;
unsigned char header[9];
std::memcpy(header, data.data() + offset, 9);
// Check syncword
if (header[0] != 0xFF || (header[1] & 0xF0) != 0xF0) {
offset++;
continue;
}
int id = (header[1] >> 3) & 0x01;
int layer = (header[1] >> 1) & 0x03;
int protection_absent = header[1] & 0x01;
int profile = (header[2] >> 6) & 0x03;
int sampling_freq_index = (header[2] >> 2) & 0x0F;
int private_bit = (header[2] >> 1) & 0x01;
int channel_config = ((header[2] & 0x01) << 2) | ((header[3] >> 6) & 0x03);
int original_copy = (header[3] >> 5) & 0x01;
int home = (header[3] >> 4) & 0x01;
int copyright_id_bit = (header[3] >> 3) & 0x01;
int copyright_id_start = (header[3] >> 2) & 0x01;
int frame_length = ((header[3] & 0x03) << 11) | (header[4] << 3) | ((header[5] >> 5) & 0x07);
int buffer_fullness = ((header[5] & 0x1F) << 6) | ((header[6] >> 2) & 0x3F);
int num_raw_blocks = (header[6] & 0x03) + 1;
if (num_raw_blocks != 1) {
throw std::runtime_error("Only single raw data block per frame supported");
}
int header_size = protection_absent ? 7 : 9;
int crc = protection_absent ? -1 : ((header[7] << 8) | header[8]);
if (offset + frame_length > data.size()) break;
std::vector<unsigned char> raw_data(data.begin() + offset + header_size, data.begin() + offset + frame_length);
Frame frame;
frame.properties["syncword"] = 0xFFF;
frame.properties["id"] = id;
frame.properties["layer"] = layer;
frame.properties["protection_absent"] = protection_absent;
frame.properties["profile"] = profile;
frame.properties["sampling_frequency_index"] = sampling_freq_index;
frame.properties["private_bit"] = private_bit;
frame.properties["channel_configuration"] = channel_config;
frame.properties["original_copy"] = original_copy;
frame.properties["home"] = home;
frame.properties["copyright_identification_bit"] = copyright_id_bit;
frame.properties["copyright_identification_start"] = copyright_id_start;
frame.properties["aac_frame_length"] = frame_length;
frame.properties["adts_buffer_fullness"] = buffer_fullness;
frame.properties["number_of_raw_data_blocks_in_frame"] = num_raw_blocks - 1;
frame.properties["crc"] = crc;
frame.raw_data = raw_data;
frames.push_back(frame);
offset += frame_length;
}
}
void write(const std::string& output_path = "") {
std::string out = output_path.empty() ? filepath : output_path;
std::ofstream file(out, std::ios::binary);
if (!file) return;
for (const auto& frame : frames) {
auto props = frame.properties;
unsigned char h0 = 0xFF;
unsigned char h1 = 0xF0 | (static_cast<unsigned char>(props.at("id") << 3)) | (static_cast<unsigned char>(props.at("layer") << 1)) | static_cast<unsigned char>(props.at("protection_absent"));
unsigned char h2 = static_cast<unsigned char>((props.at("profile") << 6) | (props.at("sampling_frequency_index") << 2) | (props.at("private_bit") << 1) | (props.at("channel_configuration") >> 2));
unsigned char h3 = static_cast<unsigned char>(((props.at("channel_configuration") & 0x03) << 6) | (props.at("original_copy") << 5) | (props.at("home") << 4) | (props.at("copyright_identification_bit") << 3) | (props.at("copyright_identification_start") << 2) | ((props.at("aac_frame_length") >> 11) & 0x03));
unsigned char h4 = static_cast<unsigned char>((props.at("aac_frame_length") >> 3) & 0xFF);
unsigned char h5 = static_cast<unsigned char>(((props.at("aac_frame_length") & 0x07) << 5) | ((props.at("adts_buffer_fullness") >> 6) & 0x1F));
unsigned char h6 = static_cast<unsigned char>(((props.at("adts_buffer_fullness") & 0x3F) << 2) | props.at("number_of_raw_data_blocks_in_frame"));
unsigned char header[7] = {h0, h1, h2, h3, h4, h5, h6};
file.write(reinterpret_cast<char*>(header), 7);
if (props.at("protection_absent") == 0) {
int crc_val = props.at("crc");
unsigned char crc_bytes[2] = {static_cast<unsigned char>((crc_val >> 8) & 0xFF), static_cast<unsigned char>(crc_val & 0xFF)};
file.write(reinterpret_cast<char*>(crc_bytes), 2);
}
file.write(reinterpret_cast<const char*>(frame.raw_data.data()), frame.raw_data.size());
}
}
};