@camstack/types 1.2.178 → 1.2.179
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 +12 -6
- package/dist/addon.mjs +12 -6
- package/dist/audio/audio-chunk-format.d.ts +73 -0
- package/dist/audio/audio-scale-epoch.d.ts +117 -0
- package/dist/capabilities/audio-analyzer.cap.d.ts +62 -1
- package/dist/capabilities/index.d.ts +2 -2
- package/dist/capabilities/notification-rules.cap.d.ts +6 -0
- package/dist/capabilities/schemas/streaming-shared.d.ts +15 -0
- package/dist/capabilities/stream-broker.cap.d.ts +21 -11
- package/dist/encode-profile.d.ts +4 -4
- package/dist/generated/addon-api.d.ts +14 -0
- package/dist/generated/cap-input-defaults.d.ts +1 -1
- package/dist/generated/device-proxy.d.ts +2 -0
- package/dist/generated/method-access-map.d.ts +1 -1
- package/dist/generated/method-device-selectors.d.ts +2 -2
- package/dist/generated/system-proxy.d.ts +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +288 -6
- package/dist/index.mjs +275 -7
- package/dist/interfaces/audio-analyzer.d.ts +28 -0
- package/dist/interfaces/event-bus.d.ts +7 -3
- package/dist/interfaces/recording-config.d.ts +24 -0
- package/dist/interfaces/stream-broker.d.ts +13 -0
- package/dist/notification/audio-condition.d.ts +49 -35
- package/dist/{sleep-CTHjJilA.js → sleep-B1ZwxAhh.js} +169 -2
- package/dist/{sleep-ox21Ig4I.mjs → sleep-BnujYGPe.mjs} +140 -3
- package/package.json +1 -1
|
@@ -74,6 +74,30 @@ export type RecordingObjectTriggerClass = z.infer<typeof RecordingObjectTriggerC
|
|
|
74
74
|
* sensori su UNA camera è già oltre qualunque installazione osservata.
|
|
75
75
|
*/
|
|
76
76
|
export declare const MAX_SENSOR_TRIGGER_DEVICES = 16;
|
|
77
|
+
/**
|
|
78
|
+
* What the bands editor OFFERS when an operator first ticks "audio above
|
|
79
|
+
* threshold" on a band. Not a schema default: an absent `audioThresholdDbfs`
|
|
80
|
+
* means the audio source is not listened to at all, and it stays absent until
|
|
81
|
+
* asked for.
|
|
82
|
+
*
|
|
83
|
+
* It lived as a bare `-30` inline in `RecordingBandsEditor.tsx`, which is how
|
|
84
|
+
* a calibration hides: unnamed, unexplained, and invisible to any guard. D460
|
|
85
|
+
* gave it a name so the claim below can be read and disagreed with.
|
|
86
|
+
*
|
|
87
|
+
* **Not shifted at the D459 epoch.** `-30 dBFS is loud enough to be worth
|
|
88
|
+
* recording` is a statement about the scale, not a number anybody read off the
|
|
89
|
+
* pre-epoch decoder — there is no recorded measurement behind it, and absent
|
|
90
|
+
* evidence of measurement the provenance test says a scale statement stays put
|
|
91
|
+
* (the reasoning is D460's, and the same answer `SILENCE_FLOOR_DBFS` gets).
|
|
92
|
+
*
|
|
93
|
+
* As with `audioMarkerMinDbfs`, the epoch makes this seed START working rather
|
|
94
|
+
* than become too permissive: on the old scale a PCMU camera had to reach a
|
|
95
|
+
* true -18 dBFS to read -30, so the trigger was nearly dead on 19 of 25
|
|
96
|
+
* cameras. A band seeded with it now fires roughly as the editor implies.
|
|
97
|
+
*
|
|
98
|
+
* @dbfs-era itu
|
|
99
|
+
*/
|
|
100
|
+
export declare const RECORDING_AUDIO_TRIGGER_OFFERED_DBFS = -30;
|
|
77
101
|
/** Which detectors trigger an `events`-mode band. */
|
|
78
102
|
export declare const RecordingTriggersSchema: z.ZodObject<{
|
|
79
103
|
motion: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -4,6 +4,7 @@ import type { BrokerConsumerAttribution } from '../capabilities/stream-broker.ca
|
|
|
4
4
|
import type { FrameRef } from '../types/frame-view.js';
|
|
5
5
|
import type { FrameFormat } from '../types/io.js';
|
|
6
6
|
import type { FrameHandle } from './frame-handle.js';
|
|
7
|
+
import type { AudioChunkFormat } from '../audio/audio-chunk-format.js';
|
|
7
8
|
export declare const DecoderStatsSchema: z.ZodObject<{
|
|
8
9
|
inputFps: z.ZodNumber;
|
|
9
10
|
outputFps: z.ZodNumber;
|
|
@@ -167,6 +168,12 @@ export interface DecodedAudioChunk {
|
|
|
167
168
|
readonly sampleRate: number;
|
|
168
169
|
readonly channels: number;
|
|
169
170
|
readonly timestamp: number;
|
|
171
|
+
/**
|
|
172
|
+
* Byte format of `data`. ABSENT MEANS `f32le` (D455). A chunk carrying
|
|
173
|
+
* `pcmu` / `pcma` holds ONE byte per sample and is only ever delivered to a
|
|
174
|
+
* subscription whose `accept` list named that format.
|
|
175
|
+
*/
|
|
176
|
+
readonly format?: AudioChunkFormat;
|
|
170
177
|
}
|
|
171
178
|
export interface AudioSubscribeOptions {
|
|
172
179
|
/**
|
|
@@ -175,6 +182,12 @@ export interface AudioSubscribeOptions {
|
|
|
175
182
|
* (`audio-analyzer`, …). Missing tag → recorded as `'unknown'`.
|
|
176
183
|
*/
|
|
177
184
|
readonly tag?: string;
|
|
185
|
+
/**
|
|
186
|
+
* Byte formats this subscriber can READ (D455). The broker serves a chunk's
|
|
187
|
+
* own format when it is named here and expands to `f32le` otherwise, so a
|
|
188
|
+
* subscriber is never handed bytes it cannot interpret. Absent = `f32le`.
|
|
189
|
+
*/
|
|
190
|
+
readonly accept?: readonly AudioChunkFormat[];
|
|
178
191
|
}
|
|
179
192
|
/**
|
|
180
193
|
* Broker lifecycle state. Inferred from `BrokerStatusSchema` — the enum the
|
|
@@ -1,36 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* audio-condition — the authoring logic behind the sustained-sound condition
|
|
3
|
-
* (`NcConditions.audio`), pure and UI-framework-free.
|
|
4
|
-
*
|
|
5
|
-
* WHY IT LIVES HERE
|
|
6
|
-
* ─────────────────
|
|
7
|
-
* Same reason as `condition-taxonomy.ts` next door: two editors author this
|
|
8
|
-
* condition (the admin UI and the viewer), the vocabulary it offers is already
|
|
9
|
-
* in this package (`AUDIO_MACRO_LABELS`), and a second copy of "what a cleared
|
|
10
|
-
* threshold writes" is a mirror free to drift. The viewer keeps its own copy
|
|
11
|
-
* because it is a separate app with its own toolchain; every surface that CAN
|
|
12
|
-
* import `@camstack/types` uses this one.
|
|
13
|
-
*
|
|
14
|
-
* Four behaviours here are not cosmetic:
|
|
15
|
-
*
|
|
16
|
-
* 1. **dBFS is negative-going.** `0` is full scale; digital silence reads
|
|
17
|
-
* {@link NC_AUDIO_DBFS_FLOOR}. A control that let an operator author `60`
|
|
18
|
-
* meaning "loud" would write a threshold no sample can reach, and the rule
|
|
19
|
-
* would look broken rather than mis-configured. Every write is clamped.
|
|
20
|
-
* 2. **Neither filter given = the engine REFUSES.** With no level floor and no
|
|
21
|
-
* labels every sample is trivially a hit, so the window would fire on
|
|
22
|
-
* silence. {@link audioIsFailClosed} is how an editor says that BEFORE the
|
|
23
|
-
* save instead of shipping a rule that never notifies and never says why.
|
|
24
|
-
* 3. **Both label spellings are the same label.** The hub's taxonomy serves
|
|
25
|
-
* `audio-dog`; the macro catalog and an operator both say `dog`, and the
|
|
26
|
-
* matcher normalizes the prefix away on both sides. Selection therefore
|
|
27
|
-
* compares NORMALIZED ids — a rule authored in the admin UI must not read
|
|
28
|
-
* as "nothing selected" on the phone.
|
|
29
|
-
* 4. **A stored label the build does not know stays visible.** It is offered
|
|
30
|
-
* as an explicitly-unknown, removable choice, never silently dropped:
|
|
31
|
-
* rewriting the rule the operator is looking at is worse than showing a
|
|
32
|
-
* stale id.
|
|
33
|
-
*/
|
|
34
1
|
import { type NcAudioCondition } from '../capabilities/notification-rules.cap.js';
|
|
35
2
|
import type { NcTaxonomy } from '../catalogs/nc-taxonomy.js';
|
|
36
3
|
/** Strip the taxonomy's `audio-` namespace: `audio-dog` and `dog` are one label. */
|
|
@@ -50,8 +17,21 @@ export declare function normalizeAudioLabel(value: string): string;
|
|
|
50
17
|
export declare function audioKindId(macroId: string): string;
|
|
51
18
|
/** Level bounds, in dBFS. Negative-going: the ceiling is full scale. */
|
|
52
19
|
export declare const NC_AUDIO_DB_MIN = -96;
|
|
20
|
+
/**
|
|
21
|
+
* Full scale. The ceiling of dBFS itself on any decoder, not a calibration —
|
|
22
|
+
* untouched by D459 and untouchable by anything like it.
|
|
23
|
+
*
|
|
24
|
+
* @dbfs-era scale-bound
|
|
25
|
+
*/
|
|
53
26
|
export declare const NC_AUDIO_DB_MAX = 0;
|
|
54
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Step of the level control — 3 dB is one perceptual notch, not one unit.
|
|
29
|
+
*
|
|
30
|
+
* A step between two levels, so it survives any uniform scale change: 3 dB is
|
|
31
|
+
* the same interval before and after the D459 epoch.
|
|
32
|
+
*
|
|
33
|
+
* @dbfs-era relative
|
|
34
|
+
*/
|
|
55
35
|
export declare const NC_AUDIO_DB_STEP = 3;
|
|
56
36
|
/**
|
|
57
37
|
* Offered when the operator turns the level filter ON. NOT a schema default:
|
|
@@ -66,8 +46,42 @@ export declare const NC_AUDIO_DB_STEP = 3;
|
|
|
66
46
|
* operator turned the filter on for, and the rule looks broken rather than
|
|
67
47
|
* mis-seeded. The offered floor is the QUIET end of that measured band; the
|
|
68
48
|
* editor says which way the number runs rather than leaving it to be inferred.
|
|
49
|
+
*
|
|
50
|
+
* ⚠ **That measurement was taken on the pre-D459 scale, and D460 moved this
|
|
51
|
+
* constant because of it.** Until {@link AUDIO_SCALE_EPOCH_MS} the native RTP
|
|
52
|
+
* G.711 decoder reported levels 12.04 dB low (PCMU) or 18.06 dB low (PCMA).
|
|
53
|
+
*
|
|
54
|
+
* This is the ONE absolute-dBFS number in the repo that is a MEASUREMENT
|
|
55
|
+
* rather than a statement about the scale, and it is the only one with a
|
|
56
|
+
* recorded provenance — which is exactly why it is also the only shared
|
|
57
|
+
* constant that converts. Three things agree that the camera it was measured
|
|
58
|
+
* on spoke PCMU:
|
|
59
|
+
*
|
|
60
|
+
* - 19 of this hub's 25 cameras with audio are PCMU, 5 AAC, 1 L16, 0 PCMA
|
|
61
|
+
* (codec census over 10 h of `audio: track detected`, 2026-09-11);
|
|
62
|
+
* - **-55..-26 dBFS is not a close cry on a true scale.** -26 is a quiet
|
|
63
|
+
* room. Read as a PCMU measurement the band is -43..-14, which is exactly
|
|
64
|
+
* where a cry a metre from the microphone sits;
|
|
65
|
+
* - there is no PCMA camera on this installation, so the only candidate
|
|
66
|
+
* correction is mu-law's.
|
|
67
|
+
*
|
|
68
|
+
* So the band is restated, not re-guessed: the offered floor stays "the QUIET
|
|
69
|
+
* end of the measured band", and the band moves to where it was always really
|
|
70
|
+
* sitting. {@link ituDbfsFromPreEpoch} does the arithmetic so the derivation
|
|
71
|
+
* survives in the source instead of becoming a number somebody preferred.
|
|
72
|
+
*
|
|
73
|
+
* `Math.round` is the control's own granularity, not a fudge: the editor's
|
|
74
|
+
* level field is an integer stepper and `patchAudio` clamps through
|
|
75
|
+
* `clampInt`, so a fractional seed could never reach storage anyway.
|
|
76
|
+
*
|
|
77
|
+
* **This changes no existing rule.** It is what an editor OFFERS when the
|
|
78
|
+
* level filter is switched on with nothing stored; every `dbThreshold` an
|
|
79
|
+
* operator already authored is untouched, and those are a separate question
|
|
80
|
+
* — see D460 on why they are declared rather than migrated.
|
|
81
|
+
*
|
|
82
|
+
* @dbfs-era pre-itu-pcmu
|
|
69
83
|
*/
|
|
70
|
-
export declare const NC_AUDIO_DB_OFFERED
|
|
84
|
+
export declare const NC_AUDIO_DB_OFFERED: number;
|
|
71
85
|
export declare const NC_AUDIO_HIT_PERCENT_MIN = 1;
|
|
72
86
|
export declare const NC_AUDIO_HIT_PERCENT_MAX = 100;
|
|
73
87
|
export declare const NC_AUDIO_SAMPLING_MIN_SEC = 1;
|
|
@@ -1,5 +1,121 @@
|
|
|
1
1
|
const require_event_category = require("./event-category-BNJ9EE5B.js");
|
|
2
2
|
let zod = require("zod");
|
|
3
|
+
//#region src/audio/audio-chunk-format.ts
|
|
4
|
+
/**
|
|
5
|
+
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6
|
+
* window to float samples (D455).
|
|
7
|
+
*
|
|
8
|
+
* ## Why a format at all
|
|
9
|
+
*
|
|
10
|
+
* D450 took the plane off its 8 → 16 kHz upsample: it carries the SOURCE
|
|
11
|
+
* RATE, and the one consumer that needs 16 kHz resamples next to the model.
|
|
12
|
+
* It left the FORMAT alone — the broker still turned each G.711 byte into a
|
|
13
|
+
* 4-byte f32le sample before the bytes entered the transport, so every leg of
|
|
14
|
+
* the plane carried four times the source. The plane crosses hub-main twice on
|
|
15
|
+
* the way to the analyzer, and the fleet's G.711 cameras are ~79 % of it.
|
|
16
|
+
*
|
|
17
|
+
* So the plane carries the source BYTES too, and whoever needs floats expands
|
|
18
|
+
* them where it needs them. That is the same argument D450 made for the rate,
|
|
19
|
+
* one step further along the same wire.
|
|
20
|
+
*
|
|
21
|
+
* ## Why the expansion lives here
|
|
22
|
+
*
|
|
23
|
+
* Two packages need it and they must never disagree: `addon-pipeline`'s broker
|
|
24
|
+
* (which still has to serve a subscriber that did NOT ask for coded bytes —
|
|
25
|
+
* `AudioChunkPlane` expands per subscription) and
|
|
26
|
+
* `addon-pipeline-orchestrator`'s `AudioWindowAccumulator` (which flushes an
|
|
27
|
+
* f32le window to the analyzer cap, whose `AudioChunkInput` contract is
|
|
28
|
+
* unchanged and stays f32le). Both bundle the bare `@camstack/types` entry
|
|
29
|
+
* into their own dist (`self-contained` externals), so this travels with a
|
|
30
|
+
* `camstack deploy` and needs no published server.
|
|
31
|
+
*
|
|
32
|
+
* A second μ-law table anywhere else is the defect this module exists to
|
|
33
|
+
* prevent. (`stream-broker.ts`'s `mulawToPcm` / `alawToPcm` are the ENCODE
|
|
34
|
+
* direction for the WebRTC egress — a different transform, not a copy.)
|
|
35
|
+
*
|
|
36
|
+
* ## Absent means f32le
|
|
37
|
+
*
|
|
38
|
+
* `format` is optional on the wire and its absence means `f32le` — today's
|
|
39
|
+
* bytes, byte for byte. A peer that never heard of the field is served what it
|
|
40
|
+
* has always been served, because the broker only emits a coded window to a
|
|
41
|
+
* subscription that DECLARED it accepts one (`AudioSubscribeOptions.accept`).
|
|
42
|
+
* That is the D448 `rawForward` negotiation, and it is what makes this
|
|
43
|
+
* deployable one addon at a time across three nodes.
|
|
44
|
+
*/
|
|
45
|
+
/** Every byte format the audio chunk plane can carry. `f32le` is the default. */
|
|
46
|
+
var AUDIO_CHUNK_FORMATS = [
|
|
47
|
+
"f32le",
|
|
48
|
+
"pcmu",
|
|
49
|
+
"pcma"
|
|
50
|
+
];
|
|
51
|
+
/** Type guard for a value that arrived over the wire as a format name. */
|
|
52
|
+
function isAudioChunkFormat(value) {
|
|
53
|
+
return typeof value === "string" && AUDIO_CHUNK_FORMATS.includes(value);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Bytes one sample of `format` occupies. This is the number every duration
|
|
57
|
+
* calculation over a chunk's `data` must use — a window accumulator that
|
|
58
|
+
* hard-codes 4 reports a quarter of the audio it actually holds and flushes
|
|
59
|
+
* four-second windows to a classifier expecting one.
|
|
60
|
+
*/
|
|
61
|
+
function audioChunkBytesPerSample(format) {
|
|
62
|
+
return format === "f32le" ? 4 : 1;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Expand a CODED audio window to freshly-owned f32le bytes, 4-byte aligned at
|
|
66
|
+
* offset 0, so a caller can build a `Float32Array` view over them directly.
|
|
67
|
+
* The input may be a msgpack subview at any offset; nothing here reads it as
|
|
68
|
+
* anything but bytes.
|
|
69
|
+
*
|
|
70
|
+
* `f32le` is accepted and returns a copy, but a caller that already knows the
|
|
71
|
+
* format is `f32le` should not call this at all — it has the bytes. Both
|
|
72
|
+
* callers branch on the format first for exactly that reason, and the copy
|
|
73
|
+
* here exists so the return type is always owned rather than sometimes
|
|
74
|
+
* aliasing the argument.
|
|
75
|
+
*/
|
|
76
|
+
function expandAudioChunkToF32le(data, format) {
|
|
77
|
+
if (format === "f32le") {
|
|
78
|
+
const copy = new Uint8Array(data.byteLength);
|
|
79
|
+
copy.set(data);
|
|
80
|
+
return copy;
|
|
81
|
+
}
|
|
82
|
+
const table = format === "pcmu" ? ULAW_TABLE : ALAW_TABLE;
|
|
83
|
+
const out = new Float32Array(data.length);
|
|
84
|
+
for (let i = 0; i < data.length; i++) out[i] = table[data[i]];
|
|
85
|
+
return new Uint8Array(out.buffer);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Build the μ-law decode table (ITU-T G.711). Each of the 256 byte values maps
|
|
89
|
+
* to a 16-bit PCM sample, normalised to [-1.0, 1.0] for f32le output.
|
|
90
|
+
*
|
|
91
|
+
* Moved here verbatim from `audio-rtp-decoder.ts`, which no longer decodes:
|
|
92
|
+
* it buffers the coded bytes and the plane's consumers expand.
|
|
93
|
+
*/
|
|
94
|
+
function buildUlawTable() {
|
|
95
|
+
const table = new Float32Array(256);
|
|
96
|
+
for (let i = 0; i < 256; i++) {
|
|
97
|
+
const complemented = ~i & 255;
|
|
98
|
+
const sign = (complemented & 128) !== 0 ? -1 : 1;
|
|
99
|
+
const exponent = complemented >> 4 & 7;
|
|
100
|
+
table[i] = sign * ((8 * (complemented & 15) + 132 << exponent) - 132) / 32768;
|
|
101
|
+
}
|
|
102
|
+
return table;
|
|
103
|
+
}
|
|
104
|
+
/** Build the A-law decode table (ITU-T G.711). */
|
|
105
|
+
function buildAlawTable() {
|
|
106
|
+
const table = new Float32Array(256);
|
|
107
|
+
for (let i = 0; i < 256; i++) {
|
|
108
|
+
const xored = i ^ 85;
|
|
109
|
+
const sign = (xored & 128) !== 0 ? 1 : -1;
|
|
110
|
+
const exponent = xored >> 4 & 7;
|
|
111
|
+
const mantissa = xored & 15;
|
|
112
|
+
table[i] = sign * (exponent === 0 ? 16 * mantissa + 8 : 16 * mantissa + 264 << exponent - 1) / 32768;
|
|
113
|
+
}
|
|
114
|
+
return table;
|
|
115
|
+
}
|
|
116
|
+
var ULAW_TABLE = buildUlawTable();
|
|
117
|
+
var ALAW_TABLE = buildAlawTable();
|
|
118
|
+
//#endregion
|
|
3
119
|
//#region src/interfaces/config-ui.ts
|
|
4
120
|
/** Predefined tabs with standard label, icon, and sort order.
|
|
5
121
|
*
|
|
@@ -1533,11 +1649,20 @@ var SubscribeFramesResultSchema = zod.z.object({
|
|
|
1533
1649
|
* (the wire-serialisable supertype of `Buffer`) to match `DecodedFrameSchema`
|
|
1534
1650
|
* / `EncodedPacketSchema`'s precedent; a `Buffer` is assignable to it.
|
|
1535
1651
|
*/
|
|
1652
|
+
var AudioChunkFormatSchema = zod.z.enum(AUDIO_CHUNK_FORMATS);
|
|
1536
1653
|
var DecodedAudioChunkSchema = zod.z.object({
|
|
1537
1654
|
data: zod.z.instanceof(Uint8Array),
|
|
1538
1655
|
sampleRate: zod.z.number().int().positive(),
|
|
1539
1656
|
channels: zod.z.number().int().positive(),
|
|
1540
|
-
timestamp: zod.z.number()
|
|
1657
|
+
timestamp: zod.z.number(),
|
|
1658
|
+
/**
|
|
1659
|
+
* Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
|
|
1660
|
+
* byte, for any peer that never heard of this field. A coded window
|
|
1661
|
+
* (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
|
|
1662
|
+
* subscription that DECLARED it accepts one, so absence can never mean
|
|
1663
|
+
* "coded bytes a consumer will read as floats" (D455).
|
|
1664
|
+
*/
|
|
1665
|
+
format: AudioChunkFormatSchema.optional()
|
|
1541
1666
|
});
|
|
1542
1667
|
/**
|
|
1543
1668
|
* Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). The
|
|
@@ -1549,7 +1674,18 @@ var DecodedAudioChunkSchema = zod.z.object({
|
|
|
1549
1674
|
var SubscribeAudioChunksInputSchema = zod.z.object({
|
|
1550
1675
|
brokerId: zod.z.string(),
|
|
1551
1676
|
/** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
|
|
1552
|
-
tag: zod.z.string().optional()
|
|
1677
|
+
tag: zod.z.string().optional(),
|
|
1678
|
+
/**
|
|
1679
|
+
* Byte formats this subscriber can READ, best first. The broker serves the
|
|
1680
|
+
* chunk's own format when it is in this list and expands to `f32le`
|
|
1681
|
+
* otherwise, so a subscriber is never handed bytes it cannot interpret.
|
|
1682
|
+
*
|
|
1683
|
+
* Absent (or without the source format) means `f32le` — the behaviour every
|
|
1684
|
+
* subscriber had before D455, unchanged. This is the negotiation half of
|
|
1685
|
+
* the source-bytes lever: it is what lets the broker and its consumers
|
|
1686
|
+
* deploy one at a time across three nodes.
|
|
1687
|
+
*/
|
|
1688
|
+
accept: zod.z.array(AudioChunkFormatSchema).readonly().optional()
|
|
1553
1689
|
});
|
|
1554
1690
|
/** Result of `stream-broker.subscribeAudioChunks`. */
|
|
1555
1691
|
var SubscribeAudioChunksResultSchema = zod.z.object({
|
|
@@ -3701,6 +3837,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
3701
3837
|
getDeviceSettings: (input) => dispatchSystem("addonSettings", "getDeviceSettings", "query", input),
|
|
3702
3838
|
updateDeviceSettings: (input) => dispatchSystem("addonSettings", "updateDeviceSettings", "mutation", input)
|
|
3703
3839
|
},
|
|
3840
|
+
audioAnalyzer: { detachDevice: (input) => dispatchSystem("audioAnalyzer", "detachDevice", "mutation", input) },
|
|
3704
3841
|
cameraPipelineConfig: {
|
|
3705
3842
|
getDeviceSettingsContribution: (input) => dispatchSystem("cameraPipelineConfig", "getDeviceSettingsContribution", "query", input),
|
|
3706
3843
|
getDeviceLiveContribution: (input) => dispatchSystem("cameraPipelineConfig", "getDeviceLiveContribution", "query", input),
|
|
@@ -4113,6 +4250,18 @@ function sleepCancellable(ms, signal) {
|
|
|
4113
4250
|
});
|
|
4114
4251
|
}
|
|
4115
4252
|
//#endregion
|
|
4253
|
+
Object.defineProperty(exports, "AUDIO_CHUNK_FORMATS", {
|
|
4254
|
+
enumerable: true,
|
|
4255
|
+
get: function() {
|
|
4256
|
+
return AUDIO_CHUNK_FORMATS;
|
|
4257
|
+
}
|
|
4258
|
+
});
|
|
4259
|
+
Object.defineProperty(exports, "AudioChunkFormatSchema", {
|
|
4260
|
+
enumerable: true,
|
|
4261
|
+
get: function() {
|
|
4262
|
+
return AudioChunkFormatSchema;
|
|
4263
|
+
}
|
|
4264
|
+
});
|
|
4116
4265
|
Object.defineProperty(exports, "BOOT_RECOVERY_BACKOFF_MS", {
|
|
4117
4266
|
enumerable: true,
|
|
4118
4267
|
get: function() {
|
|
@@ -4395,6 +4544,12 @@ Object.defineProperty(exports, "asString", {
|
|
|
4395
4544
|
return asString;
|
|
4396
4545
|
}
|
|
4397
4546
|
});
|
|
4547
|
+
Object.defineProperty(exports, "audioChunkBytesPerSample", {
|
|
4548
|
+
enumerable: true,
|
|
4549
|
+
get: function() {
|
|
4550
|
+
return audioChunkBytesPerSample;
|
|
4551
|
+
}
|
|
4552
|
+
});
|
|
4398
4553
|
Object.defineProperty(exports, "collectHydratedFieldEntries", {
|
|
4399
4554
|
enumerable: true,
|
|
4400
4555
|
get: function() {
|
|
@@ -4473,6 +4628,12 @@ Object.defineProperty(exports, "event", {
|
|
|
4473
4628
|
return event;
|
|
4474
4629
|
}
|
|
4475
4630
|
});
|
|
4631
|
+
Object.defineProperty(exports, "expandAudioChunkToF32le", {
|
|
4632
|
+
enumerable: true,
|
|
4633
|
+
get: function() {
|
|
4634
|
+
return expandAudioChunkToF32le;
|
|
4635
|
+
}
|
|
4636
|
+
});
|
|
4476
4637
|
Object.defineProperty(exports, "expandCapMethods", {
|
|
4477
4638
|
enumerable: true,
|
|
4478
4639
|
get: function() {
|
|
@@ -4485,6 +4646,12 @@ Object.defineProperty(exports, "hydrateSchema", {
|
|
|
4485
4646
|
return hydrateSchema;
|
|
4486
4647
|
}
|
|
4487
4648
|
});
|
|
4649
|
+
Object.defineProperty(exports, "isAudioChunkFormat", {
|
|
4650
|
+
enumerable: true,
|
|
4651
|
+
get: function() {
|
|
4652
|
+
return isAudioChunkFormat;
|
|
4653
|
+
}
|
|
4654
|
+
});
|
|
4488
4655
|
Object.defineProperty(exports, "isDeviceConfigCap", {
|
|
4489
4656
|
enumerable: true,
|
|
4490
4657
|
get: function() {
|
|
@@ -1,5 +1,121 @@
|
|
|
1
1
|
import { t as EventCategory } from "./event-category-DAXzJeTX.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
//#region src/audio/audio-chunk-format.ts
|
|
4
|
+
/**
|
|
5
|
+
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6
|
+
* window to float samples (D455).
|
|
7
|
+
*
|
|
8
|
+
* ## Why a format at all
|
|
9
|
+
*
|
|
10
|
+
* D450 took the plane off its 8 → 16 kHz upsample: it carries the SOURCE
|
|
11
|
+
* RATE, and the one consumer that needs 16 kHz resamples next to the model.
|
|
12
|
+
* It left the FORMAT alone — the broker still turned each G.711 byte into a
|
|
13
|
+
* 4-byte f32le sample before the bytes entered the transport, so every leg of
|
|
14
|
+
* the plane carried four times the source. The plane crosses hub-main twice on
|
|
15
|
+
* the way to the analyzer, and the fleet's G.711 cameras are ~79 % of it.
|
|
16
|
+
*
|
|
17
|
+
* So the plane carries the source BYTES too, and whoever needs floats expands
|
|
18
|
+
* them where it needs them. That is the same argument D450 made for the rate,
|
|
19
|
+
* one step further along the same wire.
|
|
20
|
+
*
|
|
21
|
+
* ## Why the expansion lives here
|
|
22
|
+
*
|
|
23
|
+
* Two packages need it and they must never disagree: `addon-pipeline`'s broker
|
|
24
|
+
* (which still has to serve a subscriber that did NOT ask for coded bytes —
|
|
25
|
+
* `AudioChunkPlane` expands per subscription) and
|
|
26
|
+
* `addon-pipeline-orchestrator`'s `AudioWindowAccumulator` (which flushes an
|
|
27
|
+
* f32le window to the analyzer cap, whose `AudioChunkInput` contract is
|
|
28
|
+
* unchanged and stays f32le). Both bundle the bare `@camstack/types` entry
|
|
29
|
+
* into their own dist (`self-contained` externals), so this travels with a
|
|
30
|
+
* `camstack deploy` and needs no published server.
|
|
31
|
+
*
|
|
32
|
+
* A second μ-law table anywhere else is the defect this module exists to
|
|
33
|
+
* prevent. (`stream-broker.ts`'s `mulawToPcm` / `alawToPcm` are the ENCODE
|
|
34
|
+
* direction for the WebRTC egress — a different transform, not a copy.)
|
|
35
|
+
*
|
|
36
|
+
* ## Absent means f32le
|
|
37
|
+
*
|
|
38
|
+
* `format` is optional on the wire and its absence means `f32le` — today's
|
|
39
|
+
* bytes, byte for byte. A peer that never heard of the field is served what it
|
|
40
|
+
* has always been served, because the broker only emits a coded window to a
|
|
41
|
+
* subscription that DECLARED it accepts one (`AudioSubscribeOptions.accept`).
|
|
42
|
+
* That is the D448 `rawForward` negotiation, and it is what makes this
|
|
43
|
+
* deployable one addon at a time across three nodes.
|
|
44
|
+
*/
|
|
45
|
+
/** Every byte format the audio chunk plane can carry. `f32le` is the default. */
|
|
46
|
+
var AUDIO_CHUNK_FORMATS = [
|
|
47
|
+
"f32le",
|
|
48
|
+
"pcmu",
|
|
49
|
+
"pcma"
|
|
50
|
+
];
|
|
51
|
+
/** Type guard for a value that arrived over the wire as a format name. */
|
|
52
|
+
function isAudioChunkFormat(value) {
|
|
53
|
+
return typeof value === "string" && AUDIO_CHUNK_FORMATS.includes(value);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Bytes one sample of `format` occupies. This is the number every duration
|
|
57
|
+
* calculation over a chunk's `data` must use — a window accumulator that
|
|
58
|
+
* hard-codes 4 reports a quarter of the audio it actually holds and flushes
|
|
59
|
+
* four-second windows to a classifier expecting one.
|
|
60
|
+
*/
|
|
61
|
+
function audioChunkBytesPerSample(format) {
|
|
62
|
+
return format === "f32le" ? 4 : 1;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Expand a CODED audio window to freshly-owned f32le bytes, 4-byte aligned at
|
|
66
|
+
* offset 0, so a caller can build a `Float32Array` view over them directly.
|
|
67
|
+
* The input may be a msgpack subview at any offset; nothing here reads it as
|
|
68
|
+
* anything but bytes.
|
|
69
|
+
*
|
|
70
|
+
* `f32le` is accepted and returns a copy, but a caller that already knows the
|
|
71
|
+
* format is `f32le` should not call this at all — it has the bytes. Both
|
|
72
|
+
* callers branch on the format first for exactly that reason, and the copy
|
|
73
|
+
* here exists so the return type is always owned rather than sometimes
|
|
74
|
+
* aliasing the argument.
|
|
75
|
+
*/
|
|
76
|
+
function expandAudioChunkToF32le(data, format) {
|
|
77
|
+
if (format === "f32le") {
|
|
78
|
+
const copy = new Uint8Array(data.byteLength);
|
|
79
|
+
copy.set(data);
|
|
80
|
+
return copy;
|
|
81
|
+
}
|
|
82
|
+
const table = format === "pcmu" ? ULAW_TABLE : ALAW_TABLE;
|
|
83
|
+
const out = new Float32Array(data.length);
|
|
84
|
+
for (let i = 0; i < data.length; i++) out[i] = table[data[i]];
|
|
85
|
+
return new Uint8Array(out.buffer);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Build the μ-law decode table (ITU-T G.711). Each of the 256 byte values maps
|
|
89
|
+
* to a 16-bit PCM sample, normalised to [-1.0, 1.0] for f32le output.
|
|
90
|
+
*
|
|
91
|
+
* Moved here verbatim from `audio-rtp-decoder.ts`, which no longer decodes:
|
|
92
|
+
* it buffers the coded bytes and the plane's consumers expand.
|
|
93
|
+
*/
|
|
94
|
+
function buildUlawTable() {
|
|
95
|
+
const table = new Float32Array(256);
|
|
96
|
+
for (let i = 0; i < 256; i++) {
|
|
97
|
+
const complemented = ~i & 255;
|
|
98
|
+
const sign = (complemented & 128) !== 0 ? -1 : 1;
|
|
99
|
+
const exponent = complemented >> 4 & 7;
|
|
100
|
+
table[i] = sign * ((8 * (complemented & 15) + 132 << exponent) - 132) / 32768;
|
|
101
|
+
}
|
|
102
|
+
return table;
|
|
103
|
+
}
|
|
104
|
+
/** Build the A-law decode table (ITU-T G.711). */
|
|
105
|
+
function buildAlawTable() {
|
|
106
|
+
const table = new Float32Array(256);
|
|
107
|
+
for (let i = 0; i < 256; i++) {
|
|
108
|
+
const xored = i ^ 85;
|
|
109
|
+
const sign = (xored & 128) !== 0 ? 1 : -1;
|
|
110
|
+
const exponent = xored >> 4 & 7;
|
|
111
|
+
const mantissa = xored & 15;
|
|
112
|
+
table[i] = sign * (exponent === 0 ? 16 * mantissa + 8 : 16 * mantissa + 264 << exponent - 1) / 32768;
|
|
113
|
+
}
|
|
114
|
+
return table;
|
|
115
|
+
}
|
|
116
|
+
var ULAW_TABLE = buildUlawTable();
|
|
117
|
+
var ALAW_TABLE = buildAlawTable();
|
|
118
|
+
//#endregion
|
|
3
119
|
//#region src/interfaces/config-ui.ts
|
|
4
120
|
/** Predefined tabs with standard label, icon, and sort order.
|
|
5
121
|
*
|
|
@@ -1533,11 +1649,20 @@ var SubscribeFramesResultSchema = z.object({
|
|
|
1533
1649
|
* (the wire-serialisable supertype of `Buffer`) to match `DecodedFrameSchema`
|
|
1534
1650
|
* / `EncodedPacketSchema`'s precedent; a `Buffer` is assignable to it.
|
|
1535
1651
|
*/
|
|
1652
|
+
var AudioChunkFormatSchema = z.enum(AUDIO_CHUNK_FORMATS);
|
|
1536
1653
|
var DecodedAudioChunkSchema = z.object({
|
|
1537
1654
|
data: z.instanceof(Uint8Array),
|
|
1538
1655
|
sampleRate: z.number().int().positive(),
|
|
1539
1656
|
channels: z.number().int().positive(),
|
|
1540
|
-
timestamp: z.number()
|
|
1657
|
+
timestamp: z.number(),
|
|
1658
|
+
/**
|
|
1659
|
+
* Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
|
|
1660
|
+
* byte, for any peer that never heard of this field. A coded window
|
|
1661
|
+
* (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
|
|
1662
|
+
* subscription that DECLARED it accepts one, so absence can never mean
|
|
1663
|
+
* "coded bytes a consumer will read as floats" (D455).
|
|
1664
|
+
*/
|
|
1665
|
+
format: AudioChunkFormatSchema.optional()
|
|
1541
1666
|
});
|
|
1542
1667
|
/**
|
|
1543
1668
|
* Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). The
|
|
@@ -1549,7 +1674,18 @@ var DecodedAudioChunkSchema = z.object({
|
|
|
1549
1674
|
var SubscribeAudioChunksInputSchema = z.object({
|
|
1550
1675
|
brokerId: z.string(),
|
|
1551
1676
|
/** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
|
|
1552
|
-
tag: z.string().optional()
|
|
1677
|
+
tag: z.string().optional(),
|
|
1678
|
+
/**
|
|
1679
|
+
* Byte formats this subscriber can READ, best first. The broker serves the
|
|
1680
|
+
* chunk's own format when it is in this list and expands to `f32le`
|
|
1681
|
+
* otherwise, so a subscriber is never handed bytes it cannot interpret.
|
|
1682
|
+
*
|
|
1683
|
+
* Absent (or without the source format) means `f32le` — the behaviour every
|
|
1684
|
+
* subscriber had before D455, unchanged. This is the negotiation half of
|
|
1685
|
+
* the source-bytes lever: it is what lets the broker and its consumers
|
|
1686
|
+
* deploy one at a time across three nodes.
|
|
1687
|
+
*/
|
|
1688
|
+
accept: z.array(AudioChunkFormatSchema).readonly().optional()
|
|
1553
1689
|
});
|
|
1554
1690
|
/** Result of `stream-broker.subscribeAudioChunks`. */
|
|
1555
1691
|
var SubscribeAudioChunksResultSchema = z.object({
|
|
@@ -3701,6 +3837,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
3701
3837
|
getDeviceSettings: (input) => dispatchSystem("addonSettings", "getDeviceSettings", "query", input),
|
|
3702
3838
|
updateDeviceSettings: (input) => dispatchSystem("addonSettings", "updateDeviceSettings", "mutation", input)
|
|
3703
3839
|
},
|
|
3840
|
+
audioAnalyzer: { detachDevice: (input) => dispatchSystem("audioAnalyzer", "detachDevice", "mutation", input) },
|
|
3704
3841
|
cameraPipelineConfig: {
|
|
3705
3842
|
getDeviceSettingsContribution: (input) => dispatchSystem("cameraPipelineConfig", "getDeviceSettingsContribution", "query", input),
|
|
3706
3843
|
getDeviceLiveContribution: (input) => dispatchSystem("cameraPipelineConfig", "getDeviceLiveContribution", "query", input),
|
|
@@ -4113,4 +4250,4 @@ function sleepCancellable(ms, signal) {
|
|
|
4113
4250
|
});
|
|
4114
4251
|
}
|
|
4115
4252
|
//#endregion
|
|
4116
|
-
export {
|
|
4253
|
+
export { CameraStreamSchema as $, DEVICE_STATUS_METHOD as A, collectHydratedFieldValues as At, toNodeId as B, ChargingStatus as C, createDurableState as Ct, adminUiCapability as D, WELL_KNOWN_TABS as Dt, DeviceType as E, isEvent as Et, resolveCapMount as F, expandAudioChunkToF32le as Ft, scopeKey as G, ReadinessTimeoutError as H, systemMethod as I, isAudioChunkFormat as It, BrokerStatusSchema as J, AudioChunkFormatSchema as K, CAP_NODE_PIN_CONTEXT_KEY as L, expandCapMethods as M, resolveHydratedFieldValue as Mt, isDeviceConfigCap as N, AUDIO_CHUNK_FORMATS as Nt, DEFAULT_RUNTIME_STATE_DURABILITY as O, WELL_KNOWN_TAB_MAP as Ot, method as P, audioChunkBytesPerSample as Pt, CamStreamResolutionSchema as Q, nodePin as R, DEVICE_CHILDREN_BATCH_MAX as S, normalizeAddonInitResult as St, DeviceRole as T, emitReadiness as Tt, emitDownForOwnedCaps as U, ReadinessRegistry as V, readinessKey as W, CamProfileSchema as X, CAM_PROFILE_ORDER as Y, CamStreamKindSchema as Z, createMirrorSource as _, selectAssignedProfileSlots as _t, asJsonObject as a, ProfileRtspEntrySchema as at, deviceOpsCapability as b, DisposerChain as bt, parseJsonArray as c, StreamSourceEntrySchema$1 as ct, BOOT_RECOVERY_BACKOFF_MS as d, SubscribeAudioChunksResultSchema as dt, DecodedAudioChunkSchema as et, DEVICE_SCOPED_CAPS as f, SubscribeFramesInputSchema as ft, createLazyTrpcSource as g, parseProfileBrokerId as gt, createEventBusSliceSource as h, makeSourceBrokerId as ht, asJsonArray as i, FrameHandleSchema as it, event as j, hydrateSchema as jt, DEVICE_SETTINGS_CONTRIBUTION_METHODS as k, collectHydratedFieldEntries as kt, parseJsonObject as l, StreamSourceSchema as lt, createDeviceProxy as m, makeProfileBrokerId as mt, sleepCancellable as n, EncodedPacketSchema as nt, asNumber as o, ProfileSlotSchema as ot, isDeviceScopedCap as p, SubscribeFramesResultSchema as pt, BrokerStatsSchema as q, asBoolean as r, FrameHandleFormatSchema as rt, asString as s, ProfileSlotStatusSchema as st, sleep as t, DecodedFrameSchema as tt, parseJsonUnknown as u, SubscribeAudioChunksInputSchema as ut, createSliceHandle as v, DATAPLANE_SECRET_HEADER as vt, DeviceFeature as w, createEvent as wt, viewerUiCapability as x, BaseAddon as xt, RawStateResultSchema as y, SHARE_VIEW_KINDS as yt, readNodePin as z };
|