@camstack/addon-pipeline 1.1.20 → 1.1.21
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/decoder-ffmpeg/index.js +540 -285
- package/dist/decoder-ffmpeg/index.mjs +539 -284
- package/dist/{ffmpeg-args-common-c3p-nWtU.mjs → frame-dropper-CwkBTPGV.mjs} +22 -22
- package/dist/stream-broker/index.js +40 -20
- package/dist/stream-broker/index.mjs +30 -10
- package/package.json +1 -1
- package/dist/{ffmpeg-args-common-CASoNt42.js → frame-dropper-7RTo_YyG.js} +21 -21
|
@@ -1,24 +1,3 @@
|
|
|
1
|
-
//#region src/stream-broker/stream-broker/frame-dropper.ts
|
|
2
|
-
var FrameDropper = class {
|
|
3
|
-
intervalMs;
|
|
4
|
-
lastPassedAt = -Infinity;
|
|
5
|
-
constructor(maxFps) {
|
|
6
|
-
this.intervalMs = maxFps > 0 ? 1e3 / maxFps : 0;
|
|
7
|
-
}
|
|
8
|
-
shouldKeep() {
|
|
9
|
-
if (this.intervalMs === 0) return true;
|
|
10
|
-
const now = Date.now();
|
|
11
|
-
if (now - this.lastPassedAt >= this.intervalMs) {
|
|
12
|
-
this.lastPassedAt = now;
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
setMaxFps(maxFps) {
|
|
18
|
-
this.intervalMs = maxFps > 0 ? 1e3 / maxFps : 0;
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
//#endregion
|
|
22
1
|
//#region src/stream-broker/stream-broker/ffmpeg-args-common.ts
|
|
23
2
|
var AUDIO_ENCODER_BY_CODEC = {
|
|
24
3
|
opus: "libopus",
|
|
@@ -48,4 +27,25 @@ function logBannerArgs(level) {
|
|
|
48
27
|
];
|
|
49
28
|
}
|
|
50
29
|
//#endregion
|
|
51
|
-
|
|
30
|
+
//#region src/stream-broker/stream-broker/frame-dropper.ts
|
|
31
|
+
var FrameDropper = class {
|
|
32
|
+
intervalMs;
|
|
33
|
+
lastPassedAt = -Infinity;
|
|
34
|
+
constructor(maxFps) {
|
|
35
|
+
this.intervalMs = maxFps > 0 ? 1e3 / maxFps : 0;
|
|
36
|
+
}
|
|
37
|
+
shouldKeep() {
|
|
38
|
+
if (this.intervalMs === 0) return true;
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
if (now - this.lastPassedAt >= this.intervalMs) {
|
|
41
|
+
this.lastPassedAt = now;
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
setMaxFps(maxFps) {
|
|
47
|
+
this.intervalMs = maxFps > 0 ? 1e3 / maxFps : 0;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
export { audioEncoderArgs as n, logBannerArgs as r, FrameDropper as t };
|
|
@@ -4,7 +4,7 @@ Object.defineProperties(exports, {
|
|
|
4
4
|
});
|
|
5
5
|
const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-BnQ_awK4.js");
|
|
6
6
|
const require_dist = require("../dist-BiP1gPeY.js");
|
|
7
|
-
const
|
|
7
|
+
const require_frame_dropper = require("../frame-dropper-7RTo_YyG.js");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
node_fs = require_model_download_service_C_IHWnXx.__toESM(node_fs);
|
|
10
10
|
let node_path = require("node:path");
|
|
@@ -352,6 +352,11 @@ var FrameHandlePlane = class {
|
|
|
352
352
|
localNodeId;
|
|
353
353
|
subscriptions = /* @__PURE__ */ new Map();
|
|
354
354
|
sessions = /* @__PURE__ */ new Map();
|
|
355
|
+
/** In-flight `ensureSession` creation per format. Coalesces concurrent
|
|
356
|
+
* same-format callers onto ONE decoder session — without it two callers
|
|
357
|
+
* both pass the `sessions.get` check, both `createSession` (two ffmpeg),
|
|
358
|
+
* and the second registration orphans the first with no `destroySession`. */
|
|
359
|
+
sessionCreating = /* @__PURE__ */ new Map();
|
|
355
360
|
disposed = false;
|
|
356
361
|
/** Re-entrancy guard for `armPendingSessions` — collapses the per-packet
|
|
357
362
|
* `pushPacket` calls into a single in-flight deferred-arm pass. */
|
|
@@ -626,15 +631,29 @@ var FrameHandlePlane = class {
|
|
|
626
631
|
existing.subscriberIds.add(subscriptionId);
|
|
627
632
|
return;
|
|
628
633
|
}
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
format
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
634
|
+
const inflight = this.sessionCreating.get(format);
|
|
635
|
+
if (inflight) {
|
|
636
|
+
await inflight;
|
|
637
|
+
this.sessions.get(format)?.subscriberIds.add(subscriptionId);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
const creation = (async () => {
|
|
641
|
+
const subscriberIds = new Set([subscriptionId]);
|
|
642
|
+
for (const subscription of this.subscriptions.values()) if (subscription.format === format) subscriberIds.add(subscription.id);
|
|
643
|
+
const session = {
|
|
644
|
+
format,
|
|
645
|
+
proxy: null,
|
|
646
|
+
nodeId: null,
|
|
647
|
+
subscriberIds
|
|
648
|
+
};
|
|
649
|
+
if (await this.startSessionDecoder(session)) this.sessions.set(format, session);
|
|
650
|
+
})();
|
|
651
|
+
this.sessionCreating.set(format, creation);
|
|
652
|
+
try {
|
|
653
|
+
await creation;
|
|
654
|
+
} finally {
|
|
655
|
+
this.sessionCreating.delete(format);
|
|
656
|
+
}
|
|
638
657
|
}
|
|
639
658
|
/**
|
|
640
659
|
* Create the `frameSink: 'shm'` decoder for a `FormatSession` and start
|
|
@@ -6502,6 +6521,7 @@ var RtspListenServer = class RtspListenServer {
|
|
|
6502
6521
|
releaseOnce();
|
|
6503
6522
|
}, isMuted);
|
|
6504
6523
|
restreamer.addSession(session);
|
|
6524
|
+
releaseOnce();
|
|
6505
6525
|
socket.unshift(Buffer.from(requestBuffer, "ascii"));
|
|
6506
6526
|
}
|
|
6507
6527
|
/**
|
|
@@ -6704,23 +6724,23 @@ function pickVideoEncoder(target, encoders, hwAccel) {
|
|
|
6704
6724
|
/** Audio encoder args for a transcode target (preset values over the shared low-level builder). */
|
|
6705
6725
|
function pickAudioEncoderArgs(audio) {
|
|
6706
6726
|
switch (audio) {
|
|
6707
|
-
case "aac": return
|
|
6727
|
+
case "aac": return require_frame_dropper.audioEncoderArgs("aac", {
|
|
6708
6728
|
bitrateKbps: 128,
|
|
6709
6729
|
sampleRateHz: 48e3,
|
|
6710
6730
|
channels: 2
|
|
6711
6731
|
});
|
|
6712
|
-
case "opus": return
|
|
6732
|
+
case "opus": return require_frame_dropper.audioEncoderArgs("opus", {
|
|
6713
6733
|
bitrateKbps: 64,
|
|
6714
6734
|
sampleRateHz: 48e3,
|
|
6715
6735
|
channels: 2
|
|
6716
6736
|
});
|
|
6717
|
-
case "pcmu": return
|
|
6737
|
+
case "pcmu": return require_frame_dropper.audioEncoderArgs("pcmu", {
|
|
6718
6738
|
sampleRateHz: 8e3,
|
|
6719
6739
|
channels: 1
|
|
6720
6740
|
});
|
|
6721
|
-
case "copy": return
|
|
6741
|
+
case "copy": return require_frame_dropper.audioEncoderArgs("copy");
|
|
6722
6742
|
case "none": return ["-an"];
|
|
6723
|
-
default: return
|
|
6743
|
+
default: return require_frame_dropper.audioEncoderArgs("aac", { bitrateKbps: 128 });
|
|
6724
6744
|
}
|
|
6725
6745
|
}
|
|
6726
6746
|
/**
|
|
@@ -6785,7 +6805,7 @@ function buildFfmpegArgs$1(inv) {
|
|
|
6785
6805
|
const threadArgs = inv.threadCount > 0 ? ["-threads", String(inv.threadCount)] : [];
|
|
6786
6806
|
const audioArgs = inv.sink.kind === "stdout" && (inv.sink.container === "h264" || inv.sink.container === "hevc") ? ["-an"] : pickAudioEncoderArgs(inv.audio);
|
|
6787
6807
|
return [
|
|
6788
|
-
...
|
|
6808
|
+
...require_frame_dropper.logBannerArgs("warning"),
|
|
6789
6809
|
...decodeArgs,
|
|
6790
6810
|
"-rtsp_transport",
|
|
6791
6811
|
"tcp",
|
|
@@ -11586,7 +11606,7 @@ function planAudioSilencePadPackets(input) {
|
|
|
11586
11606
|
* audio plane honours the profile literally.
|
|
11587
11607
|
*/
|
|
11588
11608
|
function buildTranscodeFfmpegArgs(inputFormat, profile, decodeHwAccel, sourceUrl) {
|
|
11589
|
-
const args = [...
|
|
11609
|
+
const args = [...require_frame_dropper.logBannerArgs("error")];
|
|
11590
11610
|
if (profile.inputArgs?.length) args.push(...profile.inputArgs);
|
|
11591
11611
|
if (decodeHwAccel && decodeHwAccel !== "none") args.push("-hwaccel", decodeHwAccel);
|
|
11592
11612
|
args.push("-fflags", "+discardcorrupt", "-rtsp_transport", "tcp", "-i", sourceUrl);
|
|
@@ -11608,7 +11628,7 @@ function buildTranscodeFfmpegArgs(inputFormat, profile, decodeHwAccel, sourceUrl
|
|
|
11608
11628
|
if (profile.audio === "passthrough") args.push("-an");
|
|
11609
11629
|
else {
|
|
11610
11630
|
const a = profile.audio;
|
|
11611
|
-
args.push(...
|
|
11631
|
+
args.push(...require_frame_dropper.audioEncoderArgs(a.codec, {
|
|
11612
11632
|
bitrateKbps: a.bitrateKbps,
|
|
11613
11633
|
sampleRateHz: a.sampleRateHz,
|
|
11614
11634
|
channels: a.channels
|
|
@@ -16927,7 +16947,7 @@ function codecToInputFormat(codec) {
|
|
|
16927
16947
|
function buildFfmpegArgs(config) {
|
|
16928
16948
|
const inputFormat = codecToInputFormat(config.codec);
|
|
16929
16949
|
const args = [
|
|
16930
|
-
...
|
|
16950
|
+
...require_frame_dropper.logBannerArgs("error"),
|
|
16931
16951
|
"-fflags",
|
|
16932
16952
|
"+nobuffer+flush_packets",
|
|
16933
16953
|
"-flags",
|
|
@@ -16964,7 +16984,7 @@ var FfmpegDecoderSession = class {
|
|
|
16964
16984
|
constructor(config, logger = noopLogger) {
|
|
16965
16985
|
this.config = { ...config };
|
|
16966
16986
|
this.logger = logger;
|
|
16967
|
-
this.frameDropper = new
|
|
16987
|
+
this.frameDropper = new require_frame_dropper.FrameDropper(config.maxFps);
|
|
16968
16988
|
this.spawnFfmpeg();
|
|
16969
16989
|
}
|
|
16970
16990
|
spawnFfmpeg() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __require } from "../chunk-BdkLduGY.mjs";
|
|
2
2
|
import { A as nodePin, B as BaseAddon, D as maskUrlCredentials, G as asJsonObject, H as DeviceFeature, I as streamBrokerCapability, Q as parseProfileBrokerId, R as webrtcSessionCapability, U as DeviceType, V as CAM_PROFILE_ORDER, W as EventCategory, X as makeSourceBrokerId, Y as makeProfileBrokerId, c as EncodeProfileSchema, ct as object, dt as union, f as RingBuffer, it as discriminatedUnion, lt as record, m as addonWidgetsSourceCapability, ot as literal, q as createEvent, rt as boolean, st as number, tt as _enum, ut as string, v as cameraStreamsCapability, z as errMsg } from "../dist-tzhTTRq4.mjs";
|
|
3
3
|
import { t as createFileDataPlaneHandler } from "../model-download-service-C-IHWnXx-3Mmeob3l.mjs";
|
|
4
|
-
import { n as
|
|
4
|
+
import { n as audioEncoderArgs, r as logBannerArgs, t as FrameDropper } from "../frame-dropper-CwkBTPGV.mjs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as path$1 from "node:path";
|
|
@@ -348,6 +348,11 @@ var FrameHandlePlane = class {
|
|
|
348
348
|
localNodeId;
|
|
349
349
|
subscriptions = /* @__PURE__ */ new Map();
|
|
350
350
|
sessions = /* @__PURE__ */ new Map();
|
|
351
|
+
/** In-flight `ensureSession` creation per format. Coalesces concurrent
|
|
352
|
+
* same-format callers onto ONE decoder session — without it two callers
|
|
353
|
+
* both pass the `sessions.get` check, both `createSession` (two ffmpeg),
|
|
354
|
+
* and the second registration orphans the first with no `destroySession`. */
|
|
355
|
+
sessionCreating = /* @__PURE__ */ new Map();
|
|
351
356
|
disposed = false;
|
|
352
357
|
/** Re-entrancy guard for `armPendingSessions` — collapses the per-packet
|
|
353
358
|
* `pushPacket` calls into a single in-flight deferred-arm pass. */
|
|
@@ -622,15 +627,29 @@ var FrameHandlePlane = class {
|
|
|
622
627
|
existing.subscriberIds.add(subscriptionId);
|
|
623
628
|
return;
|
|
624
629
|
}
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
format
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
630
|
+
const inflight = this.sessionCreating.get(format);
|
|
631
|
+
if (inflight) {
|
|
632
|
+
await inflight;
|
|
633
|
+
this.sessions.get(format)?.subscriberIds.add(subscriptionId);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const creation = (async () => {
|
|
637
|
+
const subscriberIds = new Set([subscriptionId]);
|
|
638
|
+
for (const subscription of this.subscriptions.values()) if (subscription.format === format) subscriberIds.add(subscription.id);
|
|
639
|
+
const session = {
|
|
640
|
+
format,
|
|
641
|
+
proxy: null,
|
|
642
|
+
nodeId: null,
|
|
643
|
+
subscriberIds
|
|
644
|
+
};
|
|
645
|
+
if (await this.startSessionDecoder(session)) this.sessions.set(format, session);
|
|
646
|
+
})();
|
|
647
|
+
this.sessionCreating.set(format, creation);
|
|
648
|
+
try {
|
|
649
|
+
await creation;
|
|
650
|
+
} finally {
|
|
651
|
+
this.sessionCreating.delete(format);
|
|
652
|
+
}
|
|
634
653
|
}
|
|
635
654
|
/**
|
|
636
655
|
* Create the `frameSink: 'shm'` decoder for a `FormatSession` and start
|
|
@@ -6498,6 +6517,7 @@ var RtspListenServer = class RtspListenServer {
|
|
|
6498
6517
|
releaseOnce();
|
|
6499
6518
|
}, isMuted);
|
|
6500
6519
|
restreamer.addSession(session);
|
|
6520
|
+
releaseOnce();
|
|
6501
6521
|
socket.unshift(Buffer.from(requestBuffer, "ascii"));
|
|
6502
6522
|
}
|
|
6503
6523
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-pipeline",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.21",
|
|
4
4
|
"description": "CamStack Pipeline bundle — runner, detection, motion, decoders, audio + stream broker. Multi-entry npm package shipping 7 addons under a single bundle.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -1,24 +1,3 @@
|
|
|
1
|
-
//#region src/stream-broker/stream-broker/frame-dropper.ts
|
|
2
|
-
var FrameDropper = class {
|
|
3
|
-
intervalMs;
|
|
4
|
-
lastPassedAt = -Infinity;
|
|
5
|
-
constructor(maxFps) {
|
|
6
|
-
this.intervalMs = maxFps > 0 ? 1e3 / maxFps : 0;
|
|
7
|
-
}
|
|
8
|
-
shouldKeep() {
|
|
9
|
-
if (this.intervalMs === 0) return true;
|
|
10
|
-
const now = Date.now();
|
|
11
|
-
if (now - this.lastPassedAt >= this.intervalMs) {
|
|
12
|
-
this.lastPassedAt = now;
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
setMaxFps(maxFps) {
|
|
18
|
-
this.intervalMs = maxFps > 0 ? 1e3 / maxFps : 0;
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
//#endregion
|
|
22
1
|
//#region src/stream-broker/stream-broker/ffmpeg-args-common.ts
|
|
23
2
|
var AUDIO_ENCODER_BY_CODEC = {
|
|
24
3
|
opus: "libopus",
|
|
@@ -48,6 +27,27 @@ function logBannerArgs(level) {
|
|
|
48
27
|
];
|
|
49
28
|
}
|
|
50
29
|
//#endregion
|
|
30
|
+
//#region src/stream-broker/stream-broker/frame-dropper.ts
|
|
31
|
+
var FrameDropper = class {
|
|
32
|
+
intervalMs;
|
|
33
|
+
lastPassedAt = -Infinity;
|
|
34
|
+
constructor(maxFps) {
|
|
35
|
+
this.intervalMs = maxFps > 0 ? 1e3 / maxFps : 0;
|
|
36
|
+
}
|
|
37
|
+
shouldKeep() {
|
|
38
|
+
if (this.intervalMs === 0) return true;
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
if (now - this.lastPassedAt >= this.intervalMs) {
|
|
41
|
+
this.lastPassedAt = now;
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
setMaxFps(maxFps) {
|
|
47
|
+
this.intervalMs = maxFps > 0 ? 1e3 / maxFps : 0;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
51
|
Object.defineProperty(exports, "FrameDropper", {
|
|
52
52
|
enumerable: true,
|
|
53
53
|
get: function() {
|