@camstack/addon-pipeline 1.2.364 → 1.2.365
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-nx2TXAGS.js → custom-model-catalog-C34VqcPo.js} +2 -2
- package/dist/{custom-model-catalog-DHFPSu0E.mjs → custom-model-catalog-CtpK3FKL.mjs} +2 -2
- package/dist/detection-pipeline/index.js +4 -4
- package/dist/detection-pipeline/index.mjs +4 -4
- package/dist/{dist-BX9VTm12.mjs → dist-BshC0NZR.mjs} +411 -291
- package/dist/{dist-CGOb--t-.js → dist-i4kS_3O7.js} +411 -291
- package/dist/{model-catalogs-DHV48cdY.js → model-catalogs-Cy7mZRWh.js} +1 -1
- package/dist/{model-catalogs-DNL4ydAg.mjs → model-catalogs-DNx5ATgm.mjs} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-C8bbmV3c.mjs → node-Dqmrb990.mjs} +1 -1
- package/dist/{node-UBMRK8SK.js → node-TzgGsCvW.js} +1 -1
- package/dist/pipeline-runner/index.js +3 -3
- package/dist/pipeline-runner/index.mjs +3 -3
- package/dist/{process-memory-1BwIq2Ks.js → process-memory-DLCPFTGC.js} +1 -1
- package/dist/{process-memory-BHG2uCeq.mjs → process-memory-DyfrbFY4.mjs} +1 -1
- package/dist/recorder/index.js +2 -2
- package/dist/recorder/index.mjs +2 -2
- package/dist/{segment-demux-js-PkDP-xQg.js → segment-demux-js-C9AYdKkD.js} +1 -1
- package/dist/{segment-demux-js-BhnT__dA.mjs → segment-demux-js-COoPqx93.mjs} +1 -1
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-vdmrukBk.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DVgnss_e.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DWI1BCfa.mjs +26 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BUBmyDde.mjs +26 -0
- 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-D_DBY5wu.mjs → hostInit-ZvA7Cl_1.mjs} +2 -2
- package/dist/stream-broker/index.js +325 -205
- package/dist/stream-broker/index.mjs +325 -205
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/package.json +4 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-ScPS8ZWM.mjs +0 -26
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Cq5NPjBX.mjs +0 -26
|
@@ -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-i4kS_3O7.js");
|
|
7
|
+
const require_node = require("../node-TzgGsCvW.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-C9AYdKkD.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");
|
|
@@ -3085,6 +3085,68 @@ var RtpPacketizer = class {
|
|
|
3085
3085
|
}
|
|
3086
3086
|
};
|
|
3087
3087
|
//#endregion
|
|
3088
|
+
//#region src/stream-broker/stream-broker/owned-buffer.ts
|
|
3089
|
+
/**
|
|
3090
|
+
* Buffers that enter a LONG-LIVED ring own their backing store.
|
|
3091
|
+
*
|
|
3092
|
+
* Node serves `Buffer.from(...)`, `Buffer.allocUnsafe(n)` and `Buffer.concat`
|
|
3093
|
+
* below `Buffer.poolSize >>> 1` (4 KiB) as views of shared 8 KiB pool slabs.
|
|
3094
|
+
* That is the right choice for the ingest path — a start code, a 4-byte
|
|
3095
|
+
* interleave header, a 1.3 KB RTP packet that is fanned out and forgotten
|
|
3096
|
+
* within a tick — and the wrong one for anything RETAINED: a 600-byte NAL kept
|
|
3097
|
+
* for 60 s in a clip ring pins its whole slab, and with it every short-lived
|
|
3098
|
+
* packet that happened to be allocated beside it. On the hub that held
|
|
3099
|
+
* 550–980 MB of ArrayBuffers for ~150–250 MB of payload, and the full GCs the
|
|
3100
|
+
* pinned slabs caused ran every 0.2–0.5 s at 40–65 % of a core.
|
|
3101
|
+
*
|
|
3102
|
+
* The ring byte caps counted payload bytes, so they never saw it. Both rings
|
|
3103
|
+
* now pass every entry through {@link detachFromPool} on the way in and
|
|
3104
|
+
* account it with {@link backingStoreBytes}, so the cap measures what the
|
|
3105
|
+
* entry costs. Short-lived traffic stays pooled.
|
|
3106
|
+
*/
|
|
3107
|
+
/** True when `buf` is the whole of its ArrayBuffer — nothing else shares it. */
|
|
3108
|
+
function ownsBackingStore(buf) {
|
|
3109
|
+
return buf.byteOffset === 0 && buf.buffer.byteLength === buf.length;
|
|
3110
|
+
}
|
|
3111
|
+
/**
|
|
3112
|
+
* What retaining `buf` really costs: its own length when it owns its
|
|
3113
|
+
* ArrayBuffer, the whole shared slab when it is a view of one.
|
|
3114
|
+
*/
|
|
3115
|
+
function backingStoreBytes(buf) {
|
|
3116
|
+
return ownsBackingStore(buf) ? buf.length : buf.buffer.byteLength;
|
|
3117
|
+
}
|
|
3118
|
+
/**
|
|
3119
|
+
* `buf` itself when it already owns its ArrayBuffer (decided by inspecting the
|
|
3120
|
+
* buffer, never by its size: whether a large `allocUnsafe` is pooled is a Node
|
|
3121
|
+
* runtime detail), else a copy that does. One copy, never two: a buffer that is
|
|
3122
|
+
* already unpooled is returned as-is.
|
|
3123
|
+
*/
|
|
3124
|
+
function detachFromPool(buf) {
|
|
3125
|
+
if (ownsBackingStore(buf)) return buf;
|
|
3126
|
+
const owned = Buffer.allocUnsafeSlow(buf.length);
|
|
3127
|
+
buf.copy(owned);
|
|
3128
|
+
return owned;
|
|
3129
|
+
}
|
|
3130
|
+
/**
|
|
3131
|
+
* A copy of `src[start, end)` that OWNS its backing store — the shape a reader
|
|
3132
|
+
* hands out when the packet it cuts from its socket buffer is going to be
|
|
3133
|
+
* retained anyway.
|
|
3134
|
+
*
|
|
3135
|
+
* Every source video packet is: `captureRtpForPreBuffer` keeps it for the life
|
|
3136
|
+
* of a GOP, and the restreamer's keyframe cache keeps the keyframe ones. With
|
|
3137
|
+
* the readers cutting packets as `Buffer.from(subarray)` — a pooled view — the
|
|
3138
|
+
* ring then had to copy each one a second time to own it: two copies of every
|
|
3139
|
+
* packet on the ingest path (measured 15.1 + 14.8 MB/s on the hub's load).
|
|
3140
|
+
* Cutting the packet owned up front makes the ring's and the cache's
|
|
3141
|
+
* `detachFromPool` an identity, so the packet is copied once and shared by the
|
|
3142
|
+
* reader's fan-out, the ring, the cache and every session's write queue.
|
|
3143
|
+
*/
|
|
3144
|
+
function copyOwned(src, start, end) {
|
|
3145
|
+
const owned = Buffer.allocUnsafeSlow(end - start);
|
|
3146
|
+
src.copy(owned, 0, start, end);
|
|
3147
|
+
return owned;
|
|
3148
|
+
}
|
|
3149
|
+
//#endregion
|
|
3088
3150
|
//#region src/stream-broker/rtsp/sdp-builder.ts
|
|
3089
3151
|
function buildSdp(options) {
|
|
3090
3152
|
const { codec, parameterSets, streamName } = options;
|
|
@@ -3755,7 +3817,7 @@ var RtspRestreamer = class {
|
|
|
3755
3817
|
if (this.collectingKeyframeRtp && !isKeyframe && !isParameterSet) this.collectingKeyframeRtp = false;
|
|
3756
3818
|
if (this.collectingKeyframeRtp) {
|
|
3757
3819
|
this.lastKeyframeRtpPackets.push({
|
|
3758
|
-
data:
|
|
3820
|
+
data: detachFromPool(rtpData),
|
|
3759
3821
|
marker: rtpPacket.marker
|
|
3760
3822
|
});
|
|
3761
3823
|
this.lastKeyframeRtpBytes += rtpData.length;
|
|
@@ -4357,6 +4419,19 @@ var RtspSession = class {
|
|
|
4357
4419
|
lastRtpAt = 0;
|
|
4358
4420
|
/** Total video RTP bytes written to the socket. */
|
|
4359
4421
|
bytesSent = 0;
|
|
4422
|
+
/**
|
|
4423
|
+
* The socket is corked for the access unit in progress — see
|
|
4424
|
+
* {@link writeInterleaved}. Cleared by the packet that ends the picture (RTP
|
|
4425
|
+
* marker, or any audio packet) or by the next-tick safety net, whichever
|
|
4426
|
+
* comes first.
|
|
4427
|
+
*/
|
|
4428
|
+
accessUnitCorked = false;
|
|
4429
|
+
/** Flush the access unit in progress. Idempotent: the marker and the safety net both call it. */
|
|
4430
|
+
uncorkAccessUnit = () => {
|
|
4431
|
+
if (!this.accessUnitCorked) return;
|
|
4432
|
+
this.accessUnitCorked = false;
|
|
4433
|
+
this.socket.uncork();
|
|
4434
|
+
};
|
|
4360
4435
|
/** Client `User-Agent` (from the first request that carries one). Lets the
|
|
4361
4436
|
* broker panel identify a consumer by purpose — e.g. the recorder sets
|
|
4362
4437
|
* `-user_agent recorder` on its RTSP pull — instead of a bare address. */
|
|
@@ -4604,7 +4679,7 @@ var RtspSession = class {
|
|
|
4604
4679
|
return "dropped-backlog";
|
|
4605
4680
|
}
|
|
4606
4681
|
}
|
|
4607
|
-
this.writeInterleaved(this.tracks[0].rtpChannel, packet.data);
|
|
4682
|
+
this.writeInterleaved(this.tracks[0].rtpChannel, packet.data, packet.marker);
|
|
4608
4683
|
this.missedKeyframes = 0;
|
|
4609
4684
|
return "written";
|
|
4610
4685
|
}
|
|
@@ -4613,7 +4688,7 @@ var RtspSession = class {
|
|
|
4613
4688
|
if (!this.playing || this.closed || this.muted) return "not-playing";
|
|
4614
4689
|
if (this.tracks.length < 2) return "not-playing";
|
|
4615
4690
|
if (this.socket.writableLength > 2097152) return "dropped-backlog";
|
|
4616
|
-
this.writeInterleaved(this.tracks[1].rtpChannel, packet.data);
|
|
4691
|
+
this.writeInterleaved(this.tracks[1].rtpChannel, packet.data, true);
|
|
4617
4692
|
return "written";
|
|
4618
4693
|
}
|
|
4619
4694
|
/**
|
|
@@ -4635,14 +4710,13 @@ var RtspSession = class {
|
|
|
4635
4710
|
* ## Why sharing the payload is safe, and what would make it unsafe
|
|
4636
4711
|
*
|
|
4637
4712
|
* The payload buffer handed here is a private allocation per packet —
|
|
4638
|
-
* `
|
|
4639
|
-
*
|
|
4640
|
-
*
|
|
4641
|
-
*
|
|
4642
|
-
*
|
|
4643
|
-
*
|
|
4644
|
-
*
|
|
4645
|
-
* to: a pending write holds its own reference, and the rule below is about
|
|
4713
|
+
* `copyOwned(readBuffer, …)` in the readers' `onData`, for video and audio
|
|
4714
|
+
* alike — never a view of the reader's socket buffer, which advances on the
|
|
4715
|
+
* next read. Nothing in the broker writes into a captured packet: every
|
|
4716
|
+
* mutation in this path targets a freshly allocated buffer. (The same object
|
|
4717
|
+
* is what the broker's `rtpRing` and the restreamer's keyframe cache retain —
|
|
4718
|
+
* it owns its backing store, so `detachFromPool` keeps it as is — and that is
|
|
4719
|
+
* fine: a pending write holds its own reference, and the rule below is about
|
|
4646
4720
|
* mutation, not retention.)
|
|
4647
4721
|
*
|
|
4648
4722
|
* The rule to keep: **`socket.write` forbids MUTATION before the write
|
|
@@ -4652,18 +4726,36 @@ var RtspSession = class {
|
|
|
4652
4726
|
* queued. Four bytes from the Buffer pool is a pointer bump; the copy this
|
|
4653
4727
|
* replaces was ~1.4 KB.
|
|
4654
4728
|
*
|
|
4655
|
-
*
|
|
4656
|
-
*
|
|
4729
|
+
* ## One cork per ACCESS UNIT, not per packet
|
|
4730
|
+
*
|
|
4731
|
+
* The pair used to be corked and uncorked on its own: one `writev` per RTP
|
|
4732
|
+
* packet per session, so a 4K keyframe fragmented into a hundred packets and
|
|
4733
|
+
* fanned out to three sessions was three hundred syscalls for one picture no
|
|
4734
|
+
* decoder can use before it is whole. The socket is corked on the first write
|
|
4735
|
+
* of an access unit and uncorked by the packet that ENDS it
|
|
4736
|
+
* (`endOfAccessUnit`: the RTP marker on video, every audio packet), so the
|
|
4737
|
+
* picture leaves in one `writev`. A `process.nextTick` safety net uncorks
|
|
4738
|
+
* whatever is still open before the tick yields to I/O — a marker-less camera
|
|
4739
|
+
* or a lost marker degrades to one `writev` per tick, never to a socket held
|
|
4740
|
+
* corked across a read.
|
|
4741
|
+
*
|
|
4742
|
+
* Backpressure sees through the cork: `writableLength` counts corked bytes,
|
|
4743
|
+
* so the `MAX_WRITE_BUFFER` gate, the latch and the missed-keyframe close
|
|
4744
|
+
* (D447) judge exactly what they judged per packet.
|
|
4657
4745
|
*/
|
|
4658
|
-
writeInterleaved(channel, payload) {
|
|
4746
|
+
writeInterleaved(channel, payload, endOfAccessUnit) {
|
|
4659
4747
|
const header = Buffer.allocUnsafe(4);
|
|
4660
4748
|
header[0] = 36;
|
|
4661
4749
|
header[1] = channel;
|
|
4662
4750
|
header.writeUInt16BE(payload.length, 2);
|
|
4663
|
-
this.
|
|
4751
|
+
if (!this.accessUnitCorked) {
|
|
4752
|
+
this.accessUnitCorked = true;
|
|
4753
|
+
this.socket.cork();
|
|
4754
|
+
process.nextTick(this.uncorkAccessUnit);
|
|
4755
|
+
}
|
|
4664
4756
|
this.socket.write(header);
|
|
4665
4757
|
this.socket.write(payload);
|
|
4666
|
-
this.
|
|
4758
|
+
if (endOfAccessUnit) this.uncorkAccessUnit();
|
|
4667
4759
|
this.lastRtpAt = Date.now();
|
|
4668
4760
|
this.bytesSent += 4 + payload.length;
|
|
4669
4761
|
}
|
|
@@ -10087,7 +10179,7 @@ var Rfc4571Reader = class {
|
|
|
10087
10179
|
while (this.readBuffer.length >= 2) {
|
|
10088
10180
|
const length = this.readBuffer.readUInt16BE(0);
|
|
10089
10181
|
if (this.readBuffer.length < 2 + length) return;
|
|
10090
|
-
const rtpPacket =
|
|
10182
|
+
const rtpPacket = copyOwned(this.readBuffer, 2, 2 + length);
|
|
10091
10183
|
this.readBuffer = this.readBuffer.subarray(2 + length);
|
|
10092
10184
|
this.dispatchRtp(rtpPacket);
|
|
10093
10185
|
}
|
|
@@ -10148,6 +10240,93 @@ function totalFragmentBytes(fragments) {
|
|
|
10148
10240
|
for (const fragment of fragments) total += fragment.length;
|
|
10149
10241
|
return total;
|
|
10150
10242
|
}
|
|
10243
|
+
/** H.264: single NAL by its type; STAP-A by every aggregated type; FU-A by the FU header. */
|
|
10244
|
+
function classifyH264Payload(payload) {
|
|
10245
|
+
const nalType = payload[0] & 31;
|
|
10246
|
+
if (nalType === H264_NAL_STAP_A) {
|
|
10247
|
+
let keyframe = false;
|
|
10248
|
+
let parameterSet = false;
|
|
10249
|
+
let offset = 1;
|
|
10250
|
+
while (offset + 2 <= payload.length) {
|
|
10251
|
+
const nalSize = payload.readUInt16BE(offset);
|
|
10252
|
+
offset += 2;
|
|
10253
|
+
if (nalSize === 0 || offset + nalSize > payload.length) break;
|
|
10254
|
+
const type = payload[offset] & 31;
|
|
10255
|
+
keyframe ||= type === H264_NAL_IDR;
|
|
10256
|
+
parameterSet ||= type === H264_NAL_SPS || type === H264_NAL_PPS;
|
|
10257
|
+
offset += nalSize;
|
|
10258
|
+
}
|
|
10259
|
+
return {
|
|
10260
|
+
keyframe,
|
|
10261
|
+
parameterSet
|
|
10262
|
+
};
|
|
10263
|
+
}
|
|
10264
|
+
if (nalType === 28) {
|
|
10265
|
+
if (payload.length < 2) return NO_PAYLOAD_CLASS;
|
|
10266
|
+
return {
|
|
10267
|
+
keyframe: (payload[1] & 31) === H264_NAL_IDR,
|
|
10268
|
+
parameterSet: false
|
|
10269
|
+
};
|
|
10270
|
+
}
|
|
10271
|
+
return {
|
|
10272
|
+
keyframe: nalType === H264_NAL_IDR,
|
|
10273
|
+
parameterSet: nalType === H264_NAL_SPS || nalType === H264_NAL_PPS
|
|
10274
|
+
};
|
|
10275
|
+
}
|
|
10276
|
+
/** H.265: single NAL by its type; AP by every aggregated type; FU by the FU header. */
|
|
10277
|
+
function classifyH265Payload(payload) {
|
|
10278
|
+
if (payload.length < 2) return NO_PAYLOAD_CLASS;
|
|
10279
|
+
const nalType = payload[0] >> 1 & 63;
|
|
10280
|
+
if (nalType === H265_NAL_AP) {
|
|
10281
|
+
let keyframe = false;
|
|
10282
|
+
let parameterSet = false;
|
|
10283
|
+
let offset = 2;
|
|
10284
|
+
while (offset + 2 <= payload.length) {
|
|
10285
|
+
const nalSize = payload.readUInt16BE(offset);
|
|
10286
|
+
offset += 2;
|
|
10287
|
+
if (nalSize === 0 || offset + nalSize > payload.length) break;
|
|
10288
|
+
const type = payload[offset] >> 1 & 63;
|
|
10289
|
+
keyframe ||= isH265IrapType(type);
|
|
10290
|
+
parameterSet ||= isH265ParameterSetType(type);
|
|
10291
|
+
offset += nalSize;
|
|
10292
|
+
}
|
|
10293
|
+
return {
|
|
10294
|
+
keyframe,
|
|
10295
|
+
parameterSet
|
|
10296
|
+
};
|
|
10297
|
+
}
|
|
10298
|
+
if (nalType === 49) {
|
|
10299
|
+
if (payload.length < 3) return NO_PAYLOAD_CLASS;
|
|
10300
|
+
return {
|
|
10301
|
+
keyframe: isH265IrapType(payload[2] & 63),
|
|
10302
|
+
parameterSet: false
|
|
10303
|
+
};
|
|
10304
|
+
}
|
|
10305
|
+
return {
|
|
10306
|
+
keyframe: isH265IrapType(nalType),
|
|
10307
|
+
parameterSet: isH265ParameterSetType(nalType)
|
|
10308
|
+
};
|
|
10309
|
+
}
|
|
10310
|
+
/**
|
|
10311
|
+
* Classify one raw RTP packet (12-byte header + payload) by what it carries —
|
|
10312
|
+
* the per-packet half of {@link RtpDepacketizer.processPacket}, without the
|
|
10313
|
+
* reassembly half. Pure: reads the headers, allocates nothing, keeps no state.
|
|
10314
|
+
* `null` for a packet too short to be RTP with a payload.
|
|
10315
|
+
*/
|
|
10316
|
+
function classifyRtpPacket(codec, rtpData) {
|
|
10317
|
+
if (rtpData.length < 12) return null;
|
|
10318
|
+
const marker = (rtpData[1] & 128) !== 0;
|
|
10319
|
+
const timestamp = rtpData.readUInt32BE(4);
|
|
10320
|
+
const payload = rtpData.subarray(12);
|
|
10321
|
+
if (payload.length === 0) return null;
|
|
10322
|
+
const carried = codec === "h264" ? classifyH264Payload(payload) : classifyH265Payload(payload);
|
|
10323
|
+
return {
|
|
10324
|
+
timestamp,
|
|
10325
|
+
marker,
|
|
10326
|
+
keyframe: carried.keyframe,
|
|
10327
|
+
parameterSet: carried.parameterSet
|
|
10328
|
+
};
|
|
10329
|
+
}
|
|
10151
10330
|
/**
|
|
10152
10331
|
* RTP → Annex-B NAL depacketizer.
|
|
10153
10332
|
*
|
|
@@ -10192,87 +10371,22 @@ var RtpDepacketizer = class {
|
|
|
10192
10371
|
* short to be RTP with a payload.
|
|
10193
10372
|
*/
|
|
10194
10373
|
processPacket(rtpData) {
|
|
10195
|
-
|
|
10196
|
-
|
|
10197
|
-
const timestamp = rtpData.readUInt32BE(4);
|
|
10374
|
+
const carried = this.classifyPacket(rtpData);
|
|
10375
|
+
if (carried === null) return null;
|
|
10198
10376
|
const payload = rtpData.subarray(12);
|
|
10199
|
-
if (
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
else this.processH265(payload, timestamp, marker);
|
|
10203
|
-
return {
|
|
10204
|
-
timestamp,
|
|
10205
|
-
marker,
|
|
10206
|
-
keyframe: carried.keyframe,
|
|
10207
|
-
parameterSet: carried.parameterSet
|
|
10208
|
-
};
|
|
10377
|
+
if (this.codec === "h264") this.processH264(payload, carried.timestamp, carried.marker);
|
|
10378
|
+
else this.processH265(payload, carried.timestamp, carried.marker);
|
|
10379
|
+
return carried;
|
|
10209
10380
|
}
|
|
10210
|
-
/**
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
|
|
10219
|
-
offset += 2;
|
|
10220
|
-
if (nalSize === 0 || offset + nalSize > payload.length) break;
|
|
10221
|
-
const type = payload[offset] & 31;
|
|
10222
|
-
keyframe ||= type === H264_NAL_IDR;
|
|
10223
|
-
parameterSet ||= type === H264_NAL_SPS || type === H264_NAL_PPS;
|
|
10224
|
-
offset += nalSize;
|
|
10225
|
-
}
|
|
10226
|
-
return {
|
|
10227
|
-
keyframe,
|
|
10228
|
-
parameterSet
|
|
10229
|
-
};
|
|
10230
|
-
}
|
|
10231
|
-
if (nalType === 28) {
|
|
10232
|
-
if (payload.length < 2) return NO_PAYLOAD_CLASS;
|
|
10233
|
-
return {
|
|
10234
|
-
keyframe: (payload[1] & 31) === H264_NAL_IDR,
|
|
10235
|
-
parameterSet: false
|
|
10236
|
-
};
|
|
10237
|
-
}
|
|
10238
|
-
return {
|
|
10239
|
-
keyframe: nalType === H264_NAL_IDR,
|
|
10240
|
-
parameterSet: nalType === H264_NAL_SPS || nalType === H264_NAL_PPS
|
|
10241
|
-
};
|
|
10242
|
-
}
|
|
10243
|
-
/** H.265: single NAL by its type; AP by every aggregated type; FU by the FU header. */
|
|
10244
|
-
classifyH265Payload(payload) {
|
|
10245
|
-
if (payload.length < 2) return NO_PAYLOAD_CLASS;
|
|
10246
|
-
const nalType = payload[0] >> 1 & 63;
|
|
10247
|
-
if (nalType === H265_NAL_AP) {
|
|
10248
|
-
let keyframe = false;
|
|
10249
|
-
let parameterSet = false;
|
|
10250
|
-
let offset = 2;
|
|
10251
|
-
while (offset + 2 <= payload.length) {
|
|
10252
|
-
const nalSize = payload.readUInt16BE(offset);
|
|
10253
|
-
offset += 2;
|
|
10254
|
-
if (nalSize === 0 || offset + nalSize > payload.length) break;
|
|
10255
|
-
const type = payload[offset] >> 1 & 63;
|
|
10256
|
-
keyframe ||= isH265IrapType(type);
|
|
10257
|
-
parameterSet ||= isH265ParameterSetType(type);
|
|
10258
|
-
offset += nalSize;
|
|
10259
|
-
}
|
|
10260
|
-
return {
|
|
10261
|
-
keyframe,
|
|
10262
|
-
parameterSet
|
|
10263
|
-
};
|
|
10264
|
-
}
|
|
10265
|
-
if (nalType === 49) {
|
|
10266
|
-
if (payload.length < 3) return NO_PAYLOAD_CLASS;
|
|
10267
|
-
return {
|
|
10268
|
-
keyframe: isH265IrapType(payload[2] & 63),
|
|
10269
|
-
parameterSet: false
|
|
10270
|
-
};
|
|
10271
|
-
}
|
|
10272
|
-
return {
|
|
10273
|
-
keyframe: isH265IrapType(nalType),
|
|
10274
|
-
parameterSet: isH265ParameterSetType(nalType)
|
|
10275
|
-
};
|
|
10381
|
+
/**
|
|
10382
|
+
* What this packet CARRIES, and nothing else: no NAL is reassembled, no buffer
|
|
10383
|
+
* allocated, no reassembly state touched. The same verdict `processPacket`
|
|
10384
|
+
* returns, for a broker whose Annex-B plane has no consumer — the source-RTP
|
|
10385
|
+
* ring, the restreamer's keyframe cache and the stats need exactly this and
|
|
10386
|
+
* none of the copies.
|
|
10387
|
+
*/
|
|
10388
|
+
classifyPacket(rtpData) {
|
|
10389
|
+
return classifyRtpPacket(this.codec, rtpData);
|
|
10276
10390
|
}
|
|
10277
10391
|
processH264(payload, timestamp, marker) {
|
|
10278
10392
|
const nalType = payload[0] & 31;
|
|
@@ -10452,11 +10566,13 @@ var RtpAccessUnitTracker = class {
|
|
|
10452
10566
|
const keyframeContent = packet.keyframe || packet.parameterSet;
|
|
10453
10567
|
const keyframeAuStart = keyframeContent && !this.keyframeAu;
|
|
10454
10568
|
if (keyframeContent) this.keyframeAu = true;
|
|
10569
|
+
const pictureStart = !packet.parameterSet && !this.pictureSeen;
|
|
10455
10570
|
if (!packet.parameterSet) this.pictureSeen = true;
|
|
10456
10571
|
return {
|
|
10457
10572
|
auStart,
|
|
10458
10573
|
keyframeAu: this.keyframeAu,
|
|
10459
10574
|
keyframeAuStart,
|
|
10575
|
+
pictureStart,
|
|
10460
10576
|
parameterSet: packet.parameterSet
|
|
10461
10577
|
};
|
|
10462
10578
|
}
|
|
@@ -10782,7 +10898,7 @@ var NativeRtspClient = class {
|
|
|
10782
10898
|
const channel = this.readBuffer[1];
|
|
10783
10899
|
const length = this.readBuffer.readUInt16BE(2);
|
|
10784
10900
|
if (this.readBuffer.length < 4 + length) return;
|
|
10785
|
-
const payload =
|
|
10901
|
+
const payload = copyOwned(this.readBuffer, 4, 4 + length);
|
|
10786
10902
|
this.readBuffer = this.readBuffer.subarray(4 + length);
|
|
10787
10903
|
this.handleInterleavedPacket(channel, payload);
|
|
10788
10904
|
} else {
|
|
@@ -11518,49 +11634,6 @@ var AudioCodecSession = class {
|
|
|
11518
11634
|
}
|
|
11519
11635
|
};
|
|
11520
11636
|
//#endregion
|
|
11521
|
-
//#region src/stream-broker/stream-broker/owned-buffer.ts
|
|
11522
|
-
/**
|
|
11523
|
-
* Buffers that enter a LONG-LIVED ring own their backing store.
|
|
11524
|
-
*
|
|
11525
|
-
* Node serves `Buffer.from(...)`, `Buffer.allocUnsafe(n)` and `Buffer.concat`
|
|
11526
|
-
* below `Buffer.poolSize >>> 1` (4 KiB) as views of shared 8 KiB pool slabs.
|
|
11527
|
-
* That is the right choice for the ingest path — a start code, a 4-byte
|
|
11528
|
-
* interleave header, a 1.3 KB RTP packet that is fanned out and forgotten
|
|
11529
|
-
* within a tick — and the wrong one for anything RETAINED: a 600-byte NAL kept
|
|
11530
|
-
* for 60 s in a clip ring pins its whole slab, and with it every short-lived
|
|
11531
|
-
* packet that happened to be allocated beside it. On the hub that held
|
|
11532
|
-
* 550–980 MB of ArrayBuffers for ~150–250 MB of payload, and the full GCs the
|
|
11533
|
-
* pinned slabs caused ran every 0.2–0.5 s at 40–65 % of a core.
|
|
11534
|
-
*
|
|
11535
|
-
* The ring byte caps counted payload bytes, so they never saw it. Both rings
|
|
11536
|
-
* now pass every entry through {@link detachFromPool} on the way in and
|
|
11537
|
-
* account it with {@link backingStoreBytes}, so the cap measures what the
|
|
11538
|
-
* entry costs. Short-lived traffic stays pooled.
|
|
11539
|
-
*/
|
|
11540
|
-
/** True when `buf` is the whole of its ArrayBuffer — nothing else shares it. */
|
|
11541
|
-
function ownsBackingStore(buf) {
|
|
11542
|
-
return buf.byteOffset === 0 && buf.buffer.byteLength === buf.length;
|
|
11543
|
-
}
|
|
11544
|
-
/**
|
|
11545
|
-
* What retaining `buf` really costs: its own length when it owns its
|
|
11546
|
-
* ArrayBuffer, the whole shared slab when it is a view of one.
|
|
11547
|
-
*/
|
|
11548
|
-
function backingStoreBytes(buf) {
|
|
11549
|
-
return ownsBackingStore(buf) ? buf.length : buf.buffer.byteLength;
|
|
11550
|
-
}
|
|
11551
|
-
/**
|
|
11552
|
-
* `buf` itself when it already owns its ArrayBuffer (decided by inspecting the
|
|
11553
|
-
* buffer, never by its size: whether a large `allocUnsafe` is pooled is a Node
|
|
11554
|
-
* runtime detail), else a copy that does. One copy, never two: a buffer that is
|
|
11555
|
-
* already unpooled is returned as-is.
|
|
11556
|
-
*/
|
|
11557
|
-
function detachFromPool(buf) {
|
|
11558
|
-
if (ownsBackingStore(buf)) return buf;
|
|
11559
|
-
const owned = Buffer.allocUnsafeSlow(buf.length);
|
|
11560
|
-
buf.copy(owned);
|
|
11561
|
-
return owned;
|
|
11562
|
-
}
|
|
11563
|
-
//#endregion
|
|
11564
11637
|
//#region src/stream-broker/stream-broker/encoded-ring-buffer.ts
|
|
11565
11638
|
/**
|
|
11566
11639
|
* Time-based ring buffer for encoded video packets.
|
|
@@ -12500,9 +12573,6 @@ var StreamBroker = class StreamBroker {
|
|
|
12500
12573
|
*/
|
|
12501
12574
|
ffmpegBinaryPathSource = () => null;
|
|
12502
12575
|
rtpDepacketizer = null;
|
|
12503
|
-
/** Flag-only depacketizer for the `push-rtp` source-RTP pre-buffer path
|
|
12504
|
-
* (sets keyframe/param-set flags without re-emitting AnnexB packets). */
|
|
12505
|
-
pushRtpFlagDepacketizer = null;
|
|
12506
12576
|
audioRtpDecoder = null;
|
|
12507
12577
|
audioCodecSession = null;
|
|
12508
12578
|
/**
|
|
@@ -12870,10 +12940,24 @@ var StreamBroker = class StreamBroker {
|
|
|
12870
12940
|
* log alongside `audioRtpSeen`, so a wake-up cycle that brings audio
|
|
12871
12941
|
* back but never video is observable from the structured logs. */
|
|
12872
12942
|
videoRtpSeen = 0;
|
|
12873
|
-
/**
|
|
12943
|
+
/**
|
|
12944
|
+
* What the source RTP packet being handled CARRIES (keyframe / parameter-set
|
|
12945
|
+
* content, read from its payload headers by `classifyRtpPacket`) — the flags
|
|
12946
|
+
* `captureRtpForPreBuffer` reads. Per packet, not per completed NAL: every
|
|
12947
|
+
* fragment of a fragmented IDR carries them, and the ring marks one keyframe
|
|
12948
|
+
* AU for the lot.
|
|
12949
|
+
*/
|
|
12874
12950
|
_lastNalKeyframe = false;
|
|
12875
12951
|
_lastNalParamSet = false;
|
|
12876
12952
|
/**
|
|
12953
|
+
* Whether the Annex-B plane (NAL reassembly + access-unit assembly →
|
|
12954
|
+
* `EncodedPacket`) is being BUILT for this stream. False while nothing
|
|
12955
|
+
* consumes it: the source-RTP path then classifies each packet and copies
|
|
12956
|
+
* nothing. Armed at the next keyframe access unit once a consumer attaches
|
|
12957
|
+
* (`needsAnnexBPlane`), so the first packet it emits is decodable.
|
|
12958
|
+
*/
|
|
12959
|
+
annexBPlaneActive = false;
|
|
12960
|
+
/**
|
|
12877
12961
|
* Source-RTP pre-buffer: a ring of raw source RTP packets trimmed to
|
|
12878
12962
|
* begin at the most recent keyframe access unit — i.e. the current GOP so
|
|
12879
12963
|
* far. A late-joining WebRTC viewer on the repacketizer path replays this
|
|
@@ -13949,26 +14033,7 @@ var StreamBroker = class StreamBroker {
|
|
|
13949
14033
|
return;
|
|
13950
14034
|
}
|
|
13951
14035
|
}
|
|
13952
|
-
if (packet.type === "video")
|
|
13953
|
-
this.packetCount++;
|
|
13954
|
-
this.totalBytes += packet.data.length;
|
|
13955
|
-
this.bytesInWindow += packet.data.length;
|
|
13956
|
-
this.packetsInWindow++;
|
|
13957
|
-
this.lastPacketAt = Date.now();
|
|
13958
|
-
const now = Date.now();
|
|
13959
|
-
const windowMs = now - this.windowStartMs;
|
|
13960
|
-
if (windowMs >= 2e3) {
|
|
13961
|
-
this.bitrateKbps = Math.round(this.bytesInWindow * 8 / windowMs);
|
|
13962
|
-
this.encodedInputFps = this.packetsInWindow / windowMs * 1e3;
|
|
13963
|
-
this.bytesInWindow = 0;
|
|
13964
|
-
this.packetsInWindow = 0;
|
|
13965
|
-
this.windowStartMs = now;
|
|
13966
|
-
}
|
|
13967
|
-
if (packet.keyframe) {
|
|
13968
|
-
if (this.lastKeyframeMs > 0) this.idrIntervalMs = now - this.lastKeyframeMs;
|
|
13969
|
-
this.lastKeyframeMs = now;
|
|
13970
|
-
}
|
|
13971
|
-
}
|
|
14036
|
+
if (packet.type === "video" && this.rtpDepacketizer === null) this.recordVideoStats(packet.data.length, true, packet.keyframe);
|
|
13972
14037
|
if (packet.type === "video" && !this.isRtpSource()) this.preBuffer.push(packet);
|
|
13973
14038
|
if (packet.type === "video" && this.clipBuffer && packet.isPlaceholder !== true) this.clipBuffer.push(packet);
|
|
13974
14039
|
this.fanOutEncoded(packet);
|
|
@@ -14159,8 +14224,8 @@ var StreamBroker = class StreamBroker {
|
|
|
14159
14224
|
index: auStart,
|
|
14160
14225
|
ts: auPacket.readUInt32BE(4)
|
|
14161
14226
|
});
|
|
14227
|
+
this.trimRtpRingToPreRollWindow();
|
|
14162
14228
|
}
|
|
14163
|
-
this.trimRtpRingToPreRollWindow();
|
|
14164
14229
|
this.rtpRingHasKeyframe = true;
|
|
14165
14230
|
}
|
|
14166
14231
|
this.rtpRingPrevMarker = marker;
|
|
@@ -14429,10 +14494,10 @@ var StreamBroker = class StreamBroker {
|
|
|
14429
14494
|
*
|
|
14430
14495
|
* `push-rtp` providers deliver AnnexB-equivalents separately via
|
|
14431
14496
|
* `pushEncodedPacket()` (see `IStreamBroker` contract) — so this path
|
|
14432
|
-
* does NOT re-emit EncodedPackets; it only
|
|
14433
|
-
* keyframe/param-set flags the pre-buffer capture needs
|
|
14434
|
-
*
|
|
14435
|
-
*
|
|
14497
|
+
* does NOT re-emit EncodedPackets; it only classifies each packet for the
|
|
14498
|
+
* keyframe/param-set flags the pre-buffer capture needs
|
|
14499
|
+
* (`classifyRtpPacket`, no reassembly), keeping the AnnexB consumers
|
|
14500
|
+
* (pipe, restreamer) single-fed by the provider.
|
|
14436
14501
|
*
|
|
14437
14502
|
* The pre-buffer is captured even with zero subscribers attached — a
|
|
14438
14503
|
* viewer that connects mid-GOP must be able to replay the GOP that
|
|
@@ -14442,9 +14507,9 @@ var StreamBroker = class StreamBroker {
|
|
|
14442
14507
|
pushVideoRtp(rtpData) {
|
|
14443
14508
|
if (this.stopping) return;
|
|
14444
14509
|
this.kickRtpActivityWatchdog();
|
|
14445
|
-
this.
|
|
14446
|
-
this.
|
|
14447
|
-
this.
|
|
14510
|
+
const carried = classifyRtpPacket(this.detectedCodec === "h265" ? "h265" : "h264", rtpData);
|
|
14511
|
+
this._lastNalKeyframe = carried?.keyframe ?? false;
|
|
14512
|
+
this._lastNalParamSet = carried?.parameterSet ?? false;
|
|
14448
14513
|
this.captureRtpForPreBuffer(rtpData);
|
|
14449
14514
|
if (this.rtpVideoCallbacks.size === 0) return;
|
|
14450
14515
|
for (const [cb, entry] of this.rtpVideoCallbacks) try {
|
|
@@ -14454,27 +14519,6 @@ var StreamBroker = class StreamBroker {
|
|
|
14454
14519
|
this.logger?.warn("rtp video subscriber threw", { meta: { error: require_dist.errMsg(err) } });
|
|
14455
14520
|
}
|
|
14456
14521
|
}
|
|
14457
|
-
/**
|
|
14458
|
-
* Lazily build a flag-only RTP depacketizer for the `push-rtp` source
|
|
14459
|
-
* path. Unlike the native reader's depacketizer (which emits AnnexB
|
|
14460
|
-
* EncodedPackets via `handleDepacketizedNal`), this callback ONLY sets
|
|
14461
|
-
* `_lastNalKeyframe`/`_lastNalParamSet` — the push provider owns the
|
|
14462
|
-
* AnnexB plane through `pushEncodedPacket`, so re-emitting here would
|
|
14463
|
-
* double-feed the pipe/restreamer. push-rtp has no SDP `onVideoTrack`,
|
|
14464
|
-
* so the codec comes from the source declaration (`detectedCodec`, seeded
|
|
14465
|
-
* in `start()`). Dropped on teardown (`resetRtpPipeline`) so a redial
|
|
14466
|
-
* starts with clean FU reassembly state.
|
|
14467
|
-
*/
|
|
14468
|
-
ensurePushRtpFlagDepacketizer() {
|
|
14469
|
-
if (!this.pushRtpFlagDepacketizer) {
|
|
14470
|
-
const codec = this.detectedCodec === "h265" ? "h265" : "h264";
|
|
14471
|
-
this.pushRtpFlagDepacketizer = new RtpDepacketizer(codec, ({ keyframe, parameterSet }) => {
|
|
14472
|
-
if (keyframe) this._lastNalKeyframe = true;
|
|
14473
|
-
if (parameterSet) this._lastNalParamSet = true;
|
|
14474
|
-
});
|
|
14475
|
-
}
|
|
14476
|
-
return this.pushRtpFlagDepacketizer;
|
|
14477
|
-
}
|
|
14478
14522
|
/** Toggle the per-device streaming debug flag. */
|
|
14479
14523
|
setStreamingDebug(enabled) {
|
|
14480
14524
|
this.streamingDebug = enabled;
|
|
@@ -14917,10 +14961,17 @@ var StreamBroker = class StreamBroker {
|
|
|
14917
14961
|
seq: this.videoRtpSeen,
|
|
14918
14962
|
bytes: rtpData.length
|
|
14919
14963
|
} });
|
|
14920
|
-
|
|
14921
|
-
this.
|
|
14922
|
-
const carried = this.rtpDepacketizer?.processPacket(rtpData) ?? null;
|
|
14964
|
+
const wantPlane = this.needsAnnexBPlane();
|
|
14965
|
+
if (!wantPlane && this.annexBPlaneActive) this.idleAnnexBPlane();
|
|
14966
|
+
const carried = this.annexBPlaneActive ? this.rtpDepacketizer?.processPacket(rtpData) ?? null : this.rtpDepacketizer?.classifyPacket(rtpData) ?? null;
|
|
14967
|
+
this._lastNalKeyframe = carried?.keyframe ?? false;
|
|
14968
|
+
this._lastNalParamSet = carried?.parameterSet ?? false;
|
|
14923
14969
|
const auFlags = carried ? this.rtpAuTracker.classify(carried) : null;
|
|
14970
|
+
if (wantPlane && !this.annexBPlaneActive && auFlags?.keyframeAuStart === true) {
|
|
14971
|
+
this.armAnnexBPlane();
|
|
14972
|
+
this.rtpDepacketizer?.processPacket(rtpData);
|
|
14973
|
+
}
|
|
14974
|
+
if (auFlags) this.recordVideoStats(rtpData.length, auFlags.pictureStart, auFlags.keyframeAu);
|
|
14924
14975
|
this.rtspRestreamer.pushRtpPassthrough(rtpData, auFlags?.keyframeAu ?? false, auFlags?.parameterSet ?? false);
|
|
14925
14976
|
this.captureRtpForPreBuffer(rtpData);
|
|
14926
14977
|
if (this.rtpVideoCallbacks.size > 0) for (const [cb, entry] of this.rtpVideoCallbacks) try {
|
|
@@ -15136,11 +15187,83 @@ var StreamBroker = class StreamBroker {
|
|
|
15136
15187
|
* not fed from here.
|
|
15137
15188
|
*/
|
|
15138
15189
|
handleDepacketizedNal(depacketized) {
|
|
15139
|
-
if (depacketized.keyframe) this._lastNalKeyframe = true;
|
|
15140
|
-
if (depacketized.parameterSet) this._lastNalParamSet = true;
|
|
15141
15190
|
this.annexBAssembler.push(depacketized);
|
|
15142
15191
|
}
|
|
15143
15192
|
/**
|
|
15193
|
+
* Does anything consume the Annex-B plane right now? An encoded subscriber
|
|
15194
|
+
* (WebRTC Annex-B, fragments, a decode session), a pipe client, a clip ring
|
|
15195
|
+
* (it retains access units continuously, so it needs them built even with
|
|
15196
|
+
* nobody attached), or a restreamer still in Annex-B mode (no camera SDP yet).
|
|
15197
|
+
* The RTSP restream and the WebRTC RTP path are served from the source RTP
|
|
15198
|
+
* and never count. Cheap — four field reads — because it runs per packet.
|
|
15199
|
+
*/
|
|
15200
|
+
needsAnnexBPlane() {
|
|
15201
|
+
return this.encodedCallbacks.size > 0 || this.clipBuffer !== null || this.pipeServer.getClientCount() > 0 || !this.rtspRestreamer.rtpPassthroughMode;
|
|
15202
|
+
}
|
|
15203
|
+
/** The consumers that want the plane, for the arm/idle lines. */
|
|
15204
|
+
annexBPlaneConsumers() {
|
|
15205
|
+
return {
|
|
15206
|
+
encodedSubscribers: this.encodedCallbacks.size,
|
|
15207
|
+
clipRing: this.clipBuffer !== null,
|
|
15208
|
+
pipeClients: this.pipeServer.getClientCount(),
|
|
15209
|
+
restreamerAnnexB: !this.rtspRestreamer.rtpPassthroughMode
|
|
15210
|
+
};
|
|
15211
|
+
}
|
|
15212
|
+
/**
|
|
15213
|
+
* Start building access units from the keyframe AU the current packet opens.
|
|
15214
|
+
* The depacketizer and the assembler are clean here (reset when the plane
|
|
15215
|
+
* went idle, or never used), so nothing stale precedes that keyframe.
|
|
15216
|
+
*/
|
|
15217
|
+
armAnnexBPlane() {
|
|
15218
|
+
this.annexBPlaneActive = true;
|
|
15219
|
+
this.logger?.info("annex-b plane armed at a keyframe access unit", {
|
|
15220
|
+
tags: { deviceId: this.numericDeviceId },
|
|
15221
|
+
meta: this.annexBPlaneConsumers()
|
|
15222
|
+
});
|
|
15223
|
+
}
|
|
15224
|
+
/**
|
|
15225
|
+
* Stop building access units: the last consumer left. The picture in
|
|
15226
|
+
* progress is dropped, not flushed — there is nobody to hand it to, and a
|
|
15227
|
+
* partial picture must never reach the next consumer's clean start. Logged,
|
|
15228
|
+
* once per transition: a plane that is idle on purpose must not read as a
|
|
15229
|
+
* stream that stopped.
|
|
15230
|
+
*/
|
|
15231
|
+
idleAnnexBPlane() {
|
|
15232
|
+
this.annexBPlaneActive = false;
|
|
15233
|
+
this.rtpDepacketizer?.reset();
|
|
15234
|
+
this.annexBAssembler.reset();
|
|
15235
|
+
this.logger?.info("annex-b plane idle — no consumer, NAL reassembly skipped", { tags: { deviceId: this.numericDeviceId } });
|
|
15236
|
+
}
|
|
15237
|
+
/**
|
|
15238
|
+
* One place for the input stats, fed from whichever path owns the pictures:
|
|
15239
|
+
* the source RTP packets while the native reader is up (`onVideoRtp`), the
|
|
15240
|
+
* `EncodedPacket`s otherwise (`pushEncodedPacket`). `bytes` accumulate per
|
|
15241
|
+
* call; `inputFps`, `packetCount` and the IDR interval advance once per
|
|
15242
|
+
* picture — the window is closed on a picture start so a window of N frames
|
|
15243
|
+
* reads N, exactly as it did when every call was one access unit.
|
|
15244
|
+
*/
|
|
15245
|
+
recordVideoStats(bytes, pictureStart, keyframe) {
|
|
15246
|
+
this.totalBytes += bytes;
|
|
15247
|
+
this.bytesInWindow += bytes;
|
|
15248
|
+
if (!pictureStart) return;
|
|
15249
|
+
this.packetCount++;
|
|
15250
|
+
this.packetsInWindow++;
|
|
15251
|
+
const now = Date.now();
|
|
15252
|
+
this.lastPacketAt = now;
|
|
15253
|
+
const windowMs = now - this.windowStartMs;
|
|
15254
|
+
if (windowMs >= 2e3) {
|
|
15255
|
+
this.bitrateKbps = Math.round(this.bytesInWindow * 8 / windowMs);
|
|
15256
|
+
this.encodedInputFps = this.packetsInWindow / windowMs * 1e3;
|
|
15257
|
+
this.bytesInWindow = 0;
|
|
15258
|
+
this.packetsInWindow = 0;
|
|
15259
|
+
this.windowStartMs = now;
|
|
15260
|
+
}
|
|
15261
|
+
if (keyframe) {
|
|
15262
|
+
if (this.lastKeyframeMs > 0) this.idrIntervalMs = now - this.lastKeyframeMs;
|
|
15263
|
+
this.lastKeyframeMs = now;
|
|
15264
|
+
}
|
|
15265
|
+
}
|
|
15266
|
+
/**
|
|
15144
15267
|
* One assembled access unit → one `EncodedPacket` for every Annex-B
|
|
15145
15268
|
* consumer (prebuffer, clip ring, pipe, WebRTC). A keyframe packet is
|
|
15146
15269
|
* self-contained (VPS/SPS/PPS + every IDR slice), which is what a decoder
|
|
@@ -15561,10 +15684,7 @@ var StreamBroker = class StreamBroker {
|
|
|
15561
15684
|
this.annexBAssembler.flush();
|
|
15562
15685
|
this.annexBAssembler.reset();
|
|
15563
15686
|
this.rtpAuTracker.reset();
|
|
15564
|
-
|
|
15565
|
-
this.pushRtpFlagDepacketizer.reset();
|
|
15566
|
-
this.pushRtpFlagDepacketizer = null;
|
|
15567
|
-
}
|
|
15687
|
+
this.annexBPlaneActive = false;
|
|
15568
15688
|
this.resetRtpPreBuffer();
|
|
15569
15689
|
if (this.audioRtpDecoder) {
|
|
15570
15690
|
this.audioRtpDecoder.flush();
|