@kenzuya/mediabunny 1.26.0 → 1.28.6
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 +1 -1
- package/dist/bundles/{mediabunny.mjs → mediabunny.js} +21963 -21390
- package/dist/bundles/mediabunny.min.js +490 -0
- package/dist/modules/shared/mp3-misc.d.ts.map +1 -1
- package/dist/modules/src/adts/adts-demuxer.d.ts +6 -6
- package/dist/modules/src/adts/adts-demuxer.d.ts.map +1 -1
- package/dist/modules/src/adts/adts-muxer.d.ts +4 -4
- package/dist/modules/src/adts/adts-muxer.d.ts.map +1 -1
- package/dist/modules/src/adts/adts-reader.d.ts +1 -1
- package/dist/modules/src/adts/adts-reader.d.ts.map +1 -1
- package/dist/modules/src/avi/avi-demuxer.d.ts +44 -0
- package/dist/modules/src/avi/avi-demuxer.d.ts.map +1 -0
- package/dist/modules/src/avi/avi-misc.d.ts +88 -0
- package/dist/modules/src/avi/avi-misc.d.ts.map +1 -0
- package/dist/modules/src/avi/avi-muxer.d.ts +45 -0
- package/dist/modules/src/avi/avi-muxer.d.ts.map +1 -0
- package/dist/modules/src/avi/riff-writer.d.ts +26 -0
- package/dist/modules/src/avi/riff-writer.d.ts.map +1 -0
- package/dist/modules/src/codec-data.d.ts +8 -3
- package/dist/modules/src/codec-data.d.ts.map +1 -1
- package/dist/modules/src/codec.d.ts +10 -10
- package/dist/modules/src/codec.d.ts.map +1 -1
- package/dist/modules/src/conversion.d.ts +33 -16
- package/dist/modules/src/conversion.d.ts.map +1 -1
- package/dist/modules/src/custom-coder.d.ts +8 -8
- package/dist/modules/src/custom-coder.d.ts.map +1 -1
- package/dist/modules/src/demuxer.d.ts +3 -3
- package/dist/modules/src/demuxer.d.ts.map +1 -1
- package/dist/modules/src/encode.d.ts +8 -8
- package/dist/modules/src/encode.d.ts.map +1 -1
- package/dist/modules/src/flac/flac-demuxer.d.ts +7 -7
- package/dist/modules/src/flac/flac-demuxer.d.ts.map +1 -1
- package/dist/modules/src/flac/flac-misc.d.ts +3 -3
- package/dist/modules/src/flac/flac-misc.d.ts.map +1 -1
- package/dist/modules/src/flac/flac-muxer.d.ts +5 -5
- package/dist/modules/src/flac/flac-muxer.d.ts.map +1 -1
- package/dist/modules/src/id3.d.ts +3 -3
- package/dist/modules/src/id3.d.ts.map +1 -1
- package/dist/modules/src/index.d.ts +20 -20
- package/dist/modules/src/index.d.ts.map +1 -1
- package/dist/modules/src/input-format.d.ts +22 -0
- package/dist/modules/src/input-format.d.ts.map +1 -1
- package/dist/modules/src/input-track.d.ts +8 -8
- package/dist/modules/src/input-track.d.ts.map +1 -1
- package/dist/modules/src/input.d.ts +12 -12
- package/dist/modules/src/isobmff/isobmff-boxes.d.ts +2 -2
- package/dist/modules/src/isobmff/isobmff-boxes.d.ts.map +1 -1
- package/dist/modules/src/isobmff/isobmff-demuxer.d.ts +12 -12
- package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
- package/dist/modules/src/isobmff/isobmff-misc.d.ts.map +1 -1
- package/dist/modules/src/isobmff/isobmff-muxer.d.ts +11 -11
- package/dist/modules/src/isobmff/isobmff-muxer.d.ts.map +1 -1
- package/dist/modules/src/isobmff/isobmff-reader.d.ts +2 -2
- package/dist/modules/src/isobmff/isobmff-reader.d.ts.map +1 -1
- package/dist/modules/src/matroska/ebml.d.ts +3 -3
- package/dist/modules/src/matroska/ebml.d.ts.map +1 -1
- package/dist/modules/src/matroska/matroska-demuxer.d.ts +13 -13
- package/dist/modules/src/matroska/matroska-demuxer.d.ts.map +1 -1
- package/dist/modules/src/matroska/matroska-input.d.ts +33 -0
- package/dist/modules/src/matroska/matroska-input.d.ts.map +1 -0
- package/dist/modules/src/matroska/matroska-misc.d.ts.map +1 -1
- package/dist/modules/src/matroska/matroska-muxer.d.ts +5 -5
- package/dist/modules/src/matroska/matroska-muxer.d.ts.map +1 -1
- package/dist/modules/src/media-sink.d.ts +5 -5
- package/dist/modules/src/media-sink.d.ts.map +1 -1
- package/dist/modules/src/media-source.d.ts +22 -4
- package/dist/modules/src/media-source.d.ts.map +1 -1
- package/dist/modules/src/metadata.d.ts +2 -2
- package/dist/modules/src/metadata.d.ts.map +1 -1
- package/dist/modules/src/misc.d.ts +5 -4
- package/dist/modules/src/misc.d.ts.map +1 -1
- package/dist/modules/src/mp3/mp3-demuxer.d.ts +7 -7
- package/dist/modules/src/mp3/mp3-demuxer.d.ts.map +1 -1
- package/dist/modules/src/mp3/mp3-muxer.d.ts +4 -4
- package/dist/modules/src/mp3/mp3-muxer.d.ts.map +1 -1
- package/dist/modules/src/mp3/mp3-reader.d.ts +2 -2
- package/dist/modules/src/mp3/mp3-reader.d.ts.map +1 -1
- package/dist/modules/src/mp3/mp3-writer.d.ts +1 -1
- package/dist/modules/src/mp3/mp3-writer.d.ts.map +1 -1
- package/dist/modules/src/muxer.d.ts +4 -4
- package/dist/modules/src/muxer.d.ts.map +1 -1
- package/dist/modules/src/ogg/ogg-demuxer.d.ts +7 -7
- package/dist/modules/src/ogg/ogg-demuxer.d.ts.map +1 -1
- package/dist/modules/src/ogg/ogg-misc.d.ts +1 -1
- package/dist/modules/src/ogg/ogg-misc.d.ts.map +1 -1
- package/dist/modules/src/ogg/ogg-muxer.d.ts +5 -5
- package/dist/modules/src/ogg/ogg-muxer.d.ts.map +1 -1
- package/dist/modules/src/ogg/ogg-reader.d.ts +1 -1
- package/dist/modules/src/ogg/ogg-reader.d.ts.map +1 -1
- package/dist/modules/src/output-format.d.ts +51 -6
- package/dist/modules/src/output-format.d.ts.map +1 -1
- package/dist/modules/src/output.d.ts +13 -13
- package/dist/modules/src/output.d.ts.map +1 -1
- package/dist/modules/src/packet.d.ts +1 -1
- package/dist/modules/src/packet.d.ts.map +1 -1
- package/dist/modules/src/pcm.d.ts.map +1 -1
- package/dist/modules/src/reader.d.ts +2 -2
- package/dist/modules/src/reader.d.ts.map +1 -1
- package/dist/modules/src/sample.d.ts +57 -15
- package/dist/modules/src/sample.d.ts.map +1 -1
- package/dist/modules/src/source.d.ts +3 -3
- package/dist/modules/src/source.d.ts.map +1 -1
- package/dist/modules/src/subtitles.d.ts +1 -1
- package/dist/modules/src/subtitles.d.ts.map +1 -1
- package/dist/modules/src/target.d.ts +2 -2
- package/dist/modules/src/target.d.ts.map +1 -1
- package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
- package/dist/modules/src/wave/riff-writer.d.ts +1 -1
- package/dist/modules/src/wave/riff-writer.d.ts.map +1 -1
- package/dist/modules/src/wave/wave-demuxer.d.ts +6 -6
- package/dist/modules/src/wave/wave-demuxer.d.ts.map +1 -1
- package/dist/modules/src/wave/wave-muxer.d.ts +4 -4
- package/dist/modules/src/wave/wave-muxer.d.ts.map +1 -1
- package/dist/modules/src/writer.d.ts +1 -1
- package/dist/modules/src/writer.d.ts.map +1 -1
- package/dist/packages/eac3/eac3.wasm +0 -0
- package/dist/packages/eac3/mediabunny-eac3.js +1058 -0
- package/dist/packages/eac3/mediabunny-eac3.min.js +44 -0
- package/dist/packages/mp3-encoder/mediabunny-mp3-encoder.js +694 -0
- package/dist/packages/mp3-encoder/mediabunny-mp3-encoder.min.js +58 -0
- package/dist/packages/mpeg4/mediabunny-mpeg4.js +1198 -0
- package/dist/packages/mpeg4/mediabunny-mpeg4.min.js +44 -0
- package/dist/packages/mpeg4/xvid.wasm +0 -0
- package/package.json +18 -57
- package/dist/bundles/mediabunny.cjs +0 -26140
- package/dist/bundles/mediabunny.min.cjs +0 -147
- package/dist/bundles/mediabunny.min.mjs +0 -146
- package/dist/mediabunny.d.ts +0 -3319
- package/dist/modules/shared/mp3-misc.js +0 -147
- package/dist/modules/src/adts/adts-demuxer.js +0 -239
- package/dist/modules/src/adts/adts-muxer.js +0 -80
- package/dist/modules/src/adts/adts-reader.js +0 -63
- package/dist/modules/src/codec-data.js +0 -1730
- package/dist/modules/src/codec.js +0 -869
- package/dist/modules/src/conversion.js +0 -1459
- package/dist/modules/src/custom-coder.js +0 -117
- package/dist/modules/src/demuxer.js +0 -12
- package/dist/modules/src/encode.js +0 -442
- package/dist/modules/src/flac/flac-demuxer.js +0 -504
- package/dist/modules/src/flac/flac-misc.js +0 -135
- package/dist/modules/src/flac/flac-muxer.js +0 -222
- package/dist/modules/src/id3.js +0 -848
- package/dist/modules/src/index.js +0 -28
- package/dist/modules/src/input-format.js +0 -480
- package/dist/modules/src/input-track.js +0 -372
- package/dist/modules/src/input.js +0 -188
- package/dist/modules/src/isobmff/isobmff-boxes.js +0 -1480
- package/dist/modules/src/isobmff/isobmff-demuxer.js +0 -2618
- package/dist/modules/src/isobmff/isobmff-misc.js +0 -20
- package/dist/modules/src/isobmff/isobmff-muxer.js +0 -966
- package/dist/modules/src/isobmff/isobmff-reader.js +0 -72
- package/dist/modules/src/matroska/ebml.js +0 -653
- package/dist/modules/src/matroska/matroska-demuxer.js +0 -2133
- package/dist/modules/src/matroska/matroska-misc.js +0 -20
- package/dist/modules/src/matroska/matroska-muxer.js +0 -1017
- package/dist/modules/src/media-sink.js +0 -1736
- package/dist/modules/src/media-source.js +0 -1825
- package/dist/modules/src/metadata.js +0 -193
- package/dist/modules/src/misc.js +0 -623
- package/dist/modules/src/mp3/mp3-demuxer.js +0 -285
- package/dist/modules/src/mp3/mp3-muxer.js +0 -123
- package/dist/modules/src/mp3/mp3-reader.js +0 -26
- package/dist/modules/src/mp3/mp3-writer.js +0 -78
- package/dist/modules/src/muxer.js +0 -50
- package/dist/modules/src/node.d.ts +0 -9
- package/dist/modules/src/node.d.ts.map +0 -1
- package/dist/modules/src/node.js +0 -9
- package/dist/modules/src/ogg/ogg-demuxer.js +0 -763
- package/dist/modules/src/ogg/ogg-misc.js +0 -78
- package/dist/modules/src/ogg/ogg-muxer.js +0 -353
- package/dist/modules/src/ogg/ogg-reader.js +0 -65
- package/dist/modules/src/output-format.js +0 -527
- package/dist/modules/src/output.js +0 -300
- package/dist/modules/src/packet.js +0 -182
- package/dist/modules/src/pcm.js +0 -85
- package/dist/modules/src/reader.js +0 -236
- package/dist/modules/src/sample.js +0 -1056
- package/dist/modules/src/source.js +0 -1182
- package/dist/modules/src/subtitles.js +0 -575
- package/dist/modules/src/target.js +0 -140
- package/dist/modules/src/wave/riff-writer.js +0 -30
- package/dist/modules/src/wave/wave-demuxer.js +0 -447
- package/dist/modules/src/wave/wave-muxer.js +0 -318
- package/dist/modules/src/writer.js +0 -370
- package/src/adts/adts-demuxer.ts +0 -331
- package/src/adts/adts-muxer.ts +0 -111
- package/src/adts/adts-reader.ts +0 -85
- package/src/codec-data.ts +0 -2078
- package/src/codec.ts +0 -1092
- package/src/conversion.ts +0 -2112
- package/src/custom-coder.ts +0 -197
- package/src/demuxer.ts +0 -24
- package/src/encode.ts +0 -739
- package/src/flac/flac-demuxer.ts +0 -730
- package/src/flac/flac-misc.ts +0 -164
- package/src/flac/flac-muxer.ts +0 -320
- package/src/id3.ts +0 -925
- package/src/index.ts +0 -221
- package/src/input-format.ts +0 -541
- package/src/input-track.ts +0 -529
- package/src/input.ts +0 -235
- package/src/isobmff/isobmff-boxes.ts +0 -1719
- package/src/isobmff/isobmff-demuxer.ts +0 -3190
- package/src/isobmff/isobmff-misc.ts +0 -29
- package/src/isobmff/isobmff-muxer.ts +0 -1348
- package/src/isobmff/isobmff-reader.ts +0 -91
- package/src/matroska/ebml.ts +0 -730
- package/src/matroska/matroska-demuxer.ts +0 -2481
- package/src/matroska/matroska-misc.ts +0 -29
- package/src/matroska/matroska-muxer.ts +0 -1276
- package/src/media-sink.ts +0 -2179
- package/src/media-source.ts +0 -2243
- package/src/metadata.ts +0 -320
- package/src/misc.ts +0 -798
- package/src/mp3/mp3-demuxer.ts +0 -383
- package/src/mp3/mp3-muxer.ts +0 -166
- package/src/mp3/mp3-reader.ts +0 -34
- package/src/mp3/mp3-writer.ts +0 -120
- package/src/muxer.ts +0 -88
- package/src/node.ts +0 -11
- package/src/ogg/ogg-demuxer.ts +0 -1053
- package/src/ogg/ogg-misc.ts +0 -116
- package/src/ogg/ogg-muxer.ts +0 -497
- package/src/ogg/ogg-reader.ts +0 -93
- package/src/output-format.ts +0 -945
- package/src/output.ts +0 -488
- package/src/packet.ts +0 -263
- package/src/pcm.ts +0 -112
- package/src/reader.ts +0 -323
- package/src/sample.ts +0 -1461
- package/src/source.ts +0 -1688
- package/src/subtitles.ts +0 -711
- package/src/target.ts +0 -204
- package/src/tsconfig.json +0 -16
- package/src/wave/riff-writer.ts +0 -36
- package/src/wave/wave-demuxer.ts +0 -529
- package/src/wave/wave-muxer.ts +0 -371
- package/src/writer.ts +0 -490
package/src/codec-data.ts
DELETED
|
@@ -1,2078 +0,0 @@
|
|
|
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
|
-
|
|
9
|
-
import { VideoCodec, VP9_LEVEL_TABLE } from './codec';
|
|
10
|
-
import {
|
|
11
|
-
assert,
|
|
12
|
-
assertNever,
|
|
13
|
-
base64ToBytes,
|
|
14
|
-
Bitstream,
|
|
15
|
-
bytesToBase64,
|
|
16
|
-
keyValueIterator,
|
|
17
|
-
getUint24,
|
|
18
|
-
last,
|
|
19
|
-
readExpGolomb,
|
|
20
|
-
readSignedExpGolomb,
|
|
21
|
-
textDecoder,
|
|
22
|
-
textEncoder,
|
|
23
|
-
toDataView,
|
|
24
|
-
toUint8Array,
|
|
25
|
-
} from './misc';
|
|
26
|
-
import { PacketType } from './packet';
|
|
27
|
-
import { MetadataTags } from './metadata';
|
|
28
|
-
|
|
29
|
-
// References for AVC/HEVC code:
|
|
30
|
-
// ISO 14496-15
|
|
31
|
-
// Rec. ITU-T H.264
|
|
32
|
-
// Rec. ITU-T H.265
|
|
33
|
-
// https://stackoverflow.com/questions/24884827
|
|
34
|
-
|
|
35
|
-
export enum AvcNalUnitType {
|
|
36
|
-
IDR = 5,
|
|
37
|
-
SPS = 7,
|
|
38
|
-
PPS = 8,
|
|
39
|
-
SPS_EXT = 13,
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export enum HevcNalUnitType {
|
|
43
|
-
RASL_N = 8,
|
|
44
|
-
RASL_R = 9,
|
|
45
|
-
BLA_W_LP = 16,
|
|
46
|
-
RSV_IRAP_VCL23 = 23,
|
|
47
|
-
VPS_NUT = 32,
|
|
48
|
-
SPS_NUT = 33,
|
|
49
|
-
PPS_NUT = 34,
|
|
50
|
-
PREFIX_SEI_NUT = 39,
|
|
51
|
-
SUFFIX_SEI_NUT = 40,
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Finds all NAL units in an AVC packet in Annex B format. */
|
|
55
|
-
export const findNalUnitsInAnnexB = (packetData: Uint8Array) => {
|
|
56
|
-
const nalUnits: Uint8Array[] = [];
|
|
57
|
-
let i = 0;
|
|
58
|
-
|
|
59
|
-
while (i < packetData.length) {
|
|
60
|
-
let startCodePos = -1;
|
|
61
|
-
let startCodeLength = 0;
|
|
62
|
-
|
|
63
|
-
for (let j = i; j < packetData.length - 3; j++) {
|
|
64
|
-
// Check for 3-byte start code (0x000001)
|
|
65
|
-
if (packetData[j] === 0 && packetData[j + 1] === 0 && packetData[j + 2] === 1) {
|
|
66
|
-
startCodePos = j;
|
|
67
|
-
startCodeLength = 3;
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Check for 4-byte start code (0x00000001)
|
|
72
|
-
if (
|
|
73
|
-
j < packetData.length - 4
|
|
74
|
-
&& packetData[j] === 0
|
|
75
|
-
&& packetData[j + 1] === 0
|
|
76
|
-
&& packetData[j + 2] === 0
|
|
77
|
-
&& packetData[j + 3] === 1
|
|
78
|
-
) {
|
|
79
|
-
startCodePos = j;
|
|
80
|
-
startCodeLength = 4;
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (startCodePos === -1) {
|
|
86
|
-
break; // No more start codes found
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// If this isn't the first start code, extract the previous NAL unit
|
|
90
|
-
if (i > 0 && startCodePos > i) {
|
|
91
|
-
const nalData = packetData.subarray(i, startCodePos);
|
|
92
|
-
if (nalData.length > 0) {
|
|
93
|
-
nalUnits.push(nalData);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
i = startCodePos + startCodeLength;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Extract the last NAL unit if there is one
|
|
101
|
-
if (i < packetData.length) {
|
|
102
|
-
const nalData = packetData.subarray(i);
|
|
103
|
-
if (nalData.length > 0) {
|
|
104
|
-
nalUnits.push(nalData);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return nalUnits;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
/** Finds all NAL units in an AVC packet in length-prefixed format. */
|
|
112
|
-
const findNalUnitsInLengthPrefixed = (packetData: Uint8Array, lengthSize: 1 | 2 | 3 | 4) => {
|
|
113
|
-
const nalUnits: Uint8Array[] = [];
|
|
114
|
-
let offset = 0;
|
|
115
|
-
|
|
116
|
-
const dataView = new DataView(packetData.buffer, packetData.byteOffset, packetData.byteLength);
|
|
117
|
-
|
|
118
|
-
while (offset + lengthSize <= packetData.length) {
|
|
119
|
-
let nalUnitLength: number;
|
|
120
|
-
if (lengthSize === 1) {
|
|
121
|
-
nalUnitLength = dataView.getUint8(offset);
|
|
122
|
-
} else if (lengthSize === 2) {
|
|
123
|
-
nalUnitLength = dataView.getUint16(offset, false);
|
|
124
|
-
} else if (lengthSize === 3) {
|
|
125
|
-
nalUnitLength = getUint24(dataView, offset, false);
|
|
126
|
-
} else if (lengthSize === 4) {
|
|
127
|
-
nalUnitLength = dataView.getUint32(offset, false);
|
|
128
|
-
} else {
|
|
129
|
-
assertNever(lengthSize);
|
|
130
|
-
assert(false);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
offset += lengthSize;
|
|
134
|
-
|
|
135
|
-
const nalUnit = packetData.subarray(offset, offset + nalUnitLength);
|
|
136
|
-
nalUnits.push(nalUnit);
|
|
137
|
-
|
|
138
|
-
offset += nalUnitLength;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return nalUnits;
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
const removeEmulationPreventionBytes = (data: Uint8Array) => {
|
|
145
|
-
const result: number[] = [];
|
|
146
|
-
const len = data.length;
|
|
147
|
-
|
|
148
|
-
for (let i = 0; i < len; i++) {
|
|
149
|
-
// Look for the 0x000003 pattern
|
|
150
|
-
if (i + 2 < len && data[i] === 0x00 && data[i + 1] === 0x00 && data[i + 2] === 0x03) {
|
|
151
|
-
result.push(0x00, 0x00); // Push the first two bytes
|
|
152
|
-
i += 2; // Skip the 0x03 byte
|
|
153
|
-
} else {
|
|
154
|
-
result.push(data[i]!);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return new Uint8Array(result);
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
/** Converts an AVC packet in Annex B format to length-prefixed format. */
|
|
162
|
-
export const transformAnnexBToLengthPrefixed = (packetData: Uint8Array) => {
|
|
163
|
-
const NAL_UNIT_LENGTH_SIZE = 4;
|
|
164
|
-
|
|
165
|
-
const nalUnits = findNalUnitsInAnnexB(packetData);
|
|
166
|
-
|
|
167
|
-
if (nalUnits.length === 0) {
|
|
168
|
-
// If no NAL units were found, it's not valid Annex B data
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
let totalSize = 0;
|
|
173
|
-
for (const nalUnit of nalUnits) {
|
|
174
|
-
totalSize += NAL_UNIT_LENGTH_SIZE + nalUnit.byteLength;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const avccData = new Uint8Array(totalSize);
|
|
178
|
-
const dataView = new DataView(avccData.buffer);
|
|
179
|
-
let offset = 0;
|
|
180
|
-
|
|
181
|
-
// Write each NAL unit with its length prefix
|
|
182
|
-
for (const nalUnit of nalUnits) {
|
|
183
|
-
const length = nalUnit.byteLength;
|
|
184
|
-
|
|
185
|
-
dataView.setUint32(offset, length, false);
|
|
186
|
-
offset += 4;
|
|
187
|
-
|
|
188
|
-
avccData.set(nalUnit, offset);
|
|
189
|
-
offset += nalUnit.byteLength;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return avccData;
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
// Data specified in ISO 14496-15
|
|
196
|
-
export type AvcDecoderConfigurationRecord = {
|
|
197
|
-
configurationVersion: number;
|
|
198
|
-
avcProfileIndication: number;
|
|
199
|
-
profileCompatibility: number;
|
|
200
|
-
avcLevelIndication: number;
|
|
201
|
-
lengthSizeMinusOne: number;
|
|
202
|
-
sequenceParameterSets: Uint8Array[];
|
|
203
|
-
pictureParameterSets: Uint8Array[];
|
|
204
|
-
|
|
205
|
-
// Fields only for specific profiles:
|
|
206
|
-
chromaFormat: number | null;
|
|
207
|
-
bitDepthLumaMinus8: number | null;
|
|
208
|
-
bitDepthChromaMinus8: number | null;
|
|
209
|
-
sequenceParameterSetExt: Uint8Array[] | null;
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
export const extractAvcNalUnits = (packetData: Uint8Array, decoderConfig: VideoDecoderConfig) => {
|
|
213
|
-
if (decoderConfig.description) {
|
|
214
|
-
// Stream is length-prefixed. Let's extract the size of the length prefix from the decoder config
|
|
215
|
-
|
|
216
|
-
const bytes = toUint8Array(decoderConfig.description);
|
|
217
|
-
const lengthSizeMinusOne = bytes[4]! & 0b11;
|
|
218
|
-
const lengthSize = (lengthSizeMinusOne + 1) as 1 | 2 | 3 | 4;
|
|
219
|
-
|
|
220
|
-
return findNalUnitsInLengthPrefixed(packetData, lengthSize);
|
|
221
|
-
} else {
|
|
222
|
-
// Stream is in Annex B format
|
|
223
|
-
return findNalUnitsInAnnexB(packetData);
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
const extractNalUnitTypeForAvc = (data: Uint8Array) => {
|
|
228
|
-
return data[0]! & 0x1F;
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
/** Builds an AvcDecoderConfigurationRecord from an AVC packet in Annex B format. */
|
|
232
|
-
export const extractAvcDecoderConfigurationRecord = (packetData: Uint8Array): AvcDecoderConfigurationRecord | null => {
|
|
233
|
-
try {
|
|
234
|
-
const nalUnits = findNalUnitsInAnnexB(packetData);
|
|
235
|
-
|
|
236
|
-
const spsUnits = nalUnits.filter(unit => extractNalUnitTypeForAvc(unit) === AvcNalUnitType.SPS);
|
|
237
|
-
const ppsUnits = nalUnits.filter(unit => extractNalUnitTypeForAvc(unit) === AvcNalUnitType.PPS);
|
|
238
|
-
const spsExtUnits = nalUnits.filter(unit => extractNalUnitTypeForAvc(unit) === AvcNalUnitType.SPS_EXT);
|
|
239
|
-
|
|
240
|
-
if (spsUnits.length === 0) {
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (ppsUnits.length === 0) {
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Let's get the first SPS for profile and level information
|
|
249
|
-
const spsData = spsUnits[0]!;
|
|
250
|
-
const spsInfo = parseAvcSps(spsData);
|
|
251
|
-
assert(spsInfo !== null);
|
|
252
|
-
|
|
253
|
-
const hasExtendedData = spsInfo.profileIdc === 100
|
|
254
|
-
|| spsInfo.profileIdc === 110
|
|
255
|
-
|| spsInfo.profileIdc === 122
|
|
256
|
-
|| spsInfo.profileIdc === 144;
|
|
257
|
-
|
|
258
|
-
return {
|
|
259
|
-
configurationVersion: 1,
|
|
260
|
-
avcProfileIndication: spsInfo.profileIdc,
|
|
261
|
-
profileCompatibility: spsInfo.constraintFlags,
|
|
262
|
-
avcLevelIndication: spsInfo.levelIdc,
|
|
263
|
-
lengthSizeMinusOne: 3, // Typically 4 bytes for length field
|
|
264
|
-
sequenceParameterSets: spsUnits,
|
|
265
|
-
pictureParameterSets: ppsUnits,
|
|
266
|
-
chromaFormat: hasExtendedData ? spsInfo.chromaFormatIdc : null,
|
|
267
|
-
bitDepthLumaMinus8: hasExtendedData ? spsInfo.bitDepthLumaMinus8 : null,
|
|
268
|
-
bitDepthChromaMinus8: hasExtendedData ? spsInfo.bitDepthChromaMinus8 : null,
|
|
269
|
-
sequenceParameterSetExt: hasExtendedData ? spsExtUnits : null,
|
|
270
|
-
};
|
|
271
|
-
} catch (error) {
|
|
272
|
-
console.error('Error building AVC Decoder Configuration Record:', error);
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
/** Serializes an AvcDecoderConfigurationRecord into the format specified in Section 5.3.3.1 of ISO 14496-15. */
|
|
278
|
-
export const serializeAvcDecoderConfigurationRecord = (record: AvcDecoderConfigurationRecord) => {
|
|
279
|
-
const bytes: number[] = [];
|
|
280
|
-
|
|
281
|
-
// Write header
|
|
282
|
-
bytes.push(record.configurationVersion);
|
|
283
|
-
bytes.push(record.avcProfileIndication);
|
|
284
|
-
bytes.push(record.profileCompatibility);
|
|
285
|
-
bytes.push(record.avcLevelIndication);
|
|
286
|
-
bytes.push(0xFC | (record.lengthSizeMinusOne & 0x03)); // Reserved bits (6) + lengthSizeMinusOne (2)
|
|
287
|
-
|
|
288
|
-
// Reserved bits (3) + numOfSequenceParameterSets (5)
|
|
289
|
-
bytes.push(0xE0 | (record.sequenceParameterSets.length & 0x1F));
|
|
290
|
-
|
|
291
|
-
// Write SPS
|
|
292
|
-
for (const sps of record.sequenceParameterSets) {
|
|
293
|
-
const length = sps.byteLength;
|
|
294
|
-
bytes.push(length >> 8); // High byte
|
|
295
|
-
bytes.push(length & 0xFF); // Low byte
|
|
296
|
-
|
|
297
|
-
for (let i = 0; i < length; i++) {
|
|
298
|
-
bytes.push(sps[i]!);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
bytes.push(record.pictureParameterSets.length);
|
|
303
|
-
|
|
304
|
-
// Write PPS
|
|
305
|
-
for (const pps of record.pictureParameterSets) {
|
|
306
|
-
const length = pps.byteLength;
|
|
307
|
-
bytes.push(length >> 8); // High byte
|
|
308
|
-
bytes.push(length & 0xFF); // Low byte
|
|
309
|
-
|
|
310
|
-
for (let i = 0; i < length; i++) {
|
|
311
|
-
bytes.push(pps[i]!);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (
|
|
316
|
-
record.avcProfileIndication === 100
|
|
317
|
-
|| record.avcProfileIndication === 110
|
|
318
|
-
|| record.avcProfileIndication === 122
|
|
319
|
-
|| record.avcProfileIndication === 144
|
|
320
|
-
) {
|
|
321
|
-
assert(record.chromaFormat !== null);
|
|
322
|
-
assert(record.bitDepthLumaMinus8 !== null);
|
|
323
|
-
assert(record.bitDepthChromaMinus8 !== null);
|
|
324
|
-
assert(record.sequenceParameterSetExt !== null);
|
|
325
|
-
|
|
326
|
-
bytes.push(0xFC | (record.chromaFormat & 0x03)); // Reserved bits + chroma_format
|
|
327
|
-
bytes.push(0xF8 | (record.bitDepthLumaMinus8 & 0x07)); // Reserved bits + bit_depth_luma_minus8
|
|
328
|
-
bytes.push(0xF8 | (record.bitDepthChromaMinus8 & 0x07)); // Reserved bits + bit_depth_chroma_minus8
|
|
329
|
-
|
|
330
|
-
bytes.push(record.sequenceParameterSetExt.length);
|
|
331
|
-
|
|
332
|
-
// Write SPS Ext
|
|
333
|
-
for (const spsExt of record.sequenceParameterSetExt) {
|
|
334
|
-
const length = spsExt.byteLength;
|
|
335
|
-
bytes.push(length >> 8); // High byte
|
|
336
|
-
bytes.push(length & 0xFF); // Low byte
|
|
337
|
-
|
|
338
|
-
for (let i = 0; i < length; i++) {
|
|
339
|
-
bytes.push(spsExt[i]!);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
return new Uint8Array(bytes);
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
/** Deserializes an AvcDecoderConfigurationRecord from the format specified in Section 5.3.3.1 of ISO 14496-15. */
|
|
348
|
-
export const deserializeAvcDecoderConfigurationRecord = (data: Uint8Array): AvcDecoderConfigurationRecord | null => {
|
|
349
|
-
try {
|
|
350
|
-
const view = toDataView(data);
|
|
351
|
-
let offset = 0;
|
|
352
|
-
|
|
353
|
-
// Read header
|
|
354
|
-
const configurationVersion = view.getUint8(offset++);
|
|
355
|
-
const avcProfileIndication = view.getUint8(offset++);
|
|
356
|
-
const profileCompatibility = view.getUint8(offset++);
|
|
357
|
-
const avcLevelIndication = view.getUint8(offset++);
|
|
358
|
-
const lengthSizeMinusOne = view.getUint8(offset++) & 0x03;
|
|
359
|
-
|
|
360
|
-
const numOfSequenceParameterSets = view.getUint8(offset++) & 0x1F;
|
|
361
|
-
|
|
362
|
-
// Read SPS
|
|
363
|
-
const sequenceParameterSets: Uint8Array[] = [];
|
|
364
|
-
for (let i = 0; i < numOfSequenceParameterSets; i++) {
|
|
365
|
-
const length = view.getUint16(offset, false);
|
|
366
|
-
offset += 2;
|
|
367
|
-
|
|
368
|
-
sequenceParameterSets.push(data.subarray(offset, offset + length));
|
|
369
|
-
offset += length;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const numOfPictureParameterSets = view.getUint8(offset++);
|
|
373
|
-
|
|
374
|
-
// Read PPS
|
|
375
|
-
const pictureParameterSets: Uint8Array[] = [];
|
|
376
|
-
for (let i = 0; i < numOfPictureParameterSets; i++) {
|
|
377
|
-
const length = view.getUint16(offset, false);
|
|
378
|
-
offset += 2;
|
|
379
|
-
|
|
380
|
-
pictureParameterSets.push(data.subarray(offset, offset + length));
|
|
381
|
-
offset += length;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const record: AvcDecoderConfigurationRecord = {
|
|
385
|
-
configurationVersion,
|
|
386
|
-
avcProfileIndication,
|
|
387
|
-
profileCompatibility,
|
|
388
|
-
avcLevelIndication,
|
|
389
|
-
lengthSizeMinusOne,
|
|
390
|
-
sequenceParameterSets,
|
|
391
|
-
pictureParameterSets,
|
|
392
|
-
chromaFormat: null,
|
|
393
|
-
bitDepthLumaMinus8: null,
|
|
394
|
-
bitDepthChromaMinus8: null,
|
|
395
|
-
sequenceParameterSetExt: null,
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
// Check if there are extended profile fields
|
|
399
|
-
if (
|
|
400
|
-
(
|
|
401
|
-
avcProfileIndication === 100
|
|
402
|
-
|| avcProfileIndication === 110
|
|
403
|
-
|| avcProfileIndication === 122
|
|
404
|
-
|| avcProfileIndication === 144
|
|
405
|
-
)
|
|
406
|
-
&& offset + 4 <= data.length
|
|
407
|
-
) {
|
|
408
|
-
const chromaFormat = view.getUint8(offset++) & 0x03;
|
|
409
|
-
const bitDepthLumaMinus8 = view.getUint8(offset++) & 0x07;
|
|
410
|
-
const bitDepthChromaMinus8 = view.getUint8(offset++) & 0x07;
|
|
411
|
-
const numOfSequenceParameterSetExt = view.getUint8(offset++);
|
|
412
|
-
|
|
413
|
-
record.chromaFormat = chromaFormat;
|
|
414
|
-
record.bitDepthLumaMinus8 = bitDepthLumaMinus8;
|
|
415
|
-
record.bitDepthChromaMinus8 = bitDepthChromaMinus8;
|
|
416
|
-
|
|
417
|
-
// Read SPS Ext
|
|
418
|
-
const sequenceParameterSetExt: Uint8Array[] = [];
|
|
419
|
-
for (let i = 0; i < numOfSequenceParameterSetExt; i++) {
|
|
420
|
-
const length = view.getUint16(offset, false);
|
|
421
|
-
offset += 2;
|
|
422
|
-
|
|
423
|
-
sequenceParameterSetExt.push(data.subarray(offset, offset + length));
|
|
424
|
-
offset += length;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
record.sequenceParameterSetExt = sequenceParameterSetExt;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
return record;
|
|
431
|
-
} catch (error) {
|
|
432
|
-
console.error('Error deserializing AVC Decoder Configuration Record:', error);
|
|
433
|
-
return null;
|
|
434
|
-
}
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
export type AvcSpsInfo = {
|
|
438
|
-
profileIdc: number;
|
|
439
|
-
constraintFlags: number;
|
|
440
|
-
levelIdc: number;
|
|
441
|
-
frameMbsOnlyFlag: number;
|
|
442
|
-
chromaFormatIdc: number | null;
|
|
443
|
-
bitDepthLumaMinus8: number | null;
|
|
444
|
-
bitDepthChromaMinus8: number | null;
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
/** Parses an AVC SPS (Sequence Parameter Set) to extract basic information. */
|
|
448
|
-
export const parseAvcSps = (sps: Uint8Array): AvcSpsInfo | null => {
|
|
449
|
-
try {
|
|
450
|
-
const bitstream = new Bitstream(removeEmulationPreventionBytes(sps));
|
|
451
|
-
|
|
452
|
-
bitstream.skipBits(1); // forbidden_zero_bit
|
|
453
|
-
bitstream.skipBits(2); // nal_ref_idc
|
|
454
|
-
const nalUnitType = bitstream.readBits(5);
|
|
455
|
-
|
|
456
|
-
if (nalUnitType !== 7) { // SPS NAL unit type is 7
|
|
457
|
-
return null;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
const profileIdc = bitstream.readAlignedByte();
|
|
461
|
-
const constraintFlags = bitstream.readAlignedByte();
|
|
462
|
-
const levelIdc = bitstream.readAlignedByte();
|
|
463
|
-
|
|
464
|
-
readExpGolomb(bitstream); // seq_parameter_set_id
|
|
465
|
-
|
|
466
|
-
let chromaFormatIdc: number | null = null;
|
|
467
|
-
let bitDepthLumaMinus8: number | null = null;
|
|
468
|
-
let bitDepthChromaMinus8: number | null = null;
|
|
469
|
-
|
|
470
|
-
// Handle high profile chroma_format_idc
|
|
471
|
-
if (
|
|
472
|
-
profileIdc === 100
|
|
473
|
-
|| profileIdc === 110
|
|
474
|
-
|| profileIdc === 122
|
|
475
|
-
|| profileIdc === 244
|
|
476
|
-
|| profileIdc === 44
|
|
477
|
-
|| profileIdc === 83
|
|
478
|
-
|| profileIdc === 86
|
|
479
|
-
|| profileIdc === 118
|
|
480
|
-
|| profileIdc === 128
|
|
481
|
-
) {
|
|
482
|
-
chromaFormatIdc = readExpGolomb(bitstream);
|
|
483
|
-
if (chromaFormatIdc === 3) {
|
|
484
|
-
bitstream.skipBits(1); // separate_colour_plane_flag
|
|
485
|
-
}
|
|
486
|
-
bitDepthLumaMinus8 = readExpGolomb(bitstream);
|
|
487
|
-
bitDepthChromaMinus8 = readExpGolomb(bitstream);
|
|
488
|
-
bitstream.skipBits(1); // qpprime_y_zero_transform_bypass_flag
|
|
489
|
-
const seqScalingMatrixPresentFlag = bitstream.readBits(1);
|
|
490
|
-
if (seqScalingMatrixPresentFlag) {
|
|
491
|
-
for (let i = 0; i < (chromaFormatIdc !== 3 ? 8 : 12); i++) {
|
|
492
|
-
const seqScalingListPresentFlag = bitstream.readBits(1);
|
|
493
|
-
if (seqScalingListPresentFlag) {
|
|
494
|
-
const sizeOfScalingList = i < 6 ? 16 : 64;
|
|
495
|
-
let lastScale = 8;
|
|
496
|
-
let nextScale = 8;
|
|
497
|
-
for (let j = 0; j < sizeOfScalingList; j++) {
|
|
498
|
-
if (nextScale !== 0) {
|
|
499
|
-
const deltaScale = readSignedExpGolomb(bitstream);
|
|
500
|
-
nextScale = (lastScale + deltaScale + 256) % 256;
|
|
501
|
-
}
|
|
502
|
-
lastScale = nextScale === 0 ? lastScale : nextScale;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
readExpGolomb(bitstream); // log2_max_frame_num_minus4
|
|
510
|
-
|
|
511
|
-
const picOrderCntType = readExpGolomb(bitstream);
|
|
512
|
-
if (picOrderCntType === 0) {
|
|
513
|
-
readExpGolomb(bitstream); // log2_max_pic_order_cnt_lsb_minus4
|
|
514
|
-
} else if (picOrderCntType === 1) {
|
|
515
|
-
bitstream.skipBits(1); // delta_pic_order_always_zero_flag
|
|
516
|
-
readSignedExpGolomb(bitstream); // offset_for_non_ref_pic
|
|
517
|
-
readSignedExpGolomb(bitstream); // offset_for_top_to_bottom_field
|
|
518
|
-
const numRefFramesInPicOrderCntCycle = readExpGolomb(bitstream);
|
|
519
|
-
for (let i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
|
|
520
|
-
readSignedExpGolomb(bitstream); // offset_for_ref_frame[i]
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
readExpGolomb(bitstream); // max_num_ref_frames
|
|
525
|
-
bitstream.skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
|
526
|
-
|
|
527
|
-
readExpGolomb(bitstream); // pic_width_in_mbs_minus1
|
|
528
|
-
readExpGolomb(bitstream); // pic_height_in_map_units_minus1
|
|
529
|
-
|
|
530
|
-
const frameMbsOnlyFlag = bitstream.readBits(1);
|
|
531
|
-
|
|
532
|
-
return {
|
|
533
|
-
profileIdc,
|
|
534
|
-
constraintFlags,
|
|
535
|
-
levelIdc,
|
|
536
|
-
frameMbsOnlyFlag,
|
|
537
|
-
chromaFormatIdc,
|
|
538
|
-
bitDepthLumaMinus8,
|
|
539
|
-
bitDepthChromaMinus8,
|
|
540
|
-
};
|
|
541
|
-
} catch (error) {
|
|
542
|
-
console.error('Error parsing AVC SPS:', error);
|
|
543
|
-
return null;
|
|
544
|
-
}
|
|
545
|
-
};
|
|
546
|
-
|
|
547
|
-
// Data specified in ISO 14496-15
|
|
548
|
-
export type HevcDecoderConfigurationRecord = {
|
|
549
|
-
configurationVersion: number;
|
|
550
|
-
generalProfileSpace: number;
|
|
551
|
-
generalTierFlag: number;
|
|
552
|
-
generalProfileIdc: number;
|
|
553
|
-
generalProfileCompatibilityFlags: number;
|
|
554
|
-
generalConstraintIndicatorFlags: Uint8Array; // 6 bytes long
|
|
555
|
-
generalLevelIdc: number;
|
|
556
|
-
minSpatialSegmentationIdc: number;
|
|
557
|
-
parallelismType: number;
|
|
558
|
-
chromaFormatIdc: number;
|
|
559
|
-
bitDepthLumaMinus8: number;
|
|
560
|
-
bitDepthChromaMinus8: number;
|
|
561
|
-
avgFrameRate: number;
|
|
562
|
-
constantFrameRate: number;
|
|
563
|
-
numTemporalLayers: number;
|
|
564
|
-
temporalIdNested: number;
|
|
565
|
-
lengthSizeMinusOne: number;
|
|
566
|
-
arrays: {
|
|
567
|
-
arrayCompleteness: number;
|
|
568
|
-
nalUnitType: number;
|
|
569
|
-
nalUnits: Uint8Array[];
|
|
570
|
-
}[];
|
|
571
|
-
};
|
|
572
|
-
|
|
573
|
-
export const extractHevcNalUnits = (packetData: Uint8Array, decoderConfig: VideoDecoderConfig) => {
|
|
574
|
-
if (decoderConfig.description) {
|
|
575
|
-
// Stream is length-prefixed. Let's extract the size of the length prefix from the decoder config
|
|
576
|
-
|
|
577
|
-
const bytes = toUint8Array(decoderConfig.description);
|
|
578
|
-
const lengthSizeMinusOne = bytes[21]! & 0b11;
|
|
579
|
-
const lengthSize = (lengthSizeMinusOne + 1) as 1 | 2 | 3 | 4;
|
|
580
|
-
|
|
581
|
-
return findNalUnitsInLengthPrefixed(packetData, lengthSize);
|
|
582
|
-
} else {
|
|
583
|
-
// Stream is in Annex B format
|
|
584
|
-
return findNalUnitsInAnnexB(packetData);
|
|
585
|
-
}
|
|
586
|
-
};
|
|
587
|
-
|
|
588
|
-
export const extractNalUnitTypeForHevc = (data: Uint8Array) => {
|
|
589
|
-
return (data[0]! >> 1) & 0x3F;
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
/** Builds a HevcDecoderConfigurationRecord from an HEVC packet in Annex B format. */
|
|
593
|
-
export const extractHevcDecoderConfigurationRecord = (
|
|
594
|
-
packetData: Uint8Array,
|
|
595
|
-
) => {
|
|
596
|
-
try {
|
|
597
|
-
const nalUnits = findNalUnitsInAnnexB(packetData);
|
|
598
|
-
|
|
599
|
-
const vpsUnits = nalUnits.filter(unit => extractNalUnitTypeForHevc(unit) === HevcNalUnitType.VPS_NUT);
|
|
600
|
-
const spsUnits = nalUnits.filter(unit => extractNalUnitTypeForHevc(unit) === HevcNalUnitType.SPS_NUT);
|
|
601
|
-
const ppsUnits = nalUnits.filter(unit => extractNalUnitTypeForHevc(unit) === HevcNalUnitType.PPS_NUT);
|
|
602
|
-
const seiUnits = nalUnits.filter(
|
|
603
|
-
unit => extractNalUnitTypeForHevc(unit) === HevcNalUnitType.PREFIX_SEI_NUT
|
|
604
|
-
|| extractNalUnitTypeForHevc(unit) === HevcNalUnitType.SUFFIX_SEI_NUT,
|
|
605
|
-
);
|
|
606
|
-
|
|
607
|
-
if (spsUnits.length === 0 || ppsUnits.length === 0) return null;
|
|
608
|
-
|
|
609
|
-
const sps = spsUnits[0]!;
|
|
610
|
-
const bitstream = new Bitstream(removeEmulationPreventionBytes(sps));
|
|
611
|
-
|
|
612
|
-
bitstream.skipBits(16); // NAL header
|
|
613
|
-
|
|
614
|
-
bitstream.readBits(4); // sps_video_parameter_set_id
|
|
615
|
-
const sps_max_sub_layers_minus1 = bitstream.readBits(3);
|
|
616
|
-
const sps_temporal_id_nesting_flag = bitstream.readBits(1);
|
|
617
|
-
|
|
618
|
-
const {
|
|
619
|
-
general_profile_space,
|
|
620
|
-
general_tier_flag,
|
|
621
|
-
general_profile_idc,
|
|
622
|
-
general_profile_compatibility_flags,
|
|
623
|
-
general_constraint_indicator_flags,
|
|
624
|
-
general_level_idc,
|
|
625
|
-
} = parseProfileTierLevel(bitstream, sps_max_sub_layers_minus1);
|
|
626
|
-
|
|
627
|
-
readExpGolomb(bitstream); // sps_seq_parameter_set_id
|
|
628
|
-
|
|
629
|
-
const chroma_format_idc = readExpGolomb(bitstream);
|
|
630
|
-
if (chroma_format_idc === 3) bitstream.skipBits(1); // separate_colour_plane_flag
|
|
631
|
-
|
|
632
|
-
readExpGolomb(bitstream); // pic_width_in_luma_samples
|
|
633
|
-
readExpGolomb(bitstream); // pic_height_in_luma_samples
|
|
634
|
-
|
|
635
|
-
if (bitstream.readBits(1)) { // conformance_window_flag
|
|
636
|
-
readExpGolomb(bitstream); // conf_win_left_offset
|
|
637
|
-
readExpGolomb(bitstream); // conf_win_right_offset
|
|
638
|
-
readExpGolomb(bitstream); // conf_win_top_offset
|
|
639
|
-
readExpGolomb(bitstream); // conf_win_bottom_offset
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
const bit_depth_luma_minus8 = readExpGolomb(bitstream);
|
|
643
|
-
const bit_depth_chroma_minus8 = readExpGolomb(bitstream);
|
|
644
|
-
|
|
645
|
-
readExpGolomb(bitstream); // log2_max_pic_order_cnt_lsb_minus4
|
|
646
|
-
|
|
647
|
-
const sps_sub_layer_ordering_info_present_flag = bitstream.readBits(1);
|
|
648
|
-
const maxNum = sps_sub_layer_ordering_info_present_flag ? 0 : sps_max_sub_layers_minus1;
|
|
649
|
-
for (let i = maxNum; i <= sps_max_sub_layers_minus1; i++) {
|
|
650
|
-
readExpGolomb(bitstream); // sps_max_dec_pic_buffering_minus1[i]
|
|
651
|
-
readExpGolomb(bitstream); // sps_max_num_reorder_pics[i]
|
|
652
|
-
readExpGolomb(bitstream); // sps_max_latency_increase_plus1[i]
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
readExpGolomb(bitstream); // log2_min_luma_coding_block_size_minus3
|
|
656
|
-
readExpGolomb(bitstream); // log2_diff_max_min_luma_coding_block_size
|
|
657
|
-
readExpGolomb(bitstream); // log2_min_luma_transform_block_size_minus2
|
|
658
|
-
readExpGolomb(bitstream); // log2_diff_max_min_luma_transform_block_size
|
|
659
|
-
readExpGolomb(bitstream); // max_transform_hierarchy_depth_inter
|
|
660
|
-
readExpGolomb(bitstream); // max_transform_hierarchy_depth_intra
|
|
661
|
-
|
|
662
|
-
if (bitstream.readBits(1)) { // scaling_list_enabled_flag
|
|
663
|
-
if (bitstream.readBits(1)) {
|
|
664
|
-
skipScalingListData(bitstream);
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
bitstream.skipBits(1); // amp_enabled_flag
|
|
669
|
-
bitstream.skipBits(1); // sample_adaptive_offset_enabled_flag
|
|
670
|
-
|
|
671
|
-
if (bitstream.readBits(1)) { // pcm_enabled_flag
|
|
672
|
-
bitstream.skipBits(4); // pcm_sample_bit_depth_luma_minus1
|
|
673
|
-
bitstream.skipBits(4); // pcm_sample_bit_depth_chroma_minus1
|
|
674
|
-
readExpGolomb(bitstream); // log2_min_pcm_luma_coding_block_size_minus3
|
|
675
|
-
readExpGolomb(bitstream); // log2_diff_max_min_pcm_luma_coding_block_size
|
|
676
|
-
bitstream.skipBits(1); // pcm_loop_filter_disabled_flag
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
const num_short_term_ref_pic_sets = readExpGolomb(bitstream);
|
|
680
|
-
skipAllStRefPicSets(bitstream, num_short_term_ref_pic_sets);
|
|
681
|
-
|
|
682
|
-
if (bitstream.readBits(1)) { // long_term_ref_pics_present_flag
|
|
683
|
-
const num_long_term_ref_pics_sps = readExpGolomb(bitstream);
|
|
684
|
-
for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
|
|
685
|
-
readExpGolomb(bitstream); // lt_ref_pic_poc_lsb_sps[i]
|
|
686
|
-
bitstream.skipBits(1); // used_by_curr_pic_lt_sps_flag[i]
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
bitstream.skipBits(1); // sps_temporal_mvp_enabled_flag
|
|
691
|
-
bitstream.skipBits(1); // strong_intra_smoothing_enabled_flag
|
|
692
|
-
|
|
693
|
-
let min_spatial_segmentation_idc = 0;
|
|
694
|
-
if (bitstream.readBits(1)) { // vui_parameters_present_flag
|
|
695
|
-
min_spatial_segmentation_idc = parseVuiForMinSpatialSegmentationIdc(bitstream, sps_max_sub_layers_minus1);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
// Parse PPS for parallelismType
|
|
699
|
-
let parallelismType = 0;
|
|
700
|
-
if (ppsUnits.length > 0) {
|
|
701
|
-
const pps = ppsUnits[0]!;
|
|
702
|
-
const ppsBitstream = new Bitstream(removeEmulationPreventionBytes(pps));
|
|
703
|
-
|
|
704
|
-
ppsBitstream.skipBits(16); // NAL header
|
|
705
|
-
readExpGolomb(ppsBitstream); // pps_pic_parameter_set_id
|
|
706
|
-
readExpGolomb(ppsBitstream); // pps_seq_parameter_set_id
|
|
707
|
-
ppsBitstream.skipBits(1); // dependent_slice_segments_enabled_flag
|
|
708
|
-
ppsBitstream.skipBits(1); // output_flag_present_flag
|
|
709
|
-
ppsBitstream.skipBits(3); // num_extra_slice_header_bits
|
|
710
|
-
ppsBitstream.skipBits(1); // sign_data_hiding_enabled_flag
|
|
711
|
-
ppsBitstream.skipBits(1); // cabac_init_present_flag
|
|
712
|
-
readExpGolomb(ppsBitstream); // num_ref_idx_l0_default_active_minus1
|
|
713
|
-
readExpGolomb(ppsBitstream); // num_ref_idx_l1_default_active_minus1
|
|
714
|
-
readSignedExpGolomb(ppsBitstream); // init_qp_minus26
|
|
715
|
-
ppsBitstream.skipBits(1); // constrained_intra_pred_flag
|
|
716
|
-
ppsBitstream.skipBits(1); // transform_skip_enabled_flag
|
|
717
|
-
if (ppsBitstream.readBits(1)) { // cu_qp_delta_enabled_flag
|
|
718
|
-
readExpGolomb(ppsBitstream); // diff_cu_qp_delta_depth
|
|
719
|
-
}
|
|
720
|
-
readSignedExpGolomb(ppsBitstream); // pps_cb_qp_offset
|
|
721
|
-
readSignedExpGolomb(ppsBitstream); // pps_cr_qp_offset
|
|
722
|
-
ppsBitstream.skipBits(1); // pps_slice_chroma_qp_offsets_present_flag
|
|
723
|
-
ppsBitstream.skipBits(1); // weighted_pred_flag
|
|
724
|
-
ppsBitstream.skipBits(1); // weighted_bipred_flag
|
|
725
|
-
ppsBitstream.skipBits(1); // transquant_bypass_enabled_flag
|
|
726
|
-
const tiles_enabled_flag = ppsBitstream.readBits(1);
|
|
727
|
-
const entropy_coding_sync_enabled_flag = ppsBitstream.readBits(1);
|
|
728
|
-
|
|
729
|
-
if (!tiles_enabled_flag && !entropy_coding_sync_enabled_flag) parallelismType = 0;
|
|
730
|
-
else if (tiles_enabled_flag && !entropy_coding_sync_enabled_flag) parallelismType = 2;
|
|
731
|
-
else if (!tiles_enabled_flag && entropy_coding_sync_enabled_flag) parallelismType = 3;
|
|
732
|
-
else parallelismType = 0;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
const arrays = [
|
|
736
|
-
...(vpsUnits.length
|
|
737
|
-
? [
|
|
738
|
-
{
|
|
739
|
-
arrayCompleteness: 1,
|
|
740
|
-
nalUnitType: HevcNalUnitType.VPS_NUT,
|
|
741
|
-
nalUnits: vpsUnits,
|
|
742
|
-
},
|
|
743
|
-
]
|
|
744
|
-
: []),
|
|
745
|
-
...(spsUnits.length
|
|
746
|
-
? [
|
|
747
|
-
{
|
|
748
|
-
arrayCompleteness: 1,
|
|
749
|
-
nalUnitType: HevcNalUnitType.SPS_NUT,
|
|
750
|
-
nalUnits: spsUnits,
|
|
751
|
-
},
|
|
752
|
-
]
|
|
753
|
-
: []),
|
|
754
|
-
...(ppsUnits.length
|
|
755
|
-
? [
|
|
756
|
-
{
|
|
757
|
-
arrayCompleteness: 1,
|
|
758
|
-
nalUnitType: HevcNalUnitType.PPS_NUT,
|
|
759
|
-
nalUnits: ppsUnits,
|
|
760
|
-
},
|
|
761
|
-
]
|
|
762
|
-
: []),
|
|
763
|
-
...(seiUnits.length
|
|
764
|
-
? [
|
|
765
|
-
{
|
|
766
|
-
arrayCompleteness: 1,
|
|
767
|
-
nalUnitType: extractNalUnitTypeForHevc(seiUnits[0]!),
|
|
768
|
-
nalUnits: seiUnits,
|
|
769
|
-
},
|
|
770
|
-
]
|
|
771
|
-
: []),
|
|
772
|
-
];
|
|
773
|
-
|
|
774
|
-
const record: HevcDecoderConfigurationRecord = {
|
|
775
|
-
configurationVersion: 1,
|
|
776
|
-
generalProfileSpace: general_profile_space,
|
|
777
|
-
generalTierFlag: general_tier_flag,
|
|
778
|
-
generalProfileIdc: general_profile_idc,
|
|
779
|
-
generalProfileCompatibilityFlags: general_profile_compatibility_flags,
|
|
780
|
-
generalConstraintIndicatorFlags: general_constraint_indicator_flags,
|
|
781
|
-
generalLevelIdc: general_level_idc,
|
|
782
|
-
minSpatialSegmentationIdc: min_spatial_segmentation_idc,
|
|
783
|
-
parallelismType,
|
|
784
|
-
chromaFormatIdc: chroma_format_idc,
|
|
785
|
-
bitDepthLumaMinus8: bit_depth_luma_minus8,
|
|
786
|
-
bitDepthChromaMinus8: bit_depth_chroma_minus8,
|
|
787
|
-
avgFrameRate: 0,
|
|
788
|
-
constantFrameRate: 0,
|
|
789
|
-
numTemporalLayers: sps_max_sub_layers_minus1 + 1,
|
|
790
|
-
temporalIdNested: sps_temporal_id_nesting_flag,
|
|
791
|
-
lengthSizeMinusOne: 3,
|
|
792
|
-
arrays,
|
|
793
|
-
};
|
|
794
|
-
|
|
795
|
-
return record;
|
|
796
|
-
} catch (error) {
|
|
797
|
-
console.error('Error building HEVC Decoder Configuration Record:', error);
|
|
798
|
-
return null;
|
|
799
|
-
}
|
|
800
|
-
};
|
|
801
|
-
|
|
802
|
-
const parseProfileTierLevel = (
|
|
803
|
-
bitstream: Bitstream,
|
|
804
|
-
maxNumSubLayersMinus1: number,
|
|
805
|
-
) => {
|
|
806
|
-
const general_profile_space = bitstream.readBits(2);
|
|
807
|
-
const general_tier_flag = bitstream.readBits(1);
|
|
808
|
-
const general_profile_idc = bitstream.readBits(5);
|
|
809
|
-
|
|
810
|
-
let general_profile_compatibility_flags = 0;
|
|
811
|
-
for (let i = 0; i < 32; i++) {
|
|
812
|
-
general_profile_compatibility_flags = (general_profile_compatibility_flags << 1) | bitstream.readBits(1);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
const general_constraint_indicator_flags = new Uint8Array(6);
|
|
816
|
-
for (let i = 0; i < 6; i++) {
|
|
817
|
-
general_constraint_indicator_flags[i] = bitstream.readBits(8);
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
const general_level_idc = bitstream.readBits(8);
|
|
821
|
-
|
|
822
|
-
const sub_layer_profile_present_flag: number[] = [];
|
|
823
|
-
const sub_layer_level_present_flag: number[] = [];
|
|
824
|
-
for (let i = 0; i < maxNumSubLayersMinus1; i++) {
|
|
825
|
-
sub_layer_profile_present_flag.push(bitstream.readBits(1));
|
|
826
|
-
sub_layer_level_present_flag.push(bitstream.readBits(1));
|
|
827
|
-
}
|
|
828
|
-
if (maxNumSubLayersMinus1 > 0) {
|
|
829
|
-
for (let i = maxNumSubLayersMinus1; i < 8; i++) {
|
|
830
|
-
bitstream.skipBits(2); // reserved_zero_2bits
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
for (let i = 0; i < maxNumSubLayersMinus1; i++) {
|
|
834
|
-
if (sub_layer_profile_present_flag[i]) bitstream.skipBits(88);
|
|
835
|
-
if (sub_layer_level_present_flag[i]) bitstream.skipBits(8);
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
return {
|
|
839
|
-
general_profile_space,
|
|
840
|
-
general_tier_flag,
|
|
841
|
-
general_profile_idc,
|
|
842
|
-
general_profile_compatibility_flags,
|
|
843
|
-
general_constraint_indicator_flags,
|
|
844
|
-
general_level_idc,
|
|
845
|
-
};
|
|
846
|
-
};
|
|
847
|
-
|
|
848
|
-
const skipScalingListData = (bitstream: Bitstream) => {
|
|
849
|
-
for (let sizeId = 0; sizeId < 4; sizeId++) {
|
|
850
|
-
for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
|
|
851
|
-
const scaling_list_pred_mode_flag = bitstream.readBits(1);
|
|
852
|
-
if (!scaling_list_pred_mode_flag) {
|
|
853
|
-
readExpGolomb(bitstream); // scaling_list_pred_matrix_id_delta
|
|
854
|
-
} else {
|
|
855
|
-
const coefNum = Math.min(64, 1 << (4 + (sizeId << 1)));
|
|
856
|
-
if (sizeId > 1) {
|
|
857
|
-
readSignedExpGolomb(bitstream); // scaling_list_dc_coef_minus8
|
|
858
|
-
}
|
|
859
|
-
for (let i = 0; i < coefNum; i++) {
|
|
860
|
-
readSignedExpGolomb(bitstream); // scaling_list_delta_coef
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
};
|
|
866
|
-
|
|
867
|
-
const skipAllStRefPicSets = (bitstream: Bitstream, num_short_term_ref_pic_sets: number) => {
|
|
868
|
-
const NumDeltaPocs: number[] = [];
|
|
869
|
-
for (let stRpsIdx = 0; stRpsIdx < num_short_term_ref_pic_sets; stRpsIdx++) {
|
|
870
|
-
NumDeltaPocs[stRpsIdx] = skipStRefPicSet(bitstream, stRpsIdx, num_short_term_ref_pic_sets, NumDeltaPocs);
|
|
871
|
-
}
|
|
872
|
-
};
|
|
873
|
-
|
|
874
|
-
const skipStRefPicSet = (
|
|
875
|
-
bitstream: Bitstream,
|
|
876
|
-
stRpsIdx: number,
|
|
877
|
-
num_short_term_ref_pic_sets: number,
|
|
878
|
-
NumDeltaPocs: number[],
|
|
879
|
-
) => {
|
|
880
|
-
let NumDeltaPocsThis = 0;
|
|
881
|
-
let inter_ref_pic_set_prediction_flag = 0;
|
|
882
|
-
let RefRpsIdx = 0;
|
|
883
|
-
|
|
884
|
-
if (stRpsIdx !== 0) {
|
|
885
|
-
inter_ref_pic_set_prediction_flag = bitstream.readBits(1);
|
|
886
|
-
}
|
|
887
|
-
if (inter_ref_pic_set_prediction_flag) {
|
|
888
|
-
if (stRpsIdx === num_short_term_ref_pic_sets) {
|
|
889
|
-
const delta_idx_minus1 = readExpGolomb(bitstream);
|
|
890
|
-
RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1);
|
|
891
|
-
} else {
|
|
892
|
-
RefRpsIdx = stRpsIdx - 1;
|
|
893
|
-
}
|
|
894
|
-
bitstream.readBits(1); // delta_rps_sign
|
|
895
|
-
readExpGolomb(bitstream); // abs_delta_rps_minus1
|
|
896
|
-
|
|
897
|
-
// The number of iterations is NumDeltaPocs[RefRpsIdx] + 1
|
|
898
|
-
const numDelta = NumDeltaPocs[RefRpsIdx] ?? 0;
|
|
899
|
-
for (let j = 0; j <= numDelta; j++) {
|
|
900
|
-
const used_by_curr_pic_flag = bitstream.readBits(1);
|
|
901
|
-
if (!used_by_curr_pic_flag) {
|
|
902
|
-
bitstream.readBits(1); // use_delta_flag
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
NumDeltaPocsThis = NumDeltaPocs[RefRpsIdx]!;
|
|
906
|
-
} else {
|
|
907
|
-
const num_negative_pics = readExpGolomb(bitstream);
|
|
908
|
-
const num_positive_pics = readExpGolomb(bitstream);
|
|
909
|
-
|
|
910
|
-
for (let i = 0; i < num_negative_pics; i++) {
|
|
911
|
-
readExpGolomb(bitstream); // delta_poc_s0_minus1[i]
|
|
912
|
-
bitstream.readBits(1); // used_by_curr_pic_s0_flag[i]
|
|
913
|
-
}
|
|
914
|
-
for (let i = 0; i < num_positive_pics; i++) {
|
|
915
|
-
readExpGolomb(bitstream); // delta_poc_s1_minus1[i]
|
|
916
|
-
bitstream.readBits(1); // used_by_curr_pic_s1_flag[i]
|
|
917
|
-
}
|
|
918
|
-
NumDeltaPocsThis = num_negative_pics + num_positive_pics;
|
|
919
|
-
}
|
|
920
|
-
return NumDeltaPocsThis;
|
|
921
|
-
};
|
|
922
|
-
|
|
923
|
-
const parseVuiForMinSpatialSegmentationIdc = (bitstream: Bitstream, sps_max_sub_layers_minus1: number) => {
|
|
924
|
-
if (bitstream.readBits(1)) { // aspect_ratio_info_present_flag
|
|
925
|
-
const aspect_ratio_idc = bitstream.readBits(8);
|
|
926
|
-
if (aspect_ratio_idc === 255) {
|
|
927
|
-
bitstream.readBits(16); // sar_width
|
|
928
|
-
bitstream.readBits(16); // sar_height
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
if (bitstream.readBits(1)) { // overscan_info_present_flag
|
|
932
|
-
bitstream.readBits(1); // overscan_appropriate_flag
|
|
933
|
-
}
|
|
934
|
-
if (bitstream.readBits(1)) { // video_signal_type_present_flag
|
|
935
|
-
bitstream.readBits(3); // video_format
|
|
936
|
-
bitstream.readBits(1); // video_full_range_flag
|
|
937
|
-
if (bitstream.readBits(1)) {
|
|
938
|
-
bitstream.readBits(8); // colour_primaries
|
|
939
|
-
bitstream.readBits(8); // transfer_characteristics
|
|
940
|
-
bitstream.readBits(8); // matrix_coeffs
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
if (bitstream.readBits(1)) { // chroma_loc_info_present_flag
|
|
944
|
-
readExpGolomb(bitstream); // chroma_sample_loc_type_top_field
|
|
945
|
-
readExpGolomb(bitstream); // chroma_sample_loc_type_bottom_field
|
|
946
|
-
}
|
|
947
|
-
bitstream.readBits(1); // neutral_chroma_indication_flag
|
|
948
|
-
bitstream.readBits(1); // field_seq_flag
|
|
949
|
-
bitstream.readBits(1); // frame_field_info_present_flag
|
|
950
|
-
if (bitstream.readBits(1)) { // default_display_window_flag
|
|
951
|
-
readExpGolomb(bitstream); // def_disp_win_left_offset
|
|
952
|
-
readExpGolomb(bitstream); // def_disp_win_right_offset
|
|
953
|
-
readExpGolomb(bitstream); // def_disp_win_top_offset
|
|
954
|
-
readExpGolomb(bitstream); // def_disp_win_bottom_offset
|
|
955
|
-
}
|
|
956
|
-
if (bitstream.readBits(1)) { // vui_timing_info_present_flag
|
|
957
|
-
bitstream.readBits(32); // vui_num_units_in_tick
|
|
958
|
-
bitstream.readBits(32); // vui_time_scale
|
|
959
|
-
if (bitstream.readBits(1)) { // vui_poc_proportional_to_timing_flag
|
|
960
|
-
readExpGolomb(bitstream); // vui_num_ticks_poc_diff_one_minus1
|
|
961
|
-
}
|
|
962
|
-
if (bitstream.readBits(1)) {
|
|
963
|
-
skipHrdParameters(bitstream, true, sps_max_sub_layers_minus1);
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
if (bitstream.readBits(1)) { // bitstream_restriction_flag
|
|
967
|
-
bitstream.readBits(1); // tiles_fixed_structure_flag
|
|
968
|
-
bitstream.readBits(1); // motion_vectors_over_pic_boundaries_flag
|
|
969
|
-
bitstream.readBits(1); // restricted_ref_pic_lists_flag
|
|
970
|
-
const min_spatial_segmentation_idc = readExpGolomb(bitstream);
|
|
971
|
-
// skip the rest
|
|
972
|
-
readExpGolomb(bitstream); // max_bytes_per_pic_denom
|
|
973
|
-
readExpGolomb(bitstream); // max_bits_per_min_cu_denom
|
|
974
|
-
readExpGolomb(bitstream); // log2_max_mv_length_horizontal
|
|
975
|
-
readExpGolomb(bitstream); // log2_max_mv_length_vertical
|
|
976
|
-
return min_spatial_segmentation_idc;
|
|
977
|
-
}
|
|
978
|
-
return 0;
|
|
979
|
-
};
|
|
980
|
-
|
|
981
|
-
const skipHrdParameters = (
|
|
982
|
-
bitstream: Bitstream,
|
|
983
|
-
commonInfPresentFlag: boolean,
|
|
984
|
-
maxNumSubLayersMinus1: number,
|
|
985
|
-
) => {
|
|
986
|
-
let nal_hrd_parameters_present_flag = false;
|
|
987
|
-
let vcl_hrd_parameters_present_flag = false;
|
|
988
|
-
let sub_pic_hrd_params_present_flag = false;
|
|
989
|
-
|
|
990
|
-
if (commonInfPresentFlag) {
|
|
991
|
-
nal_hrd_parameters_present_flag = bitstream.readBits(1) === 1;
|
|
992
|
-
vcl_hrd_parameters_present_flag = bitstream.readBits(1) === 1;
|
|
993
|
-
if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
|
|
994
|
-
sub_pic_hrd_params_present_flag = bitstream.readBits(1) === 1;
|
|
995
|
-
if (sub_pic_hrd_params_present_flag) {
|
|
996
|
-
bitstream.readBits(8); // tick_divisor_minus2
|
|
997
|
-
bitstream.readBits(5); // du_cpb_removal_delay_increment_length_minus1
|
|
998
|
-
bitstream.readBits(1); // sub_pic_cpb_params_in_pic_timing_sei_flag
|
|
999
|
-
bitstream.readBits(5); // dpb_output_delay_du_length_minus1
|
|
1000
|
-
}
|
|
1001
|
-
bitstream.readBits(4); // bit_rate_scale
|
|
1002
|
-
bitstream.readBits(4); // cpb_size_scale
|
|
1003
|
-
if (sub_pic_hrd_params_present_flag) {
|
|
1004
|
-
bitstream.readBits(4); // cpb_size_du_scale
|
|
1005
|
-
}
|
|
1006
|
-
bitstream.readBits(5); // initial_cpb_removal_delay_length_minus1
|
|
1007
|
-
bitstream.readBits(5); // au_cpb_removal_delay_length_minus1
|
|
1008
|
-
bitstream.readBits(5); // dpb_output_delay_length_minus1
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
for (let i = 0; i <= maxNumSubLayersMinus1; i++) {
|
|
1013
|
-
const fixed_pic_rate_general_flag = bitstream.readBits(1) === 1;
|
|
1014
|
-
let fixed_pic_rate_within_cvs_flag = true; // Default assumption if general is true
|
|
1015
|
-
if (!fixed_pic_rate_general_flag) {
|
|
1016
|
-
fixed_pic_rate_within_cvs_flag = bitstream.readBits(1) === 1;
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
let low_delay_hrd_flag = false; // Default assumption
|
|
1020
|
-
if (fixed_pic_rate_within_cvs_flag) {
|
|
1021
|
-
readExpGolomb(bitstream); // elemental_duration_in_tc_minus1[i]
|
|
1022
|
-
} else {
|
|
1023
|
-
low_delay_hrd_flag = bitstream.readBits(1) === 1;
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
let CpbCnt = 1; // Default if low_delay is true
|
|
1027
|
-
if (!low_delay_hrd_flag) {
|
|
1028
|
-
const cpb_cnt_minus1 = readExpGolomb(bitstream); // cpb_cnt_minus1[i]
|
|
1029
|
-
CpbCnt = cpb_cnt_minus1 + 1;
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
if (nal_hrd_parameters_present_flag) {
|
|
1033
|
-
skipSubLayerHrdParameters(bitstream, CpbCnt, sub_pic_hrd_params_present_flag);
|
|
1034
|
-
}
|
|
1035
|
-
if (vcl_hrd_parameters_present_flag) {
|
|
1036
|
-
skipSubLayerHrdParameters(bitstream, CpbCnt, sub_pic_hrd_params_present_flag);
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
};
|
|
1040
|
-
|
|
1041
|
-
const skipSubLayerHrdParameters = (
|
|
1042
|
-
bitstream: Bitstream,
|
|
1043
|
-
CpbCnt: number,
|
|
1044
|
-
sub_pic_hrd_params_present_flag: boolean,
|
|
1045
|
-
) => {
|
|
1046
|
-
for (let i = 0; i < CpbCnt; i++) {
|
|
1047
|
-
readExpGolomb(bitstream); // bit_rate_value_minus1[i]
|
|
1048
|
-
readExpGolomb(bitstream); // cpb_size_value_minus1[i]
|
|
1049
|
-
if (sub_pic_hrd_params_present_flag) {
|
|
1050
|
-
readExpGolomb(bitstream); // cpb_size_du_value_minus1[i]
|
|
1051
|
-
readExpGolomb(bitstream); // bit_rate_du_value_minus1[i]
|
|
1052
|
-
}
|
|
1053
|
-
bitstream.readBits(1); // cbr_flag[i]
|
|
1054
|
-
}
|
|
1055
|
-
};
|
|
1056
|
-
|
|
1057
|
-
/** Serializes an HevcDecoderConfigurationRecord into the format specified in Section 8.3.3.1 of ISO 14496-15. */
|
|
1058
|
-
export const serializeHevcDecoderConfigurationRecord = (record: HevcDecoderConfigurationRecord) => {
|
|
1059
|
-
const bytes: number[] = [];
|
|
1060
|
-
|
|
1061
|
-
bytes.push(record.configurationVersion);
|
|
1062
|
-
|
|
1063
|
-
bytes.push(
|
|
1064
|
-
((record.generalProfileSpace & 0x3) << 6)
|
|
1065
|
-
| ((record.generalTierFlag & 0x1) << 5)
|
|
1066
|
-
| (record.generalProfileIdc & 0x1F),
|
|
1067
|
-
);
|
|
1068
|
-
|
|
1069
|
-
bytes.push((record.generalProfileCompatibilityFlags >>> 24) & 0xFF);
|
|
1070
|
-
bytes.push((record.generalProfileCompatibilityFlags >>> 16) & 0xFF);
|
|
1071
|
-
bytes.push((record.generalProfileCompatibilityFlags >>> 8) & 0xFF);
|
|
1072
|
-
bytes.push(record.generalProfileCompatibilityFlags & 0xFF);
|
|
1073
|
-
|
|
1074
|
-
bytes.push(...record.generalConstraintIndicatorFlags);
|
|
1075
|
-
|
|
1076
|
-
bytes.push(record.generalLevelIdc & 0xFF);
|
|
1077
|
-
|
|
1078
|
-
bytes.push(0xF0 | ((record.minSpatialSegmentationIdc >> 8) & 0x0F)); // Reserved + high nibble
|
|
1079
|
-
bytes.push(record.minSpatialSegmentationIdc & 0xFF); // Low byte
|
|
1080
|
-
|
|
1081
|
-
bytes.push(0xFC | (record.parallelismType & 0x03));
|
|
1082
|
-
|
|
1083
|
-
bytes.push(0xFC | (record.chromaFormatIdc & 0x03));
|
|
1084
|
-
|
|
1085
|
-
bytes.push(0xF8 | (record.bitDepthLumaMinus8 & 0x07));
|
|
1086
|
-
|
|
1087
|
-
bytes.push(0xF8 | (record.bitDepthChromaMinus8 & 0x07));
|
|
1088
|
-
|
|
1089
|
-
bytes.push((record.avgFrameRate >> 8) & 0xFF); // High byte
|
|
1090
|
-
bytes.push(record.avgFrameRate & 0xFF); // Low byte
|
|
1091
|
-
|
|
1092
|
-
bytes.push(
|
|
1093
|
-
((record.constantFrameRate & 0x03) << 6)
|
|
1094
|
-
| ((record.numTemporalLayers & 0x07) << 3)
|
|
1095
|
-
| ((record.temporalIdNested & 0x01) << 2)
|
|
1096
|
-
| (record.lengthSizeMinusOne & 0x03),
|
|
1097
|
-
);
|
|
1098
|
-
|
|
1099
|
-
bytes.push(record.arrays.length & 0xFF);
|
|
1100
|
-
|
|
1101
|
-
for (const arr of record.arrays) {
|
|
1102
|
-
bytes.push(
|
|
1103
|
-
((arr.arrayCompleteness & 0x01) << 7)
|
|
1104
|
-
| (0 << 6)
|
|
1105
|
-
| (arr.nalUnitType & 0x3F),
|
|
1106
|
-
);
|
|
1107
|
-
|
|
1108
|
-
bytes.push((arr.nalUnits.length >> 8) & 0xFF); // High byte
|
|
1109
|
-
bytes.push(arr.nalUnits.length & 0xFF); // Low byte
|
|
1110
|
-
|
|
1111
|
-
for (const nal of arr.nalUnits) {
|
|
1112
|
-
bytes.push((nal.length >> 8) & 0xFF); // High byte
|
|
1113
|
-
bytes.push(nal.length & 0xFF); // Low byte
|
|
1114
|
-
|
|
1115
|
-
for (let i = 0; i < nal.length; i++) {
|
|
1116
|
-
bytes.push(nal[i]!);
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
return new Uint8Array(bytes);
|
|
1122
|
-
};
|
|
1123
|
-
|
|
1124
|
-
export type Vp9CodecInfo = {
|
|
1125
|
-
profile: number;
|
|
1126
|
-
level: number;
|
|
1127
|
-
bitDepth: number;
|
|
1128
|
-
chromaSubsampling: number;
|
|
1129
|
-
videoFullRangeFlag: number;
|
|
1130
|
-
colourPrimaries: number;
|
|
1131
|
-
transferCharacteristics: number;
|
|
1132
|
-
matrixCoefficients: number;
|
|
1133
|
-
};
|
|
1134
|
-
|
|
1135
|
-
export const extractVp9CodecInfoFromPacket = (
|
|
1136
|
-
packet: Uint8Array,
|
|
1137
|
-
): Vp9CodecInfo | null => {
|
|
1138
|
-
// eslint-disable-next-line @stylistic/max-len
|
|
1139
|
-
// https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.7-20170222-draft.pdf
|
|
1140
|
-
// http://downloads.webmproject.org/docs/vp9/vp9-bitstream_superframe-and-uncompressed-header_v1.0.pdf
|
|
1141
|
-
|
|
1142
|
-
const bitstream = new Bitstream(packet);
|
|
1143
|
-
|
|
1144
|
-
// Frame marker (0b10)
|
|
1145
|
-
const frameMarker = bitstream.readBits(2);
|
|
1146
|
-
if (frameMarker !== 2) {
|
|
1147
|
-
return null;
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
// Profile
|
|
1151
|
-
const profileLowBit = bitstream.readBits(1);
|
|
1152
|
-
const profileHighBit = bitstream.readBits(1);
|
|
1153
|
-
const profile = (profileHighBit << 1) + profileLowBit;
|
|
1154
|
-
|
|
1155
|
-
// Skip reserved bit for profile 3
|
|
1156
|
-
if (profile === 3) {
|
|
1157
|
-
bitstream.skipBits(1);
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
// show_existing_frame
|
|
1161
|
-
const showExistingFrame = bitstream.readBits(1);
|
|
1162
|
-
|
|
1163
|
-
if (showExistingFrame === 1) {
|
|
1164
|
-
return null;
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
// frame_type (0 = key frame)
|
|
1168
|
-
const frameType = bitstream.readBits(1);
|
|
1169
|
-
|
|
1170
|
-
if (frameType !== 0) {
|
|
1171
|
-
return null;
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
// Skip show_frame and error_resilient_mode
|
|
1175
|
-
bitstream.skipBits(2);
|
|
1176
|
-
|
|
1177
|
-
// Sync code (0x498342)
|
|
1178
|
-
const syncCode = bitstream.readBits(24);
|
|
1179
|
-
if (syncCode !== 0x498342) {
|
|
1180
|
-
return null;
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
// Color config
|
|
1184
|
-
let bitDepth = 8;
|
|
1185
|
-
if (profile >= 2) {
|
|
1186
|
-
const tenOrTwelveBit = bitstream.readBits(1);
|
|
1187
|
-
bitDepth = tenOrTwelveBit ? 12 : 10;
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
// Color space
|
|
1191
|
-
const colorSpace = bitstream.readBits(3);
|
|
1192
|
-
|
|
1193
|
-
let chromaSubsampling = 0;
|
|
1194
|
-
let videoFullRangeFlag = 0;
|
|
1195
|
-
|
|
1196
|
-
if (colorSpace !== 7) { // 7 is CS_RGB
|
|
1197
|
-
const colorRange = bitstream.readBits(1);
|
|
1198
|
-
videoFullRangeFlag = colorRange;
|
|
1199
|
-
|
|
1200
|
-
if (profile === 1 || profile === 3) {
|
|
1201
|
-
const subsamplingX = bitstream.readBits(1);
|
|
1202
|
-
const subsamplingY = bitstream.readBits(1);
|
|
1203
|
-
|
|
1204
|
-
// 0 = 4:2:0 vertical
|
|
1205
|
-
// 1 = 4:2:0 colocated
|
|
1206
|
-
// 2 = 4:2:2
|
|
1207
|
-
// 3 = 4:4:4
|
|
1208
|
-
chromaSubsampling = !subsamplingX && !subsamplingY
|
|
1209
|
-
? 3 // 0,0 = 4:4:4
|
|
1210
|
-
: subsamplingX && !subsamplingY
|
|
1211
|
-
? 2 // 1,0 = 4:2:2
|
|
1212
|
-
: 1; // 1,1 = 4:2:0 colocated (default)
|
|
1213
|
-
|
|
1214
|
-
// Skip reserved bit
|
|
1215
|
-
bitstream.skipBits(1);
|
|
1216
|
-
} else {
|
|
1217
|
-
// For profile 0 and 2, always 4:2:0
|
|
1218
|
-
chromaSubsampling = 1; // Using colocated as default
|
|
1219
|
-
}
|
|
1220
|
-
} else {
|
|
1221
|
-
// RGB is always 4:4:4
|
|
1222
|
-
chromaSubsampling = 3;
|
|
1223
|
-
videoFullRangeFlag = 1;
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
// Parse frame size
|
|
1227
|
-
const widthMinusOne = bitstream.readBits(16);
|
|
1228
|
-
const heightMinusOne = bitstream.readBits(16);
|
|
1229
|
-
|
|
1230
|
-
const width = widthMinusOne + 1;
|
|
1231
|
-
const height = heightMinusOne + 1;
|
|
1232
|
-
|
|
1233
|
-
// Calculate level based on dimensions
|
|
1234
|
-
const pictureSize = width * height;
|
|
1235
|
-
let level = last(VP9_LEVEL_TABLE)!.level; // Default to highest level
|
|
1236
|
-
for (const entry of VP9_LEVEL_TABLE) {
|
|
1237
|
-
if (pictureSize <= entry.maxPictureSize) {
|
|
1238
|
-
level = entry.level;
|
|
1239
|
-
break;
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
// Map color_space to standard values
|
|
1244
|
-
const matrixCoefficients = colorSpace === 7
|
|
1245
|
-
? 0
|
|
1246
|
-
: colorSpace === 2
|
|
1247
|
-
? 1
|
|
1248
|
-
: colorSpace === 1
|
|
1249
|
-
? 6
|
|
1250
|
-
: 2;
|
|
1251
|
-
|
|
1252
|
-
const colourPrimaries = colorSpace === 2
|
|
1253
|
-
? 1
|
|
1254
|
-
: colorSpace === 1
|
|
1255
|
-
? 6
|
|
1256
|
-
: 2;
|
|
1257
|
-
|
|
1258
|
-
const transferCharacteristics = colorSpace === 2
|
|
1259
|
-
? 1
|
|
1260
|
-
: colorSpace === 1
|
|
1261
|
-
? 6
|
|
1262
|
-
: 2;
|
|
1263
|
-
|
|
1264
|
-
return {
|
|
1265
|
-
profile,
|
|
1266
|
-
level,
|
|
1267
|
-
bitDepth,
|
|
1268
|
-
chromaSubsampling,
|
|
1269
|
-
videoFullRangeFlag,
|
|
1270
|
-
colourPrimaries,
|
|
1271
|
-
transferCharacteristics,
|
|
1272
|
-
matrixCoefficients,
|
|
1273
|
-
};
|
|
1274
|
-
};
|
|
1275
|
-
|
|
1276
|
-
export type Av1CodecInfo = {
|
|
1277
|
-
profile: number;
|
|
1278
|
-
level: number;
|
|
1279
|
-
tier: number;
|
|
1280
|
-
bitDepth: number;
|
|
1281
|
-
monochrome: number;
|
|
1282
|
-
chromaSubsamplingX: number;
|
|
1283
|
-
chromaSubsamplingY: number;
|
|
1284
|
-
chromaSamplePosition: number;
|
|
1285
|
-
};
|
|
1286
|
-
|
|
1287
|
-
/** Iterates over all OBUs in an AV1 packet bistream. */
|
|
1288
|
-
export const iterateAv1PacketObus = function* (packet: Uint8Array) {
|
|
1289
|
-
// https://aomediacodec.github.io/av1-spec/av1-spec.pdf
|
|
1290
|
-
|
|
1291
|
-
const bitstream = new Bitstream(packet);
|
|
1292
|
-
|
|
1293
|
-
const readLeb128 = (): number | null => {
|
|
1294
|
-
let value = 0;
|
|
1295
|
-
|
|
1296
|
-
for (let i = 0; i < 8; i++) {
|
|
1297
|
-
const byte = bitstream.readAlignedByte();
|
|
1298
|
-
|
|
1299
|
-
value |= ((byte & 0x7f) << (i * 7));
|
|
1300
|
-
|
|
1301
|
-
if (!(byte & 0x80)) {
|
|
1302
|
-
break;
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
// Spec requirement
|
|
1306
|
-
if (i === 7 && (byte & 0x80)) {
|
|
1307
|
-
return null;
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
// Spec requirement
|
|
1312
|
-
if (value >= 2 ** 32 - 1) {
|
|
1313
|
-
return null;
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
return value;
|
|
1317
|
-
};
|
|
1318
|
-
|
|
1319
|
-
while (bitstream.getBitsLeft() >= 8) {
|
|
1320
|
-
// Parse OBU header
|
|
1321
|
-
bitstream.skipBits(1);
|
|
1322
|
-
const obuType = bitstream.readBits(4);
|
|
1323
|
-
const obuExtension = bitstream.readBits(1);
|
|
1324
|
-
const obuHasSizeField = bitstream.readBits(1);
|
|
1325
|
-
bitstream.skipBits(1);
|
|
1326
|
-
|
|
1327
|
-
// Skip extension header if present
|
|
1328
|
-
if (obuExtension) {
|
|
1329
|
-
bitstream.skipBits(8);
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
// Read OBU size if present
|
|
1333
|
-
let obuSize: number;
|
|
1334
|
-
if (obuHasSizeField) {
|
|
1335
|
-
const obuSizeValue = readLeb128();
|
|
1336
|
-
if (obuSizeValue === null) return; // It was invalid
|
|
1337
|
-
obuSize = obuSizeValue;
|
|
1338
|
-
} else {
|
|
1339
|
-
// Calculate remaining bits and convert to bytes, rounding down
|
|
1340
|
-
obuSize = Math.floor(bitstream.getBitsLeft() / 8);
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
assert(bitstream.pos % 8 === 0);
|
|
1344
|
-
|
|
1345
|
-
yield {
|
|
1346
|
-
type: obuType,
|
|
1347
|
-
data: packet.subarray(bitstream.pos / 8, bitstream.pos / 8 + obuSize),
|
|
1348
|
-
};
|
|
1349
|
-
|
|
1350
|
-
// Move to next OBU
|
|
1351
|
-
bitstream.skipBits(obuSize * 8);
|
|
1352
|
-
}
|
|
1353
|
-
};
|
|
1354
|
-
|
|
1355
|
-
/**
|
|
1356
|
-
* When AV1 codec information is not provided by the container, we can still try to extract the information by digging
|
|
1357
|
-
* into the AV1 bitstream.
|
|
1358
|
-
*/
|
|
1359
|
-
export const extractAv1CodecInfoFromPacket = (
|
|
1360
|
-
packet: Uint8Array,
|
|
1361
|
-
): Av1CodecInfo | null => {
|
|
1362
|
-
// https://aomediacodec.github.io/av1-spec/av1-spec.pdf
|
|
1363
|
-
|
|
1364
|
-
for (const { type, data } of iterateAv1PacketObus(packet)) {
|
|
1365
|
-
if (type !== 1) {
|
|
1366
|
-
continue; // 1 == OBU_SEQUENCE_HEADER
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
const bitstream = new Bitstream(data);
|
|
1370
|
-
|
|
1371
|
-
// Read sequence header fields
|
|
1372
|
-
const seqProfile = bitstream.readBits(3);
|
|
1373
|
-
|
|
1374
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1375
|
-
const stillPicture = bitstream.readBits(1);
|
|
1376
|
-
|
|
1377
|
-
const reducedStillPictureHeader = bitstream.readBits(1);
|
|
1378
|
-
|
|
1379
|
-
let seqLevel = 0;
|
|
1380
|
-
let seqTier = 0;
|
|
1381
|
-
let bufferDelayLengthMinus1 = 0;
|
|
1382
|
-
|
|
1383
|
-
if (reducedStillPictureHeader) {
|
|
1384
|
-
seqLevel = bitstream.readBits(5);
|
|
1385
|
-
} else {
|
|
1386
|
-
// Parse timing_info_present_flag
|
|
1387
|
-
const timingInfoPresentFlag = bitstream.readBits(1);
|
|
1388
|
-
|
|
1389
|
-
if (timingInfoPresentFlag) {
|
|
1390
|
-
// Skip timing info (num_units_in_display_tick, time_scale, equal_picture_interval)
|
|
1391
|
-
bitstream.skipBits(32); // num_units_in_display_tick
|
|
1392
|
-
bitstream.skipBits(32); // time_scale
|
|
1393
|
-
const equalPictureInterval = bitstream.readBits(1);
|
|
1394
|
-
|
|
1395
|
-
if (equalPictureInterval) {
|
|
1396
|
-
// Skip num_ticks_per_picture_minus_1 (uvlc)
|
|
1397
|
-
// Since this is variable length, we'd need to implement uvlc reading
|
|
1398
|
-
// For now, we'll return null as this is rare
|
|
1399
|
-
return null;
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
// Parse decoder_model_info_present_flag
|
|
1404
|
-
const decoderModelInfoPresentFlag = bitstream.readBits(1);
|
|
1405
|
-
|
|
1406
|
-
if (decoderModelInfoPresentFlag) {
|
|
1407
|
-
// Store buffer_delay_length_minus_1 instead of just skipping
|
|
1408
|
-
bufferDelayLengthMinus1 = bitstream.readBits(5);
|
|
1409
|
-
bitstream.skipBits(32); // num_units_in_decoding_tick
|
|
1410
|
-
bitstream.skipBits(5); // buffer_removal_time_length_minus_1
|
|
1411
|
-
bitstream.skipBits(5); // frame_presentation_time_length_minus_1
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
// Parse operating_points_cnt_minus_1
|
|
1415
|
-
const operatingPointsCntMinus1 = bitstream.readBits(5);
|
|
1416
|
-
|
|
1417
|
-
// For each operating point
|
|
1418
|
-
for (let i = 0; i <= operatingPointsCntMinus1; i++) {
|
|
1419
|
-
// operating_point_idc[i]
|
|
1420
|
-
bitstream.skipBits(12);
|
|
1421
|
-
|
|
1422
|
-
// seq_level_idx[i]
|
|
1423
|
-
const seqLevelIdx = bitstream.readBits(5);
|
|
1424
|
-
|
|
1425
|
-
if (i === 0) {
|
|
1426
|
-
seqLevel = seqLevelIdx;
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
if (seqLevelIdx > 7) {
|
|
1430
|
-
// seq_tier[i]
|
|
1431
|
-
const seqTierTemp = bitstream.readBits(1);
|
|
1432
|
-
if (i === 0) {
|
|
1433
|
-
seqTier = seqTierTemp;
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
if (decoderModelInfoPresentFlag) {
|
|
1438
|
-
// decoder_model_present_for_this_op[i]
|
|
1439
|
-
const decoderModelPresentForThisOp = bitstream.readBits(1);
|
|
1440
|
-
|
|
1441
|
-
if (decoderModelPresentForThisOp) {
|
|
1442
|
-
const n = bufferDelayLengthMinus1 + 1;
|
|
1443
|
-
bitstream.skipBits(n); // decoder_buffer_delay[op]
|
|
1444
|
-
bitstream.skipBits(n); // encoder_buffer_delay[op]
|
|
1445
|
-
bitstream.skipBits(1); // low_delay_mode_flag[op]
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
// initial_display_delay_present_flag
|
|
1450
|
-
const initialDisplayDelayPresentFlag = bitstream.readBits(1);
|
|
1451
|
-
|
|
1452
|
-
if (initialDisplayDelayPresentFlag) {
|
|
1453
|
-
// initial_display_delay_minus_1[i]
|
|
1454
|
-
bitstream.skipBits(4);
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
const highBitdepth = bitstream.readBits(1);
|
|
1460
|
-
|
|
1461
|
-
let bitDepth = 8;
|
|
1462
|
-
if (seqProfile === 2 && highBitdepth) {
|
|
1463
|
-
const twelveBit = bitstream.readBits(1);
|
|
1464
|
-
bitDepth = twelveBit ? 12 : 10;
|
|
1465
|
-
} else if (seqProfile <= 2) {
|
|
1466
|
-
bitDepth = highBitdepth ? 10 : 8;
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
let monochrome = 0;
|
|
1470
|
-
if (seqProfile !== 1) {
|
|
1471
|
-
monochrome = bitstream.readBits(1);
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
let chromaSubsamplingX = 1;
|
|
1475
|
-
let chromaSubsamplingY = 1;
|
|
1476
|
-
let chromaSamplePosition = 0;
|
|
1477
|
-
|
|
1478
|
-
if (!monochrome) {
|
|
1479
|
-
if (seqProfile === 0) {
|
|
1480
|
-
chromaSubsamplingX = 1;
|
|
1481
|
-
chromaSubsamplingY = 1;
|
|
1482
|
-
} else if (seqProfile === 1) {
|
|
1483
|
-
chromaSubsamplingX = 0;
|
|
1484
|
-
chromaSubsamplingY = 0;
|
|
1485
|
-
} else {
|
|
1486
|
-
if (bitDepth === 12) {
|
|
1487
|
-
chromaSubsamplingX = bitstream.readBits(1);
|
|
1488
|
-
if (chromaSubsamplingX) {
|
|
1489
|
-
chromaSubsamplingY = bitstream.readBits(1);
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
if (chromaSubsamplingX && chromaSubsamplingY) {
|
|
1495
|
-
chromaSamplePosition = bitstream.readBits(2);
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
return {
|
|
1500
|
-
profile: seqProfile,
|
|
1501
|
-
level: seqLevel,
|
|
1502
|
-
tier: seqTier,
|
|
1503
|
-
bitDepth,
|
|
1504
|
-
monochrome,
|
|
1505
|
-
chromaSubsamplingX,
|
|
1506
|
-
chromaSubsamplingY,
|
|
1507
|
-
chromaSamplePosition,
|
|
1508
|
-
};
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
return null;
|
|
1512
|
-
};
|
|
1513
|
-
|
|
1514
|
-
export const parseOpusIdentificationHeader = (bytes: Uint8Array) => {
|
|
1515
|
-
const view = toDataView(bytes);
|
|
1516
|
-
|
|
1517
|
-
const outputChannelCount = view.getUint8(9);
|
|
1518
|
-
const preSkip = view.getUint16(10, true);
|
|
1519
|
-
const inputSampleRate = view.getUint32(12, true);
|
|
1520
|
-
const outputGain = view.getInt16(16, true);
|
|
1521
|
-
const channelMappingFamily = view.getUint8(18);
|
|
1522
|
-
|
|
1523
|
-
let channelMappingTable: Uint8Array | null = null;
|
|
1524
|
-
if (channelMappingFamily) {
|
|
1525
|
-
channelMappingTable = bytes.subarray(19, 19 + 2 + outputChannelCount);
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
return {
|
|
1529
|
-
outputChannelCount,
|
|
1530
|
-
preSkip,
|
|
1531
|
-
inputSampleRate,
|
|
1532
|
-
outputGain,
|
|
1533
|
-
channelMappingFamily,
|
|
1534
|
-
channelMappingTable,
|
|
1535
|
-
};
|
|
1536
|
-
};
|
|
1537
|
-
|
|
1538
|
-
// From https://datatracker.ietf.org/doc/html/rfc6716, in 48 kHz samples
|
|
1539
|
-
const OPUS_FRAME_DURATION_TABLE = [
|
|
1540
|
-
480, 960, 1920, 2880,
|
|
1541
|
-
480, 960, 1920, 2880,
|
|
1542
|
-
480, 960, 1920, 2880,
|
|
1543
|
-
480, 960,
|
|
1544
|
-
480, 960,
|
|
1545
|
-
120, 240, 480, 960,
|
|
1546
|
-
120, 240, 480, 960,
|
|
1547
|
-
120, 240, 480, 960,
|
|
1548
|
-
120, 240, 480, 960,
|
|
1549
|
-
];
|
|
1550
|
-
|
|
1551
|
-
export const parseOpusTocByte = (packet: Uint8Array) => {
|
|
1552
|
-
const config = packet[0]! >> 3;
|
|
1553
|
-
|
|
1554
|
-
return {
|
|
1555
|
-
durationInSamples: OPUS_FRAME_DURATION_TABLE[config]!,
|
|
1556
|
-
};
|
|
1557
|
-
};
|
|
1558
|
-
|
|
1559
|
-
// Based on vorbis_parser.c from FFmpeg.
|
|
1560
|
-
export const parseModesFromVorbisSetupPacket = (setupHeader: Uint8Array) => {
|
|
1561
|
-
// Verify that this is a Setup header.
|
|
1562
|
-
if (setupHeader.length < 7) {
|
|
1563
|
-
throw new Error('Setup header is too short.');
|
|
1564
|
-
}
|
|
1565
|
-
if (setupHeader[0] !== 5) {
|
|
1566
|
-
throw new Error('Wrong packet type in Setup header.');
|
|
1567
|
-
}
|
|
1568
|
-
const signature = String.fromCharCode(...setupHeader.slice(1, 7));
|
|
1569
|
-
if (signature !== 'vorbis') {
|
|
1570
|
-
throw new Error('Invalid packet signature in Setup header.');
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
// Reverse the entire buffer.
|
|
1574
|
-
const bufSize = setupHeader.length;
|
|
1575
|
-
const revBuffer = new Uint8Array(bufSize);
|
|
1576
|
-
for (let i = 0; i < bufSize; i++) {
|
|
1577
|
-
revBuffer[i] = setupHeader[bufSize - 1 - i]!;
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
// Initialize a Bitstream on the reversed buffer.
|
|
1581
|
-
const bitstream = new Bitstream(revBuffer);
|
|
1582
|
-
|
|
1583
|
-
// --- Find the framing bit.
|
|
1584
|
-
// In FFmpeg code, we scan until get_bits1() returns 1.
|
|
1585
|
-
let gotFramingBit = 0;
|
|
1586
|
-
while (bitstream.getBitsLeft() > 97) {
|
|
1587
|
-
if (bitstream.readBits(1) === 1) {
|
|
1588
|
-
gotFramingBit = bitstream.pos;
|
|
1589
|
-
break;
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
if (gotFramingBit === 0) {
|
|
1593
|
-
throw new Error('Invalid Setup header: framing bit not found.');
|
|
1594
|
-
}
|
|
1595
|
-
|
|
1596
|
-
// --- Search backwards for a valid mode header.
|
|
1597
|
-
// We try to “guess” the number of modes by reading a fixed pattern.
|
|
1598
|
-
let modeCount = 0;
|
|
1599
|
-
let gotModeHeader = false;
|
|
1600
|
-
let lastModeCount = 0;
|
|
1601
|
-
while (bitstream.getBitsLeft() >= 97) {
|
|
1602
|
-
const tempPos = bitstream.pos;
|
|
1603
|
-
const a = bitstream.readBits(8);
|
|
1604
|
-
const b = bitstream.readBits(16);
|
|
1605
|
-
const c = bitstream.readBits(16);
|
|
1606
|
-
// If a > 63 or b or c nonzero, assume we’ve gone too far.
|
|
1607
|
-
if (a > 63 || b !== 0 || c !== 0) {
|
|
1608
|
-
bitstream.pos = tempPos;
|
|
1609
|
-
break;
|
|
1610
|
-
}
|
|
1611
|
-
bitstream.skipBits(1);
|
|
1612
|
-
modeCount++;
|
|
1613
|
-
if (modeCount > 64) {
|
|
1614
|
-
break;
|
|
1615
|
-
}
|
|
1616
|
-
const bsClone = bitstream.clone();
|
|
1617
|
-
const candidate = bsClone.readBits(6) + 1;
|
|
1618
|
-
if (candidate === modeCount) {
|
|
1619
|
-
gotModeHeader = true;
|
|
1620
|
-
lastModeCount = modeCount;
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
if (!gotModeHeader) {
|
|
1624
|
-
throw new Error('Invalid Setup header: mode header not found.');
|
|
1625
|
-
}
|
|
1626
|
-
if (lastModeCount > 63) {
|
|
1627
|
-
throw new Error(`Unsupported mode count: ${lastModeCount}.`);
|
|
1628
|
-
}
|
|
1629
|
-
const finalModeCount = lastModeCount;
|
|
1630
|
-
|
|
1631
|
-
// --- Reinitialize the bitstream.
|
|
1632
|
-
bitstream.pos = 0;
|
|
1633
|
-
// Skip the bits up to the found framing bit.
|
|
1634
|
-
bitstream.skipBits(gotFramingBit);
|
|
1635
|
-
|
|
1636
|
-
// --- Now read, for each mode (in reverse order), 40 bits then one bit.
|
|
1637
|
-
// That one bit is the mode blockflag.
|
|
1638
|
-
const modeBlockflags = Array(finalModeCount).fill(0) as number[];
|
|
1639
|
-
for (let i = finalModeCount - 1; i >= 0; i--) {
|
|
1640
|
-
bitstream.skipBits(40);
|
|
1641
|
-
modeBlockflags[i] = bitstream.readBits(1);
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
return { modeBlockflags };
|
|
1645
|
-
};
|
|
1646
|
-
|
|
1647
|
-
/** Determines a packet's type (key or delta) by digging into the packet bitstream. */
|
|
1648
|
-
export const determineVideoPacketType = (
|
|
1649
|
-
codec: VideoCodec,
|
|
1650
|
-
decoderConfig: VideoDecoderConfig,
|
|
1651
|
-
packetData: Uint8Array,
|
|
1652
|
-
): PacketType | null => {
|
|
1653
|
-
switch (codec) {
|
|
1654
|
-
case 'avc': {
|
|
1655
|
-
const nalUnits = extractAvcNalUnits(packetData, decoderConfig);
|
|
1656
|
-
const isKeyframe = nalUnits.some(x => extractNalUnitTypeForAvc(x) === AvcNalUnitType.IDR);
|
|
1657
|
-
|
|
1658
|
-
return isKeyframe ? 'key' : 'delta';
|
|
1659
|
-
};
|
|
1660
|
-
|
|
1661
|
-
case 'hevc': {
|
|
1662
|
-
const nalUnits = extractHevcNalUnits(packetData, decoderConfig);
|
|
1663
|
-
const isKeyframe = nalUnits.some((x) => {
|
|
1664
|
-
const type = extractNalUnitTypeForHevc(x);
|
|
1665
|
-
return HevcNalUnitType.BLA_W_LP <= type && type <= HevcNalUnitType.RSV_IRAP_VCL23;
|
|
1666
|
-
});
|
|
1667
|
-
|
|
1668
|
-
return isKeyframe ? 'key' : 'delta';
|
|
1669
|
-
};
|
|
1670
|
-
|
|
1671
|
-
case 'vp8': {
|
|
1672
|
-
// VP8, once again, by far the easiest to deal with.
|
|
1673
|
-
const frameType = packetData[0]! & 0b1;
|
|
1674
|
-
return frameType === 0 ? 'key' : 'delta';
|
|
1675
|
-
};
|
|
1676
|
-
|
|
1677
|
-
case 'vp9': {
|
|
1678
|
-
const bitstream = new Bitstream(packetData);
|
|
1679
|
-
|
|
1680
|
-
if (bitstream.readBits(2) !== 2) {
|
|
1681
|
-
return null;
|
|
1682
|
-
};
|
|
1683
|
-
|
|
1684
|
-
const profileLowBit = bitstream.readBits(1);
|
|
1685
|
-
const profileHighBit = bitstream.readBits(1);
|
|
1686
|
-
const profile = (profileHighBit << 1) + profileLowBit;
|
|
1687
|
-
|
|
1688
|
-
// Skip reserved bit for profile 3
|
|
1689
|
-
if (profile === 3) {
|
|
1690
|
-
bitstream.skipBits(1);
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
const showExistingFrame = bitstream.readBits(1);
|
|
1694
|
-
if (showExistingFrame) {
|
|
1695
|
-
return null;
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
const frameType = bitstream.readBits(1);
|
|
1699
|
-
return frameType === 0 ? 'key' : 'delta';
|
|
1700
|
-
};
|
|
1701
|
-
|
|
1702
|
-
case 'av1': {
|
|
1703
|
-
let reducedStillPictureHeader = false;
|
|
1704
|
-
|
|
1705
|
-
for (const { type, data } of iterateAv1PacketObus(packetData)) {
|
|
1706
|
-
if (type === 1) { // OBU_SEQUENCE_HEADER
|
|
1707
|
-
const bitstream = new Bitstream(data);
|
|
1708
|
-
|
|
1709
|
-
bitstream.skipBits(4);
|
|
1710
|
-
reducedStillPictureHeader = !!bitstream.readBits(1);
|
|
1711
|
-
} else if (
|
|
1712
|
-
type === 3 // OBU_FRAME_HEADER
|
|
1713
|
-
|| type === 6 // OBU_FRAME
|
|
1714
|
-
|| type === 7 // OBU_REDUNDANT_FRAME_HEADER
|
|
1715
|
-
) {
|
|
1716
|
-
if (reducedStillPictureHeader) {
|
|
1717
|
-
return 'key';
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
const bitstream = new Bitstream(data);
|
|
1721
|
-
const showExistingFrame = bitstream.readBits(1);
|
|
1722
|
-
if (showExistingFrame) {
|
|
1723
|
-
return null;
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
const frameType = bitstream.readBits(2);
|
|
1727
|
-
return frameType === 0 ? 'key' : 'delta';
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
|
|
1731
|
-
return null;
|
|
1732
|
-
};
|
|
1733
|
-
|
|
1734
|
-
default: {
|
|
1735
|
-
assertNever(codec);
|
|
1736
|
-
assert(false);
|
|
1737
|
-
};
|
|
1738
|
-
}
|
|
1739
|
-
};
|
|
1740
|
-
|
|
1741
|
-
export enum FlacBlockType {
|
|
1742
|
-
STREAMINFO = 0,
|
|
1743
|
-
VORBIS_COMMENT = 4,
|
|
1744
|
-
PICTURE = 6,
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1747
|
-
export const readVorbisComments = (bytes: Uint8Array, metadataTags: MetadataTags) => {
|
|
1748
|
-
// https://datatracker.ietf.org/doc/html/rfc7845#section-5.2
|
|
1749
|
-
|
|
1750
|
-
const commentView = toDataView(bytes);
|
|
1751
|
-
let commentPos = 0;
|
|
1752
|
-
|
|
1753
|
-
const vendorStringLength = commentView.getUint32(commentPos, true);
|
|
1754
|
-
commentPos += 4;
|
|
1755
|
-
|
|
1756
|
-
const vendorString = textDecoder.decode(
|
|
1757
|
-
bytes.subarray(commentPos, commentPos + vendorStringLength),
|
|
1758
|
-
);
|
|
1759
|
-
commentPos += vendorStringLength;
|
|
1760
|
-
|
|
1761
|
-
if (vendorStringLength > 0) {
|
|
1762
|
-
// Expose the vendor string in the raw metadata
|
|
1763
|
-
metadataTags.raw ??= {};
|
|
1764
|
-
metadataTags.raw['vendor'] ??= vendorString;
|
|
1765
|
-
}
|
|
1766
|
-
|
|
1767
|
-
const listLength = commentView.getUint32(commentPos, true);
|
|
1768
|
-
commentPos += 4;
|
|
1769
|
-
|
|
1770
|
-
// Loop over all metadata tags
|
|
1771
|
-
for (let i = 0; i < listLength; i++) {
|
|
1772
|
-
const stringLength = commentView.getUint32(commentPos, true);
|
|
1773
|
-
commentPos += 4;
|
|
1774
|
-
|
|
1775
|
-
const string = textDecoder.decode(
|
|
1776
|
-
bytes.subarray(commentPos, commentPos + stringLength),
|
|
1777
|
-
);
|
|
1778
|
-
commentPos += stringLength;
|
|
1779
|
-
|
|
1780
|
-
const separatorIndex = string.indexOf('=');
|
|
1781
|
-
if (separatorIndex === -1) {
|
|
1782
|
-
continue;
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
|
-
const key = string.slice(0, separatorIndex).toUpperCase();
|
|
1786
|
-
const value = string.slice(separatorIndex + 1);
|
|
1787
|
-
|
|
1788
|
-
metadataTags.raw ??= {};
|
|
1789
|
-
metadataTags.raw[key] ??= value;
|
|
1790
|
-
|
|
1791
|
-
switch (key) {
|
|
1792
|
-
case 'TITLE': {
|
|
1793
|
-
metadataTags.title ??= value;
|
|
1794
|
-
}; break;
|
|
1795
|
-
|
|
1796
|
-
case 'DESCRIPTION': {
|
|
1797
|
-
metadataTags.description ??= value;
|
|
1798
|
-
}; break;
|
|
1799
|
-
|
|
1800
|
-
case 'ARTIST': {
|
|
1801
|
-
metadataTags.artist ??= value;
|
|
1802
|
-
}; break;
|
|
1803
|
-
|
|
1804
|
-
case 'ALBUM': {
|
|
1805
|
-
metadataTags.album ??= value;
|
|
1806
|
-
}; break;
|
|
1807
|
-
|
|
1808
|
-
case 'ALBUMARTIST': {
|
|
1809
|
-
metadataTags.albumArtist ??= value;
|
|
1810
|
-
}; break;
|
|
1811
|
-
|
|
1812
|
-
case 'COMMENT': {
|
|
1813
|
-
metadataTags.comment ??= value;
|
|
1814
|
-
}; break;
|
|
1815
|
-
|
|
1816
|
-
case 'LYRICS': {
|
|
1817
|
-
metadataTags.lyrics ??= value;
|
|
1818
|
-
}; break;
|
|
1819
|
-
|
|
1820
|
-
case 'TRACKNUMBER': {
|
|
1821
|
-
const parts = value.split('/');
|
|
1822
|
-
const trackNum = Number.parseInt(parts[0]!, 10);
|
|
1823
|
-
const tracksTotal = parts[1] && Number.parseInt(parts[1], 10);
|
|
1824
|
-
|
|
1825
|
-
if (Number.isInteger(trackNum) && trackNum > 0) {
|
|
1826
|
-
metadataTags.trackNumber ??= trackNum;
|
|
1827
|
-
}
|
|
1828
|
-
if (tracksTotal && Number.isInteger(tracksTotal) && tracksTotal > 0) {
|
|
1829
|
-
metadataTags.tracksTotal ??= tracksTotal;
|
|
1830
|
-
}
|
|
1831
|
-
}; break;
|
|
1832
|
-
|
|
1833
|
-
case 'TRACKTOTAL': {
|
|
1834
|
-
const tracksTotal = Number.parseInt(value, 10);
|
|
1835
|
-
if (Number.isInteger(tracksTotal) && tracksTotal > 0) {
|
|
1836
|
-
metadataTags.tracksTotal ??= tracksTotal;
|
|
1837
|
-
}
|
|
1838
|
-
}; break;
|
|
1839
|
-
|
|
1840
|
-
case 'DISCNUMBER': {
|
|
1841
|
-
const parts = value.split('/');
|
|
1842
|
-
const discNum = Number.parseInt(parts[0]!, 10);
|
|
1843
|
-
const discsTotal = parts[1] && Number.parseInt(parts[1], 10);
|
|
1844
|
-
|
|
1845
|
-
if (Number.isInteger(discNum) && discNum > 0) {
|
|
1846
|
-
metadataTags.discNumber ??= discNum;
|
|
1847
|
-
}
|
|
1848
|
-
if (discsTotal && Number.isInteger(discsTotal) && discsTotal > 0) {
|
|
1849
|
-
metadataTags.discsTotal ??= discsTotal;
|
|
1850
|
-
}
|
|
1851
|
-
}; break;
|
|
1852
|
-
|
|
1853
|
-
case 'DISCTOTAL': {
|
|
1854
|
-
const discsTotal = Number.parseInt(value, 10);
|
|
1855
|
-
if (Number.isInteger(discsTotal) && discsTotal > 0) {
|
|
1856
|
-
metadataTags.discsTotal ??= discsTotal;
|
|
1857
|
-
}
|
|
1858
|
-
}; break;
|
|
1859
|
-
|
|
1860
|
-
case 'DATE': {
|
|
1861
|
-
const date = new Date(value);
|
|
1862
|
-
if (!Number.isNaN(date.getTime())) {
|
|
1863
|
-
metadataTags.date ??= date;
|
|
1864
|
-
}
|
|
1865
|
-
}; break;
|
|
1866
|
-
|
|
1867
|
-
case 'GENRE': {
|
|
1868
|
-
metadataTags.genre ??= value;
|
|
1869
|
-
}; break;
|
|
1870
|
-
|
|
1871
|
-
case 'METADATA_BLOCK_PICTURE': {
|
|
1872
|
-
// https://datatracker.ietf.org/doc/rfc9639/ Section 8.8
|
|
1873
|
-
const decoded = base64ToBytes(value);
|
|
1874
|
-
|
|
1875
|
-
const view = toDataView(decoded);
|
|
1876
|
-
const pictureType = view.getUint32(0, false);
|
|
1877
|
-
const mediaTypeLength = view.getUint32(4, false);
|
|
1878
|
-
const mediaType = String.fromCharCode(...decoded.subarray(8, 8 + mediaTypeLength)); // ASCII
|
|
1879
|
-
const descriptionLength = view.getUint32(8 + mediaTypeLength, false);
|
|
1880
|
-
const description = textDecoder.decode(decoded.subarray(
|
|
1881
|
-
12 + mediaTypeLength,
|
|
1882
|
-
12 + mediaTypeLength + descriptionLength,
|
|
1883
|
-
));
|
|
1884
|
-
const dataLength = view.getUint32(mediaTypeLength + descriptionLength + 28);
|
|
1885
|
-
const data = decoded.subarray(
|
|
1886
|
-
mediaTypeLength + descriptionLength + 32,
|
|
1887
|
-
mediaTypeLength + descriptionLength + 32 + dataLength,
|
|
1888
|
-
);
|
|
1889
|
-
|
|
1890
|
-
metadataTags.images ??= [];
|
|
1891
|
-
metadataTags.images.push({
|
|
1892
|
-
data,
|
|
1893
|
-
mimeType: mediaType,
|
|
1894
|
-
kind: pictureType === 3 ? 'coverFront' : pictureType === 4 ? 'coverBack' : 'unknown',
|
|
1895
|
-
name: undefined,
|
|
1896
|
-
description: description || undefined,
|
|
1897
|
-
});
|
|
1898
|
-
}; break;
|
|
1899
|
-
}
|
|
1900
|
-
}
|
|
1901
|
-
};
|
|
1902
|
-
|
|
1903
|
-
export const createVorbisComments = (headerBytes: Uint8Array, tags: MetadataTags, writeImages: boolean) => {
|
|
1904
|
-
// https://datatracker.ietf.org/doc/html/rfc7845#section-5.2
|
|
1905
|
-
|
|
1906
|
-
const commentHeaderParts: Uint8Array[] = [
|
|
1907
|
-
headerBytes,
|
|
1908
|
-
];
|
|
1909
|
-
|
|
1910
|
-
const vendorString = 'Mediabunny';
|
|
1911
|
-
const encodedVendorString = textEncoder.encode(vendorString);
|
|
1912
|
-
|
|
1913
|
-
let currentBuffer = new Uint8Array(4 + encodedVendorString.length);
|
|
1914
|
-
let currentView = new DataView(currentBuffer.buffer);
|
|
1915
|
-
currentView.setUint32(0, encodedVendorString.length, true);
|
|
1916
|
-
currentBuffer.set(encodedVendorString, 4);
|
|
1917
|
-
|
|
1918
|
-
commentHeaderParts.push(currentBuffer);
|
|
1919
|
-
|
|
1920
|
-
const writtenTags = new Set<string>();
|
|
1921
|
-
const addCommentTag = (key: string, value: string) => {
|
|
1922
|
-
const joined = `${key}=${value}`;
|
|
1923
|
-
const encoded = textEncoder.encode(joined);
|
|
1924
|
-
|
|
1925
|
-
currentBuffer = new Uint8Array(4 + encoded.length);
|
|
1926
|
-
currentView = new DataView(currentBuffer.buffer);
|
|
1927
|
-
|
|
1928
|
-
currentView.setUint32(0, encoded.length, true);
|
|
1929
|
-
currentBuffer.set(encoded, 4);
|
|
1930
|
-
|
|
1931
|
-
commentHeaderParts.push(currentBuffer);
|
|
1932
|
-
writtenTags.add(key);
|
|
1933
|
-
};
|
|
1934
|
-
|
|
1935
|
-
for (const { key, value } of keyValueIterator(tags)) {
|
|
1936
|
-
switch (key) {
|
|
1937
|
-
case 'title': {
|
|
1938
|
-
addCommentTag('TITLE', value);
|
|
1939
|
-
}; break;
|
|
1940
|
-
|
|
1941
|
-
case 'description': {
|
|
1942
|
-
addCommentTag('DESCRIPTION', value);
|
|
1943
|
-
}; break;
|
|
1944
|
-
|
|
1945
|
-
case 'artist': {
|
|
1946
|
-
addCommentTag('ARTIST', value);
|
|
1947
|
-
}; break;
|
|
1948
|
-
|
|
1949
|
-
case 'album': {
|
|
1950
|
-
addCommentTag('ALBUM', value);
|
|
1951
|
-
}; break;
|
|
1952
|
-
|
|
1953
|
-
case 'albumArtist': {
|
|
1954
|
-
addCommentTag('ALBUMARTIST', value);
|
|
1955
|
-
}; break;
|
|
1956
|
-
|
|
1957
|
-
case 'genre': {
|
|
1958
|
-
addCommentTag('GENRE', value);
|
|
1959
|
-
}; break;
|
|
1960
|
-
|
|
1961
|
-
case 'date': {
|
|
1962
|
-
const rawVersion = tags.raw?.['DATE'] ?? tags.raw?.['date'];
|
|
1963
|
-
if (rawVersion && typeof rawVersion === 'string') {
|
|
1964
|
-
addCommentTag('DATE', rawVersion);
|
|
1965
|
-
} else {
|
|
1966
|
-
addCommentTag('DATE', value.toISOString().slice(0, 10));
|
|
1967
|
-
}
|
|
1968
|
-
}; break;
|
|
1969
|
-
|
|
1970
|
-
case 'comment': {
|
|
1971
|
-
addCommentTag('COMMENT', value);
|
|
1972
|
-
}; break;
|
|
1973
|
-
|
|
1974
|
-
case 'lyrics': {
|
|
1975
|
-
addCommentTag('LYRICS', value);
|
|
1976
|
-
}; break;
|
|
1977
|
-
|
|
1978
|
-
case 'trackNumber': {
|
|
1979
|
-
addCommentTag('TRACKNUMBER', value.toString());
|
|
1980
|
-
}; break;
|
|
1981
|
-
|
|
1982
|
-
case 'tracksTotal': {
|
|
1983
|
-
addCommentTag('TRACKTOTAL', value.toString());
|
|
1984
|
-
}; break;
|
|
1985
|
-
|
|
1986
|
-
case 'discNumber': {
|
|
1987
|
-
addCommentTag('DISCNUMBER', value.toString());
|
|
1988
|
-
}; break;
|
|
1989
|
-
|
|
1990
|
-
case 'discsTotal': {
|
|
1991
|
-
addCommentTag('DISCTOTAL', value.toString());
|
|
1992
|
-
}; break;
|
|
1993
|
-
|
|
1994
|
-
case 'images': {
|
|
1995
|
-
// For example, in .flac, we put the pictures in a different section,
|
|
1996
|
-
// not in the Vorbis comment header.
|
|
1997
|
-
if (!writeImages) {
|
|
1998
|
-
break;
|
|
1999
|
-
}
|
|
2000
|
-
for (const image of value) {
|
|
2001
|
-
// https://datatracker.ietf.org/doc/rfc9639/ Section 8.8
|
|
2002
|
-
const pictureType = image.kind === 'coverFront' ? 3 : image.kind === 'coverBack' ? 4 : 0;
|
|
2003
|
-
const encodedMediaType = new Uint8Array(image.mimeType.length);
|
|
2004
|
-
|
|
2005
|
-
for (let i = 0; i < image.mimeType.length; i++) {
|
|
2006
|
-
encodedMediaType[i] = image.mimeType.charCodeAt(i);
|
|
2007
|
-
}
|
|
2008
|
-
|
|
2009
|
-
const encodedDescription = textEncoder.encode(image.description ?? '');
|
|
2010
|
-
|
|
2011
|
-
const buffer = new Uint8Array(
|
|
2012
|
-
4 // Picture type
|
|
2013
|
-
+ 4 // MIME type length
|
|
2014
|
-
+ encodedMediaType.length // MIME type
|
|
2015
|
-
+ 4 // Description length
|
|
2016
|
-
+ encodedDescription.length // Description
|
|
2017
|
-
+ 16 // Width, height, color depth, number of colors
|
|
2018
|
-
+ 4 // Picture data length
|
|
2019
|
-
+ image.data.length, // Picture data
|
|
2020
|
-
);
|
|
2021
|
-
const view = toDataView(buffer);
|
|
2022
|
-
|
|
2023
|
-
view.setUint32(0, pictureType, false);
|
|
2024
|
-
view.setUint32(4, encodedMediaType.length, false);
|
|
2025
|
-
buffer.set(encodedMediaType, 8);
|
|
2026
|
-
view.setUint32(8 + encodedMediaType.length, encodedDescription.length, false);
|
|
2027
|
-
buffer.set(encodedDescription, 12 + encodedMediaType.length);
|
|
2028
|
-
// Skip a bunch of fields (width, height, color depth, number of colors)
|
|
2029
|
-
view.setUint32(
|
|
2030
|
-
28 + encodedMediaType.length + encodedDescription.length, image.data.length, false,
|
|
2031
|
-
);
|
|
2032
|
-
buffer.set(
|
|
2033
|
-
image.data,
|
|
2034
|
-
32 + encodedMediaType.length + encodedDescription.length,
|
|
2035
|
-
);
|
|
2036
|
-
|
|
2037
|
-
const encoded = bytesToBase64(buffer);
|
|
2038
|
-
addCommentTag('METADATA_BLOCK_PICTURE', encoded);
|
|
2039
|
-
}
|
|
2040
|
-
}; break;
|
|
2041
|
-
|
|
2042
|
-
case 'raw': {
|
|
2043
|
-
// Handled later
|
|
2044
|
-
}; break;
|
|
2045
|
-
|
|
2046
|
-
default: assertNever(key);
|
|
2047
|
-
}
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2050
|
-
if (tags.raw) {
|
|
2051
|
-
for (const key in tags.raw) {
|
|
2052
|
-
const value = tags.raw[key] ?? tags.raw[key.toLowerCase()];
|
|
2053
|
-
if (key === 'vendor' || value == null || writtenTags.has(key)) {
|
|
2054
|
-
continue;
|
|
2055
|
-
}
|
|
2056
|
-
|
|
2057
|
-
if (typeof value === 'string') {
|
|
2058
|
-
addCommentTag(key, value);
|
|
2059
|
-
}
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2062
|
-
|
|
2063
|
-
const listLengthBuffer = new Uint8Array(4);
|
|
2064
|
-
toDataView(listLengthBuffer).setUint32(0, writtenTags.size, true);
|
|
2065
|
-
commentHeaderParts.splice(2, 0, listLengthBuffer); // Insert after the header and vendor section
|
|
2066
|
-
|
|
2067
|
-
// Merge all comment header parts into a single buffer
|
|
2068
|
-
const commentHeaderLength = commentHeaderParts.reduce((a, b) => a + b.length, 0);
|
|
2069
|
-
const commentHeader = new Uint8Array(commentHeaderLength);
|
|
2070
|
-
|
|
2071
|
-
let pos = 0;
|
|
2072
|
-
for (const part of commentHeaderParts) {
|
|
2073
|
-
commentHeader.set(part, pos);
|
|
2074
|
-
pos += part.length;
|
|
2075
|
-
}
|
|
2076
|
-
|
|
2077
|
-
return commentHeader;
|
|
2078
|
-
};
|