@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.
@@ -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 ANNEX_B = { codec: "" };
3321
- var MAX_PENDING_CAPTION_FRAMES = 128;
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 = cap.bufferSize;
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 = cap.bufferSize;
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
- this.takeCaptionsFromFrame(frameToEncode);
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
- let timestamp = Number(this.packet.pts) / 1e6;
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
- Number(this.packet.pts),
3594
+ pts,
3847
3595
  (x) => x.microsecondTimestamp
3848
3596
  );
3849
3597
  const entry = preciseTimingIndex !== -1 ? this.preciseTimings[preciseTimingIndex] : null;
3850
- if (entry && entry.microsecondTimestamp === Number(this.packet.pts)) {
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 = [];