@camstack/types 1.2.40 → 1.2.42
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/addon.js +1 -1
- package/dist/addon.mjs +1 -1
- package/dist/canonical-hash-7nfBbEqR.mjs +35 -0
- package/dist/canonical-hash-BcZHRHIx.js +40 -0
- package/dist/capabilities/index.d.ts +2 -2
- package/dist/capabilities/notification-rules.cap.d.ts +41 -0
- package/dist/capabilities/pipeline-analytics.cap.d.ts +93 -6
- package/dist/capabilities/pipeline-orchestrator.cap.d.ts +123 -0
- package/dist/capabilities/pipeline-runner.cap.d.ts +143 -7
- package/dist/capabilities/platform-probe.cap.d.ts +3 -3
- package/dist/capabilities/recording.cap.d.ts +3 -0
- package/dist/capabilities/stream-broker.cap.d.ts +300 -0
- package/dist/encode-profile.d.ts +2 -0
- package/dist/ffmpeg/encode-defaults.d.ts +89 -0
- package/dist/ffmpeg/hwaccel.d.ts +98 -0
- package/dist/ffmpeg/invocation.d.ts +250 -0
- package/dist/ffmpeg/process.d.ts +135 -0
- package/dist/ffmpeg/sharing-key.d.ts +39 -0
- package/dist/generated/addon-api.d.ts +56 -0
- package/dist/generated/device-proxy.d.ts +1 -1
- package/dist/generated/method-access-map.d.ts +1 -1
- package/dist/generated/system-proxy.d.ts +2 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1595 -28
- package/dist/index.mjs +1548 -29
- package/dist/interfaces/camera-switches.d.ts +217 -0
- package/dist/interfaces/ops-log.d.ts +4 -0
- package/dist/interfaces/pipeline-runner-capability.d.ts +9 -1
- package/dist/node.d.ts +2 -0
- package/dist/node.js +270 -36
- package/dist/node.mjs +269 -36
- package/dist/pipeline/detail-crop.d.ts +122 -0
- package/dist/{sleep-CXimb854.mjs → sleep-BmNKsY7v.mjs} +5 -0
- package/dist/{sleep-DTce7-ch.js → sleep-Cvi1JxZp.js} +5 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { type InferProvider } from './capability-definition.js';
|
|
3
2
|
import type { ConfigField } from '../interfaces/config-ui.js';
|
|
3
|
+
import { type InferProvider } from './capability-definition.js';
|
|
4
4
|
/**
|
|
5
5
|
* A bounding box in NORMALIZED [0,1] frame coordinates for `getNativeCrop`. The
|
|
6
6
|
* decode worker resolves it against the RETAINED native frame's real pixel dims,
|
|
@@ -60,6 +60,27 @@ declare const DetailResultSchema: z.ZodObject<{
|
|
|
60
60
|
}, z.core.$strip>;
|
|
61
61
|
export type DetailParent = z.infer<typeof DetailParentSchema>;
|
|
62
62
|
export type DetailResult = z.infer<typeof DetailResultSchema>;
|
|
63
|
+
/**
|
|
64
|
+
* Why an executing node REFUSED a stateless step run (`runStatelessStep`).
|
|
65
|
+
*
|
|
66
|
+
* A refusal is a first-class answer, not an error, because the caller's next
|
|
67
|
+
* move depends on WHICH one it is — and because "the pass produced nothing"
|
|
68
|
+
* must never be reachable without a named, counted cause. The two tiers:
|
|
69
|
+
*
|
|
70
|
+
* - **node-level** (`unknown-step`, `model-not-servable`) — this node can
|
|
71
|
+
* never serve this (step, model) pair. The caller drops it from its rotation
|
|
72
|
+
* and retries the same work elsewhere; nothing about the work changes.
|
|
73
|
+
* - **work-level** (`unreadable-frame`, `execution-failed`) — this node is
|
|
74
|
+
* fine, this one request is not. Retrying it on another node would only
|
|
75
|
+
* spread the same failure.
|
|
76
|
+
*/
|
|
77
|
+
declare const StatelessStepRefusalSchema: z.ZodEnum<{
|
|
78
|
+
"unknown-step": "unknown-step";
|
|
79
|
+
"model-not-servable": "model-not-servable";
|
|
80
|
+
"unreadable-frame": "unreadable-frame";
|
|
81
|
+
"execution-failed": "execution-failed";
|
|
82
|
+
}>;
|
|
83
|
+
export type StatelessStepRefusal = z.infer<typeof StatelessStepRefusalSchema>;
|
|
63
84
|
/**
|
|
64
85
|
* Source enum for motion signals fed to the runner. Extensible — add
|
|
65
86
|
* new variants here when new motion-trigger paths are wired in
|
|
@@ -533,12 +554,29 @@ export declare const pipelineRunnerCapability: {
|
|
|
533
554
|
* for a single tracked detection. The per-frame plane (`runPipeline`
|
|
534
555
|
* with `plane: 'frame'`) skips crop children entirely; a track-level
|
|
535
556
|
* caller invokes this per-track, on its own cadence, instead of on
|
|
536
|
-
* every frame.
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
*
|
|
557
|
+
* every frame. `steps` narrows which configured children to run
|
|
558
|
+
* (default: all configured children for `parent.className`).
|
|
559
|
+
*
|
|
560
|
+
* ## Three pixel sources, and WHO decides the rectangle
|
|
561
|
+
*
|
|
562
|
+
* Tried in this order; the difference between them is not the pixels but
|
|
563
|
+
* which side derives the crop, and confusing the two is how the CLIP index
|
|
564
|
+
* ended up holding two incomparable feature spaces:
|
|
565
|
+
*
|
|
566
|
+
* 1. `frameHandle` — shm lease/session, preferred and zero-copy. **The
|
|
567
|
+
* runner cuts**, applying the cluster crop convention.
|
|
568
|
+
* 2. `frameJpeg` — a FULL FRAME supplied by the caller (the embedding
|
|
569
|
+
* rebuild's stored key frame). **The runner cuts**, applying the same
|
|
570
|
+
* convention to the same function, so a rebuilt vector lands in the same
|
|
571
|
+
* feature space as a live one.
|
|
572
|
+
* 3. `cropJpeg` — an ALREADY-CUT tile. **The caller decided the
|
|
573
|
+
* rectangle**; the runner applies NO padding and no squaring and feeds
|
|
574
|
+
* it to the model verbatim. Only for the lease-miss retry, where the
|
|
575
|
+
* caller holds pixels the runner can no longer reach.
|
|
576
|
+
*
|
|
577
|
+
* Returns `null` when no source resolves (handle evicted and no fallback
|
|
578
|
+
* supplied), when the camera is not attached, or when no enabled step
|
|
579
|
+
* matches — every one of those is logged on the runner with the deviceId.
|
|
542
580
|
*/
|
|
543
581
|
readonly runDetailSubtree: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
|
|
544
582
|
deviceId: z.ZodNumber;
|
|
@@ -560,6 +598,7 @@ export declare const pipelineRunnerCapability: {
|
|
|
560
598
|
nodeId: z.ZodString;
|
|
561
599
|
slotCount: z.ZodNumber;
|
|
562
600
|
}, z.core.$strip>>;
|
|
601
|
+
frameJpeg: z.ZodOptional<z.ZodString>;
|
|
563
602
|
cropJpeg: z.ZodOptional<z.ZodString>;
|
|
564
603
|
parent: z.ZodObject<{
|
|
565
604
|
bbox: z.ZodObject<{
|
|
@@ -588,6 +627,92 @@ export declare const pipelineRunnerCapability: {
|
|
|
588
627
|
nativeFaceShortSidePx: z.ZodOptional<z.ZodNumber>;
|
|
589
628
|
}, z.core.$strip>>;
|
|
590
629
|
}, z.core.$strip>>, "mutation">;
|
|
630
|
+
/**
|
|
631
|
+
* Run ONE enrichment step against caller-supplied pixels, with NO camera
|
|
632
|
+
* session — no attach, no frame handle, no device affinity.
|
|
633
|
+
*
|
|
634
|
+
* ## Why this exists next to `runDetailSubtree` and not inside it
|
|
635
|
+
*
|
|
636
|
+
* `runDetailSubtree` resolves its step tree from the LIVE ATTACH STATE. That
|
|
637
|
+
* is correct for it: its reason to exist is a frame HANDLE, and a handle is
|
|
638
|
+
* only resolvable while a decode session is open. But it makes the method
|
|
639
|
+
* useless to anything walking history — cameras run `detectionMode:
|
|
640
|
+
* 'on-motion'` and detach ~30 s after motion stops, so a fleet-wide
|
|
641
|
+
* embedding rebuild found most cameras detached and rebuilt only whatever
|
|
642
|
+
* happened to be attached at that second (measured 2026-08-06: 1125 tracks
|
|
643
|
+
* scanned, 121 rebuilt, 601 refused as "camera is not attached").
|
|
644
|
+
*
|
|
645
|
+
* A caller that ships its own pixels needs none of that machinery. So this
|
|
646
|
+
* method takes the three things a step actually needs — an image, a box, a
|
|
647
|
+
* step id — resolves the step from the CATALOG rather than from an
|
|
648
|
+
* attachment, and runs it on whichever node the caller pinned.
|
|
649
|
+
*
|
|
650
|
+
* ## What it deliberately keeps
|
|
651
|
+
*
|
|
652
|
+
* The crop rectangle is derived by the SAME `deriveDetailCropRect` call the
|
|
653
|
+
* live detail plane uses, from the same cluster-wide convention
|
|
654
|
+
* ([D52](../../../docs/decisions/adr-0052.md)). `frameJpeg` is a FULL frame,
|
|
655
|
+
* never a pre-cut tile, for exactly the reason `runDetailSubtree` documents:
|
|
656
|
+
* a caller that cuts is a second feature space.
|
|
657
|
+
*
|
|
658
|
+
* ## What it deliberately drops
|
|
659
|
+
*
|
|
660
|
+
* No device jump and no `deviceKey`: the jump roster comes from the attach
|
|
661
|
+
* config, which does not exist here. The step runs on the node's default
|
|
662
|
+
* engine. This is a maintenance path, not a latency-sensitive one.
|
|
663
|
+
*
|
|
664
|
+
* ## Routing
|
|
665
|
+
*
|
|
666
|
+
* NOT device-bound. `sourceDeviceId` is DIAGNOSTIC — the camera whose track
|
|
667
|
+
* these pixels came from, so every log line on the executing node can carry
|
|
668
|
+
* `tags: { deviceId }`. It is deliberately not named `deviceId`: that field
|
|
669
|
+
* is a routing hint that would send the call to the camera's owning node,
|
|
670
|
+
* which is the placement constraint this method exists to remove. Callers
|
|
671
|
+
* choose the node with `nodePin(nodeId)`; unpinned, an unowned call is
|
|
672
|
+
* served hub-in-process like any other singleton.
|
|
673
|
+
*/
|
|
674
|
+
readonly runStatelessStep: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
|
|
675
|
+
stepId: z.ZodString;
|
|
676
|
+
modelId: z.ZodString;
|
|
677
|
+
frameJpeg: z.ZodString;
|
|
678
|
+
bbox: z.ZodObject<{
|
|
679
|
+
x: z.ZodNumber;
|
|
680
|
+
y: z.ZodNumber;
|
|
681
|
+
w: z.ZodNumber;
|
|
682
|
+
h: z.ZodNumber;
|
|
683
|
+
}, z.core.$strip>;
|
|
684
|
+
className: z.ZodString;
|
|
685
|
+
sourceDeviceId: z.ZodNumber;
|
|
686
|
+
}, z.core.$strip>, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
687
|
+
kind: z.ZodLiteral<"ran">;
|
|
688
|
+
nodeId: z.ZodString;
|
|
689
|
+
modelId: z.ZodString;
|
|
690
|
+
details: z.ZodArray<z.ZodObject<{
|
|
691
|
+
stepId: z.ZodString;
|
|
692
|
+
className: z.ZodString;
|
|
693
|
+
score: z.ZodNumber;
|
|
694
|
+
bbox: z.ZodOptional<z.ZodObject<{
|
|
695
|
+
x: z.ZodNumber;
|
|
696
|
+
y: z.ZodNumber;
|
|
697
|
+
w: z.ZodNumber;
|
|
698
|
+
h: z.ZodNumber;
|
|
699
|
+
}, z.core.$strip>>;
|
|
700
|
+
embedding: z.ZodOptional<z.ZodString>;
|
|
701
|
+
label: z.ZodOptional<z.ZodString>;
|
|
702
|
+
alignedCropJpeg: z.ZodOptional<z.ZodString>;
|
|
703
|
+
nativeFaceShortSidePx: z.ZodOptional<z.ZodNumber>;
|
|
704
|
+
}, z.core.$strip>>;
|
|
705
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
706
|
+
kind: z.ZodLiteral<"refused">;
|
|
707
|
+
nodeId: z.ZodString;
|
|
708
|
+
reason: z.ZodEnum<{
|
|
709
|
+
"unknown-step": "unknown-step";
|
|
710
|
+
"model-not-servable": "model-not-servable";
|
|
711
|
+
"unreadable-frame": "unreadable-frame";
|
|
712
|
+
"execution-failed": "execution-failed";
|
|
713
|
+
}>;
|
|
714
|
+
detail: z.ZodString;
|
|
715
|
+
}, z.core.$strip>], "kind">, "mutation">;
|
|
591
716
|
};
|
|
592
717
|
};
|
|
593
718
|
export type IPipelineRunnerProvider = InferProvider<typeof pipelineRunnerCapability>;
|
|
@@ -605,4 +730,15 @@ export type IPipelineRunnerProvider = InferProvider<typeof pipelineRunnerCapabil
|
|
|
605
730
|
*/
|
|
606
731
|
export type RunDetailSubtreeInput = Parameters<IPipelineRunnerProvider['runDetailSubtree']>[0];
|
|
607
732
|
export type RunDetailSubtreeResult = NonNullable<Awaited<ReturnType<IPipelineRunnerProvider['runDetailSubtree']>>>;
|
|
733
|
+
/**
|
|
734
|
+
* Named input/output types for `runStatelessStep`, derived the same way and for
|
|
735
|
+
* the same reason as the `runDetailSubtree` pair above — the method's input
|
|
736
|
+
* stays an inline `z.object({…})` literal so `scripts/lib/parse-cap.ts` keeps
|
|
737
|
+
* classifying it correctly. Here the classification wanted is the OPPOSITE one:
|
|
738
|
+
* there is no `deviceId: z.number(` in the body, so the method is NOT
|
|
739
|
+
* device-bound and never lands on `DeviceProxy` — placement is the caller's
|
|
740
|
+
* `nodePin`, not the camera's owner.
|
|
741
|
+
*/
|
|
742
|
+
export type RunStatelessStepInput = Parameters<IPipelineRunnerProvider['runStatelessStep']>[0];
|
|
743
|
+
export type RunStatelessStepResult = Awaited<ReturnType<IPipelineRunnerProvider['runStatelessStep']>>;
|
|
608
744
|
export { RunnerCameraConfigSchema, RunnerLocalLoadSchema, RunnerLocalMetricsSchema };
|
|
@@ -277,12 +277,12 @@ export declare const platformProbeCapability: {
|
|
|
277
277
|
coreml: "coreml";
|
|
278
278
|
openvino: "openvino";
|
|
279
279
|
none: "none";
|
|
280
|
-
tensorrt: "tensorrt";
|
|
281
|
-
cuda: "cuda";
|
|
282
280
|
videotoolbox: "videotoolbox";
|
|
283
|
-
nvdec: "nvdec";
|
|
284
281
|
vaapi: "vaapi";
|
|
285
282
|
qsv: "qsv";
|
|
283
|
+
cuda: "cuda";
|
|
284
|
+
tensorrt: "tensorrt";
|
|
285
|
+
nvdec: "nvdec";
|
|
286
286
|
d3d11va: "d3d11va";
|
|
287
287
|
dxva2: "dxva2";
|
|
288
288
|
amf: "amf";
|
|
@@ -355,6 +355,7 @@ export declare const recordingCapability: {
|
|
|
355
355
|
operator: "operator";
|
|
356
356
|
retention: "retention";
|
|
357
357
|
quota: "quota";
|
|
358
|
+
maintenance: "maintenance";
|
|
358
359
|
}>>;
|
|
359
360
|
}, z.core.$strip>, z.ZodObject<{
|
|
360
361
|
floorMs: z.ZodNullable<z.ZodNumber>;
|
|
@@ -393,12 +394,14 @@ export declare const recordingCapability: {
|
|
|
393
394
|
rescan: "rescan";
|
|
394
395
|
"retention-run": "retention-run";
|
|
395
396
|
relocate: "relocate";
|
|
397
|
+
"orphan-audit": "orphan-audit";
|
|
396
398
|
}>;
|
|
397
399
|
reason: z.ZodEnum<{
|
|
398
400
|
manual: "manual";
|
|
399
401
|
operator: "operator";
|
|
400
402
|
retention: "retention";
|
|
401
403
|
quota: "quota";
|
|
404
|
+
maintenance: "maintenance";
|
|
402
405
|
}>;
|
|
403
406
|
deviceId: z.ZodNullable<z.ZodNumber>;
|
|
404
407
|
nodeId: z.ZodString;
|
|
@@ -279,6 +279,179 @@ export declare const RtpSourceSchema: z.ZodObject<{
|
|
|
279
279
|
pipelineKey: z.ZodString;
|
|
280
280
|
}, z.core.$strip>;
|
|
281
281
|
export type RtpSource = z.infer<typeof RtpSourceSchema>;
|
|
282
|
+
/**
|
|
283
|
+
* The encode request — **structured and serialisable, with NO raw-flag escape
|
|
284
|
+
* hatch.** This is deliberate and it is the one lesson taken from
|
|
285
|
+
* `getStreamWithCodec`: that method's `outputArgs: string[]` is simultaneously
|
|
286
|
+
* its extensibility mechanism AND part of `pipelineKeyFor`'s sharing key, so
|
|
287
|
+
* adding a flag silently forks the shared child, and two consumers that mean
|
|
288
|
+
* the same thing but spell it differently never share. Here every knob is a
|
|
289
|
+
* NAMED field: a new requirement becomes a schema field (and a codegen run),
|
|
290
|
+
* never an opaque array.
|
|
291
|
+
*
|
|
292
|
+
* `inputArgs` / `outputArgs` are omitted from the profile for the same reason.
|
|
293
|
+
* The operator-facing derived-stream transform editor still has them — that is
|
|
294
|
+
* a different surface (`publishCameraStream({ kind: 'derived' })`) with a
|
|
295
|
+
* different purpose (reshaping a badly-behaved SOURCE), and it is unchanged.
|
|
296
|
+
*/
|
|
297
|
+
export declare const EgressEncodeSchema: z.ZodObject<{
|
|
298
|
+
audio: z.ZodUnion<readonly [z.ZodLiteral<"passthrough">, z.ZodObject<{
|
|
299
|
+
codec: z.ZodEnum<{
|
|
300
|
+
copy: "copy";
|
|
301
|
+
opus: "opus";
|
|
302
|
+
aac: "aac";
|
|
303
|
+
pcmu: "pcmu";
|
|
304
|
+
pcma: "pcma";
|
|
305
|
+
}>;
|
|
306
|
+
bitrateKbps: z.ZodOptional<z.ZodNumber>;
|
|
307
|
+
sampleRateHz: z.ZodOptional<z.ZodNumber>;
|
|
308
|
+
channels: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>]>>;
|
|
309
|
+
}, z.core.$strip>]>;
|
|
310
|
+
video: z.ZodObject<{
|
|
311
|
+
codec: z.ZodEnum<{
|
|
312
|
+
h264: "h264";
|
|
313
|
+
h265: "h265";
|
|
314
|
+
copy: "copy";
|
|
315
|
+
}>;
|
|
316
|
+
profile: z.ZodOptional<z.ZodEnum<{
|
|
317
|
+
high: "high";
|
|
318
|
+
baseline: "baseline";
|
|
319
|
+
main: "main";
|
|
320
|
+
}>>;
|
|
321
|
+
level: z.ZodOptional<z.ZodString>;
|
|
322
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
323
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
324
|
+
fps: z.ZodOptional<z.ZodNumber>;
|
|
325
|
+
bitrateKbps: z.ZodOptional<z.ZodNumber>;
|
|
326
|
+
gopFrames: z.ZodOptional<z.ZodNumber>;
|
|
327
|
+
bf: z.ZodOptional<z.ZodNumber>;
|
|
328
|
+
preset: z.ZodOptional<z.ZodEnum<{
|
|
329
|
+
fast: "fast";
|
|
330
|
+
ultrafast: "ultrafast";
|
|
331
|
+
superfast: "superfast";
|
|
332
|
+
veryfast: "veryfast";
|
|
333
|
+
faster: "faster";
|
|
334
|
+
medium: "medium";
|
|
335
|
+
}>>;
|
|
336
|
+
tune: z.ZodOptional<z.ZodEnum<{
|
|
337
|
+
zerolatency: "zerolatency";
|
|
338
|
+
film: "film";
|
|
339
|
+
animation: "animation";
|
|
340
|
+
}>>;
|
|
341
|
+
}, z.core.$strip>;
|
|
342
|
+
}, z.core.$strip>;
|
|
343
|
+
export type EgressEncode = z.infer<typeof EgressEncodeSchema>;
|
|
344
|
+
/**
|
|
345
|
+
* How the encoder is bounded. `'tight'` is a one-second VBV window for a
|
|
346
|
+
* consumer whose budget is enforced per second (HomeKit); `'relaxed'` is two
|
|
347
|
+
* seconds, letting a keyframe spike borrow from the next second (a browser,
|
|
348
|
+
* an Echo). Named rather than numeric so the INTENT survives.
|
|
349
|
+
*/
|
|
350
|
+
export declare const EgressRateControlSchema: z.ZodEnum<{
|
|
351
|
+
tight: "tight";
|
|
352
|
+
relaxed: "relaxed";
|
|
353
|
+
}>;
|
|
354
|
+
export type EgressRateControl = z.infer<typeof EgressRateControlSchema>;
|
|
355
|
+
export declare const EgressTranscodeRequestSchema: z.ZodObject<{
|
|
356
|
+
deviceId: z.ZodNumber;
|
|
357
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
358
|
+
kind: z.ZodLiteral<"profile">;
|
|
359
|
+
profile: z.ZodEnum<{
|
|
360
|
+
high: "high";
|
|
361
|
+
mid: "mid";
|
|
362
|
+
low: "low";
|
|
363
|
+
}>;
|
|
364
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
365
|
+
kind: z.ZodLiteral<"cam-stream">;
|
|
366
|
+
camStreamId: z.ZodString;
|
|
367
|
+
}, z.core.$strip>], "kind">;
|
|
368
|
+
encode: z.ZodObject<{
|
|
369
|
+
audio: z.ZodUnion<readonly [z.ZodLiteral<"passthrough">, z.ZodObject<{
|
|
370
|
+
codec: z.ZodEnum<{
|
|
371
|
+
copy: "copy";
|
|
372
|
+
opus: "opus";
|
|
373
|
+
aac: "aac";
|
|
374
|
+
pcmu: "pcmu";
|
|
375
|
+
pcma: "pcma";
|
|
376
|
+
}>;
|
|
377
|
+
bitrateKbps: z.ZodOptional<z.ZodNumber>;
|
|
378
|
+
sampleRateHz: z.ZodOptional<z.ZodNumber>;
|
|
379
|
+
channels: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>]>>;
|
|
380
|
+
}, z.core.$strip>]>;
|
|
381
|
+
video: z.ZodObject<{
|
|
382
|
+
codec: z.ZodEnum<{
|
|
383
|
+
h264: "h264";
|
|
384
|
+
h265: "h265";
|
|
385
|
+
copy: "copy";
|
|
386
|
+
}>;
|
|
387
|
+
profile: z.ZodOptional<z.ZodEnum<{
|
|
388
|
+
high: "high";
|
|
389
|
+
baseline: "baseline";
|
|
390
|
+
main: "main";
|
|
391
|
+
}>>;
|
|
392
|
+
level: z.ZodOptional<z.ZodString>;
|
|
393
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
394
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
395
|
+
fps: z.ZodOptional<z.ZodNumber>;
|
|
396
|
+
bitrateKbps: z.ZodOptional<z.ZodNumber>;
|
|
397
|
+
gopFrames: z.ZodOptional<z.ZodNumber>;
|
|
398
|
+
bf: z.ZodOptional<z.ZodNumber>;
|
|
399
|
+
preset: z.ZodOptional<z.ZodEnum<{
|
|
400
|
+
fast: "fast";
|
|
401
|
+
ultrafast: "ultrafast";
|
|
402
|
+
superfast: "superfast";
|
|
403
|
+
veryfast: "veryfast";
|
|
404
|
+
faster: "faster";
|
|
405
|
+
medium: "medium";
|
|
406
|
+
}>>;
|
|
407
|
+
tune: z.ZodOptional<z.ZodEnum<{
|
|
408
|
+
zerolatency: "zerolatency";
|
|
409
|
+
film: "film";
|
|
410
|
+
animation: "animation";
|
|
411
|
+
}>>;
|
|
412
|
+
}, z.core.$strip>;
|
|
413
|
+
}, z.core.$strip>;
|
|
414
|
+
rateControl: z.ZodOptional<z.ZodEnum<{
|
|
415
|
+
tight: "tight";
|
|
416
|
+
relaxed: "relaxed";
|
|
417
|
+
}>>;
|
|
418
|
+
bitstreamFilter: z.ZodOptional<z.ZodEnum<{
|
|
419
|
+
dump_extra: "dump_extra";
|
|
420
|
+
h264_mp4toannexb: "h264_mp4toannexb";
|
|
421
|
+
hevc_mp4toannexb: "hevc_mp4toannexb";
|
|
422
|
+
}>>;
|
|
423
|
+
pixelFormat: z.ZodOptional<z.ZodEnum<{
|
|
424
|
+
yuv420p: "yuv420p";
|
|
425
|
+
nv12: "nv12";
|
|
426
|
+
}>>;
|
|
427
|
+
decodeHwAccel: z.ZodOptional<z.ZodEnum<{
|
|
428
|
+
none: "none";
|
|
429
|
+
auto: "auto";
|
|
430
|
+
videotoolbox: "videotoolbox";
|
|
431
|
+
vaapi: "vaapi";
|
|
432
|
+
qsv: "qsv";
|
|
433
|
+
cuda: "cuda";
|
|
434
|
+
}>>;
|
|
435
|
+
hostname: z.ZodOptional<z.ZodString>;
|
|
436
|
+
tag: z.ZodOptional<z.ZodString>;
|
|
437
|
+
}, z.core.$strip>;
|
|
438
|
+
export type EgressTranscodeRequest = z.infer<typeof EgressTranscodeRequestSchema>;
|
|
439
|
+
export declare const EgressTranscodeSchema: z.ZodObject<{
|
|
440
|
+
url: z.ZodString;
|
|
441
|
+
pipelineKey: z.ZodString;
|
|
442
|
+
videoCodec: z.ZodEnum<{
|
|
443
|
+
H264: "H264";
|
|
444
|
+
H265: "H265";
|
|
445
|
+
}>;
|
|
446
|
+
resolution: z.ZodObject<{
|
|
447
|
+
width: z.ZodNumber;
|
|
448
|
+
height: z.ZodNumber;
|
|
449
|
+
}, z.core.$strip>;
|
|
450
|
+
transcoded: z.ZodBoolean;
|
|
451
|
+
encoder: z.ZodString;
|
|
452
|
+
decodeHwAccel: z.ZodNullable<z.ZodString>;
|
|
453
|
+
}, z.core.$strip>;
|
|
454
|
+
export type EgressTranscode = z.infer<typeof EgressTranscodeSchema>;
|
|
282
455
|
export declare const streamBrokerCapability: {
|
|
283
456
|
readonly name: "stream-broker";
|
|
284
457
|
readonly scope: "system";
|
|
@@ -321,6 +494,7 @@ export declare const streamBrokerCapability: {
|
|
|
321
494
|
baseline: "baseline";
|
|
322
495
|
main: "main";
|
|
323
496
|
}>>;
|
|
497
|
+
level: z.ZodOptional<z.ZodString>;
|
|
324
498
|
width: z.ZodOptional<z.ZodNumber>;
|
|
325
499
|
height: z.ZodOptional<z.ZodNumber>;
|
|
326
500
|
fps: z.ZodOptional<z.ZodNumber>;
|
|
@@ -691,6 +865,132 @@ export declare const streamBrokerCapability: {
|
|
|
691
865
|
released: z.ZodBoolean;
|
|
692
866
|
refcount: z.ZodNumber;
|
|
693
867
|
}, z.core.$strip>, "mutation">;
|
|
868
|
+
/**
|
|
869
|
+
* THE ffmpeg primitive. Acquire an encoded stream matching a structured
|
|
870
|
+
* encode plan; the broker builds the argv through the ONE builder
|
|
871
|
+
* (`@camstack/types` `ffmpeg/invocation.ts`), resolves decode hardware from
|
|
872
|
+
* the DECODER ADDON's per-node ranking, and returns a dialable RTSP url.
|
|
873
|
+
*
|
|
874
|
+
* **Refcounted and deduplicated on EXACT match.** Two requesters whose
|
|
875
|
+
* requests produce the same `egressTranscodeSharingKey` receive the SAME
|
|
876
|
+
* `pipelineKey` and the same child process. Nearly-identical requests are
|
|
877
|
+
* NOT merged.
|
|
878
|
+
*
|
|
879
|
+
* **The handle is immutable.** There is deliberately no `reconfigure`
|
|
880
|
+
* method and this one never accepts a `pipelineKey` alongside encode
|
|
881
|
+
* parameters: a consumer whose requirements change releases and
|
|
882
|
+
* re-acquires. A mutable shared handle is exactly what made Alexa's old
|
|
883
|
+
* `derived:alexa-<id>` stream a co-tenant hazard — one consumer's
|
|
884
|
+
* downgrade dragged every other consumer down with it.
|
|
885
|
+
*
|
|
886
|
+
* Placement: unpinned, `classifyCapRoute` serves this hub-in-process
|
|
887
|
+
* (Priority 1). A caller that wants the transcode elsewhere passes
|
|
888
|
+
* `nodePin(nodeId)` and a `hostname` it can dial.
|
|
889
|
+
*/
|
|
890
|
+
readonly acquireEgressTranscode: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
|
|
891
|
+
deviceId: z.ZodNumber;
|
|
892
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
893
|
+
kind: z.ZodLiteral<"profile">;
|
|
894
|
+
profile: z.ZodEnum<{
|
|
895
|
+
high: "high";
|
|
896
|
+
mid: "mid";
|
|
897
|
+
low: "low";
|
|
898
|
+
}>;
|
|
899
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
900
|
+
kind: z.ZodLiteral<"cam-stream">;
|
|
901
|
+
camStreamId: z.ZodString;
|
|
902
|
+
}, z.core.$strip>], "kind">;
|
|
903
|
+
encode: z.ZodObject<{
|
|
904
|
+
audio: z.ZodUnion<readonly [z.ZodLiteral<"passthrough">, z.ZodObject<{
|
|
905
|
+
codec: z.ZodEnum<{
|
|
906
|
+
copy: "copy";
|
|
907
|
+
opus: "opus";
|
|
908
|
+
aac: "aac";
|
|
909
|
+
pcmu: "pcmu";
|
|
910
|
+
pcma: "pcma";
|
|
911
|
+
}>;
|
|
912
|
+
bitrateKbps: z.ZodOptional<z.ZodNumber>;
|
|
913
|
+
sampleRateHz: z.ZodOptional<z.ZodNumber>;
|
|
914
|
+
channels: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>]>>;
|
|
915
|
+
}, z.core.$strip>]>;
|
|
916
|
+
video: z.ZodObject<{
|
|
917
|
+
codec: z.ZodEnum<{
|
|
918
|
+
h264: "h264";
|
|
919
|
+
h265: "h265";
|
|
920
|
+
copy: "copy";
|
|
921
|
+
}>;
|
|
922
|
+
profile: z.ZodOptional<z.ZodEnum<{
|
|
923
|
+
high: "high";
|
|
924
|
+
baseline: "baseline";
|
|
925
|
+
main: "main";
|
|
926
|
+
}>>;
|
|
927
|
+
level: z.ZodOptional<z.ZodString>;
|
|
928
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
929
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
930
|
+
fps: z.ZodOptional<z.ZodNumber>;
|
|
931
|
+
bitrateKbps: z.ZodOptional<z.ZodNumber>;
|
|
932
|
+
gopFrames: z.ZodOptional<z.ZodNumber>;
|
|
933
|
+
bf: z.ZodOptional<z.ZodNumber>;
|
|
934
|
+
preset: z.ZodOptional<z.ZodEnum<{
|
|
935
|
+
fast: "fast";
|
|
936
|
+
ultrafast: "ultrafast";
|
|
937
|
+
superfast: "superfast";
|
|
938
|
+
veryfast: "veryfast";
|
|
939
|
+
faster: "faster";
|
|
940
|
+
medium: "medium";
|
|
941
|
+
}>>;
|
|
942
|
+
tune: z.ZodOptional<z.ZodEnum<{
|
|
943
|
+
zerolatency: "zerolatency";
|
|
944
|
+
film: "film";
|
|
945
|
+
animation: "animation";
|
|
946
|
+
}>>;
|
|
947
|
+
}, z.core.$strip>;
|
|
948
|
+
}, z.core.$strip>;
|
|
949
|
+
rateControl: z.ZodOptional<z.ZodEnum<{
|
|
950
|
+
tight: "tight";
|
|
951
|
+
relaxed: "relaxed";
|
|
952
|
+
}>>;
|
|
953
|
+
bitstreamFilter: z.ZodOptional<z.ZodEnum<{
|
|
954
|
+
dump_extra: "dump_extra";
|
|
955
|
+
h264_mp4toannexb: "h264_mp4toannexb";
|
|
956
|
+
hevc_mp4toannexb: "hevc_mp4toannexb";
|
|
957
|
+
}>>;
|
|
958
|
+
pixelFormat: z.ZodOptional<z.ZodEnum<{
|
|
959
|
+
yuv420p: "yuv420p";
|
|
960
|
+
nv12: "nv12";
|
|
961
|
+
}>>;
|
|
962
|
+
decodeHwAccel: z.ZodOptional<z.ZodEnum<{
|
|
963
|
+
none: "none";
|
|
964
|
+
auto: "auto";
|
|
965
|
+
videotoolbox: "videotoolbox";
|
|
966
|
+
vaapi: "vaapi";
|
|
967
|
+
qsv: "qsv";
|
|
968
|
+
cuda: "cuda";
|
|
969
|
+
}>>;
|
|
970
|
+
hostname: z.ZodOptional<z.ZodString>;
|
|
971
|
+
tag: z.ZodOptional<z.ZodString>;
|
|
972
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
973
|
+
url: z.ZodString;
|
|
974
|
+
pipelineKey: z.ZodString;
|
|
975
|
+
videoCodec: z.ZodEnum<{
|
|
976
|
+
H264: "H264";
|
|
977
|
+
H265: "H265";
|
|
978
|
+
}>;
|
|
979
|
+
resolution: z.ZodObject<{
|
|
980
|
+
width: z.ZodNumber;
|
|
981
|
+
height: z.ZodNumber;
|
|
982
|
+
}, z.core.$strip>;
|
|
983
|
+
transcoded: z.ZodBoolean;
|
|
984
|
+
encoder: z.ZodString;
|
|
985
|
+
decodeHwAccel: z.ZodNullable<z.ZodString>;
|
|
986
|
+
}, z.core.$strip>, "mutation">;
|
|
987
|
+
/** Drop one reference. The child dies when the last holder releases. */
|
|
988
|
+
readonly releaseEgressTranscode: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
|
|
989
|
+
pipelineKey: z.ZodString;
|
|
990
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
991
|
+
released: z.ZodBoolean;
|
|
992
|
+
refcount: z.ZodNumber;
|
|
993
|
+
}, z.core.$strip>, "mutation">;
|
|
694
994
|
/**
|
|
695
995
|
* ── Decoded audio-chunk plane (Phase 5 / D9) ──────────────────────
|
|
696
996
|
*
|
package/dist/encode-profile.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export declare const VideoEncodeSchema: z.ZodObject<{
|
|
|
22
22
|
baseline: "baseline";
|
|
23
23
|
main: "main";
|
|
24
24
|
}>>;
|
|
25
|
+
level: z.ZodOptional<z.ZodString>;
|
|
25
26
|
width: z.ZodOptional<z.ZodNumber>;
|
|
26
27
|
height: z.ZodOptional<z.ZodNumber>;
|
|
27
28
|
fps: z.ZodOptional<z.ZodNumber>;
|
|
@@ -68,6 +69,7 @@ export declare const EncodeProfileSchema: z.ZodObject<{
|
|
|
68
69
|
baseline: "baseline";
|
|
69
70
|
main: "main";
|
|
70
71
|
}>>;
|
|
72
|
+
level: z.ZodOptional<z.ZodString>;
|
|
71
73
|
width: z.ZodOptional<z.ZodNumber>;
|
|
72
74
|
height: z.ZodOptional<z.ZodNumber>;
|
|
73
75
|
fps: z.ZodOptional<z.ZodNumber>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE home for live-egress encode constants.
|
|
3
|
+
*
|
|
4
|
+
* Before this file there were five sets, in four packages, that nobody could
|
|
5
|
+
* diff: the WebRTC fallback, Alexa's "fallback", the transcode-down ceiling,
|
|
6
|
+
* the broker's audio presets, and HomeKit's inline block. Three of them
|
|
7
|
+
* disagreed about Opus channel count alone. They live here now because the
|
|
8
|
+
* consumers are separate ADDONS and addons may never import each other —
|
|
9
|
+
* `@camstack/types` is the only legal shared home.
|
|
10
|
+
*
|
|
11
|
+
* ## What changed value, and what kept a deliberate divergence
|
|
12
|
+
*
|
|
13
|
+
* KEPT, with the reason:
|
|
14
|
+
* - **VBV window.** HomeKit uses a ONE-second window; the browser and Alexa
|
|
15
|
+
* use TWO. HomeKit's link budget is enforced per second by the controller,
|
|
16
|
+
* so a keyframe allowed to borrow from the next second overshoots and the
|
|
17
|
+
* iOS client drops the stream. A browser wants the opposite: let a keyframe
|
|
18
|
+
* spike, keep the average. Encoded as {@link FfmpegRateControl}, not as two
|
|
19
|
+
* copies of a number.
|
|
20
|
+
* - **`preset`.** HomeKit and the browser use `ultrafast` (the encoder is on
|
|
21
|
+
* the interactive path); Alexa uses `veryfast` (Echo tolerates ~200 ms more
|
|
22
|
+
* latency and the extra quality is visible on a 10" panel).
|
|
23
|
+
* - **`-bsf:v dump_extra`** is HomeKit-only on BOTH the copy and encode
|
|
24
|
+
* branches: HAP negotiates its own SDP and cannot carry out-of-band
|
|
25
|
+
* extradata. Nothing else needs it, and emitting it elsewhere costs bytes.
|
|
26
|
+
*
|
|
27
|
+
* CHANGED — the divergences that were accidents:
|
|
28
|
+
* - **Opus channels.** Was 2 (broker preset), 1 (Alexa), 1 (HomeKit). Now 1
|
|
29
|
+
* everywhere: every consumer here is a camera microphone, which is mono at
|
|
30
|
+
* the source, and encoding a duplicated second channel spent bitrate on
|
|
31
|
+
* nothing. The broker's 2 was the outlier and it is the one that changed.
|
|
32
|
+
* - **Alexa gained `level: '3.1'`.** Its SDP advertises `profile-level-id=
|
|
33
|
+
* 42e01f` and its encoder was unconstrained. Now it matches.
|
|
34
|
+
* - **Alexa GOP.** Was 25 frames at 25 fps (1 s); the browser is 30 at 25 fps
|
|
35
|
+
* (1.2 s). Both are now expressed as `gopSeconds` × fps so the intent is
|
|
36
|
+
* legible; Alexa's numeric GOP is unchanged, the browser's rounds to 30.
|
|
37
|
+
*/
|
|
38
|
+
import type { EncodeProfile } from '../encode-profile.js';
|
|
39
|
+
import type { FfmpegAudioEncodePlan, FfmpegRateControl } from './invocation.js';
|
|
40
|
+
/**
|
|
41
|
+
* The shape every live egress starts from: H.264 Baseline 3.1 at 720p25.
|
|
42
|
+
* Baseline because it is the one profile every consumer in this repo decodes
|
|
43
|
+
* (Echo, iOS, an old browser); 3.1 because that is what the SDPs advertise.
|
|
44
|
+
*/
|
|
45
|
+
export declare const BASE_LIVE_EGRESS_PROFILE: EncodeProfile;
|
|
46
|
+
/**
|
|
47
|
+
* The browser WebRTC viewer's transcode profile. Used only when the broker
|
|
48
|
+
* MUST spawn ffmpeg (source codec ∉ the browser-accepted set — an H.265 source
|
|
49
|
+
* on an H.264-only browser). Audio is `passthrough` because werift carries the
|
|
50
|
+
* audio plane out-of-band; putting it in the video pipeline would encode a
|
|
51
|
+
* track nothing reads.
|
|
52
|
+
*/
|
|
53
|
+
export declare const WEBRTC_EGRESS_PROFILE: EncodeProfile;
|
|
54
|
+
/**
|
|
55
|
+
* Alexa's egress profile. Alexa's audio ALSO rides the WebRTC session's own
|
|
56
|
+
* audio plane (out-of-band), so this is `passthrough` exactly like the browser
|
|
57
|
+
* — see the ADR for why the previous in-band Opus was encoded and discarded.
|
|
58
|
+
*/
|
|
59
|
+
export declare const ALEXA_EGRESS_PROFILE: EncodeProfile;
|
|
60
|
+
/** VBV window for a consumer whose budget is enforced per second (HomeKit). */
|
|
61
|
+
export declare const RATE_CONTROL_TIGHT: FfmpegRateControl;
|
|
62
|
+
/** VBV window for a consumer that tolerates a keyframe spike (browser, Echo). */
|
|
63
|
+
export declare const RATE_CONTROL_RELAXED: FfmpegRateControl;
|
|
64
|
+
/**
|
|
65
|
+
* Transcode-DOWN ceiling: the shape a remote/unhealthy WebRTC session is
|
|
66
|
+
* reduced to. 360p / 400 kbps is the `low` rung of the adaptive ladder — a
|
|
67
|
+
* session steered here is already failing, so the ceiling is chosen to survive
|
|
68
|
+
* a bad link, not to look good.
|
|
69
|
+
*/
|
|
70
|
+
export declare const TRANSCODE_DOWN_MAX_HEIGHT = 360;
|
|
71
|
+
export declare const TRANSCODE_DOWN_MAX_BITRATE_KBPS = 400;
|
|
72
|
+
/**
|
|
73
|
+
* Camera-microphone audio, per codec. Defined in `./invocation.js` (an import
|
|
74
|
+
* cycle otherwise) and re-exported here, which is where to read it.
|
|
75
|
+
*/
|
|
76
|
+
export { AUDIO_PRESETS } from './invocation.js';
|
|
77
|
+
/**
|
|
78
|
+
* HomeKit's two-way audio leg. `lowdelay` + a short frame duration because the
|
|
79
|
+
* leg is interactive; `globalHeader` because HAP owns the SDP and ffmpeg must
|
|
80
|
+
* not put extradata in-band. `sampleRateHz` and `frameDurationMs` are
|
|
81
|
+
* NEGOTIATED per session — the controller picks them — so they are absent here
|
|
82
|
+
* and filled in by the caller.
|
|
83
|
+
*/
|
|
84
|
+
export declare const HAP_AUDIO_BITRATE_KBPS = 24;
|
|
85
|
+
export declare const HAP_AUDIO_BASE: FfmpegAudioEncodePlan;
|
|
86
|
+
/** HomeKit's `-bufsize` for the audio plane — four seconds of its 24 kbps. */
|
|
87
|
+
export declare const HAP_AUDIO_VBV_KBITS: number;
|
|
88
|
+
/** Seconds between forced IDRs on a HomeKit transcode. */
|
|
89
|
+
export declare const HAP_KEYFRAME_INTERVAL_SEC = 4;
|