@camstack/addon-pipeline 1.2.372 → 1.2.373
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/audio-analyzer/index.js +3 -3
- package/dist/audio-analyzer/index.mjs +3 -3
- package/dist/{custom-model-catalog-n9v8MUL6.js → custom-model-catalog-M-ECFeNJ.js} +2 -2
- package/dist/{custom-model-catalog-C6C2cSwd.mjs → custom-model-catalog-UaaDgBow.mjs} +2 -2
- package/dist/detection-pipeline/index.js +4 -4
- package/dist/detection-pipeline/index.mjs +4 -4
- package/dist/{dist-8AEiKOFO.mjs → dist-BT3uSeR7.mjs} +25 -5
- package/dist/{dist-DmuX7IFJ.js → dist-QBVFmPYQ.js} +25 -5
- package/dist/{model-catalogs-DO4aA1qo.js → model-catalogs-C42Mo0jo.js} +1 -1
- package/dist/{model-catalogs-BSWAHZJh.mjs → model-catalogs-ChW0i2P6.mjs} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-DqA6nD1z.js → node-B6ev-Hqa.js} +1 -1
- package/dist/{node-Bd4va7c-.mjs → node-BVRFl1hZ.mjs} +1 -1
- package/dist/pipeline-runner/index.js +3 -3
- package/dist/pipeline-runner/index.mjs +3 -3
- package/dist/{process-memory-2OQ2oxrz.js → process-memory-Cdf2A2qg.js} +1 -1
- package/dist/{process-memory-BvS2Z88w.mjs → process-memory-DdeedEgU.mjs} +1 -1
- package/dist/recorder/index.js +18 -2
- package/dist/recorder/index.mjs +18 -2
- package/dist/{segment-demux-js-By9MLn_g.mjs → segment-demux-js-8CfbEFeD.mjs} +1 -1
- package/dist/{segment-demux-js-BkGK1rhu.js → segment-demux-js-Dp0OI-2n.js} +1 -1
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DjoAY42S.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Dl3wrJnM.mjs} +2 -2
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-nua-KuDg.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BFRtKBOe.mjs} +1 -1
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-DPiGNc5u.mjs → hostInit-Bq36nWMP.mjs} +2 -2
- package/dist/stream-broker/index.js +407 -72
- package/dist/stream-broker/index.mjs +407 -72
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -3,12 +3,12 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../chunk-emK7D4bc.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
7
|
-
const require_node = require("../node-
|
|
6
|
+
const require_dist = require("../dist-QBVFmPYQ.js");
|
|
7
|
+
const require_node = require("../node-B6ev-Hqa.js");
|
|
8
8
|
const require_restream_intent = require("../restream-intent-7uDNqMKs.js");
|
|
9
9
|
const require_remote_restream = require("../remote-restream-CO36Sr30.js");
|
|
10
10
|
const require_retire_root_keys = require("../retire-root-keys-Csb8z2f3.js");
|
|
11
|
-
const require_segment_demux_js = require("../segment-demux-js-
|
|
11
|
+
const require_segment_demux_js = require("../segment-demux-js-Dp0OI-2n.js");
|
|
12
12
|
let node_os = require("node:os");
|
|
13
13
|
node_os = require_chunk.__toESM(node_os);
|
|
14
14
|
let node_crypto = require("node:crypto");
|
|
@@ -3089,6 +3089,223 @@ var RtpPacketizer = class {
|
|
|
3089
3089
|
};
|
|
3090
3090
|
}
|
|
3091
3091
|
};
|
|
3092
|
+
function isTransferable(buf) {
|
|
3093
|
+
return typeof Reflect.get(buf, "transfer") === "function";
|
|
3094
|
+
}
|
|
3095
|
+
var Slab = class {
|
|
3096
|
+
now;
|
|
3097
|
+
buffer;
|
|
3098
|
+
offset = 0;
|
|
3099
|
+
holds = 0;
|
|
3100
|
+
state = "active";
|
|
3101
|
+
releasedAt = 0;
|
|
3102
|
+
constructor(buffer, now) {
|
|
3103
|
+
this.now = now;
|
|
3104
|
+
this.buffer = buffer;
|
|
3105
|
+
}
|
|
3106
|
+
};
|
|
3107
|
+
/** ArrayBuffer -> slab, for every slab ArrayBuffer currently in service. */
|
|
3108
|
+
var slabOf = /* @__PURE__ */ new WeakMap();
|
|
3109
|
+
var slabsLive = 0;
|
|
3110
|
+
var slabsFree = 0;
|
|
3111
|
+
var slabAllocs = 0;
|
|
3112
|
+
var slabRecycles = 0;
|
|
3113
|
+
function readSlabTotals() {
|
|
3114
|
+
return {
|
|
3115
|
+
live: slabsLive,
|
|
3116
|
+
free: slabsFree,
|
|
3117
|
+
allocs: slabAllocs,
|
|
3118
|
+
recycles: slabRecycles
|
|
3119
|
+
};
|
|
3120
|
+
}
|
|
3121
|
+
/** True when `buf` is a view of a pool slab. */
|
|
3122
|
+
function isSlabView(buf) {
|
|
3123
|
+
return slabOf.has(buf.buffer);
|
|
3124
|
+
}
|
|
3125
|
+
/** A retaining structure keeps `buf`: its slab cannot be recycled until released. */
|
|
3126
|
+
function holdPacket(buf) {
|
|
3127
|
+
const slab = slabOf.get(buf.buffer);
|
|
3128
|
+
if (slab === void 0) return;
|
|
3129
|
+
slab.holds += 1;
|
|
3130
|
+
if (slab.state === "quarantined") slab.state = "held";
|
|
3131
|
+
}
|
|
3132
|
+
/** The matching drop. Safe on a non-slab buffer and on a stale (recycled) view. */
|
|
3133
|
+
function releasePacket(buf) {
|
|
3134
|
+
const slab = slabOf.get(buf.buffer);
|
|
3135
|
+
if (slab === void 0 || slab.holds === 0) return;
|
|
3136
|
+
slab.holds -= 1;
|
|
3137
|
+
if (slab.holds === 0 && slab.state === "held") {
|
|
3138
|
+
slab.state = "quarantined";
|
|
3139
|
+
slab.releasedAt = slab.now();
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
function releasePackets(bufs) {
|
|
3143
|
+
for (const b of bufs) releasePacket(b);
|
|
3144
|
+
}
|
|
3145
|
+
/**
|
|
3146
|
+
* A consumer's own hold on the last `limit` packets it forwarded.
|
|
3147
|
+
*
|
|
3148
|
+
* werift keeps the last RTP_HISTORY_SIZE (4096) packets of each sender for NACK
|
|
3149
|
+
* retransmission, and those packets' payloads are views of the broker's slabs.
|
|
3150
|
+
* The session mirrors that window here: each forwarded packet is held, and
|
|
3151
|
+
* the one falling out of the window is released, so a retransmit asked for
|
|
3152
|
+
* within the history is served from bytes nobody has recycled.
|
|
3153
|
+
*/
|
|
3154
|
+
var HeldPacketWindow = class {
|
|
3155
|
+
limit;
|
|
3156
|
+
slots;
|
|
3157
|
+
next = 0;
|
|
3158
|
+
constructor(limit) {
|
|
3159
|
+
this.limit = limit;
|
|
3160
|
+
this.slots = new Array(limit).fill(void 0);
|
|
3161
|
+
}
|
|
3162
|
+
hold(buf) {
|
|
3163
|
+
holdPacket(buf);
|
|
3164
|
+
const evicted = this.slots[this.next];
|
|
3165
|
+
if (evicted !== void 0) releasePacket(evicted);
|
|
3166
|
+
this.slots[this.next] = buf;
|
|
3167
|
+
this.next = (this.next + 1) % this.limit;
|
|
3168
|
+
}
|
|
3169
|
+
releaseAll() {
|
|
3170
|
+
for (let i = 0; i < this.slots.length; i++) {
|
|
3171
|
+
const held = this.slots[i];
|
|
3172
|
+
if (held !== void 0) releasePacket(held);
|
|
3173
|
+
this.slots[i] = void 0;
|
|
3174
|
+
}
|
|
3175
|
+
this.next = 0;
|
|
3176
|
+
}
|
|
3177
|
+
};
|
|
3178
|
+
var PacketSlabPool = class {
|
|
3179
|
+
slabBytes;
|
|
3180
|
+
maxPacket;
|
|
3181
|
+
quarantineMs;
|
|
3182
|
+
maxFree;
|
|
3183
|
+
now;
|
|
3184
|
+
active = null;
|
|
3185
|
+
/** Sealed slabs, oldest first; entries may have been re-held since. */
|
|
3186
|
+
sealed = [];
|
|
3187
|
+
free = [];
|
|
3188
|
+
allocs = 0;
|
|
3189
|
+
recycles = 0;
|
|
3190
|
+
ownBuffers = 0;
|
|
3191
|
+
constructor(options = {}) {
|
|
3192
|
+
this.slabBytes = options.slabBytes ?? 65536;
|
|
3193
|
+
this.maxPacket = this.slabBytes >> 2;
|
|
3194
|
+
this.quarantineMs = options.quarantineMs ?? 2e3;
|
|
3195
|
+
this.maxFree = options.maxFree ?? 2;
|
|
3196
|
+
this.now = options.now ?? Date.now;
|
|
3197
|
+
}
|
|
3198
|
+
/**
|
|
3199
|
+
* A copy of `src[start, end)` in this pool's active slab. An oversized
|
|
3200
|
+
* packet gets its own buffer (it owns its ArrayBuffer, like the old
|
|
3201
|
+
* `copyOwned`).
|
|
3202
|
+
*/
|
|
3203
|
+
copy(src, start, end) {
|
|
3204
|
+
const length = end - start;
|
|
3205
|
+
recordCopyOwned(length);
|
|
3206
|
+
if (length > this.maxPacket) {
|
|
3207
|
+
this.ownBuffers += 1;
|
|
3208
|
+
const own = Buffer.allocUnsafeSlow(length);
|
|
3209
|
+
src.copy(own, 0, start, end);
|
|
3210
|
+
return own;
|
|
3211
|
+
}
|
|
3212
|
+
let slab = this.active;
|
|
3213
|
+
if (slab === null || slab.offset + length > this.slabBytes) slab = this.rotate();
|
|
3214
|
+
const view = Buffer.from(slab.buffer, slab.offset, length);
|
|
3215
|
+
src.copy(view, 0, start, end);
|
|
3216
|
+
slab.offset += length;
|
|
3217
|
+
return view;
|
|
3218
|
+
}
|
|
3219
|
+
/**
|
|
3220
|
+
* `buf` itself when it is already owned (a slab view or a whole-ArrayBuffer
|
|
3221
|
+
* buffer), else a copy in the active slab. Used for packets that arrive from
|
|
3222
|
+
* paths that do not cut through {@link copy} (push-rtp).
|
|
3223
|
+
*/
|
|
3224
|
+
adopt(buf) {
|
|
3225
|
+
if (isSlabView(buf)) return buf;
|
|
3226
|
+
if (buf.byteOffset === 0 && buf.buffer.byteLength === buf.length) return buf;
|
|
3227
|
+
return this.copy(buf, 0, buf.length);
|
|
3228
|
+
}
|
|
3229
|
+
stats() {
|
|
3230
|
+
return {
|
|
3231
|
+
live: this.sealed.length + (this.active === null ? 0 : 1),
|
|
3232
|
+
free: this.free.length,
|
|
3233
|
+
allocs: this.allocs,
|
|
3234
|
+
recycles: this.recycles,
|
|
3235
|
+
ownBuffers: this.ownBuffers
|
|
3236
|
+
};
|
|
3237
|
+
}
|
|
3238
|
+
/** Drop every slab of this pool from the process counters (stream torn down). */
|
|
3239
|
+
dispose() {
|
|
3240
|
+
const live = this.sealed.length + (this.active === null ? 0 : 1);
|
|
3241
|
+
slabsLive -= live;
|
|
3242
|
+
slabsFree -= this.free.length;
|
|
3243
|
+
this.sealed = [];
|
|
3244
|
+
this.free = [];
|
|
3245
|
+
this.active = null;
|
|
3246
|
+
}
|
|
3247
|
+
rotate() {
|
|
3248
|
+
const previous = this.active;
|
|
3249
|
+
if (previous !== null) {
|
|
3250
|
+
this.sealed.push(previous);
|
|
3251
|
+
if (previous.holds === 0) {
|
|
3252
|
+
previous.state = "quarantined";
|
|
3253
|
+
previous.releasedAt = this.now();
|
|
3254
|
+
} else previous.state = "held";
|
|
3255
|
+
}
|
|
3256
|
+
this.reclaim();
|
|
3257
|
+
const next = this.free.pop() ?? this.allocate();
|
|
3258
|
+
if (next.state === "free") {
|
|
3259
|
+
slabsFree -= 1;
|
|
3260
|
+
slabsLive += 1;
|
|
3261
|
+
}
|
|
3262
|
+
next.state = "active";
|
|
3263
|
+
next.offset = 0;
|
|
3264
|
+
next.holds = 0;
|
|
3265
|
+
this.active = next;
|
|
3266
|
+
return next;
|
|
3267
|
+
}
|
|
3268
|
+
allocate() {
|
|
3269
|
+
this.allocs += 1;
|
|
3270
|
+
slabAllocs += 1;
|
|
3271
|
+
slabsLive += 1;
|
|
3272
|
+
const slab = new Slab(new ArrayBuffer(this.slabBytes), this.now);
|
|
3273
|
+
slabOf.set(slab.buffer, slab);
|
|
3274
|
+
return slab;
|
|
3275
|
+
}
|
|
3276
|
+
/** Move quarantined slabs whose time is up to the free list. */
|
|
3277
|
+
reclaim() {
|
|
3278
|
+
if (this.sealed.length === 0) return;
|
|
3279
|
+
const cutoff = this.now() - this.quarantineMs;
|
|
3280
|
+
const keep = [];
|
|
3281
|
+
for (const slab of this.sealed) if (slab.state === "quarantined" && slab.releasedAt <= cutoff) this.recycle(slab);
|
|
3282
|
+
else keep.push(slab);
|
|
3283
|
+
this.sealed = keep;
|
|
3284
|
+
}
|
|
3285
|
+
recycle(slab) {
|
|
3286
|
+
slabsLive -= 1;
|
|
3287
|
+
if (this.free.length >= this.maxFree) {
|
|
3288
|
+
slabOf.delete(slab.buffer);
|
|
3289
|
+
slab.state = "free";
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
3292
|
+
const old = slab.buffer;
|
|
3293
|
+
slabOf.delete(old);
|
|
3294
|
+
slab.buffer = isTransferable(old) ? old.transfer() : new ArrayBuffer(this.slabBytes);
|
|
3295
|
+
if (!isTransferable(old)) {
|
|
3296
|
+
this.allocs += 1;
|
|
3297
|
+
slabAllocs += 1;
|
|
3298
|
+
}
|
|
3299
|
+
slabOf.set(slab.buffer, slab);
|
|
3300
|
+
slab.state = "free";
|
|
3301
|
+
slab.offset = 0;
|
|
3302
|
+
slab.holds = 0;
|
|
3303
|
+
this.free.push(slab);
|
|
3304
|
+
slabsFree += 1;
|
|
3305
|
+
this.recycles += 1;
|
|
3306
|
+
slabRecycles += 1;
|
|
3307
|
+
}
|
|
3308
|
+
};
|
|
3092
3309
|
//#endregion
|
|
3093
3310
|
//#region src/stream-broker/stream-broker/owned-buffer.ts
|
|
3094
3311
|
/**
|
|
@@ -3115,10 +3332,20 @@ function ownsBackingStore(buf) {
|
|
|
3115
3332
|
}
|
|
3116
3333
|
/**
|
|
3117
3334
|
* What retaining `buf` really costs: its own length when it owns its
|
|
3118
|
-
* ArrayBuffer
|
|
3335
|
+
* ArrayBuffer (a packet slab is densely packed, so a slab view counts as its
|
|
3336
|
+
* own length too), the whole shared slab when it is a view of a pooled one.
|
|
3337
|
+
* The diagnostic `BackingTally` still sums the distinct ArrayBuffers.
|
|
3119
3338
|
*/
|
|
3120
3339
|
function backingStoreBytes(buf) {
|
|
3121
|
-
return ownsBackingStore(buf) ? buf.length : buf.buffer.byteLength;
|
|
3340
|
+
return ownsBackingStore(buf) || isSlabView(buf) ? buf.length : buf.buffer.byteLength;
|
|
3341
|
+
}
|
|
3342
|
+
/**
|
|
3343
|
+
* What `buf` PINS: its own length when it is the whole of its ArrayBuffer, the
|
|
3344
|
+
* whole ArrayBuffer otherwise — a packet slab view included. This is the
|
|
3345
|
+
* diagnostic figure (`BackingTally`); caps charge {@link backingStoreBytes}.
|
|
3346
|
+
*/
|
|
3347
|
+
function pinnedBackingBytes(buf) {
|
|
3348
|
+
return buf.byteOffset === 0 && buf.buffer.byteLength === buf.length ? buf.length : buf.buffer.byteLength;
|
|
3122
3349
|
}
|
|
3123
3350
|
/**
|
|
3124
3351
|
* `buf` itself when it already owns its ArrayBuffer (decided by inspecting the
|
|
@@ -3167,7 +3394,7 @@ var BackingTally = class {
|
|
|
3167
3394
|
}
|
|
3168
3395
|
this.entries += 1;
|
|
3169
3396
|
this.payload += buf.length;
|
|
3170
|
-
const backing =
|
|
3397
|
+
const backing = pinnedBackingBytes(buf);
|
|
3171
3398
|
this.naive += backing;
|
|
3172
3399
|
if (!this.seen.has(buf.buffer)) {
|
|
3173
3400
|
this.seen.add(buf.buffer);
|
|
@@ -3460,6 +3687,7 @@ function formatBrokerMem(input) {
|
|
|
3460
3687
|
const w = input.worker;
|
|
3461
3688
|
if (w !== void 0 && w !== null) parts.push(`workers=${w.workers}`, `workerHeapMb=${mb(w.heapBytes)}`, `workerPhysicalMb=${mb(w.physicalBytes)}`, `workerUsedMb=${mb(w.usedBytes)}`, `workerAgeS=${Math.round(w.maxAgeMs / 1e3)}`, `workerIdleExit=${w.idleTerminates ? "yes" : "never"}`);
|
|
3462
3689
|
parts.push(`brokers=${samples.length}`, `ingesting=${ingesting}`, `annexBPlanes=${planes}`, `rtsp=${rtsp}`, `webrtc=${input.webrtcSessions}`, `encoded=${kindList(consumers)}`, `audioSubs=${audio}`, `pipeClients=${pipe}`, `egress=${input.egressTranscodes}`, `children=${input.childProcesses}`, `opened=${churn.opened}`, `closed=${churn.closed}`, `openedKinds=${kindList(churn.openedByKind)}`, `closedKinds=${kindList(churn.closedByKind)}`, `preBufferMb=${mb(pre)}`, `clipMb=${mb(clip)}`, `rtpRingMb=${mb(rtp)}`, `keyframeMb=${mb(kf)}`, `writeQueueSumMb=${mb(qSum)}`, `writeQueueMaxMb=${mb(qMax)}`, `copiedPackets=${copied.packets}`, `copiedMb=${mb(copied.bytes)}`, `topRingMb=${top === "" ? "none" : top}`);
|
|
3690
|
+
if (input.slabs !== void 0) parts.push(`slabs=${input.slabs.live}/${input.slabs.free}`, `slabAllocs=${input.slabs.allocsDelta}`);
|
|
3463
3691
|
const sessions = input.sessions;
|
|
3464
3692
|
if (sessions !== void 0) parts.push(...formatSessionMem(sessions, input));
|
|
3465
3693
|
return parts.join(" ");
|
|
@@ -3849,8 +4077,7 @@ var RtspRestreamer = class {
|
|
|
3849
4077
|
this.mutedSdp = null;
|
|
3850
4078
|
this.deframer = null;
|
|
3851
4079
|
this.detectedCodec = null;
|
|
3852
|
-
this.
|
|
3853
|
-
this.lastKeyframeRtpBytes = 0;
|
|
4080
|
+
this.dropKeyframeRtpCache();
|
|
3854
4081
|
this._rtpPassthroughMode = false;
|
|
3855
4082
|
this.collectingKeyframeRtp = false;
|
|
3856
4083
|
this.egressAudioBlock = null;
|
|
@@ -4203,21 +4430,21 @@ var RtspRestreamer = class {
|
|
|
4203
4430
|
if (isKeyframe || isParameterSet) {
|
|
4204
4431
|
if (!this.collectingKeyframeRtp) {
|
|
4205
4432
|
this.collectingKeyframeRtp = true;
|
|
4206
|
-
this.
|
|
4207
|
-
this.lastKeyframeRtpBytes = 0;
|
|
4433
|
+
this.dropKeyframeRtpCache();
|
|
4208
4434
|
}
|
|
4209
4435
|
}
|
|
4210
4436
|
if (this.collectingKeyframeRtp && !isKeyframe && !isParameterSet) this.collectingKeyframeRtp = false;
|
|
4211
4437
|
if (this.collectingKeyframeRtp) {
|
|
4438
|
+
const cached = isSlabView(rtpData) ? rtpData : detachFromPool(rtpData);
|
|
4439
|
+
holdPacket(cached);
|
|
4212
4440
|
this.lastKeyframeRtpPackets.push({
|
|
4213
|
-
data:
|
|
4441
|
+
data: cached,
|
|
4214
4442
|
marker: rtpPacket.marker
|
|
4215
4443
|
});
|
|
4216
4444
|
this.lastKeyframeRtpBytes += rtpData.length;
|
|
4217
4445
|
if (this.lastKeyframeRtpBytes > MAX_KEYFRAME_CACHE_BYTES) {
|
|
4218
4446
|
this.warnCacheCapExceeded("rtp", this.lastKeyframeRtpBytes, this.lastKeyframeRtpPackets.length);
|
|
4219
|
-
this.
|
|
4220
|
-
this.lastKeyframeRtpBytes = 0;
|
|
4447
|
+
this.dropKeyframeRtpCache();
|
|
4221
4448
|
this.collectingKeyframeRtp = false;
|
|
4222
4449
|
}
|
|
4223
4450
|
}
|
|
@@ -4655,6 +4882,7 @@ var RtspRestreamer = class {
|
|
|
4655
4882
|
}
|
|
4656
4883
|
destroy() {
|
|
4657
4884
|
this._onSessionCountChanged = null;
|
|
4885
|
+
this.dropKeyframeRtpCache();
|
|
4658
4886
|
this.sentryAudioSilence?.release("restreamer destroyed");
|
|
4659
4887
|
this.abortAllReplays();
|
|
4660
4888
|
this.deframer?.flush();
|
|
@@ -4663,6 +4891,12 @@ var RtspRestreamer = class {
|
|
|
4663
4891
|
this.packetizer = null;
|
|
4664
4892
|
this.deframer = null;
|
|
4665
4893
|
}
|
|
4894
|
+
/** Empty the keyframe RTP cache and let go of the slabs it pinned. */
|
|
4895
|
+
dropKeyframeRtpCache() {
|
|
4896
|
+
for (const cached of this.lastKeyframeRtpPackets) releasePacket(cached.data);
|
|
4897
|
+
this.lastKeyframeRtpPackets = [];
|
|
4898
|
+
this.lastKeyframeRtpBytes = 0;
|
|
4899
|
+
}
|
|
4666
4900
|
/** Called by deframer for each complete NAL unit. */
|
|
4667
4901
|
handleNal(nal, isKeyframe) {
|
|
4668
4902
|
if (!this.codecParams) this.tryInitCodec(nal);
|
|
@@ -4792,6 +5026,13 @@ function resolveJoinBurstBound(input) {
|
|
|
4792
5026
|
};
|
|
4793
5027
|
}
|
|
4794
5028
|
//#endregion
|
|
5029
|
+
//#region src/stream-broker/stream-broker/held-write.ts
|
|
5030
|
+
/** Write `payload`, holding its slab until the write completes (or fails). */
|
|
5031
|
+
function writeHeld(sink, payload) {
|
|
5032
|
+
holdPacket(payload);
|
|
5033
|
+
return sink.write(payload, () => releasePacket(payload));
|
|
5034
|
+
}
|
|
5035
|
+
//#endregion
|
|
4795
5036
|
//#region src/stream-broker/rtsp/rtsp-session.ts
|
|
4796
5037
|
/**
|
|
4797
5038
|
* Handles one RTSP client connection.
|
|
@@ -5107,15 +5348,17 @@ var RtspSession = class {
|
|
|
5107
5348
|
*
|
|
5108
5349
|
* ## Why sharing the payload is safe, and what would make it unsafe
|
|
5109
5350
|
*
|
|
5110
|
-
* The payload
|
|
5111
|
-
*
|
|
5112
|
-
*
|
|
5113
|
-
*
|
|
5114
|
-
*
|
|
5115
|
-
*
|
|
5116
|
-
*
|
|
5117
|
-
*
|
|
5118
|
-
*
|
|
5351
|
+
* The payload is a view of the broker's packet slab (`slab-pool.ts`, D868),
|
|
5352
|
+
* never of the reader's socket buffer, which advances on the next read.
|
|
5353
|
+
* Nothing in the broker writes into a captured packet: every mutation in this
|
|
5354
|
+
* path targets a freshly allocated buffer. A slab is RECYCLED once the
|
|
5355
|
+
* broker's rings let go of it, and a queued socket write outlives that: Node
|
|
5356
|
+
* keeps the chunk behind a full kernel buffer for as long as the peer takes to
|
|
5357
|
+
* drain (seconds, for a recorder or a LAN decoder), and libuv reads the bytes
|
|
5358
|
+
* only when it writes. So the payload goes out through `writeHeld`, which
|
|
5359
|
+
* holds its slab until Node's write callback (success, error or destroy). A
|
|
5360
|
+
* write that skipped the hold would send a header promising N bytes followed
|
|
5361
|
+
* by zeros.
|
|
5119
5362
|
*
|
|
5120
5363
|
* The rule to keep: **`socket.write` forbids MUTATION before the write
|
|
5121
5364
|
* completes, not retention.** So the header, which IS built per call, must
|
|
@@ -5152,7 +5395,7 @@ var RtspSession = class {
|
|
|
5152
5395
|
process.nextTick(this.uncorkAccessUnit);
|
|
5153
5396
|
}
|
|
5154
5397
|
this.socket.write(header);
|
|
5155
|
-
this.socket
|
|
5398
|
+
writeHeld(this.socket, payload);
|
|
5156
5399
|
if (endOfAccessUnit) this.uncorkAccessUnit();
|
|
5157
5400
|
this.lastRtpAt = Date.now();
|
|
5158
5401
|
this.bytesSent += 4 + payload.length;
|
|
@@ -10515,6 +10758,7 @@ var Rfc4571Reader = class {
|
|
|
10515
10758
|
options;
|
|
10516
10759
|
callbacks;
|
|
10517
10760
|
logger;
|
|
10761
|
+
allocatePacket;
|
|
10518
10762
|
host;
|
|
10519
10763
|
port;
|
|
10520
10764
|
authLine;
|
|
@@ -10528,6 +10772,7 @@ var Rfc4571Reader = class {
|
|
|
10528
10772
|
constructor(options, callbacks) {
|
|
10529
10773
|
this.options = options;
|
|
10530
10774
|
this.callbacks = callbacks;
|
|
10775
|
+
this.allocatePacket = options.packetAllocator ?? copyOwned;
|
|
10531
10776
|
this.logger = options.logger;
|
|
10532
10777
|
const parsed = new URL(options.url);
|
|
10533
10778
|
this.host = parsed.hostname;
|
|
@@ -10592,7 +10837,7 @@ var Rfc4571Reader = class {
|
|
|
10592
10837
|
while (this.readBuffer.length >= 2) {
|
|
10593
10838
|
const length = this.readBuffer.readUInt16BE(0);
|
|
10594
10839
|
if (this.readBuffer.length < 2 + length) return;
|
|
10595
|
-
const rtpPacket =
|
|
10840
|
+
const rtpPacket = this.allocatePacket(this.readBuffer, 2, 2 + length);
|
|
10596
10841
|
this.readBuffer = this.readBuffer.subarray(2 + length);
|
|
10597
10842
|
this.dispatchRtp(rtpPacket);
|
|
10598
10843
|
}
|
|
@@ -11113,6 +11358,7 @@ var NativeRtspClient = class {
|
|
|
11113
11358
|
keepaliveIntervalMs;
|
|
11114
11359
|
logger;
|
|
11115
11360
|
callbacks;
|
|
11361
|
+
allocatePacket;
|
|
11116
11362
|
/** Parsed SDP from DESCRIBE response. */
|
|
11117
11363
|
parsedSdp = null;
|
|
11118
11364
|
rawSdpText = null;
|
|
@@ -11139,6 +11385,7 @@ var NativeRtspClient = class {
|
|
|
11139
11385
|
tornDown = false;
|
|
11140
11386
|
constructor(options, callbacks) {
|
|
11141
11387
|
this.callbacks = callbacks;
|
|
11388
|
+
this.allocatePacket = options.packetAllocator ?? copyOwned;
|
|
11142
11389
|
this.logger = options.logger;
|
|
11143
11390
|
this.connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
11144
11391
|
this.keepaliveIntervalMs = options.keepaliveIntervalMs ?? DEFAULT_KEEPALIVE_INTERVAL_MS;
|
|
@@ -11311,7 +11558,7 @@ var NativeRtspClient = class {
|
|
|
11311
11558
|
const channel = this.readBuffer[1];
|
|
11312
11559
|
const length = this.readBuffer.readUInt16BE(2);
|
|
11313
11560
|
if (this.readBuffer.length < 4 + length) return;
|
|
11314
|
-
const payload =
|
|
11561
|
+
const payload = this.allocatePacket(this.readBuffer, 4, 4 + length);
|
|
11315
11562
|
this.readBuffer = this.readBuffer.subarray(4 + length);
|
|
11316
11563
|
this.handleInterleavedPacket(channel, payload);
|
|
11317
11564
|
} else {
|
|
@@ -11766,7 +12013,7 @@ var AudioCodecSession = class {
|
|
|
11766
12013
|
processPacket(rtpData) {
|
|
11767
12014
|
if (this.closed) return;
|
|
11768
12015
|
if (rtpData.length <= RTP_HEADER_LENGTH) return;
|
|
11769
|
-
const payload = rtpData.subarray(RTP_HEADER_LENGTH);
|
|
12016
|
+
const payload = Buffer.from(rtpData.subarray(RTP_HEADER_LENGTH));
|
|
11770
12017
|
this.packetsIn++;
|
|
11771
12018
|
this.ensureSession().then((sid) => {
|
|
11772
12019
|
if (!sid || this.closed) return;
|
|
@@ -13384,6 +13631,13 @@ var StreamBroker = class StreamBroker {
|
|
|
13384
13631
|
* Only populated for RTP sources (`isRtpSource()`).
|
|
13385
13632
|
*/
|
|
13386
13633
|
rtpRing = [];
|
|
13634
|
+
/**
|
|
13635
|
+
* Where this stream's source packets are cut (`slab-pool.ts`, D868): the
|
|
13636
|
+
* readers copy into the pool's slabs, the ring and the restreamer's keyframe
|
|
13637
|
+
* cache hold/release them, and a slab no ring holds is recycled.
|
|
13638
|
+
*/
|
|
13639
|
+
packetPool = new PacketSlabPool();
|
|
13640
|
+
packetAllocator = (src, start, end) => this.packetPool.copy(src, start, end);
|
|
13387
13641
|
rtpRingCurAuStart = 0;
|
|
13388
13642
|
rtpRingPrevMarker = true;
|
|
13389
13643
|
rtpRingHasKeyframe = false;
|
|
@@ -13904,6 +14158,8 @@ var StreamBroker = class StreamBroker {
|
|
|
13904
14158
|
this.annexBAssembler.reset();
|
|
13905
14159
|
this.encodedCallbacks.clear();
|
|
13906
14160
|
this.audioChunkPlane.dispose();
|
|
14161
|
+
this.resetRtpPreBuffer();
|
|
14162
|
+
this.packetPool.dispose();
|
|
13907
14163
|
}
|
|
13908
14164
|
/** Whether the broker is currently suspended (idle, no RTSP connection). */
|
|
13909
14165
|
get suspended() {
|
|
@@ -14658,7 +14914,8 @@ var StreamBroker = class StreamBroker {
|
|
|
14658
14914
|
captureRtpForPreBuffer(rtpData) {
|
|
14659
14915
|
const marker = rtpData.length > 1 && (rtpData[1] & 128) !== 0;
|
|
14660
14916
|
if (this.rtpRingPrevMarker || this.rtpRing.length === 0) this.rtpRingCurAuStart = this.rtpRing.length;
|
|
14661
|
-
const owned =
|
|
14917
|
+
const owned = this.packetPool.adopt(rtpData);
|
|
14918
|
+
holdPacket(owned);
|
|
14662
14919
|
this.rtpRing.push(owned);
|
|
14663
14920
|
this.rtpRingBytes += backingStoreBytes(owned);
|
|
14664
14921
|
if (this._lastNalParamSet || this._lastNalKeyframe) {
|
|
@@ -14754,6 +15011,7 @@ var StreamBroker = class StreamBroker {
|
|
|
14754
15011
|
* non-decodable slice instead of waiting for the new dial's first keyframe.
|
|
14755
15012
|
*/
|
|
14756
15013
|
resetRtpPreBuffer() {
|
|
15014
|
+
releasePackets(this.rtpRing);
|
|
14757
15015
|
this.rtpRing = [];
|
|
14758
15016
|
this.rtpRingBytes = 0;
|
|
14759
15017
|
this.rtpRingCurAuStart = 0;
|
|
@@ -14850,6 +15108,7 @@ var StreamBroker = class StreamBroker {
|
|
|
14850
15108
|
if (cut <= 0) return;
|
|
14851
15109
|
const dropped = this.rtpRing.splice(0, cut);
|
|
14852
15110
|
for (const b of dropped) this.rtpRingBytes -= backingStoreBytes(b);
|
|
15111
|
+
releasePackets(dropped);
|
|
14853
15112
|
this.rtpRingKeyframeMarks = this.rtpRingKeyframeMarks.filter((mark) => mark.index >= cut).map((mark) => ({
|
|
14854
15113
|
index: mark.index - cut,
|
|
14855
15114
|
ts: mark.ts
|
|
@@ -15530,7 +15789,7 @@ var StreamBroker = class StreamBroker {
|
|
|
15530
15789
|
this.audioRtpDecoder?.processPacket(rtpData);
|
|
15531
15790
|
if (this.hasAudioConsumer()) this.audioCodecSession?.processPacket(rtpData);
|
|
15532
15791
|
if ((this.audioCodec === "PCMU" || this.audioCodec === "PCMA") && this.encodedCallbacks.size > 0 && rtpData.length > 12) {
|
|
15533
|
-
const payload = rtpData.subarray(12);
|
|
15792
|
+
const payload = Buffer.from(rtpData.subarray(12));
|
|
15534
15793
|
if (payload.length > 0) {
|
|
15535
15794
|
const rtpTimestamp = rtpData.readUInt32BE(4);
|
|
15536
15795
|
const pts = Math.floor(rtpTimestamp / 8);
|
|
@@ -15593,7 +15852,8 @@ var StreamBroker = class StreamBroker {
|
|
|
15593
15852
|
this.logger?.info("starting native RTSP reader");
|
|
15594
15853
|
const client = new NativeRtspClient({
|
|
15595
15854
|
url: source.url,
|
|
15596
|
-
logger: this.logger?.child("rtsp-native")
|
|
15855
|
+
logger: this.logger?.child("rtsp-native"),
|
|
15856
|
+
packetAllocator: this.packetAllocator
|
|
15597
15857
|
}, this.buildRtpStreamCallbacks("native RTSP", () => this.destroyNativeClient(), () => this.nativeClient !== client));
|
|
15598
15858
|
this.nativeClient = client;
|
|
15599
15859
|
client.connect().catch((err) => {
|
|
@@ -16100,7 +16360,8 @@ var StreamBroker = class StreamBroker {
|
|
|
16100
16360
|
const reader = new Rfc4571Reader({
|
|
16101
16361
|
url: source.url,
|
|
16102
16362
|
sdp,
|
|
16103
|
-
...this.logger ? { logger: this.logger.child("rfc4571") } : {}
|
|
16363
|
+
...this.logger ? { logger: this.logger.child("rfc4571") } : {},
|
|
16364
|
+
packetAllocator: this.packetAllocator
|
|
16104
16365
|
}, this.buildRtpStreamCallbacks("rfc4571", () => this.destroyRfc4571Reader(), () => this.rfc4571Reader !== reader));
|
|
16105
16366
|
this.rfc4571Reader = reader;
|
|
16106
16367
|
reader.connect().catch((err) => {
|
|
@@ -22690,19 +22951,19 @@ var H265Repacketizer = class {
|
|
|
22690
22951
|
return this.codecInfo;
|
|
22691
22952
|
}
|
|
22692
22953
|
updateVps(vps) {
|
|
22693
|
-
this.ensureCodecInfo().vps = vps;
|
|
22954
|
+
this.ensureCodecInfo().vps = Buffer.from(vps);
|
|
22694
22955
|
}
|
|
22695
22956
|
updateSps(sps) {
|
|
22696
|
-
this.ensureCodecInfo().sps = sps;
|
|
22957
|
+
this.ensureCodecInfo().sps = Buffer.from(sps);
|
|
22697
22958
|
}
|
|
22698
22959
|
updatePps(pps) {
|
|
22699
|
-
this.ensureCodecInfo().pps = pps;
|
|
22960
|
+
this.ensureCodecInfo().pps = Buffer.from(pps);
|
|
22700
22961
|
}
|
|
22701
22962
|
updateSeiPrefix(sei) {
|
|
22702
|
-
this.ensureCodecInfo().seiPrefix = sei;
|
|
22963
|
+
this.ensureCodecInfo().seiPrefix = Buffer.from(sei);
|
|
22703
22964
|
}
|
|
22704
22965
|
updateSeiSuffix(sei) {
|
|
22705
|
-
this.ensureCodecInfo().seiSuffix = sei;
|
|
22966
|
+
this.ensureCodecInfo().seiSuffix = Buffer.from(sei);
|
|
22706
22967
|
}
|
|
22707
22968
|
shouldFilter(_nalType) {
|
|
22708
22969
|
return false;
|
|
@@ -23013,6 +23274,48 @@ var H265Repacketizer = class {
|
|
|
23013
23274
|
}
|
|
23014
23275
|
}
|
|
23015
23276
|
};
|
|
23277
|
+
//#endregion
|
|
23278
|
+
//#region src/stream-broker/webrtc/output-arena.ts
|
|
23279
|
+
/**
|
|
23280
|
+
* A bump allocator for the buffers the WebRTC send path builds per packet
|
|
23281
|
+
* (repacketizer outputs, STAP-A aggregates).
|
|
23282
|
+
*
|
|
23283
|
+
* `Buffer.from([...])` and `Buffer.concat` below 4 KiB are served from Node's
|
|
23284
|
+
* shared 8 KiB pool: one ~1.2 KB fragment per pool slab share, interleaved
|
|
23285
|
+
* with every other short-lived pooled buffer in the process. werift keeps the
|
|
23286
|
+
* last RTP_HISTORY_SIZE (4096) outputs of a sender for NACK, so each pinned
|
|
23287
|
+
* the shared slab it came from and everything beside it. Here every output is
|
|
23288
|
+
* a dense view of an arena that holds nothing else.
|
|
23289
|
+
*
|
|
23290
|
+
* Nothing is ever reused: an arena is left to the GC once full, and lives as
|
|
23291
|
+
* long as the last view of it (a NACK slot, a datagram still in flight). That
|
|
23292
|
+
* is why there is no hold/release protocol here, unlike `slab-pool.ts`.
|
|
23293
|
+
*/
|
|
23294
|
+
/** Bytes per arena: ~50 packets of ~1.2 KB per allocation instead of one per 6. */
|
|
23295
|
+
var OUTPUT_ARENA_BYTES = 64 * 1024;
|
|
23296
|
+
/** A request larger than a quarter of an arena gets its own buffer. */
|
|
23297
|
+
var OUTPUT_ARENA_MAX_REQUEST = 16384;
|
|
23298
|
+
var OutputArena = class {
|
|
23299
|
+
chunk = Buffer.allocUnsafeSlow(0);
|
|
23300
|
+
offset = 0;
|
|
23301
|
+
chunks = 0;
|
|
23302
|
+
/** `size` writable bytes (contents unspecified: the caller fills all of them). */
|
|
23303
|
+
take(size) {
|
|
23304
|
+
if (size > OUTPUT_ARENA_MAX_REQUEST) return Buffer.allocUnsafeSlow(size);
|
|
23305
|
+
if (this.offset + size > this.chunk.length) {
|
|
23306
|
+
this.chunk = Buffer.allocUnsafeSlow(OUTPUT_ARENA_BYTES);
|
|
23307
|
+
this.chunks += 1;
|
|
23308
|
+
this.offset = 0;
|
|
23309
|
+
}
|
|
23310
|
+
const out = this.chunk.subarray(this.offset, this.offset + size);
|
|
23311
|
+
this.offset += size;
|
|
23312
|
+
return out;
|
|
23313
|
+
}
|
|
23314
|
+
/** ArrayBuffers allocated by this arena so far. */
|
|
23315
|
+
get allocations() {
|
|
23316
|
+
return this.chunks;
|
|
23317
|
+
}
|
|
23318
|
+
};
|
|
23016
23319
|
var NAL_HEADER_SIZE = 1;
|
|
23017
23320
|
var FU_A_HEADER_SIZE = 2;
|
|
23018
23321
|
var LENGTH_FIELD_SIZE = 2;
|
|
@@ -23057,6 +23360,8 @@ var H264Repacketizer = class {
|
|
|
23057
23360
|
pendingFuA;
|
|
23058
23361
|
stapa;
|
|
23059
23362
|
fuaMin;
|
|
23363
|
+
/** Dense home for every buffer this repacketizer builds (`output-arena.ts`). */
|
|
23364
|
+
arena = new OutputArena();
|
|
23060
23365
|
constructor(console, maxPacketSize, codecInfo, jitterBuffer = new JitterBuffer(console, 4)) {
|
|
23061
23366
|
this.console = console;
|
|
23062
23367
|
this.maxPacketSize = maxPacketSize;
|
|
@@ -23074,70 +23379,78 @@ var H264Repacketizer = class {
|
|
|
23074
23379
|
return this.codecInfo;
|
|
23075
23380
|
}
|
|
23076
23381
|
updateSps(sps) {
|
|
23077
|
-
this.ensureCodecInfo().sps = sps;
|
|
23382
|
+
this.ensureCodecInfo().sps = Buffer.from(sps);
|
|
23078
23383
|
}
|
|
23079
23384
|
updatePps(pps) {
|
|
23080
|
-
this.ensureCodecInfo().pps = pps;
|
|
23385
|
+
this.ensureCodecInfo().pps = Buffer.from(pps);
|
|
23081
23386
|
}
|
|
23082
23387
|
updateSei(sei) {
|
|
23083
|
-
this.ensureCodecInfo().sei = sei;
|
|
23388
|
+
this.ensureCodecInfo().sei = Buffer.from(sei);
|
|
23084
23389
|
}
|
|
23085
23390
|
shouldFilter(_nalType) {
|
|
23086
23391
|
return false;
|
|
23087
23392
|
}
|
|
23088
23393
|
packetizeFuA(data, noStart, noEnd) {
|
|
23394
|
+
let nalHeader;
|
|
23395
|
+
let bodyStart;
|
|
23089
23396
|
if ((data[0] & 31) === 28) {
|
|
23090
|
-
const fnri = data[0] & 224;
|
|
23091
|
-
const originalNalType = data[1] & 31;
|
|
23092
23397
|
const isFuStart = !!(data[1] & 128);
|
|
23093
23398
|
const isFuEnd = !!(data[1] & 64);
|
|
23094
|
-
|
|
23095
|
-
|
|
23096
|
-
data = Buffer.concat([originalNalHeader, data.subarray(FU_A_HEADER_SIZE)]);
|
|
23399
|
+
nalHeader = data[0] & 224 | data[1] & 31;
|
|
23400
|
+
bodyStart = FU_A_HEADER_SIZE;
|
|
23097
23401
|
if (isFuStart) noEnd = true;
|
|
23098
23402
|
else if (isFuEnd) noStart = true;
|
|
23099
|
-
else
|
|
23403
|
+
else {
|
|
23100
23404
|
noStart = true;
|
|
23101
23405
|
noEnd = true;
|
|
23102
23406
|
}
|
|
23407
|
+
} else {
|
|
23408
|
+
nalHeader = data[0];
|
|
23409
|
+
bodyStart = NAL_HEADER_SIZE;
|
|
23103
23410
|
}
|
|
23104
|
-
const
|
|
23105
|
-
const
|
|
23106
|
-
const
|
|
23107
|
-
const
|
|
23108
|
-
const fuHeaderStart = noStart ? fuHeaderMiddle : Buffer.from([fuIndicator, nalType | 128]);
|
|
23109
|
-
const fuHeaderEnd = noEnd ? fuHeaderMiddle : Buffer.from([fuIndicator, nalType | 64]);
|
|
23110
|
-
let fuHeader = fuHeaderStart;
|
|
23411
|
+
const nalType = nalHeader & 31;
|
|
23412
|
+
const fuIndicator = nalHeader & 224 | 28;
|
|
23413
|
+
const startHeader = noStart ? nalType : nalType | 128;
|
|
23414
|
+
const endHeader = noEnd ? nalType : nalType | 64;
|
|
23111
23415
|
const packages = [];
|
|
23112
|
-
let offset =
|
|
23416
|
+
let offset = bodyStart;
|
|
23417
|
+
let first = true;
|
|
23113
23418
|
while (offset < data.length) {
|
|
23114
23419
|
const packageSize = Math.min(this.fuaMax, data.length - offset);
|
|
23115
|
-
const
|
|
23420
|
+
const last = offset + packageSize === data.length;
|
|
23421
|
+
const out = this.arena.take(FU_A_HEADER_SIZE + packageSize);
|
|
23422
|
+
out[0] = fuIndicator;
|
|
23423
|
+
out[1] = last ? endHeader : first ? startHeader : nalType;
|
|
23424
|
+
data.copy(out, FU_A_HEADER_SIZE, offset, offset + packageSize);
|
|
23425
|
+
packages.push(out);
|
|
23116
23426
|
offset += packageSize;
|
|
23117
|
-
|
|
23118
|
-
packages.push(Buffer.concat([fuHeader, payload]));
|
|
23119
|
-
fuHeader = fuHeaderMiddle;
|
|
23427
|
+
first = false;
|
|
23120
23428
|
}
|
|
23121
23429
|
return packages;
|
|
23122
23430
|
}
|
|
23123
23431
|
packetizeOneStapA(datas) {
|
|
23124
|
-
const payload = [];
|
|
23125
23432
|
if (!datas.length) throw new Error("packetizeOneStapA requires at least one NAL");
|
|
23126
|
-
|
|
23433
|
+
const taken = [];
|
|
23127
23434
|
let availableSize = this.maxPacketSize - STAP_A_HEADER_SIZE;
|
|
23128
23435
|
const stapHeader = 24;
|
|
23129
|
-
while (datas.length && datas[0].length + LENGTH_FIELD_SIZE <= availableSize &&
|
|
23436
|
+
while (datas.length && datas[0].length + LENGTH_FIELD_SIZE <= availableSize && taken.length < 9) {
|
|
23130
23437
|
const nalu = datas.shift();
|
|
23131
23438
|
availableSize -= LENGTH_FIELD_SIZE + nalu.length;
|
|
23132
|
-
|
|
23133
|
-
|
|
23134
|
-
|
|
23135
|
-
|
|
23439
|
+
taken.push(nalu);
|
|
23440
|
+
}
|
|
23441
|
+
if (taken.length === 0) return datas.shift();
|
|
23442
|
+
if (taken.length === 1) return taken[0];
|
|
23443
|
+
let total = 1;
|
|
23444
|
+
for (const nalu of taken) total += LENGTH_FIELD_SIZE + nalu.length;
|
|
23445
|
+
const out = this.arena.take(total);
|
|
23446
|
+
out[0] = stapHeader;
|
|
23447
|
+
let offset = 1;
|
|
23448
|
+
for (const nalu of taken) {
|
|
23449
|
+
out.writeUInt16BE(nalu.length, offset);
|
|
23450
|
+
nalu.copy(out, offset + LENGTH_FIELD_SIZE);
|
|
23451
|
+
offset += LENGTH_FIELD_SIZE + nalu.length;
|
|
23136
23452
|
}
|
|
23137
|
-
|
|
23138
|
-
if (counter === 1) return payload[1];
|
|
23139
|
-
payload.unshift(Buffer.from([stapHeader]));
|
|
23140
|
-
return Buffer.concat(payload);
|
|
23453
|
+
return out;
|
|
23141
23454
|
}
|
|
23142
23455
|
packetizeStapA(datas) {
|
|
23143
23456
|
const ret = [];
|
|
@@ -23169,12 +23482,15 @@ var H264Repacketizer = class {
|
|
|
23169
23482
|
const originalNalType = first.payload[1] & 31;
|
|
23170
23483
|
const hasFuStart = !!(first.payload[1] & 128);
|
|
23171
23484
|
const hasFuEnd = !!(last.payload[1] & 64);
|
|
23172
|
-
const
|
|
23173
|
-
const originalNalHeader = Buffer.from([fnri | originalNalType]);
|
|
23485
|
+
const originalNalHeader = first.payload[0] & 224 | originalNalType;
|
|
23174
23486
|
const getDefragmentedPendingFua = () => {
|
|
23175
|
-
|
|
23176
|
-
|
|
23177
|
-
|
|
23487
|
+
let total = NAL_HEADER_SIZE;
|
|
23488
|
+
for (const packet of pending) total += packet.payload.length - FU_A_HEADER_SIZE;
|
|
23489
|
+
const out = this.arena.take(total);
|
|
23490
|
+
out[0] = originalNalHeader;
|
|
23491
|
+
let offset = NAL_HEADER_SIZE;
|
|
23492
|
+
for (const packet of pending) offset += packet.payload.copy(out, offset, FU_A_HEADER_SIZE);
|
|
23493
|
+
return out;
|
|
23178
23494
|
};
|
|
23179
23495
|
if (originalNalType === 7) {
|
|
23180
23496
|
const splits = splitH264NaluStartCode(getDefragmentedPendingFua());
|
|
@@ -23699,6 +24015,8 @@ function selectAudioClock(mimeType) {
|
|
|
23699
24015
|
*/
|
|
23700
24016
|
/** A REMB unchanged this long is no estimate (`RembWatch`). */
|
|
23701
24017
|
var REMB_STALE_MS = 3e4;
|
|
24018
|
+
/** werift's NACK history depth (patched RTP_HISTORY_SIZE, `patches/werift+0.22.9.patch`). */
|
|
24019
|
+
var SOURCE_RTP_HOLD_PACKETS = 4096;
|
|
23702
24020
|
var _werift;
|
|
23703
24021
|
async function loadWerift() {
|
|
23704
24022
|
if (_werift) return _werift;
|
|
@@ -24246,6 +24564,8 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
24246
24564
|
* recovery). While active, live source-RTP packets are enqueued behind
|
|
24247
24565
|
* the replay tail to preserve packet order. Aborted on close/rebind. */
|
|
24248
24566
|
activeRtpReplay = null;
|
|
24567
|
+
/** The source packets werift's NACK history may still read (`slab-pool.ts`). */
|
|
24568
|
+
sourceRtpHold = new HeldPacketWindow(SOURCE_RTP_HOLD_PACKETS);
|
|
24249
24569
|
/**
|
|
24250
24570
|
* Trickle ICE: the server's locally-gathered candidates, buffered as werift
|
|
24251
24571
|
* surfaces them via `onIceCandidate`. The client polls `getIceCandidates`
|
|
@@ -25124,6 +25444,7 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
25124
25444
|
this.activeRtpReplay.abort();
|
|
25125
25445
|
this.activeRtpReplay = null;
|
|
25126
25446
|
}
|
|
25447
|
+
this.sourceRtpHold.releaseAll();
|
|
25127
25448
|
await this.releaseTranscodeEgress();
|
|
25128
25449
|
try {
|
|
25129
25450
|
await this.source.return(void 0);
|
|
@@ -25257,6 +25578,7 @@ var AdaptiveSession = class AdaptiveSession {
|
|
|
25257
25578
|
} });
|
|
25258
25579
|
return false;
|
|
25259
25580
|
}
|
|
25581
|
+
this.sourceRtpHold.hold(rtpData);
|
|
25260
25582
|
if (this.sourceVideoSsrc === null) this.sourceVideoSsrc = srcPkt.header.ssrc;
|
|
25261
25583
|
const pt = this.videoSender.codec?.payloadType ?? (codec === "H265" ? 97 : 96);
|
|
25262
25584
|
let outPkts;
|
|
@@ -37862,6 +38184,16 @@ function dialAnswerFromError(err) {
|
|
|
37862
38184
|
}
|
|
37863
38185
|
//#endregion
|
|
37864
38186
|
//#region src/stream-broker/broker-mem-report.ts
|
|
38187
|
+
function readSlabReading() {
|
|
38188
|
+
const totals = readSlabTotals();
|
|
38189
|
+
const allocsDelta = totals.allocs - lastSlabAllocs;
|
|
38190
|
+
lastSlabAllocs = totals.allocs;
|
|
38191
|
+
return {
|
|
38192
|
+
live: totals.live,
|
|
38193
|
+
free: totals.free,
|
|
38194
|
+
allocsDelta
|
|
38195
|
+
};
|
|
38196
|
+
}
|
|
37865
38197
|
/** Linux only: elsewhere (or on a read error) the native view is simply absent. */
|
|
37866
38198
|
async function readProc() {
|
|
37867
38199
|
if (process.platform !== "linux") return {
|
|
@@ -37874,6 +38206,8 @@ async function readProc() {
|
|
|
37874
38206
|
maps
|
|
37875
38207
|
};
|
|
37876
38208
|
}
|
|
38209
|
+
/** `slabAllocs` is a delta: the totals the previous line saw. */
|
|
38210
|
+
var lastSlabAllocs = readSlabTotals().allocs;
|
|
37877
38211
|
async function emitOnce(deps) {
|
|
37878
38212
|
const now = Date.now();
|
|
37879
38213
|
const [proc, worker] = await Promise.all([readProc(), readDemuxWorkerHeaps(now)]);
|
|
@@ -37886,6 +38220,7 @@ async function emitOnce(deps) {
|
|
|
37886
38220
|
proc,
|
|
37887
38221
|
churn: consumerChurn.drain(),
|
|
37888
38222
|
copied: readCopyOwnedTotals(),
|
|
38223
|
+
slabs: readSlabReading(),
|
|
37889
38224
|
v8: readV8Heap(),
|
|
37890
38225
|
worker,
|
|
37891
38226
|
sessions: deps.sessionMem(),
|