diff --git a/demo/boxHtmlTable.js b/demo/boxHtmlTable.js
index 8bfa2c11..e811c4c2 100644
--- a/demo/boxHtmlTable.js
+++ b/demo/boxHtmlTable.js
@@ -12,7 +12,19 @@ function generatePropertyValue(prop, value) {
if (typeof value?.toString === 'function') {
const content = value.toString();
if (value.hasOwnProperty('toString') || content.startsWith('<')) {
- return content;
+ return content.replace(
+ /[&<>"'\-\n]/g,
+ m =>
+ ({
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": ''',
+ '-': '‑',
+ '\n': '
',
+ })[m],
+ );
}
}
diff --git a/entries/all-boxes.ts b/entries/all-boxes.ts
index 802d5ece..943af1c3 100644
--- a/entries/all-boxes.ts
+++ b/entries/all-boxes.ts
@@ -2,6 +2,7 @@ export * from '#/boxes/a1lx';
export * from '#/boxes/a1op';
export * from '#/boxes/auxC';
export * from '#/boxes/av1C';
+export * from '#/boxes/av3c';
export * from '#/boxes/avcC';
export * from '#/boxes/btrt';
export * from '#/boxes/ccst';
@@ -20,6 +21,7 @@ export * from '#/boxes/csch';
export * from '#/boxes/cslg';
export * from '#/boxes/ctts';
export * from '#/boxes/dac3';
+export * from '#/boxes/dca3';
export * from '#/boxes/dec3';
export * from '#/boxes/defaults';
export * from '#/boxes/dfLa';
diff --git a/src/BitStream.ts b/src/BitStream.ts
new file mode 100644
index 00000000..eab260a4
--- /dev/null
+++ b/src/BitStream.ts
@@ -0,0 +1,321 @@
+/*
+ * Copyright (c) 2026. Paul Higgs
+ * License: BSD-3-Clause (see LICENSE file)
+ *
+ *
+ * reads bits and bytes from a buffer that may not contain aligned values
+ * TODO: add writing support
+ */
+
+import { DataStream, Endianness } from '#/DataStream';
+import type { MultiBufferStream } from './buffer';
+
+class State {
+ rbyte: number;
+ rbit: number;
+ wbyte: number;
+ wbit: number;
+ end: number;
+ read_error: boolean;
+ write_error: boolean;
+
+ constructor() {
+ this.rbyte = this.rbit = this.wbyte = this.wbit = this.end = 0;
+ this.read_error = this.write_error = false;
+ }
+
+ readBitOffset(): number {
+ return 8 * this.rbyte + this.rbit;
+ }
+ readByteOffset() {
+ return this.rbyte;
+ }
+ writeBitOffset(): number {
+ return 8 * this.wbyte + this.wbit;
+ }
+ writeByteOffset() {
+ return this.wbyte;
+ }
+ aligned() {
+ return this.read_error && this.rbit !== 0;
+ }
+}
+
+export class BitStream {
+ private buffer: Array;
+ private stream: MultiBufferStream | DataStream;
+ private state: State;
+ private endianness: Endianness;
+
+ constructor(stream: MultiBufferStream | DataStream, endianness?: Endianness) {
+ this.state = new State();
+ this.stream = stream;
+ this.endianness = endianness ? endianness : Endianness.BIG_ENDIAN;
+ }
+
+ appendUint8(count = 1): void {
+ for (let i = 0; i < count; i++) this.buffer.push(this.stream.readUint8());
+ this.state.end = this.state.wbyte = this.buffer.length;
+ }
+
+ extend(bits: number): void {
+ let count = bits;
+ while (count > 0) {
+ this.buffer.push(0);
+ count -= 8;
+ }
+ }
+
+ readBit(): number {
+ //! Read the next bit and advance the read pointer.
+ if (this.state.read_error || this.endOfRead()) {
+ this.state.read_error = true;
+ return 0;
+ }
+ const bit: number =
+ (this.buffer[this.state.rbyte] >>
+ (this.endianness === Endianness.BIG_ENDIAN ? 7 - this.state.rbit : this.state.rbit)) &
+ 0x01;
+ if (++this.state.rbit > 7) {
+ this.state.rbyte++;
+ this.state.rbit = 0;
+ }
+ return bit;
+ }
+
+ peekBit(): number {
+ //! Read the next bit and but dont advance the read pointer.
+ if (this.state.read_error || this.endOfRead()) {
+ this.state.read_error = true;
+ return 0;
+ }
+ const bit: number =
+ (this.buffer[this.state.rbyte] >>
+ (this.endianness === Endianness.BIG_ENDIAN ? 7 - this.state.rbit : this.state.rbit)) &
+ 0x01;
+ return bit;
+ }
+
+ endOfRead(): boolean {
+ return this.state.rbyte === this.state.wbyte && this.state.rbit === this.state.wbit;
+ }
+
+ getBool(): boolean {
+ return this.readBit() !== 0;
+ }
+
+ private rdb = function (bytes: number): number {
+ let i: number, res: number;
+ // eslint-disable-next-line no-loss-of-precision
+ const ff = 0xffffffffffffffff;
+ if (this.state.read_error) return ff;
+ if (this.state.rbit === 0) {
+ // Read buffer is byte aligned. Most common case.
+ if (this.state.rbyte + bytes > this.state.wbyte) {
+ // Not enough bytes to read.
+ this.state.read_error = true;
+ return ff;
+ } else {
+ for (res = 0, i = 0; i < bytes; i++) res = (res << 8) + this.buffer[this.state.rbyte + i];
+ this.state.rbyte += bytes;
+ return res;
+ }
+ } else {
+ // Read buffer is not byte aligned, use an intermediate aligned buffer.
+ if (this.currentReadBitOffset() + 8 * bytes > this.currentWriteBitOffset()) {
+ // Not enough bytes to read.
+ this.state.read_error = true;
+ return ff;
+ } else {
+ for (res = 0, i = 0; i < bytes; i++) {
+ if (this.endianness === Endianness.BIG_ENDIAN)
+ res =
+ (res << 8) +
+ ((this.buffer[this.state.rbyte] << this.state.rbit) |
+ (this.buffer[this.state.rbyte + 1] >> (8 - this.state.rbit)));
+ else
+ res =
+ (res << 8) +
+ ((this.buffer[this.state.rbyte] >> this.state.rbit) |
+ (this.buffer[this.state.rbyte + 1] << (8 - this.state.rbit)));
+ this.state.rbyte++;
+ }
+ return res;
+ }
+ }
+ return ff; // we should never get here!!
+ };
+
+ readUint8() {
+ return this.rdb(1);
+ }
+
+ readUint16(endianness?: Endianness) {
+ return (endianness ?? this.endianness) === Endianness.BIG_ENDIAN
+ ? this.GetUInt16BE(this.rdb(2))
+ : this.GetUInt16LE(this.rdb(2));
+ }
+ private GetUInt16BE = function (val: number) {
+ return this.OSisLittleEndian() ? this.ByteSwap16(val) : val;
+ };
+ private GetUInt16LE = function (val: number) {
+ return this.OSisLittleEndian() ? val : this.ByteSwap16(val);
+ };
+ private ByteSwap16 = function (x: number): number {
+ return (x << 8) | (x >> 8);
+ };
+
+ readUint24(endianness?: Endianness) {
+ return (endianness ?? this.endianness) === Endianness.BIG_ENDIAN
+ ? this.GetUInt24BE(this.rdb(3))
+ : this.GetUInt24LE(this.rdb(3));
+ }
+
+ private ByteSwap24 = function (x: number) {
+ return ((x & 0xff0000) >> 16) | (x & 0xff00) | (x & (0xff << 16));
+ };
+ private GetUInt24BE = function (val: number) {
+ return this.OSisLittleEndian() ? this.ByteSwap24(val) : val;
+ };
+ private GetUInt24LE = function (val: number) {
+ return this.OSisLittleEndian() ? val : this.ByteSwap24(val);
+ };
+
+ readUint32(endianness?: Endianness) {
+ return (endianness ?? this.endianness) === Endianness.BIG_ENDIAN
+ ? this.GetUInt32BE(this.rdb(4))
+ : this.GetUInt32LE(this.rdb(4));
+ }
+ private ByteSwap32 = function (x: number) {
+ return (x << 24) | ((x << 8) & 0x00ff0000) | ((x >> 8) & 0x0000ff00) | (x >> 24);
+ };
+ private GetUInt32BE = function (val: number) {
+ return this.OSisLittleEndian() ? this.ByteSwap32(val) : val;
+ };
+ private GetUInt32LE = function (val: number) {
+ return this.OSisLittleEndian() ? val : this.ByteSwap32(val);
+ };
+
+ readBits(bits: number): number {
+ // No read if read error is already set or not enough bits to read.
+ if (
+ this.state.read_error ||
+ this.currentReadBitOffset() + bits > this.currentWriteBitOffset()
+ ) {
+ this.state.read_error = true;
+ return 0;
+ }
+ let val = 0;
+ if (this.endianness === Endianness.BIG_ENDIAN) {
+ // Read leading bits up to byte boundary
+ while (bits > 0 && this.state.rbit !== 0) {
+ val = (val << 1) | this.readBit();
+ --bits;
+ }
+
+ // Read complete bytes
+ while (bits > 7) {
+ val = (val << 8) | this.buffer[this.state.rbyte++];
+ bits -= 8;
+ }
+
+ // Read trailing bits
+ while (bits > 0) {
+ val = (val << 1) | this.readBit();
+ --bits;
+ }
+ } else {
+ // Little endian decoding
+ let shift = 0;
+
+ // Read leading bits up to byte boundary
+ while (bits > 0 && this.state.rbit !== 0) {
+ val |= this.readBit() << shift;
+ --bits;
+ shift++;
+ }
+
+ // Read complete bytes
+ while (bits > 7) {
+ val |= this.buffer[this.state.rbyte++] << shift;
+ bits -= 8;
+ shift += 8;
+ }
+
+ // Read trailing bits
+ while (bits > 0) {
+ val |= this.readBit() << shift;
+ --bits;
+ shift++;
+ }
+ }
+ return val;
+ }
+
+ skipBits(bits: number): boolean {
+ if (this.state.read_error) {
+ // Can't skip bits and bytes if read error is already set.
+ return false;
+ }
+ const rpos = 8 * this.state.rbyte + this.state.rbit + bits;
+ const wpos = 8 * this.state.wbyte + this.state.wbit;
+ if (rpos > wpos) {
+ this.state.rbyte = this.state.wbyte;
+ this.state.rbit = this.state.wbit;
+ this.state.read_error = true;
+ return false;
+ }
+ this.state.rbyte = rpos >> 3;
+ this.state.rbit = rpos & 7;
+ return true;
+ }
+
+ skipBit(): boolean {
+ return this.skipBits(1);
+ }
+
+ readUE(): number {
+ // read in an Unsigned Exp-Golomb code;
+ if (this.readBit() === 1) return 0;
+ let zero_count = 1;
+ while (this.peekBit() === 0) {
+ this.readBit();
+ zero_count++;
+ }
+ return this.readBits(zero_count + 1) - 1;
+ }
+
+ byte_alignment(): void {
+ while (!this.state.aligned) this.skipBit();
+ }
+
+ private OSisLittleEndian = function (): boolean {
+ return this.stream.endianness === Endianness.LITTLE_ENDIAN;
+ };
+
+ currentReadByteOffset(): number {
+ return this.state.readByteOffset();
+ }
+ currentReadBitOffset(): number {
+ return this.state.readBitOffset();
+ }
+ currentWriteByteOffset(): number {
+ return this.state.writeByteOffset();
+ }
+ currentWriteBitOffset(): number {
+ return this.state.writeBitOffset();
+ }
+ bitsRemaining(): number {
+ return this.currentWriteBitOffset() - this.currentReadBitOffset();
+ }
+ writeBitsRemaining(): number {
+ return 8 * this.buffer.length - this.currentWriteBitOffset();
+ }
+ /*
+ TODO - for near future implementation to support writing AVS3 related boxes that are not byte aligned
+ writeBit(bit: number): void {
+ if (this.writeBitsRemaining() < 1)
+ this.extend(1);
+ }
+ */
+}
diff --git a/src/boxes/av3c.ts b/src/boxes/av3c.ts
new file mode 100644
index 00000000..8a0d6920
--- /dev/null
+++ b/src/boxes/av3c.ts
@@ -0,0 +1,515 @@
+/*
+ * Copyright (c) 2025. Paul Higgs
+ * License: BSD-3-Clause (see LICENSE file)
+ */
+
+import { Box } from '#/box';
+import type { MultiBufferStream } from '#/buffer';
+import { BitStream } from '#/BitStream';
+
+import {
+ DescribedValue,
+ AVS3data,
+ HexadecimalValue,
+ BinaryValue,
+ BooleanValue,
+} from './avs-common';
+
+interface ReferencePicture {
+ library_index_flag?: number;
+ referenced_library_picture_index?: number;
+ abs_delta_doi?: number;
+ sign_delta_doi?: number;
+}
+
+class ReferencePictureSet {
+ private _list: number;
+ private _set: number;
+ private _pics: Array;
+ private _reference_to_library_enable_flag?: number;
+ private _library_enable_flag_set: boolean;
+
+ constructor(list: number, set: number) {
+ this._list = list;
+ this._set = set;
+ this._pics = [];
+ this._library_enable_flag_set = false;
+ }
+ set_reference_to_library_enable_flag(flag: number) {
+ this._library_enable_flag_set = true;
+ this._reference_to_library_enable_flag = flag;
+ }
+ get_reference_to_library_enable_flag(): number {
+ return this._library_enable_flag_set ? this._reference_to_library_enable_flag : 0;
+ }
+ push(pic: ReferencePicture) {
+ this._pics.push(pic);
+ }
+ toString() {
+ let ret = '{';
+ if (this._library_enable_flag_set)
+ ret += 'reference_to_library_enable_flag: ' + this._reference_to_library_enable_flag;
+ this._pics.forEach(e => {
+ ret += (ret.length > 3 ? ', ' : '') + JSON.stringify(e).replace(/"/g, '');
+ });
+ ret += '}';
+ return ret;
+ }
+}
+
+class ReferencePictureList {
+ private _list?: number;
+ private _sets?: Array;
+
+ constructor(list: number) {
+ this._list = list;
+ this._sets = [];
+ }
+ push(set: ReferencePictureSet) {
+ this._sets.push(set);
+ }
+ toString() {
+ if (this._sets.length === 0) return '(empty)';
+ const l: Array = [];
+ this._sets.forEach(set => {
+ l.push(set.toString());
+ });
+ return l.join(', ');
+ }
+}
+
+class WeightQuantMatrix {
+ WeightQuantMatrix4x4: Array>;
+ WeightQuantMatrix8x8: Array>;
+
+ constructor(reader: BitStream) {
+ this.WeightQuantMatrix4x4 = [];
+ this.WeightQuantMatrix8x8 = [];
+
+ for (let sizeId = 0; sizeId < 2; sizeId++) {
+ const this_size: Array> = [];
+ const WQMSize = 1 << (sizeId + 2);
+ for (let i = 0; i < WQMSize; i++) {
+ const iVal: Array = [];
+ for (let j = 0; j < WQMSize; j++) iVal.push(reader.readUE());
+ this_size.push(iVal);
+ }
+ if (sizeId === 0) this.WeightQuantMatrix4x4 = this_size;
+ else this.WeightQuantMatrix8x8 = this_size;
+ }
+ }
+ toString() {
+ let str = '';
+ if (this.WeightQuantMatrix4x4.length)
+ str += '4x4: ' + JSON.stringify(this.WeightQuantMatrix4x4);
+ if (this.WeightQuantMatrix8x8.length)
+ str += (str.length > 2 ? ',\n' : '') + '8x8: ' + JSON.stringify(this.WeightQuantMatrix8x8);
+ return str;
+ }
+}
+
+const MAIN_8 = 0x20,
+ MAIN_10 = 0x22,
+ HIGH_8 = 0x30,
+ HIGH_10 = 0x32;
+const RESERVED = 'Reserved',
+ FORBIDDEN = 'Forbidden';
+const AVS3profiles = [
+ // Table B.1 of T/AI 109.2
+ { profile: MAIN_8, description: 'Main 8 bit' },
+ { profile: MAIN_10, description: 'Main 10 bit' },
+ { profile: HIGH_8, description: 'High 8 bit' },
+ { profile: HIGH_10, description: 'High 10 bit' },
+ { profile: 0x00, description: FORBIDDEN },
+];
+const AVS3levels = [
+ // Table B.1 of T/AI 109.2
+ { level: 0x50, description: '8.0.30' },
+ { level: 0x52, description: '8.2.30' },
+ { level: 0x51, description: '8.4.30' },
+ { level: 0x53, description: '8.6.30' },
+ { level: 0x54, description: '8.0.60' },
+ { level: 0x56, description: '8.2.60' },
+ { level: 0x55, description: '8.4.60' },
+ { level: 0x57, description: '8.6.60' },
+ { level: 0x58, description: '8.0.120' },
+ { level: 0x5a, description: '8.2.120' },
+ { level: 0x59, description: '8.4.120' },
+ { level: 0x5b, description: '8.6.120' },
+ { level: 0x60, description: '10.0.30' },
+ { level: 0x62, description: '10.2.30' },
+ { level: 0x61, description: '10.4.30' },
+ { level: 0x63, description: '10.6.30' },
+ { level: 0x64, description: '10.0.60' },
+ { level: 0x66, description: '10.2.60' },
+ { level: 0x65, description: '10.4.60' },
+ { level: 0x67, description: '10.6.60' },
+ { level: 0x68, description: '10.0.120' },
+ { level: 0x6a, description: '10.2.120' },
+ { level: 0x69, description: '10.4.120' },
+ { level: 0x6b, description: '10.6.120' },
+ { level: 0x10, description: '2.0.15' },
+ { level: 0x12, description: '2.0.30' },
+ { level: 0x14, description: '2.0.60' },
+ { level: 0x20, description: '4.0.30' },
+ { level: 0x22, description: '4.0.60' },
+ { level: 0x40, description: '6.0.30' },
+ { level: 0x42, description: '6.2.30' },
+ { level: 0x41, description: '6.4.30' },
+ { level: 0x43, description: '6.6.30' },
+ { level: 0x44, description: '6.0.60' },
+ { level: 0x46, description: '6.2.60' },
+ { level: 0x45, description: '6.4.60' },
+ { level: 0x47, description: '6.6.60' },
+ { level: 0x48, description: '6.0.120' },
+ { level: 0x4a, description: '6.2.120' },
+ { level: 0x49, description: '6.4.120' },
+ { level: 0x4b, description: '6.6.120' },
+ { level: 0x00, description: FORBIDDEN },
+];
+const AVS3precisions = [
+ // Table 45 of T/AI 109.2
+ { precision: 1, description: '8-bit' },
+ { precision: 2, description: '10-bit' },
+];
+const AVS3framerates = [
+ '',
+ '24/1.001',
+ '24',
+ '25',
+ '30/1.001',
+ '30',
+ '50',
+ '60/1.001',
+ '60',
+ '100',
+ '120',
+ '200',
+ '240',
+ '300',
+ '120/1.001',
+];
+const AVS3ratios = ['', '1.0', '4:3', '16:9', '2.21:1'];
+
+const AVS3Vconfiguration = (version: number) => (version !== 1 ? 'not supported' : '');
+
+function AVS3profile(profile: number) {
+ const t = AVS3profiles.find(e => e.profile === profile);
+ return t === undefined ? RESERVED : t.description;
+}
+
+function AVS3level(level: number) {
+ const t = AVS3levels.find(e => e.level === level);
+ return t === undefined ? RESERVED : t.description;
+}
+
+function AVS3precision(precision: number) {
+ const t = AVS3precisions.find(e => e.precision === precision);
+ return t === undefined ? RESERVED : t.description;
+}
+
+const AVS3chroma = (chroma: number) => (chroma === 1 ? '4:2:0' : RESERVED);
+
+const AVS3aspectratio = (ratio: number) =>
+ ratio === 0 ? FORBIDDEN : ratio >= AVS3ratios.length ? RESERVED : AVS3ratios[ratio];
+
+const AVS3framerate = (framerate: number) =>
+ framerate === 0
+ ? FORBIDDEN
+ : framerate >= AVS3framerates.length
+ ? RESERVED
+ : AVS3framerates[framerate] + ' fps';
+
+interface SequenceHeaderElements {
+ video_sequence_start_code?: HexadecimalValue;
+ profile_id?: HexadecimalValue;
+ level_id?: HexadecimalValue;
+ progressive_sequence?: BooleanValue;
+ field_coded_sequence?: BooleanValue;
+ library_stream_flag?: BooleanValue;
+ library_picture_enable_flag?: BooleanValue;
+ duplicate_sequence_number_flag?: BooleanValue;
+ horizontal_size?: number;
+ vertical_size?: number;
+ chroma_format?: BinaryValue;
+ sample_precision?: BinaryValue;
+ encoding_precision?: BinaryValue;
+ aspect_ratio?: BinaryValue;
+ frame_rate_code?: BinaryValue;
+ bit_rate_lower?: number;
+ bit_rate_upper?: number;
+ low_delay?: number;
+ temporal_id_enable_flag?: BooleanValue;
+ max_dpb_minus1?: number;
+ bbv_buffer_size?: number;
+ rpl1_index_exist_flag?: BooleanValue;
+ rpl1_same_as_rpl0_flag?: BooleanValue;
+ num_ref_pic_list_set0?: number;
+ rpl0?: ReferencePictureList;
+ num_ref_pic_list_set1?: number;
+ rpl1?: ReferencePictureList;
+ num_ref_default_active_minus1_0?: number;
+ num_ref_default_active_minus1_1?: number;
+ log2_lcu_size_minus2?: number;
+ log2_min_cu_size_minus2?: number;
+ log2_max_part_ratio_minus2?: number;
+ max_split_times_minus6?: number;
+ log2_min_qt_size_minus2?: number;
+ log2_max_bt_size_minus2?: number;
+ log2_max_eqt_size_minus3?: number;
+ weight_quant_enable_flag?: BooleanValue;
+ load_seq_weight_quant_data_flag?: BooleanValue;
+ weight_quant_matrix?: WeightQuantMatrix;
+ st_enable_flag?: BooleanValue;
+ sao_enable_flag?: BooleanValue;
+ alf_enable_flag?: BooleanValue;
+ affine_enable_flag?: BooleanValue;
+ smvd_enable_flag?: BooleanValue;
+ ipcm_enable_flag?: BooleanValue;
+ amvr_enable_flag?: BooleanValue;
+ num_of_hmvp_cand?: number;
+ umve_enable_flag?: BooleanValue;
+ emvr_enable_flag?: BooleanValue;
+ intra_pf_enable_flag?: BooleanValue;
+ tscpm_enable_flag?: BooleanValue;
+ dt_enable_flag?: BooleanValue;
+ log2_max_dt_size_minus4?: number;
+ pbt_enable_flag?: BooleanValue;
+ pmc_enable_flag?: BooleanValue;
+ iip_enable_flag?: BooleanValue;
+ sawp_enable_flag?: BooleanValue;
+ asr_enable_flag?: BooleanValue;
+ awp_enable_flag?: BooleanValue;
+ etmvp_mvap_enable_flag?: BooleanValue;
+ dmvr_enable_flag?: BooleanValue;
+ bio_enable_flag?: BooleanValue;
+ bgc_enable_flag?: BooleanValue;
+ inter_pf_enable_flag?: BooleanValue;
+ inter_pfc_enable_flag?: BooleanValue;
+ obmc_enable_flag?: BooleanValue;
+ sbt_enable_flag?: BooleanValue;
+ ist_enable_flag?: BooleanValue;
+ esao_enable_flag?: BooleanValue;
+ ccsao_enable_flag?: BooleanValue;
+ ealf_enable_flag?: BooleanValue;
+ ibc_enable_flag?: BooleanValue;
+ isc_enable_flag?: BooleanValue;
+ num_of_intra_hmvp_cand?: number;
+ fimc_enable_flag?: BooleanValue;
+ nn_tools_set_hook?: number;
+ num_of_nn_filter_minus1?: number;
+ output_reorder_delay?: number;
+ cross_patch_loop_filter_enable_flag?: BooleanValue;
+ ref_colocated_patch_flag?: BooleanValue;
+ stable_patch_flag?: BooleanValue;
+ uniform_patch_flag?: BooleanValue;
+ patch_width_minus1?: number;
+ patch_height_minus1?: number;
+}
+
+class AVS3SequenceHeader extends AVS3data {
+ data: SequenceHeaderElements;
+
+ constructor(bit_reader: BitStream) {
+ super();
+ this.data = {};
+ this.deserialise(bit_reader);
+ }
+ deserialise(bit_reader: BitStream) {
+ this.data.video_sequence_start_code = new HexadecimalValue(bit_reader.readUint32());
+ this.data.profile_id = new HexadecimalValue(bit_reader.readUint8(), AVS3profile);
+ this.data.level_id = new HexadecimalValue(bit_reader.readUint8(), AVS3level);
+ this.data.progressive_sequence = new BooleanValue(bit_reader.readBit());
+ this.data.field_coded_sequence = new BooleanValue(bit_reader.readBit());
+ this.data.library_stream_flag = new BooleanValue(bit_reader.readBit());
+ if (!this.data.library_stream_flag.value) {
+ this.data.library_picture_enable_flag = new BooleanValue(bit_reader.readBit());
+ if (this.data.library_picture_enable_flag.value)
+ this.data.duplicate_sequence_number_flag = new BooleanValue(bit_reader.readBit());
+ }
+ bit_reader.skipBit(); // marker_bit
+
+ this.data.horizontal_size = bit_reader.readBits(14);
+ bit_reader.skipBit(); // marker_bit
+
+ this.data.vertical_size = bit_reader.readBits(14);
+ this.data.chroma_format = new BinaryValue(bit_reader.readBits(2), 2, AVS3chroma);
+ this.data.sample_precision = new BinaryValue(bit_reader.readBits(3), 3, AVS3precision);
+
+ if (this.data.profile_id.value === MAIN_10 || this.data.profile_id.value === HIGH_10)
+ this.data.encoding_precision = new BinaryValue(bit_reader.readBits(3), 3, AVS3precision);
+ bit_reader.skipBit(); // marker_bit
+
+ this.data.aspect_ratio = new BinaryValue(bit_reader.readBits(4), 4, AVS3aspectratio);
+ this.data.frame_rate_code = new BinaryValue(bit_reader.readBits(4), 4, AVS3framerate);
+ bit_reader.skipBit(); // marker_bit
+
+ this.data.bit_rate_lower = bit_reader.readBits(18);
+ bit_reader.skipBit(); // marker_bit
+
+ this.data.bit_rate_upper = bit_reader.readBits(12);
+ this.data.low_delay = bit_reader.readBit();
+ this.data.temporal_id_enable_flag = new BooleanValue(bit_reader.readBit());
+ bit_reader.skipBit(); // marker_bit
+
+ this.data.bbv_buffer_size = bit_reader.readBits(18);
+ bit_reader.skipBit(); // marker_bit
+
+ this.data.max_dpb_minus1 = bit_reader.readBits(4);
+ this.data.rpl1_index_exist_flag = new BooleanValue(bit_reader.readBit());
+ this.data.rpl1_same_as_rpl0_flag = new BooleanValue(bit_reader.readBit());
+ bit_reader.skipBit(); // marker_bit
+
+ const reference_picture_list = function (
+ list: number,
+ rpls: number,
+ library_picture_enable_flag: boolean,
+ ) {
+ const this_set = new ReferencePictureSet(list, rpls);
+ if (library_picture_enable_flag)
+ this_set.set_reference_to_library_enable_flag(bit_reader.readBit());
+ const num_of_ref_pic = bit_reader.readUE();
+ for (let i = 0; i < num_of_ref_pic; i++) {
+ const this_pic: ReferencePicture = {};
+ let LibraryIndexFlag = 0;
+ if (this_set.get_reference_to_library_enable_flag())
+ LibraryIndexFlag = this_pic.library_index_flag = bit_reader.readBit();
+ if (LibraryIndexFlag !== 0) this_pic.referenced_library_picture_index = bit_reader.readUE();
+ else {
+ this_pic.abs_delta_doi = bit_reader.readUE();
+ if (this_pic.abs_delta_doi > 0) this_pic.sign_delta_doi = bit_reader.readBit();
+ }
+ this_set.push(this_pic);
+ }
+ return this_set;
+ };
+
+ this.data.num_ref_pic_list_set0 = bit_reader.readUE();
+ this.data.rpl0 = new ReferencePictureList(0);
+ for (let j = 0; j < this.data.num_ref_pic_list_set0; j++)
+ this.data.rpl0.push(
+ reference_picture_list(0, j, this.data.library_picture_enable_flag.value),
+ );
+
+ if (!this.data.rpl1_same_as_rpl0_flag) {
+ this.data.num_ref_pic_list_set1 = bit_reader.readUE();
+ this.data.rpl1 = new ReferencePictureList(1);
+ for (let j = 0; j < this.data.num_ref_pic_list_set1; j++)
+ this.data.rpl1.push(
+ reference_picture_list(1, j, this.data.library_picture_enable_flag.value),
+ );
+ }
+
+ this.data.num_ref_default_active_minus1_0 = bit_reader.readUE();
+ this.data.num_ref_default_active_minus1_1 = bit_reader.readUE();
+ this.data.log2_lcu_size_minus2 = bit_reader.readBits(3);
+ this.data.log2_min_cu_size_minus2 = bit_reader.readBits(2);
+ this.data.log2_max_part_ratio_minus2 = bit_reader.readBits(2);
+ this.data.max_split_times_minus6 = bit_reader.readBits(3);
+ this.data.log2_min_qt_size_minus2 = bit_reader.readBits(3);
+ this.data.log2_max_bt_size_minus2 = bit_reader.readBits(3);
+ this.data.log2_max_eqt_size_minus3 = bit_reader.readBits(2);
+ bit_reader.skipBit(); // marker_bit
+
+ this.data.weight_quant_enable_flag = new BooleanValue(bit_reader.readBit());
+ if (this.data.weight_quant_enable_flag.value) {
+ this.data.load_seq_weight_quant_data_flag = new BooleanValue(bit_reader.readBit());
+ if (this.data.load_seq_weight_quant_data_flag.value)
+ this.data.weight_quant_matrix = new WeightQuantMatrix(bit_reader);
+ }
+
+ this.data.st_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.sao_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.alf_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.affine_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.smvd_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.ipcm_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.amvr_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.num_of_hmvp_cand = bit_reader.readBits(4);
+ this.data.umve_enable_flag = new BooleanValue(bit_reader.readBit());
+ if (this.data.num_of_hmvp_cand !== 0 && this.data.amvr_enable_flag.value)
+ this.data.emvr_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.intra_pf_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.tscpm_enable_flag = new BooleanValue(bit_reader.readBit());
+ bit_reader.skipBit(); // marker_bit
+
+ this.data.dt_enable_flag = new BooleanValue(bit_reader.readBit());
+ if (this.data.dt_enable_flag.value) this.data.log2_max_dt_size_minus4 = bit_reader.readBits(2);
+ this.data.pbt_enable_flag = new BooleanValue(bit_reader.readBit());
+
+ if (this.data.profile_id.value === MAIN_10 || this.data.profile_id.value === HIGH_10) {
+ this.data.pmc_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.iip_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.sawp_enable_flag = new BooleanValue(bit_reader.readBit());
+ if (this.data.affine_enable_flag.value)
+ this.data.asr_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.awp_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.etmvp_mvap_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.dmvr_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.bio_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.bgc_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.inter_pf_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.inter_pfc_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.obmc_enable_flag = new BooleanValue(bit_reader.readBit());
+
+ this.data.sbt_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.ist_enable_flag = new BooleanValue(bit_reader.readBit());
+
+ this.data.esao_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.ccsao_enable_flag = new BooleanValue(bit_reader.readBit());
+ if (this.data.alf_enable_flag.value)
+ this.data.ealf_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.ibc_enable_flag = new BooleanValue(bit_reader.readBit());
+ bit_reader.skipBit(); // marker_bit
+
+ this.data.isc_enable_flag = new BooleanValue(bit_reader.readBit());
+ if (this.data.ibc_enable_flag.value || this.data.isc_enable_flag.value)
+ this.data.num_of_intra_hmvp_cand = bit_reader.readBits(4);
+ this.data.fimc_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.nn_tools_set_hook = bit_reader.readBits(8);
+ if (this.data.nn_tools_set_hook & 0x01)
+ this.data.num_of_nn_filter_minus1 = bit_reader.readUE();
+ bit_reader.skipBit(); // marker_bit
+ }
+ if (this.data.low_delay === 0) this.data.output_reorder_delay = bit_reader.readBits(5);
+ this.data.cross_patch_loop_filter_enable_flag = new BooleanValue(bit_reader.readBit());
+ this.data.ref_colocated_patch_flag = new BooleanValue(bit_reader.readBit());
+ this.data.stable_patch_flag = new BooleanValue(bit_reader.readBit());
+ if (this.data.stable_patch_flag.value) {
+ this.data.uniform_patch_flag = new BooleanValue(bit_reader.readBit());
+ if (this.data.uniform_patch_flag.value) {
+ bit_reader.skipBit(); // marker_bit
+ this.data.patch_width_minus1 = bit_reader.readUE();
+ this.data.patch_height_minus1 = bit_reader.readUE();
+ }
+ }
+ bit_reader.skipBits(2); // reserved bits
+ }
+ toString() {
+ return super.toString(this.data);
+ }
+}
+
+export class av3cBox extends Box {
+ static override readonly fourcc = 'av3c' as const;
+ box_name = 'AVS3ConfigurationBox' as const;
+
+ configurationVersion: DescribedValue;
+ sequence_header_length?: number;
+ sequence_header?: AVS3SequenceHeader;
+ library_dependency_idc?: BinaryValue;
+
+ parse(stream: MultiBufferStream) {
+ const bit_reader = new BitStream(stream);
+ this.configurationVersion = new DescribedValue(stream.readUint8(), AVS3Vconfiguration);
+ if (this.configurationVersion.value === 1) {
+ this.sequence_header_length = stream.readUint16();
+ bit_reader.appendUint8(this.sequence_header_length);
+
+ this.sequence_header = new AVS3SequenceHeader(bit_reader);
+
+ // library_dependency_idc is in the AVS3DecoderConfigurationRecord
+ this.library_dependency_idc = new BinaryValue(stream.readUint8(), 2);
+ }
+ }
+}
diff --git a/src/boxes/avs-common.ts b/src/boxes/avs-common.ts
new file mode 100644
index 00000000..35cd9e74
--- /dev/null
+++ b/src/boxes/avs-common.ts
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2025. Paul Higgs
+ * License: BSD-3-Clause (see LICENSE file)
+ */
+
+export type DescriberFunction = (n: number) => string;
+
+export class DescribedValue {
+ private _value: number;
+ private _description: string;
+
+ constructor(value: number, descriptionFn?: DescriberFunction) {
+ this._value = value;
+ this._description = descriptionFn ? descriptionFn(value) : undefined;
+ }
+ hasOwnProperty(name: string) {
+ // we need to 'trick' the check done in generatePropertyValue() og boxHTMLTable.js
+ return name === 'toString';
+ }
+ toString() {
+ return `${this.value}${this._description && this._description.length > 0 ? ' (' + this._description + ')' : ''}`;
+ }
+ get value(): number {
+ return this._value;
+ }
+}
+
+export class HexadecimalValue {
+ private _value: number;
+ private _description: string;
+
+ constructor(value: number, descriptionFn?: DescriberFunction) {
+ this._value = value;
+ this._description = descriptionFn ? descriptionFn(value) : undefined;
+ }
+ hasOwnProperty(name: string) {
+ // we need to 'trick' the check done in generatePropertyValue() og boxHTMLTable.js
+ return name === 'toString';
+ }
+ toString() {
+ return `0x${this._value.toString(16)}${this._description && this._description.length > 0 ? ' (' + this._description + ')' : ''}`;
+ }
+ get value(): number {
+ return this._value;
+ }
+}
+
+export class BinaryValue {
+ private _value: number;
+ private _bits: number;
+ private _description: string;
+
+ constructor(value: number, bits: number, descriptionFn?: DescriberFunction) {
+ this._value = value;
+ this._bits = bits;
+ this._description = descriptionFn ? descriptionFn(value) : undefined;
+ }
+ hasOwnProperty(name: string) {
+ // we need to 'trick' the check done in generatePropertyValue() og boxHTMLTable.js
+ return name === 'toString';
+ }
+ toString() {
+ let res = 'b';
+ for (let i = this._bits; i > 0; i--) res += this._value & (1 << (i - 1)) ? '1' : '0';
+ return (
+ res +
+ (this._description && this._description.length > 0 ? ' (' + this._description + ')' : '')
+ );
+ }
+ get value(): number {
+ return this._value;
+ }
+}
+
+export class BooleanValue {
+ private _value: boolean;
+
+ constructor(value: boolean | number) {
+ this._value = typeof value === 'number' ? value > 0 : value;
+ }
+ hasOwnProperty(name: string) {
+ // we need to 'trick' the check done in generatePropertyValue() og boxHTMLTable.js
+ return name === 'toString';
+ }
+ toString() {
+ return `${this._value ? 1 : 0} (${this._value ? 'true' : 'false'})`;
+ }
+ get value(): boolean {
+ return this._value;
+ }
+}
+
+export class AVS3data {
+ /* not currently used to beautify output
+ toHTML(data: object): string {
+ let res = '';
+ const props = Object.getOwnPropertyNames(data);
+ if (props)
+ props.forEach(function (val) {
+ let fmt_val = '';
+ if (Array.isArray(data[val])) {
+ for (let i = 0; i < data[val].length; i++) {
+ const hex = data[val][i].toString(16);
+ fmt_val += hex.length === 1 ? '0' + hex : hex;
+ if (i % 4 === 3) fmt_val += ' ';
+ }
+ } else fmt_val = data[val];
+ res += `${val} | ${fmt_val} |
`;
+ });
+ return ``;
+ } */
+
+ hasOwnProperty(name: string) {
+ // we need to 'trick' the check done in generatePropertyValue() og boxHTMLTable.js
+ return name === 'toString';
+ }
+ toString(data: object): string {
+ let res = '';
+ const props = Object.getOwnPropertyNames(data);
+ props.forEach(function (val) {
+ let fmt_val = '';
+ if (Array.isArray(data[val])) {
+ for (let i = 0; i < data[val].length; i++) {
+ const hex = data[val][i].toString(16);
+ fmt_val += hex.length === 1 ? '0' + hex : hex;
+ if (i % 4 === 3) fmt_val += ' ';
+ }
+ } else fmt_val = data[val];
+ res += `${val}: ${fmt_val}\n`;
+ });
+ return res;
+ }
+}
diff --git a/src/boxes/dca3.ts b/src/boxes/dca3.ts
new file mode 100644
index 00000000..68d2c233
--- /dev/null
+++ b/src/boxes/dca3.ts
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2025. Paul Higgs
+ * License: BSD-3-Clause (see LICENSE file)
+ */
+
+import { Box } from '#/box';
+import type { MultiBufferStream } from '#/buffer';
+import { BitStream } from '#/BitStream';
+
+import { DescribedValue, AVS3data } from './avs-common';
+
+// values for audio_codec_id
+const HIGH_RATE_CODING = 0,
+ LOSSLESS_CODING = 1,
+ FULL_RATE_CODING = 2;
+
+// values for content_type
+const CHANNEL_BASED = 0,
+ OBJECT_BASED = 1,
+ CHANNEL_AND_OBJECT = 2,
+ HOA = 3;
+
+function AVS3Acodec(codec_id: number) {
+ const codecs = ['General High Rate', 'Lossless', 'General Full Rate'];
+ return codec_id < codecs.length ? codecs[codec_id] : 'undefined';
+}
+
+function AVS3Achannel_number(channel_number_index: number) {
+ const configs = [
+ 'Mono',
+ 'Stereo',
+ '5.1',
+ '7.1',
+ '10.2',
+ '22.2',
+ '4.0/FOA',
+ '5.1.2',
+ '5.1.4',
+ '7.1.2',
+ '7.1.4',
+ '3rd HOA',
+ '2nd HOA',
+ ];
+ return channel_number_index < configs.length ? configs[channel_number_index] : 'undefined';
+}
+
+function AVS3Asampling_frequency(sampling_frequency_index: number) {
+ const frequencies = [192000, 96000, 48000, 44100, 32000, 24000, 22050, 16000, 8000];
+ return sampling_frequency_index < frequencies.length
+ ? frequencies[sampling_frequency_index] + 'Hz'
+ : 'reserved';
+}
+
+function AVS3Aresolution(resolution: number) {
+ switch (resolution) {
+ case 0:
+ return '8 bits/sample';
+ case 1:
+ return '16 bits/sample';
+ case 2:
+ return '24 bits/sample';
+ }
+ return 'reserved';
+}
+
+function AVS3Anntype(nn_type: number) {
+ switch (nn_type) {
+ case 0:
+ return 'basic neural network';
+ case 1:
+ return 'low-complexity neural network';
+ }
+ return 'reserved';
+}
+
+function AVS3Acodingprofile(conding_profile: number) {
+ switch (conding_profile) {
+ case 0:
+ return 'basic framework';
+ case 1:
+ return 'object metadata framework';
+ case 2:
+ return 'HOA data coding framework';
+ }
+ return 'reserved';
+}
+
+interface GAconfig {
+ sampling_frequency_index?: DescribedValue;
+ nn_type?: DescribedValue;
+ content_type?: number;
+ channel_number_index?: DescribedValue;
+ number_objects?: number;
+ hoa_order?: number;
+ total_bitrate?: number;
+ resolution?: DescribedValue;
+}
+class AVS3GAConfig extends AVS3data {
+ data: GAconfig;
+ constructor(bit_reader: BitStream) {
+ super();
+ this.data = {};
+ this.deserialise(bit_reader);
+ }
+ deserialise(bit_reader: BitStream) {
+ this.data.sampling_frequency_index = new DescribedValue(
+ bit_reader.readBits(4),
+ AVS3Asampling_frequency,
+ );
+ this.data.nn_type = new DescribedValue(bit_reader.readBits(3), AVS3Anntype);
+ bit_reader.skipBits(1);
+ this.data.content_type = bit_reader.readBits(4);
+ if (this.data.content_type === CHANNEL_BASED) {
+ this.data.channel_number_index = new DescribedValue(
+ bit_reader.readBits(7),
+ AVS3Achannel_number,
+ );
+ bit_reader.skipBits(1);
+ } else if (this.data.content_type === OBJECT_BASED) {
+ this.data.number_objects = bit_reader.readBits(7);
+ bit_reader.skipBits(1);
+ } else if (this.data.content_type === CHANNEL_AND_OBJECT) {
+ this.data.channel_number_index = new DescribedValue(
+ bit_reader.readBits(7),
+ AVS3Achannel_number,
+ );
+ bit_reader.skipBits(1);
+ this.data.number_objects = bit_reader.readBits(7);
+ bit_reader.skipBits(1);
+ } else if (this.data.content_type === HOA) {
+ this.data.hoa_order = bit_reader.readBits(4);
+ }
+ this.data.total_bitrate = bit_reader.readUint16();
+ this.data.resolution = new DescribedValue(bit_reader.readBits(2), AVS3Aresolution);
+ }
+ toString(): string {
+ return super.toString(this.data);
+ }
+}
+
+interface GHconfig {
+ sampling_frequency_index?: number;
+ anc_data_index?: number;
+ coding_profile?: DescribedValue;
+ bitstream_type?: number;
+ channel_number_index?: number;
+ bitrate_index?: number;
+ raw_frame_length?: number;
+ resolution?: DescribedValue;
+ addition_info?: Array;
+}
+class AVS3GHConfig extends AVS3data {
+ data: GHconfig;
+ constructor(bit_reader: BitStream) {
+ super();
+ this.data = {};
+ this.deserialise(bit_reader);
+ }
+ deserialise(bit_reader: BitStream) {
+ this.data.sampling_frequency_index = bit_reader.readBits(4);
+ this.data.anc_data_index = bit_reader.readBit();
+ this.data.coding_profile = new DescribedValue(bit_reader.readBits(3), AVS3Acodingprofile);
+ this.data.bitstream_type = bit_reader.readBits(1);
+ this.data.channel_number_index = bit_reader.readBits(7);
+ this.data.bitrate_index = bit_reader.readBits(4);
+ this.data.raw_frame_length = bit_reader.readUint16();
+ this.data.resolution = new DescribedValue(bit_reader.readBits(2), AVS3Aresolution);
+ const addition_info_length = bit_reader.readUint16();
+ if (addition_info_length > 0) {
+ this.data.addition_info = [];
+ for (let i = 0; i < addition_info_length; i++)
+ this.data.addition_info.push(bit_reader.readUint8());
+ }
+ }
+ toString(): string {
+ return super.toString(this.data);
+ }
+}
+
+interface LLconfig {
+ sampling_frequency_index?: number;
+ sampling_frequency?: number;
+ anc_data_index?: number;
+ coding_profile?: DescribedValue;
+ channel_number?: number;
+ resolution?: DescribedValue;
+ addition_info?: Array;
+}
+class AVS3LLConfig extends AVS3data {
+ data: LLconfig;
+ constructor(bit_reader: BitStream) {
+ super();
+ this.data = {};
+ this.deserialise(bit_reader);
+ }
+ deserialise(bit_reader: BitStream) {
+ this.data.sampling_frequency_index = bit_reader.readBits(4);
+ if (this.data.sampling_frequency_index === 0xf)
+ this.data.sampling_frequency = bit_reader.readUint24();
+ this.data.anc_data_index = bit_reader.readBit();
+ this.data.coding_profile = new DescribedValue(bit_reader.readBits(3), AVS3Acodingprofile);
+ this.data.channel_number = bit_reader.readUint8();
+ this.data.resolution = new DescribedValue(bit_reader.readBits(2), AVS3Aresolution);
+ const addition_info_length = bit_reader.readUint16();
+ if (addition_info_length > 0) {
+ this.data.addition_info = [];
+ for (let i = 0; i < addition_info_length; i++)
+ this.data.addition_info.push(bit_reader.readUint8());
+ }
+ bit_reader.skipBits(2); // reserved
+ }
+ toString(): string {
+ return super.toString(this.data);
+ }
+}
+
+export class dca3Box extends Box {
+ static override readonly fourcc = 'dca3' as const;
+ box_name = 'AVS3AConfigurationBox' as const;
+
+ private audio_codec_id: DescribedValue;
+ private Avs3AudioGAConfig?: AVS3GAConfig;
+ private Avs3AudioGHConfig?: AVS3GHConfig;
+ private Avs3AudioLLConfig?: AVS3LLConfig;
+
+ parse(stream: MultiBufferStream) {
+ const bit_reader = new BitStream(stream);
+ bit_reader.appendUint8(this.size - this.hdr_size);
+ this.audio_codec_id = new DescribedValue(bit_reader.readBits(4), AVS3Acodec);
+
+ switch (this.audio_codec_id.value) {
+ case FULL_RATE_CODING:
+ this.Avs3AudioGAConfig = new AVS3GAConfig(bit_reader);
+ break;
+ case HIGH_RATE_CODING:
+ this.Avs3AudioGHConfig = new AVS3GHConfig(bit_reader);
+ break;
+ case LOSSLESS_CODING:
+ this.Avs3AudioLLConfig = new AVS3LLConfig(bit_reader);
+ break;
+ }
+ bit_reader.byte_alignment();
+ }
+
+ get_audio_codec_id_str() {
+ return this.audio_codec_id.value.toString(10).padStart(2, '0');
+ }
+}
diff --git a/src/boxes/sampleentries/sampleentry.ts b/src/boxes/sampleentries/sampleentry.ts
index 2d8c1bc8..2d1a354e 100644
--- a/src/boxes/sampleentries/sampleentry.ts
+++ b/src/boxes/sampleentries/sampleentry.ts
@@ -1,5 +1,7 @@
import { av1CBox } from '#/boxes/av1C';
import { avcCBox } from '#/boxes/avcC';
+import { av3cBox } from '#/boxes/av3c';
+import { dca3Box } from '#/boxes/dca3';
import { sinfBox } from '#/boxes/defaults';
import { esdsBox } from '#/boxes/esds';
import { hvcCBox } from '#/boxes/hvcC';
@@ -104,6 +106,23 @@ export class av01SampleEntry extends VisualSampleEntry {
}
}
+export class avs3SampleEntry extends VisualSampleEntry {
+ static override readonly fourcc = 'avs3' as const;
+ box_name = 'AVS3VideoSampleEntry' as const;
+
+ av3c: av3cBox;
+
+ getCodec(): string {
+ const profile = this.av3c.sequence_header?.data?.profile_id
+ ? this.av3c.sequence_header.data?.profile_id.value.toString(16)
+ : 'XX';
+ const level = this.av3c.sequence_header?.data?.level_id
+ ? this.av3c.sequence_header.data?.level_id.value.toString(16)
+ : 'XX';
+ return `${super.getCodec()}.${profile}.${level}`;
+ }
+}
+
export class dav1SampleEntry extends VisualSampleEntry {
static override readonly fourcc = 'dav1' as const;
}
@@ -347,10 +366,6 @@ export class vp09SampleEntry extends vpcCSampleEntryBase {
static override readonly fourcc = 'vp09' as const;
}
-export class avs3SampleEntry extends VisualSampleEntry {
- static override readonly fourcc = 'avs3' as const;
-}
-
export class j2kiSampleEntry extends VisualSampleEntry {
static override readonly fourcc = 'j2ki' as const;
// ISO/IEC 15444-16:2021 Section 7.3
@@ -437,6 +452,22 @@ export class fLaCSampleEntry extends AudioSampleEntry {
static override readonly fourcc = 'fLaC' as const;
}
+export class av3aSampleEntry extends AudioSampleEntry {
+ // AATF (AVS3 Audio Transport Format) audio sample entry
+ static override readonly fourcc = 'av3a' as const;
+ box_name = 'AVS3AudioSampleEntry' as const;
+
+ dca3: dca3Box;
+
+ getCodec() {
+ return `${super.getCodec()}.${this.dca3.get_audio_codec_id_str()}`;
+ }
+}
+
+export class a3asSampleEntry extends AudioSampleEntry {
+ static override readonly fourcc = 'a3as' as const;
+}
+
// Encrypted sample entries
export class encvSampleEntry extends VisualSampleEntry {
static override readonly fourcc = 'encv' as const;