diff --git a/.vscode/settings.json b/.vscode/settings.json index 4c2e136e..2477f5be 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,6 @@ { + "editor.tabSize": 2, + "editor.insertSpaces": true, "eslint.useFlatConfig": true, "eslint.workingDirectories": [{ "mode": "auto" }], "eslint.options": { diff --git a/demo/boxHtmlTable.js b/demo/boxHtmlTable.js index 8bfef615..7cee6745 100644 --- a/demo/boxHtmlTable.js +++ b/demo/boxHtmlTable.js @@ -59,7 +59,8 @@ function generateBoxTable(box, excluded_fields, additional_props, no_header) { if (i % 4 === 3) html += ' '; } } else { - if (box[prop].hasOwnProperty('toString') && typeof box[prop].toString === 'function') + if (typeof box[prop].toHTML === 'function') html += box[prop].toHTML(); + else if (box[prop].hasOwnProperty('toString') && typeof box[prop].toString === 'function') html += box[prop].toString(); else html += box[prop]; } diff --git a/entries/all-boxes.ts b/entries/all-boxes.ts index 87749bf0..a5765fd4 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'; @@ -18,6 +19,7 @@ export * from '#/boxes/cprt'; 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/BitBuffer.ts b/src/BitBuffer.ts new file mode 100644 index 00000000..09a036b4 --- /dev/null +++ b/src/BitBuffer.ts @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2023. Paul Higgs + * License: BSD-3-Clause (see LICENSE file) + * + * + * reads bits and bytes from a buffer that may not contain aligned values + */ + +import { Endianness } from '#/DataStream'; + +class State { + rbyte: number; + rbit: number; + wbyte: number; + wbit: number; + end: number; + read_error: boolean; + write_error: boolean; + + constructor() { + this.rbyte = this.rbit = this.rbyte = this.rbit = this.end - 0; + this.read_error = this.write_error = false; + } +} + +export class BitBuffer { + private endianness: Endianness; + private _buffer: Array; + private _buffer_size: number; + private _state: State; + private _big_endian = true; // results are returned Big Endian + + constructor(stream?: Uint8Array, endianness?: Endianness) { + this._state = new State(); + this.load(stream ? stream : new Uint8Array([])); + this.endianness = endianness ? endianness : this._ENDIANNESS(); + } + + load(stream: Uint8Array): void { + this._buffer = [...stream]; + this._buffer_size = stream.length; + this._state.rbit = this._state.rbyte = 0; + this._state.wbit = 0; + this._state.wbyte = this._state.end = this._buffer_size; + this._state.read_error = this._state.write_error = false; + } + appendUint8(byte: number): void { + this._buffer.push(byte); + this._buffer_size += 1; + this._state.end = this._state.wbyte = this._buffer_size; + } + + getBit(): 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._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._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.getBit() !== 0; + } + + private _rdb(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._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!! + } + + getUint8() { + return this._rdb(1); + } + + getUint16() { + return this._big_endian ? this._GetUInt16BE(this._rdb(2)) : this._GetUInt16LE(this._rdb(2)); + } + private _ByteSwap16 = function (x) { + return (x << 8) | (x >> 8); + }; + private _CondByteSwap16BE = function (val) { + return this._OSisLittleEndian() ? this._ByteSwap16(val) : val; + }; + private _CondByteSwap16LE = function (val) { + return this._OSisLittleEndian() ? val : this._ByteSwap16(val); + }; + private _GetUInt16BE = function (val) { + return this._CondByteSwap16BE(val); + }; + private _GetUInt16LE = function (val) { + return this._CondByteSwap16LE(val); + }; + + getUint24() { + return this._big_endian ? this._GetUInt24BE(this._rdb(3)) : this._GetUInt24LE(this._rdb(3)); + } + + private _ByteSwap24 = function (x) { + return ((x & 0xff0000) >> 16) | (x & 0xff00) | (x & (0xff << 16)); + }; + private _CondByteSwap24BE = function (val) { + return this._OSisLittleEndian() ? this._ByteSwap24(val) : val; + }; + private _CondByteSwap24LE = function (val) { + return this._OSisLittleEndian() ? val : this._ByteSwap24(val); + }; + private _GetUInt24BE = function (val) { + return this._CondByteSwap24BE(val); + }; + private _GetUInt24LE = function (val) { + return this._CondByteSwap24LE(val); + }; + + getUint32() { + return this._big_endian ? this._GetUInt32BE(this._rdb(4)) : this._GetUInt32LE(this._rdb(4)); + } + + private _ByteSwap32(x) { + return (x << 24) | ((x << 8) & 0x00ff0000) | ((x >> 8) & 0x0000ff00) | (x >> 24); + } + private _CondByteSwap32BE(val) { + return this._OSisLittleEndian() ? this._ByteSwap32(val) : val; + } + private _CondByteSwap32LE(val) { + return this._OSisLittleEndian() ? val : this._ByteSwap32(val); + } + private _GetUInt32BE(val) { + return this._CondByteSwap32BE(val); + } + private _GetUInt32LE(val) { + return this._CondByteSwap32LE(val); + } + + getBits(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._big_endian) { + // Read leading bits up to byte boundary + while (bits > 0 && this._state.rbit !== 0) { + val = (val << 1) | this.getBit(); + --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.getBit(); + --bits; + } + } else { + // Little endian decoding + let shift = 0; + + // Read leading bits up to byte boundary + while (bits > 0 && this._state.rbit !== 0) { + val |= this.getBit() << 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.getBit() << 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); + } + + getUE(): number { + // read in an unsigned Exp-Golomb code; + if (this.getBit() === 1) return 0; + let zero_count = 1; + while (this.peekBit() === 0) { + this.getBit(); + zero_count++; + } + return this.getBits(zero_count + 1) - 1; + } + + byte_alignment() { + while (!this._state.read_error && this._state.rbit !== 0) this.skipBit(); + } + + private _OSisLittleEndian() { + return this.endianness === Endianness.LITTLE_ENDIAN; + } + + private _ENDIANNESS(): Endianness { + const buf = new ArrayBuffer(4); + const u32data = new Uint32Array(buf); + const u8data = new Uint8Array(buf); + u32data[0] = 0xcafebabe; + return u8data[3] === 0xca ? Endianness.BIG_ENDIAN : Endianness.LITTLE_ENDIAN; + } + + currentReadByteOffset() { + return this._state.rbyte; + } + currentReadBitOffset() { + return 8 * this._state.rbyte + this._state.rbit; + } + currentWriteByteOffset() { + return this._state.wbyte; + } + currentWriteBitOffset() { + return 8 * this._state.wbyte + this._state.wbit; + } + bitsRemaining() { + return this.currentWriteBitOffset() - this.currentReadBitOffset(); + } +} diff --git a/src/boxes/av3c.ts b/src/boxes/av3c.ts new file mode 100644 index 00000000..1d165816 --- /dev/null +++ b/src/boxes/av3c.ts @@ -0,0 +1,510 @@ +/* + * Copyright (c) 2025. Paul Higgs + * License: BSD-3-Clause (see LICENSE file) + */ + +import { Box } from '#/box'; +import { MP4BoxStream } from '#/stream'; +import { BitBuffer } from '#/BitBuffer'; + +import { DescribedValue, AVS3data, HexadecimalValue, BinaryValue } from './avs-common'; + +interface ReferencePicture { + library_index_flag?: number; + referenced_library_picture_index?: number; + abs_delta_doi?: number; + sign_delta_doi?: number; +} + +class ReferencePictureSet { + list: number; + set: number; + pics: Array; + 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) { + 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 { + list?: number; + 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: BitBuffer) { + 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.getUE()); + 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(function (e) { + return e.profile === profile; + }); + return t === undefined ? RESERVED : t.description; +} + +function AVS3level(level: number) { + const t = AVS3levels.find(function (e) { + return e.level === level; + }); + return t === undefined ? RESERVED : t.description; +} + +function AVS3precision(precision: number) { + const t = AVS3precisions.find(function (e) { + return 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?: number; + field_coded_sequence?: number; + library_stream_flag?: number; + library_picture_enable_flag?: number; + duplicate_sequence_number_flag?: number; + 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?: number; + max_dpb_minus1?: number; + bbv_buffer_size?: number; + rpl1_index_exist_flag?: number; + rpl1_same_as_rpl0_flag?: number; + 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?: number; + load_seq_weight_quant_data_flag?: number; + weight_quant_matrix?: WeightQuantMatrix; + st_enable_flag?: number; + sao_enable_flag?: number; + alf_enable_flag?: number; + affine_enable_flag?: number; + smvd_enable_flag?: number; + ipcm_enable_flag?: number; + amvr_enable_flag?: number; + num_of_hmvp_cand?: number; + umve_enable_flag?: number; + emvr_enable_flag?: number; + intra_pf_enable_flag?: number; + tscpm_enable_flag?: number; + dt_enable_flag?: number; + log2_max_dt_size_minus4?: number; + pbt_enable_flag?: number; + pmc_enable_flag?: number; + iip_enable_flag?: number; + sawp_enable_flag?: number; + asr_enable_flag?: number; + awp_enable_flag?: number; + etmvp_mvap_enable_flag?: number; + dmvr_enable_flag?: number; + bio_enable_flag?: number; + bgc_enable_flag?: number; + inter_pf_enable_flag?: number; + inter_pfc_enable_flag?: number; + obmc_enable_flag?: number; + sbt_enable_flag?: number; + ist_enable_flag?: number; + esao_enable_flag?: number; + ccsao_enable_flag?: number; + ealf_enable_flag?: number; + ibc_enable_flag?: number; + isc_enable_flag?: number; + num_of_intra_hmvp_cand?: number; + fimc_enable_flag?: number; + nn_tools_set_hook?: number; + num_of_nn_filter_minus1?: number; + output_reorder_delay?: number; + cross_patch_loop_filter_enable_flag?: number; + ref_colocated_patch_flag?: number; + stable_patch_flag?: number; + uniform_patch_flag?: number; + patch_width_minus1?: number; + patch_height_minus1?: number; +} + +class AVS3SequenceHeader extends AVS3data { + data: SequenceHeaderElements; + + constructor(bit_reader: BitBuffer) { + super(); + this.data = {}; + this.load(bit_reader); + } + load(bit_reader: BitBuffer) { + this.data.video_sequence_start_code = new HexadecimalValue(bit_reader.getUint32()); + this.data.profile_id = new HexadecimalValue(bit_reader.getUint8(), AVS3profile); + this.data.level_id = new HexadecimalValue(bit_reader.getUint8(), AVS3level); + this.data.progressive_sequence = bit_reader.getBit(); + this.data.field_coded_sequence = bit_reader.getBit(); + this.data.library_stream_flag = bit_reader.getBit(); + if (!this.data.library_stream_flag) { + this.data.library_picture_enable_flag = bit_reader.getBit(); + if (this.data.library_picture_enable_flag) + this.data.duplicate_sequence_number_flag = bit_reader.getBit(); + } + bit_reader.skipBit(); // marker_bit + + this.data.horizontal_size = bit_reader.getBits(14); + bit_reader.skipBit(); // marker_bit + + this.data.vertical_size = bit_reader.getBits(14); + this.data.chroma_format = new BinaryValue(bit_reader.getBits(2), 2, AVS3chroma); + this.data.sample_precision = new BinaryValue(bit_reader.getBits(3), 3, AVS3precision); + + if (this.data.profile_id.get() === MAIN_10 || this.data.profile_id.get() === HIGH_10) + this.data.encoding_precision = new BinaryValue(bit_reader.getBits(3), 3, AVS3precision); + bit_reader.skipBit(); // marker_bit + + this.data.aspect_ratio = new BinaryValue(bit_reader.getBits(4), 4, AVS3aspectratio); + this.data.frame_rate_code = new BinaryValue(bit_reader.getBits(4), 4, AVS3framerate); + bit_reader.skipBit(); // marker_bit + + this.data.bit_rate_lower = bit_reader.getBits(18); + bit_reader.skipBit(); // marker_bit + + this.data.bit_rate_upper = bit_reader.getBits(12); + this.data.low_delay = bit_reader.getBit(); + this.data.temporal_id_enable_flag = bit_reader.getBit(); + bit_reader.skipBit(); // marker_bit + + this.data.bbv_buffer_size = bit_reader.getBits(18); + bit_reader.skipBit(); // marker_bit + + this.data.max_dpb_minus1 = bit_reader.getBits(4); + this.data.rpl1_index_exist_flag = bit_reader.getBit(); + this.data.rpl1_same_as_rpl0_flag = bit_reader.getBit(); + bit_reader.skipBit(); // marker_bit + + const reference_picture_list = function ( + list: number, + rpls: number, + library_picture_enable_flag: number, + ) { + const this_set = new ReferencePictureSet(list, rpls); + if (library_picture_enable_flag) + this_set.set_reference_to_library_enable_flag(bit_reader.getBit()); + const num_of_ref_pic = bit_reader.getUE(); + 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.getBit(); + if (LibraryIndexFlag !== 0) this_pic.referenced_library_picture_index = bit_reader.getUE(); + else { + this_pic.abs_delta_doi = bit_reader.getUE(); + if (this_pic.abs_delta_doi > 0) this_pic.sign_delta_doi = bit_reader.getBit(); + } + this_set.push(this_pic); + } + return this_set; + }; + + this.data.num_ref_pic_list_set0 = bit_reader.getUE(); + 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)); + + if (!this.data.rpl1_same_as_rpl0_flag) { + this.data.num_ref_pic_list_set1 = bit_reader.getUE(); + 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)); + } + + this.data.num_ref_default_active_minus1_0 = bit_reader.getUE(); + this.data.num_ref_default_active_minus1_1 = bit_reader.getUE(); + this.data.log2_lcu_size_minus2 = bit_reader.getBits(3); + this.data.log2_min_cu_size_minus2 = bit_reader.getBits(2); + this.data.log2_max_part_ratio_minus2 = bit_reader.getBits(2); + this.data.max_split_times_minus6 = bit_reader.getBits(3); + this.data.log2_min_qt_size_minus2 = bit_reader.getBits(3); + this.data.log2_max_bt_size_minus2 = bit_reader.getBits(3); + this.data.log2_max_eqt_size_minus3 = bit_reader.getBits(2); + bit_reader.skipBit(); // marker_bit + + this.data.weight_quant_enable_flag = bit_reader.getBit(); + if (this.data.weight_quant_enable_flag) { + this.data.load_seq_weight_quant_data_flag = bit_reader.getBit(); + if (this.data.load_seq_weight_quant_data_flag) + this.data.weight_quant_matrix = new WeightQuantMatrix(bit_reader); + } + + this.data.st_enable_flag = bit_reader.getBit(); + this.data.sao_enable_flag = bit_reader.getBit(); + this.data.alf_enable_flag = bit_reader.getBit(); + this.data.affine_enable_flag = bit_reader.getBit(); + this.data.smvd_enable_flag = bit_reader.getBit(); + this.data.ipcm_enable_flag = bit_reader.getBit(); + this.data.amvr_enable_flag = bit_reader.getBit(); + this.data.num_of_hmvp_cand = bit_reader.getBits(4); + this.data.umve_enable_flag = bit_reader.getBit(); + if (this.data.num_of_hmvp_cand !== 0 && this.data.amvr_enable_flag) + this.data.emvr_enable_flag = bit_reader.getBit(); + this.data.intra_pf_enable_flag = bit_reader.getBit(); + this.data.tscpm_enable_flag = bit_reader.getBit(); + bit_reader.skipBit(); // marker_bit + + this.data.dt_enable_flag = bit_reader.getBit(); + if (this.data.dt_enable_flag) this.data.log2_max_dt_size_minus4 = bit_reader.getBits(2); + this.data.pbt_enable_flag = bit_reader.getBit(); + + if (this.data.profile_id.get() === MAIN_10 || this.data.profile_id.get() === HIGH_10) { + this.data.pmc_enable_flag = bit_reader.getBit(); + this.data.iip_enable_flag = bit_reader.getBit(); + this.data.sawp_enable_flag = bit_reader.getBit(); + if (this.data.affine_enable_flag) this.data.asr_enable_flag = bit_reader.getBit(); + this.data.awp_enable_flag = bit_reader.getBit(); + this.data.etmvp_mvap_enable_flag = bit_reader.getBit(); + this.data.dmvr_enable_flag = bit_reader.getBit(); + this.data.bio_enable_flag = bit_reader.getBit(); + this.data.bgc_enable_flag = bit_reader.getBit(); + this.data.inter_pf_enable_flag = bit_reader.getBit(); + this.data.inter_pfc_enable_flag = bit_reader.getBit(); + this.data.obmc_enable_flag = bit_reader.getBit(); + + this.data.sbt_enable_flag = bit_reader.getBit(); + this.data.ist_enable_flag = bit_reader.getBit(); + + this.data.esao_enable_flag = bit_reader.getBit(); + this.data.ccsao_enable_flag = bit_reader.getBit(); + if (this.data.alf_enable_flag) this.data.ealf_enable_flag = bit_reader.getBit(); + this.data.ibc_enable_flag = bit_reader.getBit(); + bit_reader.skipBit(); // marker_bit + + this.data.isc_enable_flag = bit_reader.getBit(); + if (this.data.ibc_enable_flag || this.data.isc_enable_flag) + this.data.num_of_intra_hmvp_cand = bit_reader.getBits(4); + this.data.fimc_enable_flag = bit_reader.getBit(); + this.data.nn_tools_set_hook = bit_reader.getBits(8); + if (this.data.nn_tools_set_hook & 0x01) + this.data.num_of_nn_filter_minus1 = bit_reader.getUE(); + bit_reader.skipBit(); // marker_bit + } + if (this.data.low_delay === 0) this.data.output_reorder_delay = bit_reader.getBits(5); + this.data.cross_patch_loop_filter_enable_flag = bit_reader.getBit(); + this.data.ref_colocated_patch_flag = bit_reader.getBit(); + this.data.stable_patch_flag = bit_reader.getBit(); + if (this.data.stable_patch_flag) { + this.data.uniform_patch_flag = bit_reader.getBit(); + if (this.data.uniform_patch_flag) { + bit_reader.skipBit(); // marker_bit + this.data.patch_width_minus1 = bit_reader.getUE(); + this.data.patch_height_minus1 = bit_reader.getUE(); + } + } + bit_reader.skipBits(2); // reserved bits + } + toHTML() { + return super.toHTML(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: MP4BoxStream) { + const bit_reader = new BitBuffer(); + this.configurationVersion = new DescribedValue(stream.readUint8(), AVS3Vconfiguration); + if (this.configurationVersion.get() === 1) { + this.sequence_header_length = stream.readUint16(); + for (let i = 0; i < this.sequence_header_length; i++) + bit_reader.appendUint8(stream.readUint8()); + + 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..80449f23 --- /dev/null +++ b/src/boxes/avs-common.ts @@ -0,0 +1,78 @@ +/* + * 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) : null; + } + toString() { + return this.value + (this.description ? ' (' + this.description + ')' : ''); + } + get() { + 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) : null; + } + toString() { + return '0x' + this.value.toString(16) + (this.description ? ' (' + this.description + ')' : ''); + } + get() { + 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) : null; + } + 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 + ')' : ''); + } + public get() { + return this.value; + } +} + +export class AVS3data { + toHTML(data): 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 '' + res + '
'; + } +} diff --git a/src/boxes/dca3.ts b/src/boxes/dca3.ts new file mode 100644 index 00000000..abb4133a --- /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 { MP4BoxStream } from '#/stream'; +import { BitBuffer } from '#/BitBuffer'; + +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: BitBuffer) { + super(); + this.data = {}; + this.parse(bit_reader); + } + parse(bit_reader: BitBuffer) { + this.data.sampling_frequency_index = new DescribedValue( + bit_reader.getBits(4), + AVS3Asampling_frequency, + ); + this.data.nn_type = new DescribedValue(bit_reader.getBits(3), AVS3Anntype); + bit_reader.skipBits(1); + this.data.content_type = bit_reader.getBits(4); + if (this.data.content_type === CHANNEL_BASED) { + this.data.channel_number_index = new DescribedValue( + bit_reader.getBits(7), + AVS3Achannel_number, + ); + bit_reader.skipBits(1); + } else if (this.data.content_type === OBJECT_BASED) { + this.data.number_objects = bit_reader.getBits(7); + bit_reader.skipBits(1); + } else if (this.data.content_type === CHANNEL_AND_OBJECT) { + this.data.channel_number_index = new DescribedValue( + bit_reader.getBits(7), + AVS3Achannel_number, + ); + bit_reader.skipBits(1); + this.data.number_objects = bit_reader.getBits(7); + bit_reader.skipBits(1); + } else if (this.data.content_type === HOA) { + this.data.hoa_order = bit_reader.getBits(4); + } + this.data.total_bitrate = bit_reader.getUint16(); + this.data.resolution = new DescribedValue(bit_reader.getBits(2), AVS3Aresolution); + } + toHTML(): string { + return super.toHTML(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: BitBuffer) { + super(); + this.data = {}; + this.parse(bit_reader); + } + parse(bit_reader: BitBuffer) { + this.data.sampling_frequency_index = bit_reader.getBits(4); + this.data.anc_data_index = bit_reader.getBit(); + this.data.coding_profile = new DescribedValue(bit_reader.getBits(3), AVS3Acodingprofile); + this.data.bitstream_type = bit_reader.getBits(1); + this.data.channel_number_index = bit_reader.getBits(7); + this.data.bitrate_index = bit_reader.getBits(4); + this.data.raw_frame_length = bit_reader.getUint16(); + this.data.resolution = new DescribedValue(bit_reader.getBits(2), AVS3Aresolution); + const addition_info_length = bit_reader.getUint16(); + 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.getUint8()); + } + } + toHTML(): string { + return super.toHTML(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: BitBuffer) { + super(); + this.data = {}; + this.parse(bit_reader); + } + parse(bit_reader: BitBuffer) { + this.data.sampling_frequency_index = bit_reader.getBits(4); + if (this.data.sampling_frequency_index === 0xf) + this.data.sampling_frequency = bit_reader.getUint24(); + this.data.anc_data_index = bit_reader.getBit(); + this.data.coding_profile = new DescribedValue(bit_reader.getBits(3), AVS3Acodingprofile); + this.data.channel_number = bit_reader.getUint8(); + this.data.resolution = new DescribedValue(bit_reader.getBits(2), AVS3Aresolution); + const addition_info_length = bit_reader.getUint16(); + 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.getUint8()); + } + bit_reader.skipBits(2); // reserved + } + toHTML(): string { + return super.toHTML(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: MP4BoxStream) { + const bit_reader = new BitBuffer(); + for (let i = 0; i < this.size - this.hdr_size; i++) bit_reader.appendUint8(stream.readUint8()); + this.audio_codec_id = new DescribedValue(bit_reader.getBits(4), AVS3Acodec); + + switch (this.audio_codec_id.get()) { + 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.get() < 9 ? '0' : '') + this.audio_codec_id.get(); + } +} diff --git a/src/boxes/sampleentries/sampleentry.ts b/src/boxes/sampleentries/sampleentry.ts index da769006..b222b62d 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'; @@ -92,6 +94,26 @@ export class av01SampleEntry extends VisualSampleEntry { } } +export class avs3SampleEntry extends VisualSampleEntry { + av3c: av3cBox; + static override readonly fourcc = 'avs3' as const; + + getCodec(): string { + const baseCodec = super.getCodec(); + return ( + baseCodec + + '.' + + (this.av3c.sequence_header?.data?.profile_id + ? this.av3c.sequence_header.data?.profile_id.get().toString(16) + : 'XX') + + '.' + + (this.av3c.sequence_header?.data?.level_id + ? this.av3c.sequence_header.data?.level_id.get().toString(16) + : 'XX') + ); + } +} + export class dav1SampleEntry extends VisualSampleEntry { static override readonly fourcc = 'dav1' as const; } @@ -294,10 +316,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; } @@ -376,6 +394,19 @@ export class fLaCSampleEntry extends AudioSampleEntry { static override readonly fourcc = 'fLaC' as const; } +export class av3aSampleEntry extends AudioSampleEntry { + dca3: dca3Box; + static override readonly fourcc = 'av3a' as const; + getCodec() { + const baseCodec = super.getCodec(); + return baseCodec + '.' + 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;