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.
Files changed (188) hide show
  1. package/README.md +4 -4
  2. package/dist/mediabunny.d.ts +4 -2
  3. package/dist/modules/codec-data.d.ts +95 -0
  4. package/dist/modules/codec-data.d.ts.map +1 -0
  5. package/dist/modules/codec-data.js +1103 -0
  6. package/dist/modules/codec.d.ts +250 -0
  7. package/dist/modules/codec.d.ts.map +1 -0
  8. package/dist/modules/codec.js +1100 -0
  9. package/dist/modules/conversion.d.ts +158 -0
  10. package/dist/modules/conversion.d.ts.map +1 -0
  11. package/dist/modules/conversion.js +866 -0
  12. package/dist/modules/custom-coder.d.ts +119 -0
  13. package/dist/modules/custom-coder.d.ts.map +1 -0
  14. package/dist/modules/custom-coder.js +91 -0
  15. package/dist/modules/demuxer.d.ts +17 -0
  16. package/dist/modules/demuxer.d.ts.map +1 -0
  17. package/dist/modules/demuxer.js +12 -0
  18. package/dist/modules/index.d.ts +25 -0
  19. package/dist/modules/index.d.ts.map +1 -0
  20. package/dist/modules/index.js +24 -0
  21. package/dist/modules/input-format.d.ts +121 -0
  22. package/dist/modules/input-format.d.ts.map +1 -0
  23. package/dist/modules/input-format.js +333 -0
  24. package/dist/modules/input-track.d.ts +138 -0
  25. package/dist/modules/input-track.d.ts.map +1 -0
  26. package/dist/modules/input-track.js +230 -0
  27. package/dist/modules/input.d.ts +55 -0
  28. package/dist/modules/input.d.ts.map +1 -0
  29. package/dist/modules/input.js +104 -0
  30. package/dist/modules/isobmff/isobmff-boxes.d.ts +202 -0
  31. package/dist/modules/isobmff/isobmff-boxes.d.ts.map +1 -0
  32. package/dist/modules/isobmff/isobmff-boxes.js +1111 -0
  33. package/dist/modules/isobmff/isobmff-demuxer.d.ts +163 -0
  34. package/dist/modules/isobmff/isobmff-demuxer.d.ts.map +1 -0
  35. package/dist/modules/isobmff/isobmff-demuxer.js +2080 -0
  36. package/dist/modules/isobmff/isobmff-misc.d.ts +14 -0
  37. package/dist/modules/isobmff/isobmff-misc.d.ts.map +1 -0
  38. package/dist/modules/isobmff/isobmff-misc.js +20 -0
  39. package/dist/modules/isobmff/isobmff-muxer.d.ts +142 -0
  40. package/dist/modules/isobmff/isobmff-muxer.d.ts.map +1 -0
  41. package/dist/modules/isobmff/isobmff-muxer.js +858 -0
  42. package/dist/modules/isobmff/isobmff-reader.d.ts +36 -0
  43. package/dist/modules/isobmff/isobmff-reader.d.ts.map +1 -0
  44. package/dist/modules/isobmff/isobmff-reader.js +105 -0
  45. package/dist/modules/matroska/ebml.d.ts +156 -0
  46. package/dist/modules/matroska/ebml.d.ts.map +1 -0
  47. package/dist/modules/matroska/ebml.js +529 -0
  48. package/dist/modules/matroska/matroska-demuxer.d.ts +119 -0
  49. package/dist/modules/matroska/matroska-demuxer.d.ts.map +1 -0
  50. package/dist/modules/matroska/matroska-demuxer.js +1330 -0
  51. package/dist/modules/matroska/matroska-misc.d.ts +14 -0
  52. package/dist/modules/matroska/matroska-misc.d.ts.map +1 -0
  53. package/dist/modules/matroska/matroska-misc.js +20 -0
  54. package/dist/modules/matroska/matroska-muxer.d.ts +71 -0
  55. package/dist/modules/matroska/matroska-muxer.d.ts.map +1 -0
  56. package/dist/modules/matroska/matroska-muxer.js +729 -0
  57. package/dist/modules/media-sink.d.ts +272 -0
  58. package/dist/modules/media-sink.d.ts.map +1 -0
  59. package/dist/modules/media-sink.js +1264 -0
  60. package/dist/modules/media-source.d.ts +230 -0
  61. package/dist/modules/media-source.d.ts.map +1 -0
  62. package/dist/modules/media-source.js +1095 -0
  63. package/dist/modules/misc.d.ts +127 -0
  64. package/dist/modules/misc.d.ts.map +1 -0
  65. package/dist/modules/misc.js +462 -0
  66. package/dist/modules/mp3/mp3-demuxer.d.ts +32 -0
  67. package/dist/modules/mp3/mp3-demuxer.d.ts.map +1 -0
  68. package/dist/modules/mp3/mp3-demuxer.js +160 -0
  69. package/dist/modules/mp3/mp3-misc.d.ts +37 -0
  70. package/dist/modules/mp3/mp3-misc.d.ts.map +1 -0
  71. package/dist/modules/mp3/mp3-misc.js +122 -0
  72. package/dist/modules/mp3/mp3-muxer.d.ts +27 -0
  73. package/dist/modules/mp3/mp3-muxer.d.ts.map +1 -0
  74. package/dist/modules/mp3/mp3-muxer.js +112 -0
  75. package/dist/modules/mp3/mp3-reader.d.ts +25 -0
  76. package/dist/modules/mp3/mp3-reader.d.ts.map +1 -0
  77. package/dist/modules/mp3/mp3-reader.js +73 -0
  78. package/dist/modules/mp3/mp3-writer.d.ts +30 -0
  79. package/dist/modules/mp3/mp3-writer.d.ts.map +1 -0
  80. package/dist/modules/mp3/mp3-writer.js +72 -0
  81. package/dist/modules/muxer.d.ts +32 -0
  82. package/dist/modules/muxer.d.ts.map +1 -0
  83. package/dist/modules/muxer.js +50 -0
  84. package/dist/modules/ogg/ogg-demuxer.d.ts +53 -0
  85. package/dist/modules/ogg/ogg-demuxer.d.ts.map +1 -0
  86. package/dist/modules/ogg/ogg-demuxer.js +701 -0
  87. package/dist/modules/ogg/ogg-misc.d.ts +27 -0
  88. package/dist/modules/ogg/ogg-misc.d.ts.map +1 -0
  89. package/dist/modules/ogg/ogg-misc.js +78 -0
  90. package/dist/modules/ogg/ogg-muxer.d.ts +58 -0
  91. package/dist/modules/ogg/ogg-muxer.d.ts.map +1 -0
  92. package/dist/modules/ogg/ogg-muxer.js +345 -0
  93. package/dist/modules/ogg/ogg-reader.d.ts +37 -0
  94. package/dist/modules/ogg/ogg-reader.d.ts.map +1 -0
  95. package/dist/modules/ogg/ogg-reader.js +104 -0
  96. package/dist/modules/output-format.d.ts +285 -0
  97. package/dist/modules/output-format.d.ts.map +1 -0
  98. package/dist/modules/output-format.js +385 -0
  99. package/dist/modules/output.d.ts +134 -0
  100. package/dist/modules/output.d.ts.map +1 -0
  101. package/dist/modules/output.js +269 -0
  102. package/dist/modules/packet.d.ts +86 -0
  103. package/dist/modules/packet.d.ts.map +1 -0
  104. package/dist/modules/packet.js +133 -0
  105. package/dist/modules/pcm.d.ts +12 -0
  106. package/dist/modules/pcm.d.ts.map +1 -0
  107. package/dist/modules/pcm.js +85 -0
  108. package/dist/modules/reader.d.ts +40 -0
  109. package/dist/modules/reader.d.ts.map +1 -0
  110. package/dist/modules/reader.js +153 -0
  111. package/dist/modules/sample.d.ts +186 -0
  112. package/dist/modules/sample.d.ts.map +1 -0
  113. package/dist/modules/sample.js +798 -0
  114. package/dist/modules/source.d.ts +76 -0
  115. package/dist/modules/source.d.ts.map +1 -0
  116. package/dist/modules/source.js +183 -0
  117. package/dist/modules/subtitles.d.ts +37 -0
  118. package/dist/modules/subtitles.d.ts.map +1 -0
  119. package/dist/modules/subtitles.js +89 -0
  120. package/dist/modules/target.d.ts +58 -0
  121. package/dist/modules/target.d.ts.map +1 -0
  122. package/dist/modules/target.js +63 -0
  123. package/dist/modules/wave/riff-reader.d.ts +19 -0
  124. package/dist/modules/wave/riff-reader.d.ts.map +1 -0
  125. package/dist/modules/wave/riff-reader.js +38 -0
  126. package/dist/modules/wave/riff-writer.d.ts +18 -0
  127. package/dist/modules/wave/riff-writer.d.ts.map +1 -0
  128. package/dist/modules/wave/riff-writer.js +25 -0
  129. package/dist/modules/wave/wave-demuxer.d.ts +42 -0
  130. package/dist/modules/wave/wave-demuxer.d.ts.map +1 -0
  131. package/dist/modules/wave/wave-demuxer.js +239 -0
  132. package/dist/modules/wave/wave-muxer.d.ts +27 -0
  133. package/dist/modules/wave/wave-muxer.d.ts.map +1 -0
  134. package/dist/modules/wave/wave-muxer.js +108 -0
  135. package/dist/modules/writer.d.ts +85 -0
  136. package/dist/modules/writer.d.ts.map +1 -0
  137. package/dist/modules/writer.js +347 -0
  138. package/package.json +14 -10
  139. package/src/codec-data.ts +1394 -0
  140. package/src/codec.ts +1392 -0
  141. package/src/conversion.ts +1199 -0
  142. package/src/custom-coder.ts +162 -0
  143. package/src/demuxer.ts +22 -0
  144. package/src/index.ts +148 -0
  145. package/src/input-format.ts +386 -0
  146. package/src/input-track.ts +341 -0
  147. package/src/input.ts +138 -0
  148. package/src/isobmff/isobmff-boxes.ts +1365 -0
  149. package/src/isobmff/isobmff-demuxer.ts +2646 -0
  150. package/src/isobmff/isobmff-misc.ts +29 -0
  151. package/src/isobmff/isobmff-muxer.ts +1210 -0
  152. package/src/isobmff/isobmff-reader.ts +137 -0
  153. package/src/matroska/ebml.ts +595 -0
  154. package/src/matroska/matroska-demuxer.ts +1677 -0
  155. package/src/matroska/matroska-misc.ts +29 -0
  156. package/src/matroska/matroska-muxer.ts +999 -0
  157. package/src/media-sink.ts +1577 -0
  158. package/src/media-source.ts +1406 -0
  159. package/src/misc.ts +584 -0
  160. package/src/mp3/mp3-demuxer.ts +228 -0
  161. package/src/mp3/mp3-misc.ts +154 -0
  162. package/src/mp3/mp3-muxer.ts +153 -0
  163. package/src/mp3/mp3-reader.ts +93 -0
  164. package/src/mp3/mp3-writer.ts +112 -0
  165. package/src/muxer.ts +88 -0
  166. package/src/ogg/ogg-demuxer.ts +985 -0
  167. package/src/ogg/ogg-misc.ts +116 -0
  168. package/src/ogg/ogg-muxer.ts +480 -0
  169. package/src/ogg/ogg-reader.ts +143 -0
  170. package/src/output-format.ts +689 -0
  171. package/src/output.ts +427 -0
  172. package/src/packet.ts +180 -0
  173. package/src/pcm.ts +112 -0
  174. package/src/reader.ts +205 -0
  175. package/src/sample.ts +1029 -0
  176. package/src/source.ts +279 -0
  177. package/src/subtitles.ts +131 -0
  178. package/src/target.ts +106 -0
  179. package/src/tsconfig.json +13 -0
  180. package/src/wave/riff-reader.ts +48 -0
  181. package/src/wave/riff-writer.ts +30 -0
  182. package/src/wave/wave-demuxer.ts +317 -0
  183. package/src/wave/wave-muxer.ts +145 -0
  184. package/src/writer.ts +461 -0
  185. /package/dist/{mediabunny.js → bundles/mediabunny.cjs} +0 -0
  186. /package/dist/{mediabunny.min.js → bundles/mediabunny.min.cjs} +0 -0
  187. /package/dist/{mediabunny.min.mjs → bundles/mediabunny.min.mjs} +0 -0
  188. /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
+ };