@ahmedrowaihi/mediabunny-server 1.65.0-beta.261 → 1.65.0-beta.263
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/dist/bundles/mediabunny-server.cjs +203 -461
- package/dist/bundles/mediabunny-server.min.cjs +3 -3
- package/dist/bundles/mediabunny-server.min.mjs +3 -3
- package/dist/bundles/mediabunny-server.mjs +208 -466
- package/dist/modules/src/video-decoder.d.ts.map +1 -1
- package/dist/modules/src/video-decoder.js +4 -0
- package/dist/modules/src/video-encoder.d.ts +0 -4
- package/dist/modules/src/video-encoder.d.ts.map +1 -1
- package/dist/modules/src/video-encoder.js +25 -78
- package/dist/modules/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/video-decoder.ts +5 -0
- package/src/video-encoder.ts +29 -95
|
@@ -735,29 +735,6 @@ var MediabunnyServer = (() => {
|
|
|
735
735
|
var assertNever = (x) => {
|
|
736
736
|
throw new Error(`Unexpected value: ${x}`);
|
|
737
737
|
};
|
|
738
|
-
var getUint24 = (view, byteOffset, littleEndian) => {
|
|
739
|
-
const byte1 = view.getUint8(byteOffset);
|
|
740
|
-
const byte2 = view.getUint8(byteOffset + 1);
|
|
741
|
-
const byte3 = view.getUint8(byteOffset + 2);
|
|
742
|
-
if (littleEndian) {
|
|
743
|
-
return byte1 | byte2 << 8 | byte3 << 16;
|
|
744
|
-
} else {
|
|
745
|
-
return byte1 << 16 | byte2 << 8 | byte3;
|
|
746
|
-
}
|
|
747
|
-
};
|
|
748
|
-
var setUint24 = (view, byteOffset, value, littleEndian) => {
|
|
749
|
-
value = value >>> 0;
|
|
750
|
-
value = value & 16777215;
|
|
751
|
-
if (littleEndian) {
|
|
752
|
-
view.setUint8(byteOffset, value & 255);
|
|
753
|
-
view.setUint8(byteOffset + 1, value >>> 8 & 255);
|
|
754
|
-
view.setUint8(byteOffset + 2, value >>> 16 & 255);
|
|
755
|
-
} else {
|
|
756
|
-
view.setUint8(byteOffset, value >>> 16 & 255);
|
|
757
|
-
view.setUint8(byteOffset + 1, value >>> 8 & 255);
|
|
758
|
-
view.setUint8(byteOffset + 2, value & 255);
|
|
759
|
-
}
|
|
760
|
-
};
|
|
761
738
|
var SECOND_TO_MICROSECOND_FACTOR = 1e6 * (1 + Number.EPSILON);
|
|
762
739
|
var isWebKitCache = null;
|
|
763
740
|
var isWebKit = () => {
|
|
@@ -1093,170 +1070,6 @@ var MediabunnyServer = (() => {
|
|
|
1093
1070
|
}
|
|
1094
1071
|
};
|
|
1095
1072
|
|
|
1096
|
-
// packages/server/src/video-decoder.ts
|
|
1097
|
-
var NodeAvVideoDecoder = class extends import_mediabunny2.CustomVideoDecoder {
|
|
1098
|
-
constructor() {
|
|
1099
|
-
super(...arguments);
|
|
1100
|
-
this.codecContext = null;
|
|
1101
|
-
// Bookkeeping to restore the original timing information
|
|
1102
|
-
this.preciseTimings = [];
|
|
1103
|
-
}
|
|
1104
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1105
|
-
static supports(codec, config) {
|
|
1106
|
-
return codec === "avc" || codec === "hevc" || codec === "vp8" || codec === "vp9" || codec === "av1";
|
|
1107
|
-
}
|
|
1108
|
-
async init() {
|
|
1109
|
-
this.frame = new NodeAv3.Frame();
|
|
1110
|
-
this.frame.alloc();
|
|
1111
|
-
this.packet = new NodeAv3.Packet();
|
|
1112
|
-
this.packet.alloc();
|
|
1113
|
-
}
|
|
1114
|
-
async initCodecContext(packet) {
|
|
1115
|
-
assert(this.codecContext === null);
|
|
1116
|
-
const codecId = CODEC_TO_CODEC_ID[this.codec];
|
|
1117
|
-
assert(codecId !== void 0);
|
|
1118
|
-
let codec;
|
|
1119
|
-
if (this.codec === "vp9" && packet.sideData.alpha) {
|
|
1120
|
-
codec = NodeAv3.Codec.findDecoderByName(NodeAv3.FF_DECODER_LIBVPX_VP9) ?? NodeAv3.Codec.findDecoder(codecId);
|
|
1121
|
-
} else if (
|
|
1122
|
-
// This check used to be "is not prefer-hardware", meaning it would default to using software decode. I
|
|
1123
|
-
// didn't leave a comment for that back then so I actually don't know what it was for. I'm sure it was to
|
|
1124
|
-
// work around an issue but, I don't know. Not using hardware decode at all by defaults feels wrong to me,
|
|
1125
|
-
// so I changed it to what it is right now. If an issue is encountered, I can always change it.
|
|
1126
|
-
this.config.hardwareAcceleration === "prefer-software" || this.codec === "av1"
|
|
1127
|
-
) {
|
|
1128
|
-
codec = NodeAv3.Codec.findDecoder(codecId);
|
|
1129
|
-
} else {
|
|
1130
|
-
codec = await getHardwareDecoderCodec(codecId) ?? NodeAv3.Codec.findDecoder(codecId);
|
|
1131
|
-
}
|
|
1132
|
-
if (!codec) {
|
|
1133
|
-
throw new Error(`Unable to obtain libav codec for '${this.codec}'.`);
|
|
1134
|
-
}
|
|
1135
|
-
const codecContext = new NodeAv3.CodecContext();
|
|
1136
|
-
codecContext.allocContext3(codec);
|
|
1137
|
-
this.pixelAspectRatio = simplifyRational({
|
|
1138
|
-
num: (this.config.displayAspectWidth ?? this.config.codedWidth ?? 0) * (this.config.codedHeight ?? 0),
|
|
1139
|
-
den: (this.config.displayAspectHeight ?? this.config.codedHeight ?? 0) * (this.config.codedWidth ?? 0) || 1
|
|
1140
|
-
});
|
|
1141
|
-
codecContext.width = this.config.codedWidth ?? 0;
|
|
1142
|
-
codecContext.height = this.config.codedHeight ?? 0;
|
|
1143
|
-
codecContext.codecType = NodeAv3.AVMEDIA_TYPE_VIDEO;
|
|
1144
|
-
codecContext.codecId = codecId;
|
|
1145
|
-
codecContext.extraData = this.config.description ? Buffer.from(toUint8Array(this.config.description)) : null;
|
|
1146
|
-
codecContext.sampleAspectRatio = new NodeAv3.Rational(this.pixelAspectRatio.num, this.pixelAspectRatio.den);
|
|
1147
|
-
codecContext.threadCount = 0;
|
|
1148
|
-
const options = NodeAv3.Dictionary.fromObject({ strict: NodeAv3.FF_COMPLIANCE_STRICT });
|
|
1149
|
-
const ret = await codecContext.open2(codec, options);
|
|
1150
|
-
NodeAv3.FFmpegError.throwIfError(ret, "Open codec context");
|
|
1151
|
-
this.codecContext = codecContext;
|
|
1152
|
-
}
|
|
1153
|
-
async decode(packet) {
|
|
1154
|
-
if (this.codecContext === null) {
|
|
1155
|
-
await this.initCodecContext(packet);
|
|
1156
|
-
}
|
|
1157
|
-
assert(this.codecContext);
|
|
1158
|
-
this.packet.isKeyframe = packet.type === "key";
|
|
1159
|
-
this.packet.data = Buffer.from(packet.data);
|
|
1160
|
-
this.packet.timeBase = { num: 1, den: 1e6 };
|
|
1161
|
-
this.packet.pts = BigInt(packet.microsecondTimestamp);
|
|
1162
|
-
this.packet.dts = NodeAv3.AV_NOPTS_VALUE;
|
|
1163
|
-
this.packet.duration = BigInt(packet.microsecondDuration);
|
|
1164
|
-
if (packet.sideData.alpha) {
|
|
1165
|
-
const matroskaBlockAdditional = Buffer.alloc(8 + packet.sideData.alpha.byteLength);
|
|
1166
|
-
matroskaBlockAdditional[7] = 1;
|
|
1167
|
-
matroskaBlockAdditional.set(packet.sideData.alpha, 8);
|
|
1168
|
-
this.packet.addSideData(NodeAv3.AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL, matroskaBlockAdditional);
|
|
1169
|
-
}
|
|
1170
|
-
const preciseTimingIndex = binarySearchLessOrEqual(
|
|
1171
|
-
this.preciseTimings,
|
|
1172
|
-
packet.microsecondTimestamp,
|
|
1173
|
-
(x) => x.microsecondTimestamp
|
|
1174
|
-
);
|
|
1175
|
-
const existingEntry = preciseTimingIndex !== -1 ? this.preciseTimings[preciseTimingIndex] : null;
|
|
1176
|
-
if (existingEntry && existingEntry.microsecondTimestamp === packet.microsecondTimestamp) {
|
|
1177
|
-
if (existingEntry.timestamp !== packet.timestamp) {
|
|
1178
|
-
existingEntry.timestampIsValid = false;
|
|
1179
|
-
}
|
|
1180
|
-
if (existingEntry.duration !== packet.duration) {
|
|
1181
|
-
existingEntry.durationIsValid = false;
|
|
1182
|
-
}
|
|
1183
|
-
} else {
|
|
1184
|
-
this.preciseTimings.splice(preciseTimingIndex + 1, 0, {
|
|
1185
|
-
microsecondTimestamp: packet.microsecondTimestamp,
|
|
1186
|
-
timestamp: packet.timestamp,
|
|
1187
|
-
duration: packet.duration,
|
|
1188
|
-
timestampIsValid: true,
|
|
1189
|
-
durationIsValid: true
|
|
1190
|
-
});
|
|
1191
|
-
if (this.preciseTimings.length > 128) {
|
|
1192
|
-
this.preciseTimings.shift();
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
const ret = await this.codecContext.sendPacket(this.packet);
|
|
1196
|
-
NodeAv3.FFmpegError.throwIfError(ret, "Send packet");
|
|
1197
|
-
this.packet.unref();
|
|
1198
|
-
while (true) {
|
|
1199
|
-
const receiveRet = await this.codecContext.receiveFrame(this.frame);
|
|
1200
|
-
if (receiveRet === NodeAv3.AVERROR_EAGAIN || receiveRet === NodeAv3.AVERROR_EOF) {
|
|
1201
|
-
break;
|
|
1202
|
-
}
|
|
1203
|
-
this.receiveFrame(receiveRet);
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
receiveFrame(ret) {
|
|
1207
|
-
NodeAv3.FFmpegError.throwIfError(ret, "Receive frame");
|
|
1208
|
-
this.frame.sampleAspectRatio = new NodeAv3.Rational(this.pixelAspectRatio.num, this.pixelAspectRatio.den);
|
|
1209
|
-
let timestamp = Number(this.frame.pts) / 1e6;
|
|
1210
|
-
let duration = Number(this.frame.duration) / 1e6;
|
|
1211
|
-
const preciseTimingIndex = binarySearchLessOrEqual(
|
|
1212
|
-
this.preciseTimings,
|
|
1213
|
-
Number(this.frame.pts),
|
|
1214
|
-
(x) => x.microsecondTimestamp
|
|
1215
|
-
);
|
|
1216
|
-
const entry = preciseTimingIndex !== -1 ? this.preciseTimings[preciseTimingIndex] : null;
|
|
1217
|
-
if (entry && entry.microsecondTimestamp === Number(this.frame.pts)) {
|
|
1218
|
-
if (entry.timestampIsValid) {
|
|
1219
|
-
timestamp = entry.timestamp;
|
|
1220
|
-
}
|
|
1221
|
-
if (entry.durationIsValid) {
|
|
1222
|
-
duration = entry.duration;
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
const clone = this.frame.clone();
|
|
1226
|
-
if (!clone) {
|
|
1227
|
-
throw new Error("Frame clone allocation failed.");
|
|
1228
|
-
}
|
|
1229
|
-
this.onSample(new import_mediabunny2.VideoSample(new AvFrameVideoSampleResource(clone), {
|
|
1230
|
-
timestamp,
|
|
1231
|
-
duration
|
|
1232
|
-
}));
|
|
1233
|
-
}
|
|
1234
|
-
async flush() {
|
|
1235
|
-
if (!this.codecContext) {
|
|
1236
|
-
return;
|
|
1237
|
-
}
|
|
1238
|
-
const ret = await this.codecContext.sendPacket(null);
|
|
1239
|
-
NodeAv3.FFmpegError.throwIfError(ret, "Flush decoder");
|
|
1240
|
-
while (true) {
|
|
1241
|
-
const receiveRet = await this.codecContext.receiveFrame(this.frame);
|
|
1242
|
-
if (receiveRet === NodeAv3.AVERROR_EAGAIN || receiveRet === NodeAv3.AVERROR_EOF) {
|
|
1243
|
-
break;
|
|
1244
|
-
}
|
|
1245
|
-
this.receiveFrame(receiveRet);
|
|
1246
|
-
}
|
|
1247
|
-
this.codecContext.flushBuffers();
|
|
1248
|
-
}
|
|
1249
|
-
close() {
|
|
1250
|
-
this.codecContext?.freeContext();
|
|
1251
|
-
this.frame.free();
|
|
1252
|
-
this.packet.free();
|
|
1253
|
-
}
|
|
1254
|
-
};
|
|
1255
|
-
|
|
1256
|
-
// packages/server/src/video-encoder.ts
|
|
1257
|
-
var import_mediabunny3 = __require("mediabunny");
|
|
1258
|
-
var NodeAv4 = __toESM(__require("node-av"));
|
|
1259
|
-
|
|
1260
1073
|
// shared/bitstream.ts
|
|
1261
1074
|
var Bitstream = class _Bitstream {
|
|
1262
1075
|
constructor(bytes) {
|
|
@@ -1445,100 +1258,6 @@ var MediabunnyServer = (() => {
|
|
|
1445
1258
|
return { header, bitstream };
|
|
1446
1259
|
};
|
|
1447
1260
|
|
|
1448
|
-
// src/sei.ts
|
|
1449
|
-
var SEI_FRAMINGS = {
|
|
1450
|
-
avc: {
|
|
1451
|
-
// forbidden_zero_bit = 0, nal_ref_idc = 0 (an SEI is never a reference picture), nal_unit_type = 6.
|
|
1452
|
-
get nalHeader() {
|
|
1453
|
-
return [6 /* SEI */];
|
|
1454
|
-
},
|
|
1455
|
-
isSeiNalUnit: (firstByte) => extractNalUnitTypeForAvc(firstByte) === 6 /* SEI */,
|
|
1456
|
-
// AVC VCL NAL unit types are 1..5; the parameter sets, AUD and SEI that may precede them are all above.
|
|
1457
|
-
isVcl: (firstByte) => {
|
|
1458
|
-
const type = extractNalUnitTypeForAvc(firstByte);
|
|
1459
|
-
return type >= 1 /* NON_IDR_SLICE */ && type <= 5 /* IDR */;
|
|
1460
|
-
},
|
|
1461
|
-
iterateNalUnits: (packetData, decoderConfig) => iterateAvcNalUnits(packetData, decoderConfig),
|
|
1462
|
-
concatNalUnits: (nalUnits, decoderConfig) => concatAvcNalUnits(nalUnits, decoderConfig)
|
|
1463
|
-
},
|
|
1464
|
-
hevc: {
|
|
1465
|
-
// nuh_layer_id = 0, nuh_temporal_id_plus1 = 1
|
|
1466
|
-
get nalHeader() {
|
|
1467
|
-
return [39 /* PREFIX_SEI_NUT */ << 1, 1];
|
|
1468
|
-
},
|
|
1469
|
-
isSeiNalUnit: (firstByte) => {
|
|
1470
|
-
const type = extractNalUnitTypeForHevc(firstByte);
|
|
1471
|
-
return type === 39 /* PREFIX_SEI_NUT */ || type === 40 /* SUFFIX_SEI_NUT */;
|
|
1472
|
-
},
|
|
1473
|
-
// HEVC VCL NAL unit types are 0..31; everything at or above VPS_NUT precedes them in an access unit.
|
|
1474
|
-
isVcl: (firstByte) => extractNalUnitTypeForHevc(firstByte) < 32 /* VPS_NUT */,
|
|
1475
|
-
iterateNalUnits: (packetData, decoderConfig) => iterateHevcNalUnits(packetData, decoderConfig),
|
|
1476
|
-
concatNalUnits: (nalUnits, decoderConfig) => concatHevcNalUnits(nalUnits, decoderConfig)
|
|
1477
|
-
}
|
|
1478
|
-
};
|
|
1479
|
-
var appendSeiMessage = (out, message) => {
|
|
1480
|
-
for (const value of [message.payloadType, message.payload.length]) {
|
|
1481
|
-
let remaining = value;
|
|
1482
|
-
while (remaining >= 255) {
|
|
1483
|
-
out.push(255);
|
|
1484
|
-
remaining -= 255;
|
|
1485
|
-
}
|
|
1486
|
-
out.push(remaining);
|
|
1487
|
-
}
|
|
1488
|
-
out.push(...message.payload);
|
|
1489
|
-
};
|
|
1490
|
-
var buildSeiNalUnit = (messages, codec) => {
|
|
1491
|
-
if (messages.length === 0) {
|
|
1492
|
-
return null;
|
|
1493
|
-
}
|
|
1494
|
-
const rbsp = [];
|
|
1495
|
-
for (const message of messages) {
|
|
1496
|
-
appendSeiMessage(rbsp, message);
|
|
1497
|
-
}
|
|
1498
|
-
rbsp.push(128);
|
|
1499
|
-
const framing = SEI_FRAMINGS[codec];
|
|
1500
|
-
const escaped = addEmulationPreventionBytes(new Uint8Array(rbsp));
|
|
1501
|
-
const nalUnit = new Uint8Array(framing.nalHeader.length + escaped.length);
|
|
1502
|
-
nalUnit.set(framing.nalHeader, 0);
|
|
1503
|
-
nalUnit.set(escaped, framing.nalHeader.length);
|
|
1504
|
-
return nalUnit;
|
|
1505
|
-
};
|
|
1506
|
-
var spliceSeiNalUnit = (packetData, decoderConfig, codec, seiNalUnit) => {
|
|
1507
|
-
const framing = SEI_FRAMINGS[codec];
|
|
1508
|
-
const nalUnits = [];
|
|
1509
|
-
let firstVclIndex = -1;
|
|
1510
|
-
for (const location of framing.iterateNalUnits(packetData, decoderConfig)) {
|
|
1511
|
-
const nalUnit = packetData.subarray(location.offset, location.offset + location.length);
|
|
1512
|
-
if (firstVclIndex === -1 && framing.isVcl(nalUnit[0])) {
|
|
1513
|
-
firstVclIndex = nalUnits.length;
|
|
1514
|
-
}
|
|
1515
|
-
nalUnits.push(nalUnit);
|
|
1516
|
-
}
|
|
1517
|
-
if (firstVclIndex === -1) {
|
|
1518
|
-
throw new Error(`Expected a VCL NAL unit in the ${codec.toUpperCase()} access unit, found none.`);
|
|
1519
|
-
}
|
|
1520
|
-
nalUnits.splice(firstVclIndex, 0, seiNalUnit);
|
|
1521
|
-
return framing.concatNalUnits(nalUnits, decoderConfig);
|
|
1522
|
-
};
|
|
1523
|
-
|
|
1524
|
-
// src/closed-captions.ts
|
|
1525
|
-
var USER_DATA_REGISTERED_ITU_T_T35 = 4;
|
|
1526
|
-
var ATSC_CC_DATA_HEADER = [181, 0, 49, 71, 65, 57, 52, 3];
|
|
1527
|
-
var buildClosedCaptionSeiMessage = (bytePairs) => {
|
|
1528
|
-
const payload = new Uint8Array(ATSC_CC_DATA_HEADER.length + 2 + bytePairs.length * 3 + 1);
|
|
1529
|
-
payload.set(ATSC_CC_DATA_HEADER, 0);
|
|
1530
|
-
let pos = ATSC_CC_DATA_HEADER.length;
|
|
1531
|
-
payload[pos++] = 192 | bytePairs.length;
|
|
1532
|
-
payload[pos++] = 255;
|
|
1533
|
-
for (const bytePair of bytePairs) {
|
|
1534
|
-
payload[pos++] = 252 | bytePair.type;
|
|
1535
|
-
payload[pos++] = bytePair.byte0;
|
|
1536
|
-
payload[pos++] = bytePair.byte1;
|
|
1537
|
-
}
|
|
1538
|
-
payload[pos] = 255;
|
|
1539
|
-
return { payloadType: USER_DATA_REGISTERED_ITU_T_T35, payload };
|
|
1540
|
-
};
|
|
1541
|
-
|
|
1542
1261
|
// src/codec.ts
|
|
1543
1262
|
var PCM_AUDIO_CODECS = [
|
|
1544
1263
|
"pcm-s16",
|
|
@@ -1864,39 +1583,6 @@ var MediabunnyServer = (() => {
|
|
|
1864
1583
|
};
|
|
1865
1584
|
}
|
|
1866
1585
|
};
|
|
1867
|
-
var iterateNalUnitsInLengthPrefixed = function* (packetData, lengthSize) {
|
|
1868
|
-
let offset = 0;
|
|
1869
|
-
const dataView = new DataView(packetData.buffer, packetData.byteOffset, packetData.byteLength);
|
|
1870
|
-
while (offset + lengthSize <= packetData.length) {
|
|
1871
|
-
let nalUnitLength;
|
|
1872
|
-
if (lengthSize === 1) {
|
|
1873
|
-
nalUnitLength = dataView.getUint8(offset);
|
|
1874
|
-
} else if (lengthSize === 2) {
|
|
1875
|
-
nalUnitLength = dataView.getUint16(offset, false);
|
|
1876
|
-
} else if (lengthSize === 3) {
|
|
1877
|
-
nalUnitLength = getUint24(dataView, offset, false);
|
|
1878
|
-
} else {
|
|
1879
|
-
assert(lengthSize === 4);
|
|
1880
|
-
nalUnitLength = dataView.getUint32(offset, false);
|
|
1881
|
-
}
|
|
1882
|
-
offset += lengthSize;
|
|
1883
|
-
yield {
|
|
1884
|
-
offset,
|
|
1885
|
-
length: Math.min(nalUnitLength, packetData.length - offset)
|
|
1886
|
-
};
|
|
1887
|
-
offset += nalUnitLength;
|
|
1888
|
-
}
|
|
1889
|
-
};
|
|
1890
|
-
var iterateAvcNalUnits = (packetData, decoderConfig) => {
|
|
1891
|
-
if (decoderConfig.description) {
|
|
1892
|
-
const bytes = toUint8Array(decoderConfig.description);
|
|
1893
|
-
const lengthSizeMinusOne = bytes[4] & 3;
|
|
1894
|
-
const lengthSize = lengthSizeMinusOne + 1;
|
|
1895
|
-
return iterateNalUnitsInLengthPrefixed(packetData, lengthSize);
|
|
1896
|
-
} else {
|
|
1897
|
-
return iterateNalUnitsInAnnexB(packetData);
|
|
1898
|
-
}
|
|
1899
|
-
};
|
|
1900
1586
|
var extractNalUnitTypeForAvc = (byte) => {
|
|
1901
1587
|
return byte & 31;
|
|
1902
1588
|
};
|
|
@@ -1915,68 +1601,7 @@ var MediabunnyServer = (() => {
|
|
|
1915
1601
|
}
|
|
1916
1602
|
return result.subarray(0, resultLength);
|
|
1917
1603
|
};
|
|
1918
|
-
var addEmulationPreventionBytes = (data) => {
|
|
1919
|
-
const result = [];
|
|
1920
|
-
let zeroCount = 0;
|
|
1921
|
-
for (const byte of data) {
|
|
1922
|
-
if (zeroCount === 2 && byte <= 3) {
|
|
1923
|
-
result.push(3);
|
|
1924
|
-
zeroCount = 0;
|
|
1925
|
-
}
|
|
1926
|
-
result.push(byte);
|
|
1927
|
-
zeroCount = byte === 0 ? zeroCount + 1 : 0;
|
|
1928
|
-
}
|
|
1929
|
-
return new Uint8Array(result);
|
|
1930
|
-
};
|
|
1931
1604
|
var ANNEX_B_START_CODE = new Uint8Array([0, 0, 0, 1]);
|
|
1932
|
-
var concatNalUnitsInAnnexB = (nalUnits) => {
|
|
1933
|
-
const totalLength = nalUnits.reduce((a, b) => a + ANNEX_B_START_CODE.byteLength + b.byteLength, 0);
|
|
1934
|
-
const result = new Uint8Array(totalLength);
|
|
1935
|
-
let offset = 0;
|
|
1936
|
-
for (const nalUnit of nalUnits) {
|
|
1937
|
-
result.set(ANNEX_B_START_CODE, offset);
|
|
1938
|
-
offset += ANNEX_B_START_CODE.byteLength;
|
|
1939
|
-
result.set(nalUnit, offset);
|
|
1940
|
-
offset += nalUnit.byteLength;
|
|
1941
|
-
}
|
|
1942
|
-
return result;
|
|
1943
|
-
};
|
|
1944
|
-
var concatNalUnitsInLengthPrefixed = (nalUnits, lengthSize) => {
|
|
1945
|
-
const totalLength = nalUnits.reduce((a, b) => a + lengthSize + b.byteLength, 0);
|
|
1946
|
-
const result = new Uint8Array(totalLength);
|
|
1947
|
-
let offset = 0;
|
|
1948
|
-
for (const nalUnit of nalUnits) {
|
|
1949
|
-
const dataView = new DataView(result.buffer, result.byteOffset, result.byteLength);
|
|
1950
|
-
switch (lengthSize) {
|
|
1951
|
-
case 1:
|
|
1952
|
-
dataView.setUint8(offset, nalUnit.byteLength);
|
|
1953
|
-
break;
|
|
1954
|
-
case 2:
|
|
1955
|
-
dataView.setUint16(offset, nalUnit.byteLength, false);
|
|
1956
|
-
break;
|
|
1957
|
-
case 3:
|
|
1958
|
-
setUint24(dataView, offset, nalUnit.byteLength, false);
|
|
1959
|
-
break;
|
|
1960
|
-
case 4:
|
|
1961
|
-
dataView.setUint32(offset, nalUnit.byteLength, false);
|
|
1962
|
-
break;
|
|
1963
|
-
}
|
|
1964
|
-
offset += lengthSize;
|
|
1965
|
-
result.set(nalUnit, offset);
|
|
1966
|
-
offset += nalUnit.byteLength;
|
|
1967
|
-
}
|
|
1968
|
-
return result;
|
|
1969
|
-
};
|
|
1970
|
-
var concatAvcNalUnits = (nalUnits, decoderConfig) => {
|
|
1971
|
-
if (decoderConfig.description) {
|
|
1972
|
-
const bytes = toUint8Array(decoderConfig.description);
|
|
1973
|
-
const lengthSizeMinusOne = bytes[4] & 3;
|
|
1974
|
-
const lengthSize = lengthSizeMinusOne + 1;
|
|
1975
|
-
return concatNalUnitsInLengthPrefixed(nalUnits, lengthSize);
|
|
1976
|
-
} else {
|
|
1977
|
-
return concatNalUnitsInAnnexB(nalUnits);
|
|
1978
|
-
}
|
|
1979
|
-
};
|
|
1980
1605
|
var extractAvcDecoderConfigurationRecord = (packetData) => {
|
|
1981
1606
|
try {
|
|
1982
1607
|
const spsUnits = [];
|
|
@@ -2323,24 +1948,6 @@ var MediabunnyServer = (() => {
|
|
|
2323
1948
|
bitstream.skipBits(5);
|
|
2324
1949
|
bitstream.skipBits(5);
|
|
2325
1950
|
};
|
|
2326
|
-
var concatHevcNalUnits = (nalUnits, decoderConfig) => {
|
|
2327
|
-
if (decoderConfig.description) {
|
|
2328
|
-
return concatNalUnitsInLengthPrefixed(nalUnits, hevcNalUnitLengthSize(decoderConfig.description));
|
|
2329
|
-
} else {
|
|
2330
|
-
return concatNalUnitsInAnnexB(nalUnits);
|
|
2331
|
-
}
|
|
2332
|
-
};
|
|
2333
|
-
var hevcNalUnitLengthSize = (description) => {
|
|
2334
|
-
const bytes = toUint8Array(description);
|
|
2335
|
-
return (bytes[21] & 3) + 1;
|
|
2336
|
-
};
|
|
2337
|
-
var iterateHevcNalUnits = (packetData, decoderConfig) => {
|
|
2338
|
-
if (decoderConfig.description) {
|
|
2339
|
-
return iterateNalUnitsInLengthPrefixed(packetData, hevcNalUnitLengthSize(decoderConfig.description));
|
|
2340
|
-
} else {
|
|
2341
|
-
return iterateNalUnitsInAnnexB(packetData);
|
|
2342
|
-
}
|
|
2343
|
-
};
|
|
2344
1951
|
var extractNalUnitTypeForHevc = (byte) => {
|
|
2345
1952
|
return byte >> 1 & 63;
|
|
2346
1953
|
};
|
|
@@ -3316,9 +2923,193 @@ var MediabunnyServer = (() => {
|
|
|
3316
2923
|
var AC3_REGISTRATION_DESCRIPTOR = new Uint8Array([5, 4, 65, 67, 45, 51]);
|
|
3317
2924
|
var EAC3_REGISTRATION_DESCRIPTOR = new Uint8Array([5, 4, 69, 65, 67, 51]);
|
|
3318
2925
|
|
|
2926
|
+
// src/closed-captions.ts
|
|
2927
|
+
var MAX_CLOSED_CAPTION_BYTE_PAIRS = 31;
|
|
2928
|
+
var parseClosedCaptionBytePairs = (ccData) => {
|
|
2929
|
+
const bytePairs = [];
|
|
2930
|
+
for (let i = 0; i + 2 < ccData.length; i += 3) {
|
|
2931
|
+
const header = ccData[i];
|
|
2932
|
+
if ((header & 4) === 0) {
|
|
2933
|
+
continue;
|
|
2934
|
+
}
|
|
2935
|
+
bytePairs.push({
|
|
2936
|
+
type: header & 3,
|
|
2937
|
+
byte0: ccData[i + 1],
|
|
2938
|
+
byte1: ccData[i + 2]
|
|
2939
|
+
});
|
|
2940
|
+
}
|
|
2941
|
+
if (bytePairs.length === 0 || bytePairs.length > MAX_CLOSED_CAPTION_BYTE_PAIRS) {
|
|
2942
|
+
return null;
|
|
2943
|
+
}
|
|
2944
|
+
return bytePairs;
|
|
2945
|
+
};
|
|
2946
|
+
|
|
2947
|
+
// packages/server/src/video-decoder.ts
|
|
2948
|
+
var NodeAvVideoDecoder = class extends import_mediabunny2.CustomVideoDecoder {
|
|
2949
|
+
constructor() {
|
|
2950
|
+
super(...arguments);
|
|
2951
|
+
this.codecContext = null;
|
|
2952
|
+
// Bookkeeping to restore the original timing information
|
|
2953
|
+
this.preciseTimings = [];
|
|
2954
|
+
}
|
|
2955
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2956
|
+
static supports(codec, config) {
|
|
2957
|
+
return codec === "avc" || codec === "hevc" || codec === "vp8" || codec === "vp9" || codec === "av1";
|
|
2958
|
+
}
|
|
2959
|
+
async init() {
|
|
2960
|
+
this.frame = new NodeAv3.Frame();
|
|
2961
|
+
this.frame.alloc();
|
|
2962
|
+
this.packet = new NodeAv3.Packet();
|
|
2963
|
+
this.packet.alloc();
|
|
2964
|
+
}
|
|
2965
|
+
async initCodecContext(packet) {
|
|
2966
|
+
assert(this.codecContext === null);
|
|
2967
|
+
const codecId = CODEC_TO_CODEC_ID[this.codec];
|
|
2968
|
+
assert(codecId !== void 0);
|
|
2969
|
+
let codec;
|
|
2970
|
+
if (this.codec === "vp9" && packet.sideData.alpha) {
|
|
2971
|
+
codec = NodeAv3.Codec.findDecoderByName(NodeAv3.FF_DECODER_LIBVPX_VP9) ?? NodeAv3.Codec.findDecoder(codecId);
|
|
2972
|
+
} else if (
|
|
2973
|
+
// This check used to be "is not prefer-hardware", meaning it would default to using software decode. I
|
|
2974
|
+
// didn't leave a comment for that back then so I actually don't know what it was for. I'm sure it was to
|
|
2975
|
+
// work around an issue but, I don't know. Not using hardware decode at all by defaults feels wrong to me,
|
|
2976
|
+
// so I changed it to what it is right now. If an issue is encountered, I can always change it.
|
|
2977
|
+
this.config.hardwareAcceleration === "prefer-software" || this.codec === "av1"
|
|
2978
|
+
) {
|
|
2979
|
+
codec = NodeAv3.Codec.findDecoder(codecId);
|
|
2980
|
+
} else {
|
|
2981
|
+
codec = await getHardwareDecoderCodec(codecId) ?? NodeAv3.Codec.findDecoder(codecId);
|
|
2982
|
+
}
|
|
2983
|
+
if (!codec) {
|
|
2984
|
+
throw new Error(`Unable to obtain libav codec for '${this.codec}'.`);
|
|
2985
|
+
}
|
|
2986
|
+
const codecContext = new NodeAv3.CodecContext();
|
|
2987
|
+
codecContext.allocContext3(codec);
|
|
2988
|
+
this.pixelAspectRatio = simplifyRational({
|
|
2989
|
+
num: (this.config.displayAspectWidth ?? this.config.codedWidth ?? 0) * (this.config.codedHeight ?? 0),
|
|
2990
|
+
den: (this.config.displayAspectHeight ?? this.config.codedHeight ?? 0) * (this.config.codedWidth ?? 0) || 1
|
|
2991
|
+
});
|
|
2992
|
+
codecContext.width = this.config.codedWidth ?? 0;
|
|
2993
|
+
codecContext.height = this.config.codedHeight ?? 0;
|
|
2994
|
+
codecContext.codecType = NodeAv3.AVMEDIA_TYPE_VIDEO;
|
|
2995
|
+
codecContext.codecId = codecId;
|
|
2996
|
+
codecContext.extraData = this.config.description ? Buffer.from(toUint8Array(this.config.description)) : null;
|
|
2997
|
+
codecContext.sampleAspectRatio = new NodeAv3.Rational(this.pixelAspectRatio.num, this.pixelAspectRatio.den);
|
|
2998
|
+
codecContext.threadCount = 0;
|
|
2999
|
+
const options = NodeAv3.Dictionary.fromObject({ strict: NodeAv3.FF_COMPLIANCE_STRICT });
|
|
3000
|
+
const ret = await codecContext.open2(codec, options);
|
|
3001
|
+
NodeAv3.FFmpegError.throwIfError(ret, "Open codec context");
|
|
3002
|
+
this.codecContext = codecContext;
|
|
3003
|
+
}
|
|
3004
|
+
async decode(packet) {
|
|
3005
|
+
if (this.codecContext === null) {
|
|
3006
|
+
await this.initCodecContext(packet);
|
|
3007
|
+
}
|
|
3008
|
+
assert(this.codecContext);
|
|
3009
|
+
this.packet.isKeyframe = packet.type === "key";
|
|
3010
|
+
this.packet.data = Buffer.from(packet.data);
|
|
3011
|
+
this.packet.timeBase = { num: 1, den: 1e6 };
|
|
3012
|
+
this.packet.pts = BigInt(packet.microsecondTimestamp);
|
|
3013
|
+
this.packet.dts = NodeAv3.AV_NOPTS_VALUE;
|
|
3014
|
+
this.packet.duration = BigInt(packet.microsecondDuration);
|
|
3015
|
+
if (packet.sideData.alpha) {
|
|
3016
|
+
const matroskaBlockAdditional = Buffer.alloc(8 + packet.sideData.alpha.byteLength);
|
|
3017
|
+
matroskaBlockAdditional[7] = 1;
|
|
3018
|
+
matroskaBlockAdditional.set(packet.sideData.alpha, 8);
|
|
3019
|
+
this.packet.addSideData(NodeAv3.AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL, matroskaBlockAdditional);
|
|
3020
|
+
}
|
|
3021
|
+
const preciseTimingIndex = binarySearchLessOrEqual(
|
|
3022
|
+
this.preciseTimings,
|
|
3023
|
+
packet.microsecondTimestamp,
|
|
3024
|
+
(x) => x.microsecondTimestamp
|
|
3025
|
+
);
|
|
3026
|
+
const existingEntry = preciseTimingIndex !== -1 ? this.preciseTimings[preciseTimingIndex] : null;
|
|
3027
|
+
if (existingEntry && existingEntry.microsecondTimestamp === packet.microsecondTimestamp) {
|
|
3028
|
+
if (existingEntry.timestamp !== packet.timestamp) {
|
|
3029
|
+
existingEntry.timestampIsValid = false;
|
|
3030
|
+
}
|
|
3031
|
+
if (existingEntry.duration !== packet.duration) {
|
|
3032
|
+
existingEntry.durationIsValid = false;
|
|
3033
|
+
}
|
|
3034
|
+
} else {
|
|
3035
|
+
this.preciseTimings.splice(preciseTimingIndex + 1, 0, {
|
|
3036
|
+
microsecondTimestamp: packet.microsecondTimestamp,
|
|
3037
|
+
timestamp: packet.timestamp,
|
|
3038
|
+
duration: packet.duration,
|
|
3039
|
+
timestampIsValid: true,
|
|
3040
|
+
durationIsValid: true
|
|
3041
|
+
});
|
|
3042
|
+
if (this.preciseTimings.length > 128) {
|
|
3043
|
+
this.preciseTimings.shift();
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
const ret = await this.codecContext.sendPacket(this.packet);
|
|
3047
|
+
NodeAv3.FFmpegError.throwIfError(ret, "Send packet");
|
|
3048
|
+
this.packet.unref();
|
|
3049
|
+
while (true) {
|
|
3050
|
+
const receiveRet = await this.codecContext.receiveFrame(this.frame);
|
|
3051
|
+
if (receiveRet === NodeAv3.AVERROR_EAGAIN || receiveRet === NodeAv3.AVERROR_EOF) {
|
|
3052
|
+
break;
|
|
3053
|
+
}
|
|
3054
|
+
this.receiveFrame(receiveRet);
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
receiveFrame(ret) {
|
|
3058
|
+
NodeAv3.FFmpegError.throwIfError(ret, "Receive frame");
|
|
3059
|
+
this.frame.sampleAspectRatio = new NodeAv3.Rational(this.pixelAspectRatio.num, this.pixelAspectRatio.den);
|
|
3060
|
+
let timestamp = Number(this.frame.pts) / 1e6;
|
|
3061
|
+
let duration = Number(this.frame.duration) / 1e6;
|
|
3062
|
+
const preciseTimingIndex = binarySearchLessOrEqual(
|
|
3063
|
+
this.preciseTimings,
|
|
3064
|
+
Number(this.frame.pts),
|
|
3065
|
+
(x) => x.microsecondTimestamp
|
|
3066
|
+
);
|
|
3067
|
+
const entry = preciseTimingIndex !== -1 ? this.preciseTimings[preciseTimingIndex] : null;
|
|
3068
|
+
if (entry && entry.microsecondTimestamp === Number(this.frame.pts)) {
|
|
3069
|
+
if (entry.timestampIsValid) {
|
|
3070
|
+
timestamp = entry.timestamp;
|
|
3071
|
+
}
|
|
3072
|
+
if (entry.durationIsValid) {
|
|
3073
|
+
duration = entry.duration;
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
const clone = this.frame.clone();
|
|
3077
|
+
if (!clone) {
|
|
3078
|
+
throw new Error("Frame clone allocation failed.");
|
|
3079
|
+
}
|
|
3080
|
+
const ccData = this.frame.getSideData(NodeAv3.AV_FRAME_DATA_A53_CC);
|
|
3081
|
+
this.onSample(new import_mediabunny2.VideoSample(new AvFrameVideoSampleResource(clone), {
|
|
3082
|
+
timestamp,
|
|
3083
|
+
duration,
|
|
3084
|
+
// The captions ride in the bitstream, so the frame is where they surface
|
|
3085
|
+
closedCaptions: ccData ? parseClosedCaptionBytePairs(toUint8Array(ccData)) : null
|
|
3086
|
+
}));
|
|
3087
|
+
}
|
|
3088
|
+
async flush() {
|
|
3089
|
+
if (!this.codecContext) {
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
const ret = await this.codecContext.sendPacket(null);
|
|
3093
|
+
NodeAv3.FFmpegError.throwIfError(ret, "Flush decoder");
|
|
3094
|
+
while (true) {
|
|
3095
|
+
const receiveRet = await this.codecContext.receiveFrame(this.frame);
|
|
3096
|
+
if (receiveRet === NodeAv3.AVERROR_EAGAIN || receiveRet === NodeAv3.AVERROR_EOF) {
|
|
3097
|
+
break;
|
|
3098
|
+
}
|
|
3099
|
+
this.receiveFrame(receiveRet);
|
|
3100
|
+
}
|
|
3101
|
+
this.codecContext.flushBuffers();
|
|
3102
|
+
}
|
|
3103
|
+
close() {
|
|
3104
|
+
this.codecContext?.freeContext();
|
|
3105
|
+
this.frame.free();
|
|
3106
|
+
this.packet.free();
|
|
3107
|
+
}
|
|
3108
|
+
};
|
|
3109
|
+
|
|
3319
3110
|
// packages/server/src/video-encoder.ts
|
|
3320
|
-
var
|
|
3321
|
-
var
|
|
3111
|
+
var import_mediabunny3 = __require("mediabunny");
|
|
3112
|
+
var NodeAv4 = __toESM(__require("node-av"));
|
|
3322
3113
|
var PRORES_FOURCC_TO_PROFILE = {
|
|
3323
3114
|
apco: NodeAv4.AV_PROFILE_PRORES_PROXY,
|
|
3324
3115
|
apcs: NodeAv4.AV_PROFILE_PRORES_LT,
|
|
@@ -3437,11 +3228,7 @@ var MediabunnyServer = (() => {
|
|
|
3437
3228
|
};
|
|
3438
3229
|
var getQualityCap = (config) => {
|
|
3439
3230
|
const { _maxBitrate, _bufferSize } = config;
|
|
3440
|
-
return _maxBitrate === void 0 ? null : {
|
|
3441
|
-
maxBitrate: _maxBitrate,
|
|
3442
|
-
bufferSize: _bufferSize ?? 2 * _maxBitrate,
|
|
3443
|
-
statesBufferSize: _bufferSize !== void 0
|
|
3444
|
-
};
|
|
3231
|
+
return _maxBitrate === void 0 ? null : { maxBitrate: _maxBitrate, bufferSize: _bufferSize };
|
|
3445
3232
|
};
|
|
3446
3233
|
var NodeAvVideoEncoder = class extends import_mediabunny3.CustomVideoEncoder {
|
|
3447
3234
|
constructor() {
|
|
@@ -3454,7 +3241,6 @@ var MediabunnyServer = (() => {
|
|
|
3454
3241
|
this.quantizer = null;
|
|
3455
3242
|
// Bookkeeping to restore the original timing information
|
|
3456
3243
|
this.preciseTimings = [];
|
|
3457
|
-
this.pendingCaptions = /* @__PURE__ */ new Map();
|
|
3458
3244
|
}
|
|
3459
3245
|
static supports(codec, config) {
|
|
3460
3246
|
const codecSupported = config.bitrateMode === "quantizer" ? codec === "avc" || codec === "hevc" || codec === "vp9" || codec === "av1" : codec === "avc" || codec === "hevc" || codec === "vp8" || codec === "vp9" || codec === "av1" || codec === "prores";
|
|
@@ -3626,22 +3412,21 @@ var MediabunnyServer = (() => {
|
|
|
3626
3412
|
}
|
|
3627
3413
|
const cap = getQualityCap(this.config);
|
|
3628
3414
|
if (cap) {
|
|
3415
|
+
const bufferSize = cap.bufferSize ?? 2 * cap.maxBitrate;
|
|
3416
|
+
let honoursBufferSize = false;
|
|
3629
3417
|
if (this.avCodec.name === "libx264" || this.avCodec.name === "libx265") {
|
|
3630
3418
|
codecContext.setOption("crf", String(mapped));
|
|
3631
3419
|
codecContext.rcMaxRate = BigInt(cap.maxBitrate);
|
|
3632
|
-
codecContext.rcBufferSize =
|
|
3420
|
+
codecContext.rcBufferSize = bufferSize;
|
|
3421
|
+
honoursBufferSize = true;
|
|
3633
3422
|
} else if (this.avCodec.name === "libsvtav1") {
|
|
3634
3423
|
codecContext.setOption("crf", String(Math.max(mapped, 1)));
|
|
3635
3424
|
codecContext.rcMaxRate = BigInt(cap.maxBitrate);
|
|
3636
|
-
codecContext.rcBufferSize =
|
|
3425
|
+
codecContext.rcBufferSize = bufferSize;
|
|
3426
|
+
honoursBufferSize = true;
|
|
3637
3427
|
} else if (this.avCodec.name === "libvpx-vp9" || this.avCodec.name === "libaom-av1") {
|
|
3638
3428
|
codecContext.setOption("crf", String(mapped));
|
|
3639
3429
|
codecContext.bitRate = BigInt(cap.maxBitrate);
|
|
3640
|
-
if (this.avCodec.name === "libvpx-vp9" && cap.statesBufferSize) {
|
|
3641
|
-
import_mediabunny3.Logging._warn(
|
|
3642
|
-
"bufferSize has no effect with libvpx-vp9: its capped mode bounds the bitrate over the stream, not over a buffer, so maxBitrate alone is the cap. Encode avc, hevc or av1 for a buffer model."
|
|
3643
|
-
);
|
|
3644
|
-
}
|
|
3645
3430
|
} else if (this.avCodec.name?.endsWith("_nvenc")) {
|
|
3646
3431
|
codecContext.setOption("rc", "vbr");
|
|
3647
3432
|
codecContext.setOption("cq", String(mapped));
|
|
@@ -3651,6 +3436,11 @@ var MediabunnyServer = (() => {
|
|
|
3651
3436
|
`Encoder '${this.avCodec.name}' cannot be used for capped quantizer-based encoding.`
|
|
3652
3437
|
);
|
|
3653
3438
|
}
|
|
3439
|
+
if (cap.bufferSize !== void 0 && !honoursBufferSize) {
|
|
3440
|
+
import_mediabunny3.Logging._warn(
|
|
3441
|
+
`bufferSize has no effect with '${this.avCodec.name}', whose capped mode bounds the bitrate rather than a buffer, so maxBitrate alone is the cap. Encode avc, hevc or av1 with their software encoders for a buffer model.`
|
|
3442
|
+
);
|
|
3443
|
+
}
|
|
3654
3444
|
} else if (this.avCodec.name === "libx264" || this.avCodec.name === "libx265") {
|
|
3655
3445
|
codecContext.setOption("crf", String(mapped));
|
|
3656
3446
|
} else if (this.avCodec.name === "libvpx-vp9" || this.avCodec.name === "libaom-av1") {
|
|
@@ -3778,7 +3568,7 @@ var MediabunnyServer = (() => {
|
|
|
3778
3568
|
this.preciseTimings.shift();
|
|
3779
3569
|
}
|
|
3780
3570
|
}
|
|
3781
|
-
|
|
3571
|
+
frameToEncode.removeSideData(NodeAv4.AV_FRAME_DATA_A53_CC);
|
|
3782
3572
|
const ret = await this.codecContext.sendFrame(frameToEncode);
|
|
3783
3573
|
NodeAv4.FFmpegError.throwIfError(ret, "Send frame");
|
|
3784
3574
|
while (true) {
|
|
@@ -3789,49 +3579,6 @@ var MediabunnyServer = (() => {
|
|
|
3789
3579
|
this.receivePacket(receiveRet);
|
|
3790
3580
|
}
|
|
3791
3581
|
}
|
|
3792
|
-
// Every encoder is relieved of the captions and they are written onto the packet instead: VideoToolbox
|
|
3793
|
-
// errors out of sendFrame on a frame that has them ("Unexpected end of SEI NAL Unit parsing type") and
|
|
3794
|
-
// libx265 silently drops them, so leaving it to the encoder means the same input loses captions, keeps
|
|
3795
|
-
// them or fails outright depending on which one was selected.
|
|
3796
|
-
takeCaptionsFromFrame(frame) {
|
|
3797
|
-
if (this.codec !== "avc" && this.codec !== "hevc") {
|
|
3798
|
-
return;
|
|
3799
|
-
}
|
|
3800
|
-
const sideData = frame.getSideData(NodeAv4.AV_FRAME_DATA_A53_CC);
|
|
3801
|
-
if (!sideData) {
|
|
3802
|
-
return;
|
|
3803
|
-
}
|
|
3804
|
-
frame.removeSideData(NodeAv4.AV_FRAME_DATA_A53_CC);
|
|
3805
|
-
const bytes = toUint8Array(sideData);
|
|
3806
|
-
const bytePairs = [];
|
|
3807
|
-
for (let i = 0; i + 2 < bytes.length; i += 3) {
|
|
3808
|
-
const header = bytes[i];
|
|
3809
|
-
if ((header & 4) === 0) {
|
|
3810
|
-
continue;
|
|
3811
|
-
}
|
|
3812
|
-
bytePairs.push({
|
|
3813
|
-
type: header & 3,
|
|
3814
|
-
byte0: bytes[i + 1],
|
|
3815
|
-
byte1: bytes[i + 2]
|
|
3816
|
-
});
|
|
3817
|
-
}
|
|
3818
|
-
if (bytePairs.length === 0) {
|
|
3819
|
-
return;
|
|
3820
|
-
}
|
|
3821
|
-
this.pendingCaptions.set(Number(frame.pts), bytePairs);
|
|
3822
|
-
while (this.pendingCaptions.size > MAX_PENDING_CAPTION_FRAMES) {
|
|
3823
|
-
this.pendingCaptions.delete(this.pendingCaptions.keys().next().value);
|
|
3824
|
-
}
|
|
3825
|
-
}
|
|
3826
|
-
/** The captions of the frame this packet was encoded from, matched on the presentation time they share. */
|
|
3827
|
-
claimCaptionsFor(packetPts) {
|
|
3828
|
-
const bytePairs = this.pendingCaptions.get(packetPts);
|
|
3829
|
-
if (!bytePairs) {
|
|
3830
|
-
return null;
|
|
3831
|
-
}
|
|
3832
|
-
this.pendingCaptions.delete(packetPts);
|
|
3833
|
-
return bytePairs;
|
|
3834
|
-
}
|
|
3835
3582
|
receivePacket(ret) {
|
|
3836
3583
|
assert(this.codecContext);
|
|
3837
3584
|
NodeAv4.FFmpegError.throwIfError(ret, "Receive packet");
|
|
@@ -3839,15 +3586,16 @@ var MediabunnyServer = (() => {
|
|
|
3839
3586
|
return;
|
|
3840
3587
|
}
|
|
3841
3588
|
let packetData = toUint8Array(this.packet.data);
|
|
3842
|
-
|
|
3589
|
+
const pts = Number(this.packet.pts);
|
|
3590
|
+
let timestamp = pts / 1e6;
|
|
3843
3591
|
let duration = Number(this.packet.duration) / 1e6;
|
|
3844
3592
|
const preciseTimingIndex = binarySearchLessOrEqual(
|
|
3845
3593
|
this.preciseTimings,
|
|
3846
|
-
|
|
3594
|
+
pts,
|
|
3847
3595
|
(x) => x.microsecondTimestamp
|
|
3848
3596
|
);
|
|
3849
3597
|
const entry = preciseTimingIndex !== -1 ? this.preciseTimings[preciseTimingIndex] : null;
|
|
3850
|
-
if (entry && entry.microsecondTimestamp ===
|
|
3598
|
+
if (entry && entry.microsecondTimestamp === pts) {
|
|
3851
3599
|
if (entry.timestampIsValid) {
|
|
3852
3600
|
timestamp = entry.timestamp;
|
|
3853
3601
|
}
|
|
@@ -3900,12 +3648,6 @@ var MediabunnyServer = (() => {
|
|
|
3900
3648
|
decoderConfigDescription = serializedRecord;
|
|
3901
3649
|
}
|
|
3902
3650
|
}
|
|
3903
|
-
const captions = this.claimCaptionsFor(Number(this.packet.pts));
|
|
3904
|
-
if (captions) {
|
|
3905
|
-
const seiNalUnit = buildSeiNalUnit([buildClosedCaptionSeiMessage(captions)], this.codec);
|
|
3906
|
-
assert(seiNalUnit);
|
|
3907
|
-
packetData = spliceSeiNalUnit(packetData, ANNEX_B, this.codec, seiNalUnit);
|
|
3908
|
-
}
|
|
3909
3651
|
if (!expectsAnnexB) {
|
|
3910
3652
|
const NAL_UNIT_LENGTH_SIZE = 4;
|
|
3911
3653
|
const nalUnits = [];
|