Task 413: .MP2 File Format

Task 413: .MP2 File Format

  1. Properties of the .MP2 File Format:
    The .MP2 file format (MPEG-1 Audio Layer II) is a lossy audio compression format consisting of a sequence of independent frames, each starting with a 32-bit (4-byte) header followed by audio data. There is no overall file header; properties are defined per frame but are typically constant throughout the file. The format supports MPEG-1 and MPEG-2 (extension) versions, with Layer II encoding. Intrinsic properties are derived from the frame header fields and overall stream characteristics. Below is a comprehensive list based on the ISO/IEC 11172-3 and ISO/IEC 13818-3 specifications:
  • Frame Sync: 11 bits set to '11111111111' for synchronization.
  • MPEG Version: 2 bits indicating the version.
  • '11': MPEG-1
  • '10': MPEG-2
  • '00': MPEG-2.5 (extension)
  • '01': Reserved
  • Layer: 2 bits, must be '10' for Layer II.
  • Protection (CRC): 1 bit indicating if a 16-bit CRC follows the header.
  • '0': Protected (CRC present)
  • '1': Not protected
  • Bitrate: 4 bits index mapping to bitrate in kbps (varies by MPEG version; certain combinations invalid for stereo/dual channels).
  • For MPEG-1 Layer II: 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 (or 'free' for custom, 'bad' for invalid).
  • For MPEG-2/2.5 Layer II: 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256 (or 'free', 'bad').
  • Sampling Rate: 2 bits index for frequency in Hz.
  • MPEG-1: 44100, 48000, 32000 (or reserved).
  • MPEG-2: 22050, 24000, 16000 (or reserved).
  • MPEG-2.5: 11025, 12000, 8000 (or reserved).
  • Padding: 1 bit indicating if the frame is padded with an extra 8-bit slot to adjust for exact bitrate.
  • '0': No padding
  • '1': Padded
  • Private Bit: 1 bit for application-specific use (informative only).
  • Channel Mode: 2 bits defining channels.
  • '00': Stereo
  • '01': Joint stereo
  • '10': Dual channel (two mono)
  • '11': Mono (single channel)
  • Mode Extension: 2 bits (relevant for joint stereo), defining subband range for intensity stereo.
  • '00': Bands 4-31
  • '01': Bands 8-31
  • '10': Bands 12-31
  • '11': Bands 16-31
  • Copyright: 1 bit.
  • '0': Not copyrighted
  • '1': Copyrighted
  • Original: 1 bit.
  • '0': Copy
  • '1': Original media
  • Emphasis: 2 bits for de-emphasis filter.
  • '00': None
  • '01': 50/15 µs
  • '10': Reserved
  • '11': CCITT J.17
  • Frame Size: Calculated as (bitrate * 144000 / sampling_rate) + padding (in bytes for Layer II; slot size is 1 byte).
  • Samples per Frame: Fixed at 1152 for Layer II.
  • Variable Bitrate (VBR) Support: Allowed via bitrate switching per frame (though not always supported by decoders).
  • Channel Support: Up to 2 channels in MPEG-1; up to 5.1 (multichannel) in MPEG-2 extension.
  • Compression Type: Sub-band coding with 32 subbands, perceptual model for masking.
  • File Extensions: .mp2, .mpa, .m2a.
  • MIME Type: audio/mpeg, audio/MPA.
  • Overall File Properties (derived): Total frames, duration (total_samples / sampling_rate), average bitrate (if VBR).

These properties are intrinsic to the format's structure and can be extracted by parsing frames.

  1. Direct Download Links for .MP2 Files:
  1. HTML/JavaScript for Drag-and-Drop .MP2 File Dumper (Embeddable in Ghost Blog):
MP2 File Properties Dumper
Drag and drop .MP2 file here

This HTML/JS can be embedded in a Ghost blog post. It parses the file, extracts properties from each frame (assuming constant for simplicity, but lists per frame if VBR), and dumps them to the screen.

  1. Python Class for .MP2 Handling:
import struct
import os

class MP2File:
    def __init__(self, filename):
        self.filename = filename
        self.properties = []
        self.frames = []
        self._parse()

    def _parse(self):
        with open(self.filename, 'rb') as f:
            data = f.read()
        offset = 0
        while offset < len(data) - 4:
            sync = struct.unpack_from('>H', data, offset)[0]
            if sync not in (0xFFFC, 0xFFFD):  # Layer II sync
                offset += 1
                continue
            header = struct.unpack_from('>I', data, offset)[0]
            version_bits = (header >> 19) & 0x03
            version = {0b11: 'MPEG-1', 0b10: 'MPEG-2', 0b00: 'MPEG-2.5', 0b01: 'Reserved'}[version_bits]
            layer_bits = (header >> 17) & 0x03
            if layer_bits != 0b10:
                break  # Not Layer II
            crc = 'Protected' if (header >> 16) & 0x01 == 0 else 'Not protected'
            bitrate_idx = (header >> 12) & 0x0F
            bitrates_v1 = [0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,0]
            bitrates_v2 = [0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,0]
            bitrate = bitrates_v1[bitrate_idx] if version == 'MPEG-1' else bitrates_v2[bitrate_idx]
            sr_idx = (header >> 10) & 0x03
            sr_v1 = [44100,48000,32000,0]
            sr_v2 = [22050,24000,16000,0]
            sr_v25 = [11025,12000,8000,0]
            sr = sr_v1[sr_idx] if version == 'MPEG-1' else sr_v2[sr_idx] if version == 'MPEG-2' else sr_v25[sr_idx]
            padding = 'Yes' if (header >> 9) & 0x01 else 'No'
            private = 'Set' if (header >> 8) & 0x01 else 'Not set'
            channel_bits = (header >> 6) & 0x03
            channels = {0b00: 'Stereo', 0b01: 'Joint stereo', 0b10: 'Dual channel', 0b11: 'Mono'}[channel_bits]
            mode_ext = (header >> 4) & 0x03
            mode_ext_str = ['Bands 4-31', 'Bands 8-31', 'Bands 12-31', 'Bands 16-31'][mode_ext]
            copyright = 'Yes' if (header >> 3) & 0x01 else 'No'
            original = 'Yes' if (header >> 2) & 0x01 else 'No'
            emphasis_bits = header & 0x03
            emphasis = {0b00: 'None', 0b01: '50/15 µs', 0b11: 'CCITT J.17', 0b10: 'Reserved'}[emphasis_bits]
            props = {
                'Version': version, 'CRC': crc, 'Bitrate': bitrate, 'Sampling Rate': sr, 'Padding': padding,
                'Private': private, 'Channels': channels, 'Mode Ext': mode_ext_str, 'Copyright': copyright,
                'Original': original, 'Emphasis': emphasis
            }
            self.properties.append(props)
            frame_size = (bitrate * 144000 // sr) + ((header >> 9) & 0x01)
            self.frames.append(data[offset:offset + frame_size])
            offset += frame_size

    def print_properties(self):
        for i, props in enumerate(self.properties):
            print(f"Frame {i+1}:")
            for k, v in props.items():
                print(f"  {k}: {v}")
        print(f"Total Frames: {len(self.properties)}")
        if self.properties:
            total_samples = len(self.properties) * 1152
            avg_sr = self.properties[0]['Sampling Rate']
            duration = total_samples / avg_sr if avg_sr else 0
            print(f"Estimated Duration: {duration:.2f} seconds")

    def write(self, output_filename):
        # Write the parsed frames back to a new file (unmodified; full encoding not implemented)
        with open(output_filename, 'wb') as f:
            for frame in self.frames:
                f.write(frame)
        print(f"File written to {output_filename}")

# Example usage:
# mp2 = MP2File('sample.mp2')
# mp2.print_properties()
# mp2.write('output.mp2')

This class opens a .MP2 file, decodes headers to extract properties per frame, prints them to console, and writes the file (copy) to disk.

  1. Java Class for .MP2 Handling:
import java.io.*;
import java.nio.*;
import java.util.*;

public class MP2File {
    private String filename;
    private List<Map<String, Object>> properties = new ArrayList<>();
    private List<byte[]> frames = new ArrayList<>();

    public MP2File(String filename) {
        this.filename = filename;
        parse();
    }

    private void parse() {
        try (FileInputStream fis = new FileInputStream(filename);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            byte[] data = new byte[(int) new File(filename).length()];
            fis.read(data);
            ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
            int offset = 0;
            while (offset < data.length - 4) {
                short sync = bb.getShort(offset);
                if (sync != (short)0xFFFC && sync != (short)0xFFFD) {
                    offset++;
                    continue;
                }
                int header = bb.getInt(offset);
                int versionBits = (header >> 19) & 0x03;
                String version = versionBits == 0b11 ? "MPEG-1" : versionBits == 0b10 ? "MPEG-2" : versionBits == 0b00 ? "MPEG-2.5" : "Reserved";
                int layerBits = (header >> 17) & 0x03;
                if (layerBits != 0b10) break;
                String crc = ((header >> 16) & 0x01) == 0 ? "Protected" : "Not protected";
                int bitrateIdx = (header >> 12) & 0x0F;
                int[] bitratesV1 = {0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,0};
                int[] bitratesV2 = {0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,0};
                int bitrate = "MPEG-1".equals(version) ? bitratesV1[bitrateIdx] : bitratesV2[bitrateIdx];
                int srIdx = (header >> 10) & 0x03;
                int[] srV1 = {44100,48000,32000,0};
                int[] srV2 = {22050,24000,16000,0};
                int[] srV25 = {11025,12000,8000,0};
                int sr = "MPEG-1".equals(version) ? srV1[srIdx] : "MPEG-2".equals(version) ? srV2[srIdx] : srV25[srIdx];
                String padding = ((header >> 9) & 0x01) == 1 ? "Yes" : "No";
                String privateBit = ((header >> 8) & 0x01) == 1 ? "Set" : "Not set";
                int channelBits = (header >> 6) & 0x03;
                String channels = channelBits == 0b00 ? "Stereo" : channelBits == 0b01 ? "Joint stereo" : channelBits == 0b10 ? "Dual channel" : "Mono";
                int modeExt = (header >> 4) & 0x03;
                String modeExtStr = new String[]{"Bands 4-31", "Bands 8-31", "Bands 12-31", "Bands 16-31"}[modeExt];
                String copyright = ((header >> 3) & 0x01) == 1 ? "Yes" : "No";
                String original = ((header >> 2) & 0x01) == 1 ? "Yes" : "No";
                int emphasisBits = header & 0x03;
                String emphasis = emphasisBits == 0b00 ? "None" : emphasisBits == 0b01 ? "50/15 µs" : emphasisBits == 0b11 ? "CCITT J.17" : "Reserved";
                Map<String, Object> props = new HashMap<>();
                props.put("Version", version);
                props.put("CRC", crc);
                props.put("Bitrate", bitrate);
                props.put("Sampling Rate", sr);
                props.put("Padding", padding);
                props.put("Private", privateBit);
                props.put("Channels", channels);
                props.put("Mode Ext", modeExtStr);
                props.put("Copyright", copyright);
                props.put("Original", original);
                props.put("Emphasis", emphasis);
                properties.add(props);
                int frameSize = (bitrate * 144000 / sr) + ((header >> 9) & 0x01);
                byte[] frame = new byte[frameSize];
                System.arraycopy(data, offset, frame, 0, frameSize);
                frames.add(frame);
                offset += frameSize;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void printProperties() {
        for (int i = 0; i < properties.size(); i++) {
            System.out.println("Frame " + (i + 1) + ":");
            properties.get(i).forEach((k, v) -> System.out.println("  " + k + ": " + v));
        }
        System.out.println("Total Frames: " + properties.size());
        if (!properties.isEmpty()) {
            long totalSamples = (long) properties.size() * 1152;
            int avgSr = (int) properties.get(0).get("Sampling Rate");
            double duration = avgSr != 0 ? totalSamples / (double) avgSr : 0;
            System.out.printf("Estimated Duration: %.2f seconds%n", duration);
        }
    }

    public void write(String outputFilename) throws IOException {
        // Write frames back (unmodified)
        try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
            for (byte[] frame : frames) {
                fos.write(frame);
            }
        }
        System.out.println("File written to " + outputFilename);
    }

    // Example usage:
    // public static void main(String[] args) {
    //     MP2File mp2 = new MP2File("sample.mp2");
    //     mp2.printProperties();
    //     mp2.write("output.mp2");
    // }
}

This Java class opens a .MP2 file, decodes headers, prints properties to console, and writes the file (copy).

  1. JavaScript Class for .MP2 Handling:
class MP2File {
    constructor(buffer) {
        this.buffer = buffer;
        this.properties = [];
        this.frames = [];
        this._parse();
    }

    _parse() {
        const view = new DataView(this.buffer);
        let offset = 0;
        while (offset < this.buffer.byteLength - 4) {
            const sync = view.getUint16(offset);
            if (sync !== 0xFFFC && sync !== 0xFFFD) {
                offset++;
                continue;
            }
            const header = view.getUint32(offset);
            const versionBits = (header >> 19) & 0x03;
            const version = versionBits === 0b11 ? 'MPEG-1' : versionBits === 0b10 ? 'MPEG-2' : versionBits === 0b00 ? 'MPEG-2.5' : 'Reserved';
            const layerBits = (header >> 17) & 0x03;
            if (layerBits !== 0b10) break;
            const crc = ((header >> 16) & 0x01) === 0 ? 'Protected' : 'Not protected';
            const bitrateIdx = (header >> 12) & 0x0F;
            const bitratesV1 = [0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,0];
            const bitratesV2 = [0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,0];
            const bitrate = version === 'MPEG-1' ? bitratesV1[bitrateIdx] : bitratesV2[bitrateIdx];
            const srIdx = (header >> 10) & 0x03;
            const srV1 = [44100,48000,32000,0];
            const srV2 = [22050,24000,16000,0];
            const srV25 = [11025,12000,8000,0];
            const sr = version === 'MPEG-1' ? srV1[srIdx] : version === 'MPEG-2' ? srV2[srIdx] : srV25[srIdx];
            const padding = ((header >> 9) & 0x01) === 1 ? 'Yes' : 'No';
            const privateBit = ((header >> 8) & 0x01) === 1 ? 'Set' : 'Not set';
            const channelBits = (header >> 6) & 0x03;
            const channels = channelBits === 0b00 ? 'Stereo' : channelBits === 0b01 ? 'Joint stereo' : channelBits === 0b10 ? 'Dual channel' : 'Mono';
            const modeExt = (header >> 4) & 0x03;
            const modeExtStr = ['Bands 4-31', 'Bands 8-31', 'Bands 12-31', 'Bands 16-31'][modeExt];
            const copyright = ((header >> 3) & 0x01) === 1 ? 'Yes' : 'No';
            const original = ((header >> 2) & 0x01) === 1 ? 'Yes' : 'No';
            const emphasisBits = header & 0x03;
            const emphasis = emphasisBits === 0b00 ? 'None' : emphasisBits === 0b01 ? '50/15 µs' : emphasisBits === 0b11 ? 'CCITT J.17' : 'Reserved';
            this.properties.push({
                Version: version, CRC: crc, Bitrate: bitrate, 'Sampling Rate': sr, Padding: padding,
                Private: privateBit, Channels: channels, 'Mode Ext': modeExtStr, Copyright: copyright,
                Original: original, Emphasis: emphasis
            });
            const frameSize = Math.floor((bitrate * 144000) / sr) + ((header >> 9) & 0x01);
            const frame = this.buffer.slice(offset, offset + frameSize);
            this.frames.push(frame);
            offset += frameSize;
        }
    }

    printProperties() {
        this.properties.forEach((props, i) => {
            console.log(`Frame ${i + 1}:`);
            Object.entries(props).forEach(([k, v]) => console.log(`  ${k}: ${v}`));
        });
        console.log(`Total Frames: ${this.properties.length}`);
        if (this.properties.length > 0) {
            const totalSamples = this.properties.length * 1152;
            const avgSr = this.properties[0]['Sampling Rate'];
            const duration = avgSr ? totalSamples / avgSr : 0;
            console.log(`Estimated Duration: ${duration.toFixed(2)} seconds`);
        }
    }

    write() {
        // Write to blob (unmodified); can be saved via download
        const blob = new Blob(this.frames, { type: 'audio/mp2' });
        const url = URL.createObjectURL(blob);
        console.log(`Download URL: ${url}`);
        // To save: const a = document.createElement('a'); a.href = url; a.download = 'output.mp2'; a.click();
        return blob;
    }
}

// Example usage (in browser with FileReader):
// const reader = new FileReader();
// reader.onload = (e) => {
//     const mp2 = new MP2File(e.target.result);
//     mp2.printProperties();
//     mp2.write();
// };
// reader.readAsArrayBuffer(file);

This JS class takes an ArrayBuffer, decodes headers, prints properties to console, and creates a Blob for writing/saving.

  1. C Implementation for .MP2 Handling (Using Struct and Functions, as C Has No Classes):
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef struct {
    char *version;
    char *crc;
    int bitrate;
    int sampling_rate;
    char *padding;
    char *private_bit;
    char *channels;
    char *mode_ext;
    char *copyright;
    char *original;
    char *emphasis;
} MP2Properties;

typedef struct {
    char *filename;
    MP2Properties *properties;
    uint8_t **frames;
    int frame_count;
    size_t *frame_sizes;
} MP2File;

MP2File* mp2_open(const char *filename) {
    MP2File *mp2 = malloc(sizeof(MP2File));
    mp2->filename = strdup(filename);
    mp2->properties = NULL;
    mp2->frames = NULL;
    mp2->frame_count = 0;
    mp2->frame_sizes = NULL;

    FILE *f = fopen(filename, "rb");
    if (!f) return NULL;
    fseek(f, 0, SEEK_END);
    long len = ftell(f);
    fseek(f, 0, SEEK_SET);
    uint8_t *data = malloc(len);
    fread(data, 1, len, f);
    fclose(f);

    int offset = 0;
    while (offset < len - 4) {
        uint16_t sync = (data[offset] << 8) | data[offset + 1];
        if (sync != 0xFFFC && sync != 0xFFFD) {
            offset++;
            continue;
        }
        uint32_t header = (data[offset] << 24) | (data[offset+1] << 16) | (data[offset+2] << 8) | data[offset+3];
        uint32_t version_bits = (header >> 19) & 0x03;
        char *version = version_bits == 0b11 ? "MPEG-1" : version_bits == 0b10 ? "MPEG-2" : version_bits == 0b00 ? "MPEG-2.5" : "Reserved";
        uint32_t layer_bits = (header >> 17) & 0x03;
        if (layer_bits != 0b10) break;
        char *crc = ((header >> 16) & 0x01) == 0 ? "Protected" : "Not protected";
        uint32_t bitrate_idx = (header >> 12) & 0x0F;
        int bitrates_v1[16] = {0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,0};
        int bitrates_v2[16] = {0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,0};
        int bitrate = strcmp(version, "MPEG-1") == 0 ? bitrates_v1[bitrate_idx] : bitrates_v2[bitrate_idx];
        uint32_t sr_idx = (header >> 10) & 0x03;
        int sr_v1[4] = {44100,48000,32000,0};
        int sr_v2[4] = {22050,24000,16000,0};
        int sr_v25[4] = {11025,12000,8000,0};
        int sr = strcmp(version, "MPEG-1") == 0 ? sr_v1[sr_idx] : strcmp(version, "MPEG-2") == 0 ? sr_v2[sr_idx] : sr_v25[sr_idx];
        char *padding = ((header >> 9) & 0x01) ? "Yes" : "No";
        char *private_bit = ((header >> 8) & 0x01) ? "Set" : "Not set";
        uint32_t channel_bits = (header >> 6) & 0x03;
        char *channels = channel_bits == 0b00 ? "Stereo" : channel_bits == 0b01 ? "Joint stereo" : channel_bits == 0b10 ? "Dual channel" : "Mono";
        uint32_t mode_ext_idx = (header >> 4) & 0x03;
        char *mode_ext_arr[4] = {"Bands 4-31", "Bands 8-31", "Bands 12-31", "Bands 16-31"};
        char *mode_ext = mode_ext_arr[mode_ext_idx];
        char *copyright = ((header >> 3) & 0x01) ? "Yes" : "No";
        char *original = ((header >> 2) & 0x01) ? "Yes" : "No";
        uint32_t emphasis_bits = header & 0x03;
        char *emphasis = emphasis_bits == 0b00 ? "None" : emphasis_bits == 0b01 ? "50/15 us" : emphasis_bits == 0b11 ? "CCITT J.17" : "Reserved";

        mp2->properties = realloc(mp2->properties, sizeof(MP2Properties) * (mp2->frame_count + 1));
        MP2Properties *props = &mp2->properties[mp2->frame_count];
        props->version = strdup(version);
        props->crc = strdup(crc);
        props->bitrate = bitrate;
        props->sampling_rate = sr;
        props->padding = strdup(padding);
        props->private_bit = strdup(private_bit);
        props->channels = strdup(channels);
        props->mode_ext = strdup(mode_ext);
        props->copyright = strdup(copyright);
        props->original = strdup(original);
        props->emphasis = strdup(emphasis);

        int frame_size = (bitrate * 144000 / sr) + ((header >> 9) & 0x01);
        mp2->frames = realloc(mp2->frames, sizeof(uint8_t*) * (mp2->frame_count + 1));
        mp2->frames[mp2->frame_count] = malloc(frame_size);
        memcpy(mp2->frames[mp2->frame_count], data + offset, frame_size);
        mp2->frame_sizes = realloc(mp2->frame_sizes, sizeof(size_t) * (mp2->frame_count + 1));
        mp2->frame_sizes[mp2->frame_count] = frame_size;

        offset += frame_size;
        mp2->frame_count++;
    }
    free(data);
    return mp2;
}

void mp2_print_properties(MP2File *mp2) {
    for (int i = 0; i < mp2->frame_count; i++) {
        MP2Properties *p = &mp2->properties[i];
        printf("Frame %d:\n", i + 1);
        printf("  Version: %s\n", p->version);
        printf("  CRC: %s\n", p->crc);
        printf("  Bitrate: %d\n", p->bitrate);
        printf("  Sampling Rate: %d\n", p->sampling_rate);
        printf("  Padding: %s\n", p->padding);
        printf("  Private: %s\n", p->private_bit);
        printf("  Channels: %s\n", p->channels);
        printf("  Mode Ext: %s\n", p->mode_ext);
        printf("  Copyright: %s\n", p->copyright);
        printf("  Original: %s\n", p->original);
        printf("  Emphasis: %s\n", p->emphasis);
    }
    printf("Total Frames: %d\n", mp2->frame_count);
    if (mp2->frame_count > 0) {
        long total_samples = (long)mp2->frame_count * 1152;
        int avg_sr = mp2->properties[0].sampling_rate;
        double duration = avg_sr ? (double)total_samples / avg_sr : 0;
        printf("Estimated Duration: %.2f seconds\n", duration);
    }
}

void mp2_write(MP2File *mp2, const char *output_filename) {
    FILE *f = fopen(output_filename, "wb");
    if (!f) return;
    for (int i = 0; i < mp2->frame_count; i++) {
        fwrite(mp2->frames[i], 1, mp2->frame_sizes[i], f);
    }
    fclose(f);
    printf("File written to %s\n", output_filename);
}

void mp2_close(MP2File *mp2) {
    for (int i = 0; i < mp2->frame_count; i++) {
        free(mp2->properties[i].version);
        free(mp2->properties[i].crc);
        free(mp2->properties[i].padding);
        free(mp2->properties[i].private_bit);
        free(mp2->properties[i].channels);
        free(mp2->properties[i].mode_ext);
        free(mp2->properties[i].copyright);
        free(mp2->properties[i].original);
        free(mp2->properties[i].emphasis);
        free(mp2->frames[i]);
    }
    free(mp2->properties);
    free(mp2->frames);
    free(mp2->frame_sizes);
    free(mp2->filename);
    free(mp2);
}

// Example usage:
// int main() {
//     MP2File *mp2 = mp2_open("sample.mp2");
//     if (mp2) {
//         mp2_print_properties(mp2);
//         mp2_write(mp2, "output.mp2");
//         mp2_close(mp2);
//     }
//     return 0;
// }

This C implementation uses a struct to mimic a class, opens a .MP2 file, decodes headers, prints properties to console, and writes the file (copy).