mediabunny 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/mediabunny.d.ts +4 -2
- package/dist/modules/codec-data.d.ts +95 -0
- package/dist/modules/codec-data.d.ts.map +1 -0
- package/dist/modules/codec-data.js +1103 -0
- package/dist/modules/codec.d.ts +250 -0
- package/dist/modules/codec.d.ts.map +1 -0
- package/dist/modules/codec.js +1100 -0
- package/dist/modules/conversion.d.ts +158 -0
- package/dist/modules/conversion.d.ts.map +1 -0
- package/dist/modules/conversion.js +866 -0
- package/dist/modules/custom-coder.d.ts +119 -0
- package/dist/modules/custom-coder.d.ts.map +1 -0
- package/dist/modules/custom-coder.js +91 -0
- package/dist/modules/demuxer.d.ts +17 -0
- package/dist/modules/demuxer.d.ts.map +1 -0
- package/dist/modules/demuxer.js +12 -0
- package/dist/modules/index.d.ts +25 -0
- package/dist/modules/index.d.ts.map +1 -0
- package/dist/modules/index.js +24 -0
- package/dist/modules/input-format.d.ts +121 -0
- package/dist/modules/input-format.d.ts.map +1 -0
- package/dist/modules/input-format.js +333 -0
- package/dist/modules/input-track.d.ts +138 -0
- package/dist/modules/input-track.d.ts.map +1 -0
- package/dist/modules/input-track.js +230 -0
- package/dist/modules/input.d.ts +55 -0
- package/dist/modules/input.d.ts.map +1 -0
- package/dist/modules/input.js +104 -0
- package/dist/modules/isobmff/isobmff-boxes.d.ts +202 -0
- package/dist/modules/isobmff/isobmff-boxes.d.ts.map +1 -0
- package/dist/modules/isobmff/isobmff-boxes.js +1111 -0
- package/dist/modules/isobmff/isobmff-demuxer.d.ts +163 -0
- package/dist/modules/isobmff/isobmff-demuxer.d.ts.map +1 -0
- package/dist/modules/isobmff/isobmff-demuxer.js +2080 -0
- package/dist/modules/isobmff/isobmff-misc.d.ts +14 -0
- package/dist/modules/isobmff/isobmff-misc.d.ts.map +1 -0
- package/dist/modules/isobmff/isobmff-misc.js +20 -0
- package/dist/modules/isobmff/isobmff-muxer.d.ts +142 -0
- package/dist/modules/isobmff/isobmff-muxer.d.ts.map +1 -0
- package/dist/modules/isobmff/isobmff-muxer.js +858 -0
- package/dist/modules/isobmff/isobmff-reader.d.ts +36 -0
- package/dist/modules/isobmff/isobmff-reader.d.ts.map +1 -0
- package/dist/modules/isobmff/isobmff-reader.js +105 -0
- package/dist/modules/matroska/ebml.d.ts +156 -0
- package/dist/modules/matroska/ebml.d.ts.map +1 -0
- package/dist/modules/matroska/ebml.js +529 -0
- package/dist/modules/matroska/matroska-demuxer.d.ts +119 -0
- package/dist/modules/matroska/matroska-demuxer.d.ts.map +1 -0
- package/dist/modules/matroska/matroska-demuxer.js +1330 -0
- package/dist/modules/matroska/matroska-misc.d.ts +14 -0
- package/dist/modules/matroska/matroska-misc.d.ts.map +1 -0
- package/dist/modules/matroska/matroska-misc.js +20 -0
- package/dist/modules/matroska/matroska-muxer.d.ts +71 -0
- package/dist/modules/matroska/matroska-muxer.d.ts.map +1 -0
- package/dist/modules/matroska/matroska-muxer.js +729 -0
- package/dist/modules/media-sink.d.ts +272 -0
- package/dist/modules/media-sink.d.ts.map +1 -0
- package/dist/modules/media-sink.js +1264 -0
- package/dist/modules/media-source.d.ts +230 -0
- package/dist/modules/media-source.d.ts.map +1 -0
- package/dist/modules/media-source.js +1095 -0
- package/dist/modules/misc.d.ts +127 -0
- package/dist/modules/misc.d.ts.map +1 -0
- package/dist/modules/misc.js +462 -0
- package/dist/modules/mp3/mp3-demuxer.d.ts +32 -0
- package/dist/modules/mp3/mp3-demuxer.d.ts.map +1 -0
- package/dist/modules/mp3/mp3-demuxer.js +160 -0
- package/dist/modules/mp3/mp3-misc.d.ts +37 -0
- package/dist/modules/mp3/mp3-misc.d.ts.map +1 -0
- package/dist/modules/mp3/mp3-misc.js +122 -0
- package/dist/modules/mp3/mp3-muxer.d.ts +27 -0
- package/dist/modules/mp3/mp3-muxer.d.ts.map +1 -0
- package/dist/modules/mp3/mp3-muxer.js +112 -0
- package/dist/modules/mp3/mp3-reader.d.ts +25 -0
- package/dist/modules/mp3/mp3-reader.d.ts.map +1 -0
- package/dist/modules/mp3/mp3-reader.js +73 -0
- package/dist/modules/mp3/mp3-writer.d.ts +30 -0
- package/dist/modules/mp3/mp3-writer.d.ts.map +1 -0
- package/dist/modules/mp3/mp3-writer.js +72 -0
- package/dist/modules/muxer.d.ts +32 -0
- package/dist/modules/muxer.d.ts.map +1 -0
- package/dist/modules/muxer.js +50 -0
- package/dist/modules/ogg/ogg-demuxer.d.ts +53 -0
- package/dist/modules/ogg/ogg-demuxer.d.ts.map +1 -0
- package/dist/modules/ogg/ogg-demuxer.js +701 -0
- package/dist/modules/ogg/ogg-misc.d.ts +27 -0
- package/dist/modules/ogg/ogg-misc.d.ts.map +1 -0
- package/dist/modules/ogg/ogg-misc.js +78 -0
- package/dist/modules/ogg/ogg-muxer.d.ts +58 -0
- package/dist/modules/ogg/ogg-muxer.d.ts.map +1 -0
- package/dist/modules/ogg/ogg-muxer.js +345 -0
- package/dist/modules/ogg/ogg-reader.d.ts +37 -0
- package/dist/modules/ogg/ogg-reader.d.ts.map +1 -0
- package/dist/modules/ogg/ogg-reader.js +104 -0
- package/dist/modules/output-format.d.ts +285 -0
- package/dist/modules/output-format.d.ts.map +1 -0
- package/dist/modules/output-format.js +385 -0
- package/dist/modules/output.d.ts +134 -0
- package/dist/modules/output.d.ts.map +1 -0
- package/dist/modules/output.js +269 -0
- package/dist/modules/packet.d.ts +86 -0
- package/dist/modules/packet.d.ts.map +1 -0
- package/dist/modules/packet.js +133 -0
- package/dist/modules/pcm.d.ts +12 -0
- package/dist/modules/pcm.d.ts.map +1 -0
- package/dist/modules/pcm.js +85 -0
- package/dist/modules/reader.d.ts +40 -0
- package/dist/modules/reader.d.ts.map +1 -0
- package/dist/modules/reader.js +153 -0
- package/dist/modules/sample.d.ts +186 -0
- package/dist/modules/sample.d.ts.map +1 -0
- package/dist/modules/sample.js +798 -0
- package/dist/modules/source.d.ts +76 -0
- package/dist/modules/source.d.ts.map +1 -0
- package/dist/modules/source.js +183 -0
- package/dist/modules/subtitles.d.ts +37 -0
- package/dist/modules/subtitles.d.ts.map +1 -0
- package/dist/modules/subtitles.js +89 -0
- package/dist/modules/target.d.ts +58 -0
- package/dist/modules/target.d.ts.map +1 -0
- package/dist/modules/target.js +63 -0
- package/dist/modules/wave/riff-reader.d.ts +19 -0
- package/dist/modules/wave/riff-reader.d.ts.map +1 -0
- package/dist/modules/wave/riff-reader.js +38 -0
- package/dist/modules/wave/riff-writer.d.ts +18 -0
- package/dist/modules/wave/riff-writer.d.ts.map +1 -0
- package/dist/modules/wave/riff-writer.js +25 -0
- package/dist/modules/wave/wave-demuxer.d.ts +42 -0
- package/dist/modules/wave/wave-demuxer.d.ts.map +1 -0
- package/dist/modules/wave/wave-demuxer.js +239 -0
- package/dist/modules/wave/wave-muxer.d.ts +27 -0
- package/dist/modules/wave/wave-muxer.d.ts.map +1 -0
- package/dist/modules/wave/wave-muxer.js +108 -0
- package/dist/modules/writer.d.ts +85 -0
- package/dist/modules/writer.d.ts.map +1 -0
- package/dist/modules/writer.js +347 -0
- package/package.json +14 -10
- package/src/codec-data.ts +1394 -0
- package/src/codec.ts +1392 -0
- package/src/conversion.ts +1199 -0
- package/src/custom-coder.ts +162 -0
- package/src/demuxer.ts +22 -0
- package/src/index.ts +148 -0
- package/src/input-format.ts +386 -0
- package/src/input-track.ts +341 -0
- package/src/input.ts +138 -0
- package/src/isobmff/isobmff-boxes.ts +1365 -0
- package/src/isobmff/isobmff-demuxer.ts +2646 -0
- package/src/isobmff/isobmff-misc.ts +29 -0
- package/src/isobmff/isobmff-muxer.ts +1210 -0
- package/src/isobmff/isobmff-reader.ts +137 -0
- package/src/matroska/ebml.ts +595 -0
- package/src/matroska/matroska-demuxer.ts +1677 -0
- package/src/matroska/matroska-misc.ts +29 -0
- package/src/matroska/matroska-muxer.ts +999 -0
- package/src/media-sink.ts +1577 -0
- package/src/media-source.ts +1406 -0
- package/src/misc.ts +584 -0
- package/src/mp3/mp3-demuxer.ts +228 -0
- package/src/mp3/mp3-misc.ts +154 -0
- package/src/mp3/mp3-muxer.ts +153 -0
- package/src/mp3/mp3-reader.ts +93 -0
- package/src/mp3/mp3-writer.ts +112 -0
- package/src/muxer.ts +88 -0
- package/src/ogg/ogg-demuxer.ts +985 -0
- package/src/ogg/ogg-misc.ts +116 -0
- package/src/ogg/ogg-muxer.ts +480 -0
- package/src/ogg/ogg-reader.ts +143 -0
- package/src/output-format.ts +689 -0
- package/src/output.ts +427 -0
- package/src/packet.ts +180 -0
- package/src/pcm.ts +112 -0
- package/src/reader.ts +205 -0
- package/src/sample.ts +1029 -0
- package/src/source.ts +279 -0
- package/src/subtitles.ts +131 -0
- package/src/target.ts +106 -0
- package/src/tsconfig.json +13 -0
- package/src/wave/riff-reader.ts +48 -0
- package/src/wave/riff-writer.ts +30 -0
- package/src/wave/wave-demuxer.ts +317 -0
- package/src/wave/wave-muxer.ts +145 -0
- package/src/writer.ts +461 -0
- /package/dist/{mediabunny.js → bundles/mediabunny.cjs} +0 -0
- /package/dist/{mediabunny.min.js → bundles/mediabunny.min.cjs} +0 -0
- /package/dist/{mediabunny.min.mjs → bundles/mediabunny.min.mjs} +0 -0
- /package/dist/{mediabunny.mjs → bundles/mediabunny.mjs} +0 -0
|
@@ -0,0 +1,1103 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2025-present, Vanilagy and contributors
|
|
3
|
+
*
|
|
4
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
import { VP9_LEVEL_TABLE } from './codec.js';
|
|
9
|
+
import { assert, Bitstream, last, readExpGolomb, readSignedExpGolomb, toDataView } from './misc.js';
|
|
10
|
+
// References for AVC/HEVC code:
|
|
11
|
+
// ISO 14496-15
|
|
12
|
+
// Rec. ITU-T H.264
|
|
13
|
+
// Rec. ITU-T H.265
|
|
14
|
+
// https://stackoverflow.com/questions/24884827
|
|
15
|
+
/** Finds all NAL units in an AVC packet in Annex B format. */
|
|
16
|
+
const findNalUnitsInAnnexB = (packetData) => {
|
|
17
|
+
const nalUnits = [];
|
|
18
|
+
let i = 0;
|
|
19
|
+
while (i < packetData.length) {
|
|
20
|
+
let startCodePos = -1;
|
|
21
|
+
let startCodeLength = 0;
|
|
22
|
+
for (let j = i; j < packetData.length - 3; j++) {
|
|
23
|
+
// Check for 3-byte start code (0x000001)
|
|
24
|
+
if (packetData[j] === 0 && packetData[j + 1] === 0 && packetData[j + 2] === 1) {
|
|
25
|
+
startCodePos = j;
|
|
26
|
+
startCodeLength = 3;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
// Check for 4-byte start code (0x00000001)
|
|
30
|
+
if (j < packetData.length - 4
|
|
31
|
+
&& packetData[j] === 0
|
|
32
|
+
&& packetData[j + 1] === 0
|
|
33
|
+
&& packetData[j + 2] === 0
|
|
34
|
+
&& packetData[j + 3] === 1) {
|
|
35
|
+
startCodePos = j;
|
|
36
|
+
startCodeLength = 4;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (startCodePos === -1) {
|
|
41
|
+
break; // No more start codes found
|
|
42
|
+
}
|
|
43
|
+
// If this isn't the first start code, extract the previous NAL unit
|
|
44
|
+
if (i > 0 && startCodePos > i) {
|
|
45
|
+
const nalData = packetData.subarray(i, startCodePos);
|
|
46
|
+
if (nalData.length > 0) {
|
|
47
|
+
nalUnits.push(nalData);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
i = startCodePos + startCodeLength;
|
|
51
|
+
}
|
|
52
|
+
// Extract the last NAL unit if there is one
|
|
53
|
+
if (i < packetData.length) {
|
|
54
|
+
const nalData = packetData.subarray(i);
|
|
55
|
+
if (nalData.length > 0) {
|
|
56
|
+
nalUnits.push(nalData);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return nalUnits;
|
|
60
|
+
};
|
|
61
|
+
const removeEmulationPreventionBytes = (data) => {
|
|
62
|
+
const result = [];
|
|
63
|
+
const len = data.length;
|
|
64
|
+
for (let i = 0; i < len; i++) {
|
|
65
|
+
// Look for the 0x000003 pattern
|
|
66
|
+
if (i + 2 < len && data[i] === 0x00 && data[i + 1] === 0x00 && data[i + 2] === 0x03) {
|
|
67
|
+
result.push(0x00, 0x00); // Push the first two bytes
|
|
68
|
+
i += 2; // Skip the 0x03 byte
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
result.push(data[i]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return new Uint8Array(result);
|
|
75
|
+
};
|
|
76
|
+
/** Converts an AVC packet in Annex B format to length-prefixed format. */
|
|
77
|
+
export const transformAnnexBToLengthPrefixed = (packetData) => {
|
|
78
|
+
const NAL_UNIT_LENGTH_SIZE = 4;
|
|
79
|
+
const nalUnits = findNalUnitsInAnnexB(packetData);
|
|
80
|
+
if (nalUnits.length === 0) {
|
|
81
|
+
// If no NAL units were found, it's not valid Annex B data
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
let totalSize = 0;
|
|
85
|
+
for (const nalUnit of nalUnits) {
|
|
86
|
+
totalSize += NAL_UNIT_LENGTH_SIZE + nalUnit.byteLength;
|
|
87
|
+
}
|
|
88
|
+
const avccData = new Uint8Array(totalSize);
|
|
89
|
+
const dataView = new DataView(avccData.buffer);
|
|
90
|
+
let offset = 0;
|
|
91
|
+
// Write each NAL unit with its length prefix
|
|
92
|
+
for (const nalUnit of nalUnits) {
|
|
93
|
+
const length = nalUnit.byteLength;
|
|
94
|
+
dataView.setUint32(offset, length, false);
|
|
95
|
+
offset += 4;
|
|
96
|
+
avccData.set(nalUnit, offset);
|
|
97
|
+
offset += nalUnit.byteLength;
|
|
98
|
+
}
|
|
99
|
+
return avccData;
|
|
100
|
+
};
|
|
101
|
+
const extractNalUnitTypeForAvc = (data) => {
|
|
102
|
+
return data[0] & 0x1F;
|
|
103
|
+
};
|
|
104
|
+
/** Builds an AvcDecoderConfigurationRecord from an AVC packet in Annex B format. */
|
|
105
|
+
export const extractAvcDecoderConfigurationRecord = (packetData) => {
|
|
106
|
+
try {
|
|
107
|
+
const nalUnits = findNalUnitsInAnnexB(packetData);
|
|
108
|
+
const spsUnits = nalUnits.filter(unit => extractNalUnitTypeForAvc(unit) === 7);
|
|
109
|
+
const ppsUnits = nalUnits.filter(unit => extractNalUnitTypeForAvc(unit) === 8);
|
|
110
|
+
const spsExtUnits = nalUnits.filter(unit => extractNalUnitTypeForAvc(unit) === 13);
|
|
111
|
+
if (spsUnits.length === 0) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
if (ppsUnits.length === 0) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
// Let's get the first SPS for profile and level information
|
|
118
|
+
const spsData = spsUnits[0];
|
|
119
|
+
const bitstream = new Bitstream(removeEmulationPreventionBytes(spsData));
|
|
120
|
+
bitstream.skipBits(1); // forbidden_zero_bit
|
|
121
|
+
bitstream.skipBits(2); // nal_ref_idc
|
|
122
|
+
const nal_unit_type = bitstream.readBits(5);
|
|
123
|
+
if (nal_unit_type !== 7) { // SPS NAL unit type is 7
|
|
124
|
+
console.error('Invalid SPS NAL unit type');
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const profile_idc = bitstream.readAlignedByte();
|
|
128
|
+
const constraint_flags = bitstream.readAlignedByte();
|
|
129
|
+
const level_idc = bitstream.readAlignedByte();
|
|
130
|
+
const record = {
|
|
131
|
+
configurationVersion: 1,
|
|
132
|
+
avcProfileIndication: profile_idc,
|
|
133
|
+
profileCompatibility: constraint_flags,
|
|
134
|
+
avcLevelIndication: level_idc,
|
|
135
|
+
lengthSizeMinusOne: 3, // Typically 4 bytes for length field
|
|
136
|
+
sequenceParameterSets: spsUnits,
|
|
137
|
+
pictureParameterSets: ppsUnits,
|
|
138
|
+
chromaFormat: null,
|
|
139
|
+
bitDepthLumaMinus8: null,
|
|
140
|
+
bitDepthChromaMinus8: null,
|
|
141
|
+
sequenceParameterSetExt: null,
|
|
142
|
+
};
|
|
143
|
+
if (profile_idc === 100
|
|
144
|
+
|| profile_idc === 110
|
|
145
|
+
|| profile_idc === 122
|
|
146
|
+
|| profile_idc === 144) {
|
|
147
|
+
readExpGolomb(bitstream); // seq_parameter_set_id
|
|
148
|
+
const chroma_format_idc = readExpGolomb(bitstream);
|
|
149
|
+
if (chroma_format_idc === 3) {
|
|
150
|
+
bitstream.skipBits(1); // separate_colour_plane_flag
|
|
151
|
+
}
|
|
152
|
+
const bit_depth_luma_minus8 = readExpGolomb(bitstream);
|
|
153
|
+
const bit_depth_chroma_minus8 = readExpGolomb(bitstream);
|
|
154
|
+
record.chromaFormat = chroma_format_idc;
|
|
155
|
+
record.bitDepthLumaMinus8 = bit_depth_luma_minus8;
|
|
156
|
+
record.bitDepthChromaMinus8 = bit_depth_chroma_minus8;
|
|
157
|
+
record.sequenceParameterSetExt = spsExtUnits;
|
|
158
|
+
}
|
|
159
|
+
return record;
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
console.error('Error building AVC Decoder Configuration Record:', error);
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
/** Serializes an AvcDecoderConfigurationRecord into the format specified in Section 5.3.3.1 of ISO 14496-15. */
|
|
167
|
+
export const serializeAvcDecoderConfigurationRecord = (record) => {
|
|
168
|
+
const bytes = [];
|
|
169
|
+
// Write header
|
|
170
|
+
bytes.push(record.configurationVersion);
|
|
171
|
+
bytes.push(record.avcProfileIndication);
|
|
172
|
+
bytes.push(record.profileCompatibility);
|
|
173
|
+
bytes.push(record.avcLevelIndication);
|
|
174
|
+
bytes.push(0xFC | (record.lengthSizeMinusOne & 0x03)); // Reserved bits (6) + lengthSizeMinusOne (2)
|
|
175
|
+
// Reserved bits (3) + numOfSequenceParameterSets (5)
|
|
176
|
+
bytes.push(0xE0 | (record.sequenceParameterSets.length & 0x1F));
|
|
177
|
+
// Write SPS
|
|
178
|
+
for (const sps of record.sequenceParameterSets) {
|
|
179
|
+
const length = sps.byteLength;
|
|
180
|
+
bytes.push(length >> 8); // High byte
|
|
181
|
+
bytes.push(length & 0xFF); // Low byte
|
|
182
|
+
for (let i = 0; i < length; i++) {
|
|
183
|
+
bytes.push(sps[i]);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
bytes.push(record.pictureParameterSets.length);
|
|
187
|
+
// Write PPS
|
|
188
|
+
for (const pps of record.pictureParameterSets) {
|
|
189
|
+
const length = pps.byteLength;
|
|
190
|
+
bytes.push(length >> 8); // High byte
|
|
191
|
+
bytes.push(length & 0xFF); // Low byte
|
|
192
|
+
for (let i = 0; i < length; i++) {
|
|
193
|
+
bytes.push(pps[i]);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (record.avcProfileIndication === 100
|
|
197
|
+
|| record.avcProfileIndication === 110
|
|
198
|
+
|| record.avcProfileIndication === 122
|
|
199
|
+
|| record.avcProfileIndication === 144) {
|
|
200
|
+
assert(record.chromaFormat !== null);
|
|
201
|
+
assert(record.bitDepthLumaMinus8 !== null);
|
|
202
|
+
assert(record.bitDepthChromaMinus8 !== null);
|
|
203
|
+
assert(record.sequenceParameterSetExt !== null);
|
|
204
|
+
bytes.push(0xFC | (record.chromaFormat & 0x03)); // Reserved bits + chroma_format
|
|
205
|
+
bytes.push(0xF8 | (record.bitDepthLumaMinus8 & 0x07)); // Reserved bits + bit_depth_luma_minus8
|
|
206
|
+
bytes.push(0xF8 | (record.bitDepthChromaMinus8 & 0x07)); // Reserved bits + bit_depth_chroma_minus8
|
|
207
|
+
bytes.push(record.sequenceParameterSetExt.length);
|
|
208
|
+
// Write SPS Ext
|
|
209
|
+
for (const spsExt of record.sequenceParameterSetExt) {
|
|
210
|
+
const length = spsExt.byteLength;
|
|
211
|
+
bytes.push(length >> 8); // High byte
|
|
212
|
+
bytes.push(length & 0xFF); // Low byte
|
|
213
|
+
for (let i = 0; i < length; i++) {
|
|
214
|
+
bytes.push(spsExt[i]);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return new Uint8Array(bytes);
|
|
219
|
+
};
|
|
220
|
+
const NALU_TYPE_VPS = 32;
|
|
221
|
+
const NALU_TYPE_SPS = 33;
|
|
222
|
+
const NALU_TYPE_PPS = 34;
|
|
223
|
+
const NALU_TYPE_SEI_PREFIX = 39;
|
|
224
|
+
const NALU_TYPE_SEI_SUFFIX = 40;
|
|
225
|
+
const extractNalUnitTypeForHevc = (data) => {
|
|
226
|
+
return (data[0] >> 1) & 0x3F;
|
|
227
|
+
};
|
|
228
|
+
/** Builds a HevcDecoderConfigurationRecord from an HEVC packet in Annex B format. */
|
|
229
|
+
export const extractHevcDecoderConfigurationRecord = (packetData) => {
|
|
230
|
+
try {
|
|
231
|
+
const nalUnits = findNalUnitsInAnnexB(packetData);
|
|
232
|
+
const vpsUnits = nalUnits.filter(unit => extractNalUnitTypeForHevc(unit) === NALU_TYPE_VPS);
|
|
233
|
+
const spsUnits = nalUnits.filter(unit => extractNalUnitTypeForHevc(unit) === NALU_TYPE_SPS);
|
|
234
|
+
const ppsUnits = nalUnits.filter(unit => extractNalUnitTypeForHevc(unit) === NALU_TYPE_PPS);
|
|
235
|
+
const seiUnits = nalUnits.filter(unit => extractNalUnitTypeForHevc(unit) === NALU_TYPE_SEI_PREFIX
|
|
236
|
+
|| extractNalUnitTypeForHevc(unit) === NALU_TYPE_SEI_SUFFIX);
|
|
237
|
+
if (spsUnits.length === 0 || ppsUnits.length === 0)
|
|
238
|
+
return null;
|
|
239
|
+
const sps = spsUnits[0];
|
|
240
|
+
const bitstream = new Bitstream(removeEmulationPreventionBytes(sps));
|
|
241
|
+
bitstream.skipBits(16); // NAL header
|
|
242
|
+
bitstream.readBits(4); // sps_video_parameter_set_id
|
|
243
|
+
const sps_max_sub_layers_minus1 = bitstream.readBits(3);
|
|
244
|
+
const sps_temporal_id_nesting_flag = bitstream.readBits(1);
|
|
245
|
+
const { general_profile_space, general_tier_flag, general_profile_idc, general_profile_compatibility_flags, general_constraint_indicator_flags, general_level_idc, } = parseProfileTierLevel(bitstream, sps_max_sub_layers_minus1);
|
|
246
|
+
readExpGolomb(bitstream); // sps_seq_parameter_set_id
|
|
247
|
+
const chroma_format_idc = readExpGolomb(bitstream);
|
|
248
|
+
if (chroma_format_idc === 3)
|
|
249
|
+
bitstream.skipBits(1); // separate_colour_plane_flag
|
|
250
|
+
readExpGolomb(bitstream); // pic_width_in_luma_samples
|
|
251
|
+
readExpGolomb(bitstream); // pic_height_in_luma_samples
|
|
252
|
+
if (bitstream.readBits(1)) { // conformance_window_flag
|
|
253
|
+
readExpGolomb(bitstream); // conf_win_left_offset
|
|
254
|
+
readExpGolomb(bitstream); // conf_win_right_offset
|
|
255
|
+
readExpGolomb(bitstream); // conf_win_top_offset
|
|
256
|
+
readExpGolomb(bitstream); // conf_win_bottom_offset
|
|
257
|
+
}
|
|
258
|
+
const bit_depth_luma_minus8 = readExpGolomb(bitstream);
|
|
259
|
+
const bit_depth_chroma_minus8 = readExpGolomb(bitstream);
|
|
260
|
+
readExpGolomb(bitstream); // log2_max_pic_order_cnt_lsb_minus4
|
|
261
|
+
const sps_sub_layer_ordering_info_present_flag = bitstream.readBits(1);
|
|
262
|
+
const maxNum = sps_sub_layer_ordering_info_present_flag ? 0 : sps_max_sub_layers_minus1;
|
|
263
|
+
for (let i = maxNum; i <= sps_max_sub_layers_minus1; i++) {
|
|
264
|
+
readExpGolomb(bitstream); // sps_max_dec_pic_buffering_minus1[i]
|
|
265
|
+
readExpGolomb(bitstream); // sps_max_num_reorder_pics[i]
|
|
266
|
+
readExpGolomb(bitstream); // sps_max_latency_increase_plus1[i]
|
|
267
|
+
}
|
|
268
|
+
readExpGolomb(bitstream); // log2_min_luma_coding_block_size_minus3
|
|
269
|
+
readExpGolomb(bitstream); // log2_diff_max_min_luma_coding_block_size
|
|
270
|
+
readExpGolomb(bitstream); // log2_min_luma_transform_block_size_minus2
|
|
271
|
+
readExpGolomb(bitstream); // log2_diff_max_min_luma_transform_block_size
|
|
272
|
+
readExpGolomb(bitstream); // max_transform_hierarchy_depth_inter
|
|
273
|
+
readExpGolomb(bitstream); // max_transform_hierarchy_depth_intra
|
|
274
|
+
if (bitstream.readBits(1)) { // scaling_list_enabled_flag
|
|
275
|
+
if (bitstream.readBits(1)) {
|
|
276
|
+
skipScalingListData(bitstream);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
bitstream.skipBits(1); // amp_enabled_flag
|
|
280
|
+
bitstream.skipBits(1); // sample_adaptive_offset_enabled_flag
|
|
281
|
+
if (bitstream.readBits(1)) { // pcm_enabled_flag
|
|
282
|
+
bitstream.skipBits(4); // pcm_sample_bit_depth_luma_minus1
|
|
283
|
+
bitstream.skipBits(4); // pcm_sample_bit_depth_chroma_minus1
|
|
284
|
+
readExpGolomb(bitstream); // log2_min_pcm_luma_coding_block_size_minus3
|
|
285
|
+
readExpGolomb(bitstream); // log2_diff_max_min_pcm_luma_coding_block_size
|
|
286
|
+
bitstream.skipBits(1); // pcm_loop_filter_disabled_flag
|
|
287
|
+
}
|
|
288
|
+
const num_short_term_ref_pic_sets = readExpGolomb(bitstream);
|
|
289
|
+
skipAllStRefPicSets(bitstream, num_short_term_ref_pic_sets);
|
|
290
|
+
if (bitstream.readBits(1)) { // long_term_ref_pics_present_flag
|
|
291
|
+
const num_long_term_ref_pics_sps = readExpGolomb(bitstream);
|
|
292
|
+
for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
|
|
293
|
+
readExpGolomb(bitstream); // lt_ref_pic_poc_lsb_sps[i]
|
|
294
|
+
bitstream.skipBits(1); // used_by_curr_pic_lt_sps_flag[i]
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
bitstream.skipBits(1); // sps_temporal_mvp_enabled_flag
|
|
298
|
+
bitstream.skipBits(1); // strong_intra_smoothing_enabled_flag
|
|
299
|
+
let min_spatial_segmentation_idc = 0;
|
|
300
|
+
if (bitstream.readBits(1)) { // vui_parameters_present_flag
|
|
301
|
+
min_spatial_segmentation_idc = parseVuiForMinSpatialSegmentationIdc(bitstream, sps_max_sub_layers_minus1);
|
|
302
|
+
}
|
|
303
|
+
// Parse PPS for parallelismType
|
|
304
|
+
let parallelismType = 0;
|
|
305
|
+
if (ppsUnits.length > 0) {
|
|
306
|
+
const pps = ppsUnits[0];
|
|
307
|
+
const ppsBitstream = new Bitstream(removeEmulationPreventionBytes(pps));
|
|
308
|
+
ppsBitstream.skipBits(16); // NAL header
|
|
309
|
+
readExpGolomb(ppsBitstream); // pps_pic_parameter_set_id
|
|
310
|
+
readExpGolomb(ppsBitstream); // pps_seq_parameter_set_id
|
|
311
|
+
ppsBitstream.skipBits(1); // dependent_slice_segments_enabled_flag
|
|
312
|
+
ppsBitstream.skipBits(1); // output_flag_present_flag
|
|
313
|
+
ppsBitstream.skipBits(3); // num_extra_slice_header_bits
|
|
314
|
+
ppsBitstream.skipBits(1); // sign_data_hiding_enabled_flag
|
|
315
|
+
ppsBitstream.skipBits(1); // cabac_init_present_flag
|
|
316
|
+
readExpGolomb(ppsBitstream); // num_ref_idx_l0_default_active_minus1
|
|
317
|
+
readExpGolomb(ppsBitstream); // num_ref_idx_l1_default_active_minus1
|
|
318
|
+
readSignedExpGolomb(ppsBitstream); // init_qp_minus26
|
|
319
|
+
ppsBitstream.skipBits(1); // constrained_intra_pred_flag
|
|
320
|
+
ppsBitstream.skipBits(1); // transform_skip_enabled_flag
|
|
321
|
+
if (ppsBitstream.readBits(1)) { // cu_qp_delta_enabled_flag
|
|
322
|
+
readExpGolomb(ppsBitstream); // diff_cu_qp_delta_depth
|
|
323
|
+
}
|
|
324
|
+
readSignedExpGolomb(ppsBitstream); // pps_cb_qp_offset
|
|
325
|
+
readSignedExpGolomb(ppsBitstream); // pps_cr_qp_offset
|
|
326
|
+
ppsBitstream.skipBits(1); // pps_slice_chroma_qp_offsets_present_flag
|
|
327
|
+
ppsBitstream.skipBits(1); // weighted_pred_flag
|
|
328
|
+
ppsBitstream.skipBits(1); // weighted_bipred_flag
|
|
329
|
+
ppsBitstream.skipBits(1); // transquant_bypass_enabled_flag
|
|
330
|
+
const tiles_enabled_flag = ppsBitstream.readBits(1);
|
|
331
|
+
const entropy_coding_sync_enabled_flag = ppsBitstream.readBits(1);
|
|
332
|
+
if (!tiles_enabled_flag && !entropy_coding_sync_enabled_flag)
|
|
333
|
+
parallelismType = 0;
|
|
334
|
+
else if (tiles_enabled_flag && !entropy_coding_sync_enabled_flag)
|
|
335
|
+
parallelismType = 2;
|
|
336
|
+
else if (!tiles_enabled_flag && entropy_coding_sync_enabled_flag)
|
|
337
|
+
parallelismType = 3;
|
|
338
|
+
else
|
|
339
|
+
parallelismType = 0;
|
|
340
|
+
}
|
|
341
|
+
const arrays = [
|
|
342
|
+
...(vpsUnits.length
|
|
343
|
+
? [
|
|
344
|
+
{
|
|
345
|
+
arrayCompleteness: 1,
|
|
346
|
+
nalUnitType: NALU_TYPE_VPS,
|
|
347
|
+
nalUnits: vpsUnits,
|
|
348
|
+
},
|
|
349
|
+
]
|
|
350
|
+
: []),
|
|
351
|
+
...(spsUnits.length
|
|
352
|
+
? [
|
|
353
|
+
{
|
|
354
|
+
arrayCompleteness: 1,
|
|
355
|
+
nalUnitType: NALU_TYPE_SPS,
|
|
356
|
+
nalUnits: spsUnits,
|
|
357
|
+
},
|
|
358
|
+
]
|
|
359
|
+
: []),
|
|
360
|
+
...(ppsUnits.length
|
|
361
|
+
? [
|
|
362
|
+
{
|
|
363
|
+
arrayCompleteness: 1,
|
|
364
|
+
nalUnitType: NALU_TYPE_PPS,
|
|
365
|
+
nalUnits: ppsUnits,
|
|
366
|
+
},
|
|
367
|
+
]
|
|
368
|
+
: []),
|
|
369
|
+
...(seiUnits.length
|
|
370
|
+
? [
|
|
371
|
+
{
|
|
372
|
+
arrayCompleteness: 1,
|
|
373
|
+
nalUnitType: extractNalUnitTypeForHevc(seiUnits[0]),
|
|
374
|
+
nalUnits: seiUnits,
|
|
375
|
+
},
|
|
376
|
+
]
|
|
377
|
+
: []),
|
|
378
|
+
];
|
|
379
|
+
const record = {
|
|
380
|
+
configurationVersion: 1,
|
|
381
|
+
generalProfileSpace: general_profile_space,
|
|
382
|
+
generalTierFlag: general_tier_flag,
|
|
383
|
+
generalProfileIdc: general_profile_idc,
|
|
384
|
+
generalProfileCompatibilityFlags: general_profile_compatibility_flags,
|
|
385
|
+
generalConstraintIndicatorFlags: general_constraint_indicator_flags,
|
|
386
|
+
generalLevelIdc: general_level_idc,
|
|
387
|
+
minSpatialSegmentationIdc: min_spatial_segmentation_idc,
|
|
388
|
+
parallelismType,
|
|
389
|
+
chromaFormatIdc: chroma_format_idc,
|
|
390
|
+
bitDepthLumaMinus8: bit_depth_luma_minus8,
|
|
391
|
+
bitDepthChromaMinus8: bit_depth_chroma_minus8,
|
|
392
|
+
avgFrameRate: 0,
|
|
393
|
+
constantFrameRate: 0,
|
|
394
|
+
numTemporalLayers: sps_max_sub_layers_minus1 + 1,
|
|
395
|
+
temporalIdNested: sps_temporal_id_nesting_flag,
|
|
396
|
+
lengthSizeMinusOne: 3,
|
|
397
|
+
arrays,
|
|
398
|
+
};
|
|
399
|
+
return record;
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
console.error('Error building HEVC Decoder Configuration Record:', error);
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
const parseProfileTierLevel = (bitstream, maxNumSubLayersMinus1) => {
|
|
407
|
+
const general_profile_space = bitstream.readBits(2);
|
|
408
|
+
const general_tier_flag = bitstream.readBits(1);
|
|
409
|
+
const general_profile_idc = bitstream.readBits(5);
|
|
410
|
+
let general_profile_compatibility_flags = 0;
|
|
411
|
+
for (let i = 0; i < 32; i++) {
|
|
412
|
+
general_profile_compatibility_flags = (general_profile_compatibility_flags << 1) | bitstream.readBits(1);
|
|
413
|
+
}
|
|
414
|
+
const general_constraint_indicator_flags = new Uint8Array(6);
|
|
415
|
+
for (let i = 0; i < 6; i++) {
|
|
416
|
+
general_constraint_indicator_flags[i] = bitstream.readBits(8);
|
|
417
|
+
}
|
|
418
|
+
const general_level_idc = bitstream.readBits(8);
|
|
419
|
+
const sub_layer_profile_present_flag = [];
|
|
420
|
+
const sub_layer_level_present_flag = [];
|
|
421
|
+
for (let i = 0; i < maxNumSubLayersMinus1; i++) {
|
|
422
|
+
sub_layer_profile_present_flag.push(bitstream.readBits(1));
|
|
423
|
+
sub_layer_level_present_flag.push(bitstream.readBits(1));
|
|
424
|
+
}
|
|
425
|
+
if (maxNumSubLayersMinus1 > 0) {
|
|
426
|
+
for (let i = maxNumSubLayersMinus1; i < 8; i++) {
|
|
427
|
+
bitstream.skipBits(2); // reserved_zero_2bits
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
for (let i = 0; i < maxNumSubLayersMinus1; i++) {
|
|
431
|
+
if (sub_layer_profile_present_flag[i])
|
|
432
|
+
bitstream.skipBits(88);
|
|
433
|
+
if (sub_layer_level_present_flag[i])
|
|
434
|
+
bitstream.skipBits(8);
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
general_profile_space,
|
|
438
|
+
general_tier_flag,
|
|
439
|
+
general_profile_idc,
|
|
440
|
+
general_profile_compatibility_flags,
|
|
441
|
+
general_constraint_indicator_flags,
|
|
442
|
+
general_level_idc,
|
|
443
|
+
};
|
|
444
|
+
};
|
|
445
|
+
const skipScalingListData = (bitstream) => {
|
|
446
|
+
for (let sizeId = 0; sizeId < 4; sizeId++) {
|
|
447
|
+
for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
|
|
448
|
+
const scaling_list_pred_mode_flag = bitstream.readBits(1);
|
|
449
|
+
if (!scaling_list_pred_mode_flag) {
|
|
450
|
+
readExpGolomb(bitstream); // scaling_list_pred_matrix_id_delta
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
const coefNum = Math.min(64, 1 << (4 + (sizeId << 1)));
|
|
454
|
+
if (sizeId > 1) {
|
|
455
|
+
readSignedExpGolomb(bitstream); // scaling_list_dc_coef_minus8
|
|
456
|
+
}
|
|
457
|
+
for (let i = 0; i < coefNum; i++) {
|
|
458
|
+
readSignedExpGolomb(bitstream); // scaling_list_delta_coef
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
const skipAllStRefPicSets = (bitstream, num_short_term_ref_pic_sets) => {
|
|
465
|
+
const NumDeltaPocs = [];
|
|
466
|
+
for (let stRpsIdx = 0; stRpsIdx < num_short_term_ref_pic_sets; stRpsIdx++) {
|
|
467
|
+
NumDeltaPocs[stRpsIdx] = skipStRefPicSet(bitstream, stRpsIdx, num_short_term_ref_pic_sets, NumDeltaPocs);
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
const skipStRefPicSet = (bitstream, stRpsIdx, num_short_term_ref_pic_sets, NumDeltaPocs) => {
|
|
471
|
+
let NumDeltaPocsThis = 0;
|
|
472
|
+
let inter_ref_pic_set_prediction_flag = 0;
|
|
473
|
+
let RefRpsIdx = 0;
|
|
474
|
+
if (stRpsIdx !== 0) {
|
|
475
|
+
inter_ref_pic_set_prediction_flag = bitstream.readBits(1);
|
|
476
|
+
}
|
|
477
|
+
if (inter_ref_pic_set_prediction_flag) {
|
|
478
|
+
if (stRpsIdx === num_short_term_ref_pic_sets) {
|
|
479
|
+
const delta_idx_minus1 = readExpGolomb(bitstream);
|
|
480
|
+
RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1);
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
RefRpsIdx = stRpsIdx - 1;
|
|
484
|
+
}
|
|
485
|
+
bitstream.readBits(1); // delta_rps_sign
|
|
486
|
+
readExpGolomb(bitstream); // abs_delta_rps_minus1
|
|
487
|
+
// The number of iterations is NumDeltaPocs[RefRpsIdx] + 1
|
|
488
|
+
const numDelta = NumDeltaPocs[RefRpsIdx] ?? 0;
|
|
489
|
+
for (let j = 0; j <= numDelta; j++) {
|
|
490
|
+
const used_by_curr_pic_flag = bitstream.readBits(1);
|
|
491
|
+
if (!used_by_curr_pic_flag) {
|
|
492
|
+
bitstream.readBits(1); // use_delta_flag
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
NumDeltaPocsThis = NumDeltaPocs[RefRpsIdx];
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
const num_negative_pics = readExpGolomb(bitstream);
|
|
499
|
+
const num_positive_pics = readExpGolomb(bitstream);
|
|
500
|
+
for (let i = 0; i < num_negative_pics; i++) {
|
|
501
|
+
readExpGolomb(bitstream); // delta_poc_s0_minus1[i]
|
|
502
|
+
bitstream.readBits(1); // used_by_curr_pic_s0_flag[i]
|
|
503
|
+
}
|
|
504
|
+
for (let i = 0; i < num_positive_pics; i++) {
|
|
505
|
+
readExpGolomb(bitstream); // delta_poc_s1_minus1[i]
|
|
506
|
+
bitstream.readBits(1); // used_by_curr_pic_s1_flag[i]
|
|
507
|
+
}
|
|
508
|
+
NumDeltaPocsThis = num_negative_pics + num_positive_pics;
|
|
509
|
+
}
|
|
510
|
+
return NumDeltaPocsThis;
|
|
511
|
+
};
|
|
512
|
+
const parseVuiForMinSpatialSegmentationIdc = (bitstream, sps_max_sub_layers_minus1) => {
|
|
513
|
+
if (bitstream.readBits(1)) { // aspect_ratio_info_present_flag
|
|
514
|
+
const aspect_ratio_idc = bitstream.readBits(8);
|
|
515
|
+
if (aspect_ratio_idc === 255) {
|
|
516
|
+
bitstream.readBits(16); // sar_width
|
|
517
|
+
bitstream.readBits(16); // sar_height
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (bitstream.readBits(1)) { // overscan_info_present_flag
|
|
521
|
+
bitstream.readBits(1); // overscan_appropriate_flag
|
|
522
|
+
}
|
|
523
|
+
if (bitstream.readBits(1)) { // video_signal_type_present_flag
|
|
524
|
+
bitstream.readBits(3); // video_format
|
|
525
|
+
bitstream.readBits(1); // video_full_range_flag
|
|
526
|
+
if (bitstream.readBits(1)) {
|
|
527
|
+
bitstream.readBits(8); // colour_primaries
|
|
528
|
+
bitstream.readBits(8); // transfer_characteristics
|
|
529
|
+
bitstream.readBits(8); // matrix_coeffs
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (bitstream.readBits(1)) { // chroma_loc_info_present_flag
|
|
533
|
+
readExpGolomb(bitstream); // chroma_sample_loc_type_top_field
|
|
534
|
+
readExpGolomb(bitstream); // chroma_sample_loc_type_bottom_field
|
|
535
|
+
}
|
|
536
|
+
bitstream.readBits(1); // neutral_chroma_indication_flag
|
|
537
|
+
bitstream.readBits(1); // field_seq_flag
|
|
538
|
+
bitstream.readBits(1); // frame_field_info_present_flag
|
|
539
|
+
if (bitstream.readBits(1)) { // default_display_window_flag
|
|
540
|
+
readExpGolomb(bitstream); // def_disp_win_left_offset
|
|
541
|
+
readExpGolomb(bitstream); // def_disp_win_right_offset
|
|
542
|
+
readExpGolomb(bitstream); // def_disp_win_top_offset
|
|
543
|
+
readExpGolomb(bitstream); // def_disp_win_bottom_offset
|
|
544
|
+
}
|
|
545
|
+
if (bitstream.readBits(1)) { // vui_timing_info_present_flag
|
|
546
|
+
bitstream.readBits(32); // vui_num_units_in_tick
|
|
547
|
+
bitstream.readBits(32); // vui_time_scale
|
|
548
|
+
if (bitstream.readBits(1)) { // vui_poc_proportional_to_timing_flag
|
|
549
|
+
readExpGolomb(bitstream); // vui_num_ticks_poc_diff_one_minus1
|
|
550
|
+
}
|
|
551
|
+
if (bitstream.readBits(1)) {
|
|
552
|
+
skipHrdParameters(bitstream, true, sps_max_sub_layers_minus1);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (bitstream.readBits(1)) { // bitstream_restriction_flag
|
|
556
|
+
bitstream.readBits(1); // tiles_fixed_structure_flag
|
|
557
|
+
bitstream.readBits(1); // motion_vectors_over_pic_boundaries_flag
|
|
558
|
+
bitstream.readBits(1); // restricted_ref_pic_lists_flag
|
|
559
|
+
const min_spatial_segmentation_idc = readExpGolomb(bitstream);
|
|
560
|
+
// skip the rest
|
|
561
|
+
readExpGolomb(bitstream); // max_bytes_per_pic_denom
|
|
562
|
+
readExpGolomb(bitstream); // max_bits_per_min_cu_denom
|
|
563
|
+
readExpGolomb(bitstream); // log2_max_mv_length_horizontal
|
|
564
|
+
readExpGolomb(bitstream); // log2_max_mv_length_vertical
|
|
565
|
+
return min_spatial_segmentation_idc;
|
|
566
|
+
}
|
|
567
|
+
return 0;
|
|
568
|
+
};
|
|
569
|
+
const skipHrdParameters = (bitstream, commonInfPresentFlag, maxNumSubLayersMinus1) => {
|
|
570
|
+
let nal_hrd_parameters_present_flag = false;
|
|
571
|
+
let vcl_hrd_parameters_present_flag = false;
|
|
572
|
+
let sub_pic_hrd_params_present_flag = false;
|
|
573
|
+
if (commonInfPresentFlag) {
|
|
574
|
+
nal_hrd_parameters_present_flag = bitstream.readBits(1) === 1;
|
|
575
|
+
vcl_hrd_parameters_present_flag = bitstream.readBits(1) === 1;
|
|
576
|
+
if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
|
|
577
|
+
sub_pic_hrd_params_present_flag = bitstream.readBits(1) === 1;
|
|
578
|
+
if (sub_pic_hrd_params_present_flag) {
|
|
579
|
+
bitstream.readBits(8); // tick_divisor_minus2
|
|
580
|
+
bitstream.readBits(5); // du_cpb_removal_delay_increment_length_minus1
|
|
581
|
+
bitstream.readBits(1); // sub_pic_cpb_params_in_pic_timing_sei_flag
|
|
582
|
+
bitstream.readBits(5); // dpb_output_delay_du_length_minus1
|
|
583
|
+
}
|
|
584
|
+
bitstream.readBits(4); // bit_rate_scale
|
|
585
|
+
bitstream.readBits(4); // cpb_size_scale
|
|
586
|
+
if (sub_pic_hrd_params_present_flag) {
|
|
587
|
+
bitstream.readBits(4); // cpb_size_du_scale
|
|
588
|
+
}
|
|
589
|
+
bitstream.readBits(5); // initial_cpb_removal_delay_length_minus1
|
|
590
|
+
bitstream.readBits(5); // au_cpb_removal_delay_length_minus1
|
|
591
|
+
bitstream.readBits(5); // dpb_output_delay_length_minus1
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
for (let i = 0; i <= maxNumSubLayersMinus1; i++) {
|
|
595
|
+
const fixed_pic_rate_general_flag = bitstream.readBits(1) === 1;
|
|
596
|
+
let fixed_pic_rate_within_cvs_flag = true; // Default assumption if general is true
|
|
597
|
+
if (!fixed_pic_rate_general_flag) {
|
|
598
|
+
fixed_pic_rate_within_cvs_flag = bitstream.readBits(1) === 1;
|
|
599
|
+
}
|
|
600
|
+
let low_delay_hrd_flag = false; // Default assumption
|
|
601
|
+
if (fixed_pic_rate_within_cvs_flag) {
|
|
602
|
+
readExpGolomb(bitstream); // elemental_duration_in_tc_minus1[i]
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
low_delay_hrd_flag = bitstream.readBits(1) === 1;
|
|
606
|
+
}
|
|
607
|
+
let CpbCnt = 1; // Default if low_delay is true
|
|
608
|
+
if (!low_delay_hrd_flag) {
|
|
609
|
+
const cpb_cnt_minus1 = readExpGolomb(bitstream); // cpb_cnt_minus1[i]
|
|
610
|
+
CpbCnt = cpb_cnt_minus1 + 1;
|
|
611
|
+
}
|
|
612
|
+
if (nal_hrd_parameters_present_flag) {
|
|
613
|
+
skipSubLayerHrdParameters(bitstream, CpbCnt, sub_pic_hrd_params_present_flag);
|
|
614
|
+
}
|
|
615
|
+
if (vcl_hrd_parameters_present_flag) {
|
|
616
|
+
skipSubLayerHrdParameters(bitstream, CpbCnt, sub_pic_hrd_params_present_flag);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
const skipSubLayerHrdParameters = (bitstream, CpbCnt, sub_pic_hrd_params_present_flag) => {
|
|
621
|
+
for (let i = 0; i < CpbCnt; i++) {
|
|
622
|
+
readExpGolomb(bitstream); // bit_rate_value_minus1[i]
|
|
623
|
+
readExpGolomb(bitstream); // cpb_size_value_minus1[i]
|
|
624
|
+
if (sub_pic_hrd_params_present_flag) {
|
|
625
|
+
readExpGolomb(bitstream); // cpb_size_du_value_minus1[i]
|
|
626
|
+
readExpGolomb(bitstream); // bit_rate_du_value_minus1[i]
|
|
627
|
+
}
|
|
628
|
+
bitstream.readBits(1); // cbr_flag[i]
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
/** Serializes an HevcDecoderConfigurationRecord into the format specified in Section 8.3.3.1 of ISO 14496-15. */
|
|
632
|
+
export const serializeHevcDecoderConfigurationRecord = (record) => {
|
|
633
|
+
const bytes = [];
|
|
634
|
+
bytes.push(record.configurationVersion);
|
|
635
|
+
bytes.push(((record.generalProfileSpace & 0x3) << 6)
|
|
636
|
+
| ((record.generalTierFlag & 0x1) << 5)
|
|
637
|
+
| (record.generalProfileIdc & 0x1F));
|
|
638
|
+
bytes.push((record.generalProfileCompatibilityFlags >>> 24) & 0xFF);
|
|
639
|
+
bytes.push((record.generalProfileCompatibilityFlags >>> 16) & 0xFF);
|
|
640
|
+
bytes.push((record.generalProfileCompatibilityFlags >>> 8) & 0xFF);
|
|
641
|
+
bytes.push(record.generalProfileCompatibilityFlags & 0xFF);
|
|
642
|
+
bytes.push(...record.generalConstraintIndicatorFlags);
|
|
643
|
+
bytes.push(record.generalLevelIdc & 0xFF);
|
|
644
|
+
bytes.push(0xF0 | ((record.minSpatialSegmentationIdc >> 8) & 0x0F)); // Reserved + high nibble
|
|
645
|
+
bytes.push(record.minSpatialSegmentationIdc & 0xFF); // Low byte
|
|
646
|
+
bytes.push(0xFC | (record.parallelismType & 0x03));
|
|
647
|
+
bytes.push(0xFC | (record.chromaFormatIdc & 0x03));
|
|
648
|
+
bytes.push(0xF8 | (record.bitDepthLumaMinus8 & 0x07));
|
|
649
|
+
bytes.push(0xF8 | (record.bitDepthChromaMinus8 & 0x07));
|
|
650
|
+
bytes.push((record.avgFrameRate >> 8) & 0xFF); // High byte
|
|
651
|
+
bytes.push(record.avgFrameRate & 0xFF); // Low byte
|
|
652
|
+
bytes.push(((record.constantFrameRate & 0x03) << 6)
|
|
653
|
+
| ((record.numTemporalLayers & 0x07) << 3)
|
|
654
|
+
| ((record.temporalIdNested & 0x01) << 2)
|
|
655
|
+
| (record.lengthSizeMinusOne & 0x03));
|
|
656
|
+
bytes.push(record.arrays.length & 0xFF);
|
|
657
|
+
for (const arr of record.arrays) {
|
|
658
|
+
bytes.push(((arr.arrayCompleteness & 0x01) << 7)
|
|
659
|
+
| (0 << 6)
|
|
660
|
+
| (arr.nalUnitType & 0x3F));
|
|
661
|
+
bytes.push((arr.nalUnits.length >> 8) & 0xFF); // High byte
|
|
662
|
+
bytes.push(arr.nalUnits.length & 0xFF); // Low byte
|
|
663
|
+
for (const nal of arr.nalUnits) {
|
|
664
|
+
bytes.push((nal.length >> 8) & 0xFF); // High byte
|
|
665
|
+
bytes.push(nal.length & 0xFF); // Low byte
|
|
666
|
+
for (let i = 0; i < nal.length; i++) {
|
|
667
|
+
bytes.push(nal[i]);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return new Uint8Array(bytes);
|
|
672
|
+
};
|
|
673
|
+
export const extractVp9CodecInfoFromPacket = (packet) => {
|
|
674
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
675
|
+
// https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.7-20170222-draft.pdf
|
|
676
|
+
// http://downloads.webmproject.org/docs/vp9/vp9-bitstream_superframe-and-uncompressed-header_v1.0.pdf
|
|
677
|
+
// Handle superframe
|
|
678
|
+
const lastByte = packet[packet.length - 1];
|
|
679
|
+
if (lastByte && (lastByte & 0xe0) === 0xc0) { // Is superframe
|
|
680
|
+
const bytesPerFrameSize = ((lastByte & 0x18) >> 3) + 1;
|
|
681
|
+
const numFrames = (lastByte & 0x07) + 1;
|
|
682
|
+
const indexSize = 2 + numFrames * bytesPerFrameSize;
|
|
683
|
+
// Verify matching marker bytes
|
|
684
|
+
if (packet[packet.length - indexSize] !== lastByte) {
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
// Get first frame size
|
|
688
|
+
let frameSize = 0;
|
|
689
|
+
const offset = packet.length - indexSize + 1;
|
|
690
|
+
for (let i = 0; i < bytesPerFrameSize; i++) {
|
|
691
|
+
if (!packet[offset + i])
|
|
692
|
+
return null;
|
|
693
|
+
frameSize |= packet[offset + i] << (8 * i);
|
|
694
|
+
}
|
|
695
|
+
packet = packet.subarray(0, frameSize);
|
|
696
|
+
}
|
|
697
|
+
const bitstream = new Bitstream(packet);
|
|
698
|
+
// Frame marker (0b10)
|
|
699
|
+
const frameMarker = bitstream.readBits(2);
|
|
700
|
+
if (frameMarker !== 2) {
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
// Profile
|
|
704
|
+
const profileLowBit = bitstream.readBits(1);
|
|
705
|
+
const profileHighBit = bitstream.readBits(1);
|
|
706
|
+
const profile = (profileHighBit << 1) + profileLowBit;
|
|
707
|
+
// Skip reserved bit for profile 3
|
|
708
|
+
if (profile === 3) {
|
|
709
|
+
bitstream.skipBits(1);
|
|
710
|
+
}
|
|
711
|
+
// show_existing_frame
|
|
712
|
+
const showExistingFrame = bitstream.readBits(1);
|
|
713
|
+
if (showExistingFrame === 1) {
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
// frame_type (0 = key frame)
|
|
717
|
+
const frameType = bitstream.readBits(1);
|
|
718
|
+
if (frameType !== 0) {
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
// Skip show_frame and error_resilient_mode
|
|
722
|
+
bitstream.skipBits(2);
|
|
723
|
+
// Sync code (0x498342)
|
|
724
|
+
const syncCode = bitstream.readBits(24);
|
|
725
|
+
if (syncCode !== 0x498342) {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
// Color config
|
|
729
|
+
let bitDepth = 8;
|
|
730
|
+
if (profile >= 2) {
|
|
731
|
+
const tenOrTwelveBit = bitstream.readBits(1);
|
|
732
|
+
bitDepth = tenOrTwelveBit ? 12 : 10;
|
|
733
|
+
}
|
|
734
|
+
// Color space
|
|
735
|
+
const colorSpace = bitstream.readBits(3);
|
|
736
|
+
let chromaSubsampling = 0;
|
|
737
|
+
let videoFullRangeFlag = 0;
|
|
738
|
+
if (colorSpace !== 7) { // 7 is CS_RGB
|
|
739
|
+
const colorRange = bitstream.readBits(1);
|
|
740
|
+
videoFullRangeFlag = colorRange;
|
|
741
|
+
if (profile === 1 || profile === 3) {
|
|
742
|
+
const subsamplingX = bitstream.readBits(1);
|
|
743
|
+
const subsamplingY = bitstream.readBits(1);
|
|
744
|
+
// 0 = 4:2:0 vertical
|
|
745
|
+
// 1 = 4:2:0 colocated
|
|
746
|
+
// 2 = 4:2:2
|
|
747
|
+
// 3 = 4:4:4
|
|
748
|
+
chromaSubsampling = !subsamplingX && !subsamplingY
|
|
749
|
+
? 3 // 0,0 = 4:4:4
|
|
750
|
+
: subsamplingX && !subsamplingY
|
|
751
|
+
? 2 // 1,0 = 4:2:2
|
|
752
|
+
: 1; // 1,1 = 4:2:0 colocated (default)
|
|
753
|
+
// Skip reserved bit
|
|
754
|
+
bitstream.skipBits(1);
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
// For profile 0 and 2, always 4:2:0
|
|
758
|
+
chromaSubsampling = 1; // Using colocated as default
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
else {
|
|
762
|
+
// RGB is always 4:4:4
|
|
763
|
+
chromaSubsampling = 3;
|
|
764
|
+
videoFullRangeFlag = 1;
|
|
765
|
+
}
|
|
766
|
+
// Parse frame size
|
|
767
|
+
const widthMinusOne = bitstream.readBits(16);
|
|
768
|
+
const heightMinusOne = bitstream.readBits(16);
|
|
769
|
+
const width = widthMinusOne + 1;
|
|
770
|
+
const height = heightMinusOne + 1;
|
|
771
|
+
// Calculate level based on dimensions
|
|
772
|
+
const pictureSize = width * height;
|
|
773
|
+
let level = last(VP9_LEVEL_TABLE).level; // Default to highest level
|
|
774
|
+
for (const entry of VP9_LEVEL_TABLE) {
|
|
775
|
+
if (pictureSize <= entry.maxPictureSize) {
|
|
776
|
+
level = entry.level;
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
// Map color_space to standard values
|
|
781
|
+
const matrixCoefficients = colorSpace === 7
|
|
782
|
+
? 0
|
|
783
|
+
: colorSpace === 2
|
|
784
|
+
? 1
|
|
785
|
+
: colorSpace === 1
|
|
786
|
+
? 6
|
|
787
|
+
: 2;
|
|
788
|
+
const colourPrimaries = colorSpace === 2
|
|
789
|
+
? 1
|
|
790
|
+
: colorSpace === 1
|
|
791
|
+
? 6
|
|
792
|
+
: 2;
|
|
793
|
+
const transferCharacteristics = colorSpace === 2
|
|
794
|
+
? 1
|
|
795
|
+
: colorSpace === 1
|
|
796
|
+
? 6
|
|
797
|
+
: 2;
|
|
798
|
+
return {
|
|
799
|
+
profile,
|
|
800
|
+
level,
|
|
801
|
+
bitDepth,
|
|
802
|
+
chromaSubsampling,
|
|
803
|
+
videoFullRangeFlag,
|
|
804
|
+
colourPrimaries,
|
|
805
|
+
transferCharacteristics,
|
|
806
|
+
matrixCoefficients,
|
|
807
|
+
};
|
|
808
|
+
};
|
|
809
|
+
/**
|
|
810
|
+
* When AV1 codec information is not provided by the container, we can still try to extract the information by digging
|
|
811
|
+
* into the AV1 bitstream.
|
|
812
|
+
*/
|
|
813
|
+
export const extractAv1CodecInfoFromPacket = (packet) => {
|
|
814
|
+
// https://aomediacodec.github.io/av1-spec/av1-spec.pdf
|
|
815
|
+
const bitstream = new Bitstream(packet);
|
|
816
|
+
const readLeb128 = () => {
|
|
817
|
+
let value = 0;
|
|
818
|
+
for (let i = 0; i < 8; i++) {
|
|
819
|
+
const byte = bitstream.readAlignedByte();
|
|
820
|
+
if (byte === undefined)
|
|
821
|
+
return 0;
|
|
822
|
+
value |= ((byte & 0x7f) << (i * 7));
|
|
823
|
+
if (!(byte & 0x80)) {
|
|
824
|
+
break;
|
|
825
|
+
}
|
|
826
|
+
// Spec requirement
|
|
827
|
+
if (i === 7 && (byte & 0x80)) {
|
|
828
|
+
return null;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
// Spec requirement
|
|
832
|
+
if (value >= 2 ** 32 - 1) {
|
|
833
|
+
return null;
|
|
834
|
+
}
|
|
835
|
+
return value;
|
|
836
|
+
};
|
|
837
|
+
while (bitstream.getBitsLeft() >= 8) {
|
|
838
|
+
// Parse OBU header
|
|
839
|
+
const obuHeader = bitstream.readBits(8);
|
|
840
|
+
const obuType = (obuHeader >> 3) & 0xf;
|
|
841
|
+
const obuExtension = (obuHeader >> 2) & 0x1;
|
|
842
|
+
const obuHasSizeField = (obuHeader >> 1) & 0x1;
|
|
843
|
+
// Skip extension header if present
|
|
844
|
+
if (obuExtension) {
|
|
845
|
+
bitstream.skipBits(8);
|
|
846
|
+
}
|
|
847
|
+
// Read OBU size if present
|
|
848
|
+
let obuSize;
|
|
849
|
+
if (obuHasSizeField) {
|
|
850
|
+
const obuSizeValue = readLeb128();
|
|
851
|
+
if (obuSizeValue === null)
|
|
852
|
+
return null; // It was invalid
|
|
853
|
+
obuSize = obuSizeValue;
|
|
854
|
+
}
|
|
855
|
+
else {
|
|
856
|
+
// Calculate remaining bits and convert to bytes, rounding down
|
|
857
|
+
obuSize = Math.floor(bitstream.getBitsLeft() / 8);
|
|
858
|
+
}
|
|
859
|
+
// We're only interested in Sequence Header OBU (type 1)
|
|
860
|
+
if (obuType === 1) {
|
|
861
|
+
// Read sequence header fields
|
|
862
|
+
const seqProfile = bitstream.readBits(3);
|
|
863
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
864
|
+
const stillPicture = bitstream.readBits(1);
|
|
865
|
+
const reducedStillPictureHeader = bitstream.readBits(1);
|
|
866
|
+
let seqLevel = 0;
|
|
867
|
+
let seqTier = 0;
|
|
868
|
+
let bufferDelayLengthMinus1 = 0;
|
|
869
|
+
if (reducedStillPictureHeader) {
|
|
870
|
+
seqLevel = bitstream.readBits(5);
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
// Parse timing_info_present_flag
|
|
874
|
+
const timingInfoPresentFlag = bitstream.readBits(1);
|
|
875
|
+
if (timingInfoPresentFlag) {
|
|
876
|
+
// Skip timing info (num_units_in_display_tick, time_scale, equal_picture_interval)
|
|
877
|
+
bitstream.skipBits(32); // num_units_in_display_tick
|
|
878
|
+
bitstream.skipBits(32); // time_scale
|
|
879
|
+
const equalPictureInterval = bitstream.readBits(1);
|
|
880
|
+
if (equalPictureInterval) {
|
|
881
|
+
// Skip num_ticks_per_picture_minus_1 (uvlc)
|
|
882
|
+
// Since this is variable length, we'd need to implement uvlc reading
|
|
883
|
+
// For now, we'll return null as this is rare
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
// Parse decoder_model_info_present_flag
|
|
888
|
+
const decoderModelInfoPresentFlag = bitstream.readBits(1);
|
|
889
|
+
if (decoderModelInfoPresentFlag) {
|
|
890
|
+
// Store buffer_delay_length_minus_1 instead of just skipping
|
|
891
|
+
bufferDelayLengthMinus1 = bitstream.readBits(5);
|
|
892
|
+
bitstream.skipBits(32); // num_units_in_decoding_tick
|
|
893
|
+
bitstream.skipBits(5); // buffer_removal_time_length_minus_1
|
|
894
|
+
bitstream.skipBits(5); // frame_presentation_time_length_minus_1
|
|
895
|
+
}
|
|
896
|
+
// Parse operating_points_cnt_minus_1
|
|
897
|
+
const operatingPointsCntMinus1 = bitstream.readBits(5);
|
|
898
|
+
// For each operating point
|
|
899
|
+
for (let i = 0; i <= operatingPointsCntMinus1; i++) {
|
|
900
|
+
// operating_point_idc[i]
|
|
901
|
+
bitstream.skipBits(12);
|
|
902
|
+
// seq_level_idx[i]
|
|
903
|
+
const seqLevelIdx = bitstream.readBits(5);
|
|
904
|
+
if (i === 0) {
|
|
905
|
+
seqLevel = seqLevelIdx;
|
|
906
|
+
}
|
|
907
|
+
if (seqLevelIdx > 7) {
|
|
908
|
+
// seq_tier[i]
|
|
909
|
+
const seqTierTemp = bitstream.readBits(1);
|
|
910
|
+
if (i === 0) {
|
|
911
|
+
seqTier = seqTierTemp;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
if (decoderModelInfoPresentFlag) {
|
|
915
|
+
// decoder_model_present_for_this_op[i]
|
|
916
|
+
const decoderModelPresentForThisOp = bitstream.readBits(1);
|
|
917
|
+
if (decoderModelPresentForThisOp) {
|
|
918
|
+
const n = bufferDelayLengthMinus1 + 1;
|
|
919
|
+
bitstream.skipBits(n); // decoder_buffer_delay[op]
|
|
920
|
+
bitstream.skipBits(n); // encoder_buffer_delay[op]
|
|
921
|
+
bitstream.skipBits(1); // low_delay_mode_flag[op]
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
// initial_display_delay_present_flag
|
|
925
|
+
const initialDisplayDelayPresentFlag = bitstream.readBits(1);
|
|
926
|
+
if (initialDisplayDelayPresentFlag) {
|
|
927
|
+
// initial_display_delay_minus_1[i]
|
|
928
|
+
bitstream.skipBits(4);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
const highBitdepth = bitstream.readBits(1);
|
|
933
|
+
let bitDepth = 8;
|
|
934
|
+
if (seqProfile === 2 && highBitdepth) {
|
|
935
|
+
const twelveBit = bitstream.readBits(1);
|
|
936
|
+
bitDepth = twelveBit ? 12 : 10;
|
|
937
|
+
}
|
|
938
|
+
else if (seqProfile <= 2) {
|
|
939
|
+
bitDepth = highBitdepth ? 10 : 8;
|
|
940
|
+
}
|
|
941
|
+
let monochrome = 0;
|
|
942
|
+
if (seqProfile !== 1) {
|
|
943
|
+
monochrome = bitstream.readBits(1);
|
|
944
|
+
}
|
|
945
|
+
let chromaSubsamplingX = 1;
|
|
946
|
+
let chromaSubsamplingY = 1;
|
|
947
|
+
let chromaSamplePosition = 0;
|
|
948
|
+
if (!monochrome) {
|
|
949
|
+
if (seqProfile === 0) {
|
|
950
|
+
chromaSubsamplingX = 1;
|
|
951
|
+
chromaSubsamplingY = 1;
|
|
952
|
+
}
|
|
953
|
+
else if (seqProfile === 1) {
|
|
954
|
+
chromaSubsamplingX = 0;
|
|
955
|
+
chromaSubsamplingY = 0;
|
|
956
|
+
}
|
|
957
|
+
else {
|
|
958
|
+
if (bitDepth === 12) {
|
|
959
|
+
chromaSubsamplingX = bitstream.readBits(1);
|
|
960
|
+
if (chromaSubsamplingX) {
|
|
961
|
+
chromaSubsamplingY = bitstream.readBits(1);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
if (chromaSubsamplingX && chromaSubsamplingY) {
|
|
966
|
+
chromaSamplePosition = bitstream.readBits(2);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return {
|
|
970
|
+
profile: seqProfile,
|
|
971
|
+
level: seqLevel,
|
|
972
|
+
tier: seqTier,
|
|
973
|
+
bitDepth,
|
|
974
|
+
monochrome,
|
|
975
|
+
chromaSubsamplingX,
|
|
976
|
+
chromaSubsamplingY,
|
|
977
|
+
chromaSamplePosition,
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
// Move to next OBU
|
|
981
|
+
// The OBU size is in bytes, so skip that many bytes.
|
|
982
|
+
bitstream.skipBits(obuSize * 8);
|
|
983
|
+
}
|
|
984
|
+
return null;
|
|
985
|
+
};
|
|
986
|
+
export const parseOpusIdentificationHeader = (bytes) => {
|
|
987
|
+
const view = toDataView(bytes);
|
|
988
|
+
const outputChannelCount = view.getUint8(9);
|
|
989
|
+
const preSkip = view.getUint16(10, true);
|
|
990
|
+
const inputSampleRate = view.getUint32(12, true);
|
|
991
|
+
const outputGain = view.getInt16(16, true);
|
|
992
|
+
const channelMappingFamily = view.getUint8(18);
|
|
993
|
+
let channelMappingTable = null;
|
|
994
|
+
if (channelMappingFamily) {
|
|
995
|
+
channelMappingTable = bytes.subarray(19, 19 + 2 + outputChannelCount);
|
|
996
|
+
}
|
|
997
|
+
return {
|
|
998
|
+
outputChannelCount,
|
|
999
|
+
preSkip,
|
|
1000
|
+
inputSampleRate,
|
|
1001
|
+
outputGain,
|
|
1002
|
+
channelMappingFamily,
|
|
1003
|
+
channelMappingTable,
|
|
1004
|
+
};
|
|
1005
|
+
};
|
|
1006
|
+
// From https://datatracker.ietf.org/doc/html/rfc6716, in 48 kHz samples
|
|
1007
|
+
const OPUS_FRAME_DURATION_TABLE = [
|
|
1008
|
+
480, 960, 1920, 2880,
|
|
1009
|
+
480, 960, 1920, 2880,
|
|
1010
|
+
480, 960, 1920, 2880,
|
|
1011
|
+
480, 960,
|
|
1012
|
+
480, 960,
|
|
1013
|
+
120, 240, 480, 960,
|
|
1014
|
+
120, 240, 480, 960,
|
|
1015
|
+
120, 240, 480, 960,
|
|
1016
|
+
120, 240, 480, 960,
|
|
1017
|
+
];
|
|
1018
|
+
export const parseOpusTocByte = (packet) => {
|
|
1019
|
+
const config = packet[0] >> 3;
|
|
1020
|
+
return {
|
|
1021
|
+
durationInSamples: OPUS_FRAME_DURATION_TABLE[config],
|
|
1022
|
+
};
|
|
1023
|
+
};
|
|
1024
|
+
// Based on vorbis_parser.c from FFmpeg.
|
|
1025
|
+
export const parseModesFromVorbisSetupPacket = (setupHeader) => {
|
|
1026
|
+
// Verify that this is a Setup header.
|
|
1027
|
+
if (setupHeader.length < 7) {
|
|
1028
|
+
throw new Error('Setup header is too short.');
|
|
1029
|
+
}
|
|
1030
|
+
if (setupHeader[0] !== 5) {
|
|
1031
|
+
throw new Error('Wrong packet type in Setup header.');
|
|
1032
|
+
}
|
|
1033
|
+
const signature = String.fromCharCode(...setupHeader.slice(1, 7));
|
|
1034
|
+
if (signature !== 'vorbis') {
|
|
1035
|
+
throw new Error('Invalid packet signature in Setup header.');
|
|
1036
|
+
}
|
|
1037
|
+
// Reverse the entire buffer.
|
|
1038
|
+
const bufSize = setupHeader.length;
|
|
1039
|
+
const revBuffer = new Uint8Array(bufSize);
|
|
1040
|
+
for (let i = 0; i < bufSize; i++) {
|
|
1041
|
+
revBuffer[i] = setupHeader[bufSize - 1 - i];
|
|
1042
|
+
}
|
|
1043
|
+
// Initialize a Bitstream on the reversed buffer.
|
|
1044
|
+
const bitstream = new Bitstream(revBuffer);
|
|
1045
|
+
// --- Find the framing bit.
|
|
1046
|
+
// In FFmpeg code, we scan until get_bits1() returns 1.
|
|
1047
|
+
let gotFramingBit = 0;
|
|
1048
|
+
while (bitstream.getBitsLeft() > 97) {
|
|
1049
|
+
if (bitstream.readBits(1) === 1) {
|
|
1050
|
+
gotFramingBit = bitstream.pos;
|
|
1051
|
+
break;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
if (gotFramingBit === 0) {
|
|
1055
|
+
throw new Error('Invalid Setup header: framing bit not found.');
|
|
1056
|
+
}
|
|
1057
|
+
// --- Search backwards for a valid mode header.
|
|
1058
|
+
// We try to “guess” the number of modes by reading a fixed pattern.
|
|
1059
|
+
let modeCount = 0;
|
|
1060
|
+
let gotModeHeader = false;
|
|
1061
|
+
let lastModeCount = 0;
|
|
1062
|
+
while (bitstream.getBitsLeft() >= 97) {
|
|
1063
|
+
const tempPos = bitstream.pos;
|
|
1064
|
+
const a = bitstream.readBits(8);
|
|
1065
|
+
const b = bitstream.readBits(16);
|
|
1066
|
+
const c = bitstream.readBits(16);
|
|
1067
|
+
// If a > 63 or b or c nonzero, assume we’ve gone too far.
|
|
1068
|
+
if (a > 63 || b !== 0 || c !== 0) {
|
|
1069
|
+
bitstream.pos = tempPos;
|
|
1070
|
+
break;
|
|
1071
|
+
}
|
|
1072
|
+
bitstream.skipBits(1);
|
|
1073
|
+
modeCount++;
|
|
1074
|
+
if (modeCount > 64) {
|
|
1075
|
+
break;
|
|
1076
|
+
}
|
|
1077
|
+
const bsClone = bitstream.clone();
|
|
1078
|
+
const candidate = bsClone.readBits(6) + 1;
|
|
1079
|
+
if (candidate === modeCount) {
|
|
1080
|
+
gotModeHeader = true;
|
|
1081
|
+
lastModeCount = modeCount;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
if (!gotModeHeader) {
|
|
1085
|
+
throw new Error('Invalid Setup header: mode header not found.');
|
|
1086
|
+
}
|
|
1087
|
+
if (lastModeCount > 63) {
|
|
1088
|
+
throw new Error(`Unsupported mode count: ${lastModeCount}.`);
|
|
1089
|
+
}
|
|
1090
|
+
const finalModeCount = lastModeCount;
|
|
1091
|
+
// --- Reinitialize the bitstream.
|
|
1092
|
+
bitstream.pos = 0;
|
|
1093
|
+
// Skip the bits up to the found framing bit.
|
|
1094
|
+
bitstream.skipBits(gotFramingBit);
|
|
1095
|
+
// --- Now read, for each mode (in reverse order), 40 bits then one bit.
|
|
1096
|
+
// That one bit is the mode blockflag.
|
|
1097
|
+
const modeBlockflags = Array(finalModeCount).fill(0);
|
|
1098
|
+
for (let i = finalModeCount - 1; i >= 0; i--) {
|
|
1099
|
+
bitstream.skipBits(40);
|
|
1100
|
+
modeBlockflags[i] = bitstream.readBits(1);
|
|
1101
|
+
}
|
|
1102
|
+
return { modeBlockflags };
|
|
1103
|
+
};
|