@camstack/addon-decoder-nodeav 0.1.1

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.
@@ -0,0 +1,16 @@
1
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+ var __commonJS = (cb, mod) => function __require2() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+
12
+ export {
13
+ __require,
14
+ __commonJS
15
+ };
16
+ //# sourceMappingURL=chunk-3GDQP6AS.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,293 @@
1
+ import * as _camstack_types from '@camstack/types';
2
+ import { BaseAddon, DecoderHwAccelConfig, IDecoderCapProvider, ProviderRegistration, FrameFormat, DecoderStats, IDecoderSession, DecoderSessionConfig, IScopedLogger, HwAccelBackend, IKernelHwAccel, EncodedPacket, DecodedFrame, Unsubscribe } from '@camstack/types';
3
+
4
+ /**
5
+ * Phase 2d of the pipeline-settings migration — decoder addon now
6
+ * owns its own `hwaccel` choice + `probedBestHwaccel` hint. Sessions
7
+ * resolve the effective backend from this addon's global settings
8
+ * instead of the orchestrator's legacy `AgentPipelineSettings.hwaccel`.
9
+ * Shared config shape + UI options live in `@camstack/types`
10
+ * (`DecoderHwAccelConfig` / `HWACCEL_OPTIONS`) so every decoder addon
11
+ * speaks the same vocabulary.
12
+ */
13
+ /** Cap-compatible frame shape — format must match DecodedFrameSchema enum values. */
14
+ type CapDecodedFrame = {
15
+ data: Uint8Array<ArrayBuffer>;
16
+ width: number;
17
+ height: number;
18
+ format: FrameFormat;
19
+ timestamp: number;
20
+ };
21
+ /** Cap-compatible session config — matches DecoderSessionConfigSchema output type. */
22
+ type CapDecoderSessionConfig = {
23
+ codec: string;
24
+ maxFps: number;
25
+ outputFormat: FrameFormat;
26
+ scale: number;
27
+ width?: number;
28
+ height?: number;
29
+ };
30
+ /** Cap-compatible encoded packet — data is Uint8Array matching EncodedPacketSchema. */
31
+ type CapEncodedPacket = {
32
+ type: 'video' | 'audio';
33
+ data: Uint8Array<ArrayBuffer>;
34
+ pts: number;
35
+ dts: number;
36
+ keyframe: boolean;
37
+ codec: string;
38
+ };
39
+ declare class DecoderNodeAvAddon extends BaseAddon<DecoderHwAccelConfig> implements IDecoderCapProvider {
40
+ private readonly sessions;
41
+ private readonly frameBuffers;
42
+ private readonly unsubscribers;
43
+ private readonly sessionMeta;
44
+ constructor();
45
+ protected globalSettingsSchema(): _camstack_types.ConfigUISchema;
46
+ protected onInitialize(): Promise<ProviderRegistration[]>;
47
+ /**
48
+ * Resolve the effective hwaccel backend for a new session. Reads
49
+ * this addon's own `hwaccel` setting first. `'auto'` defers to the
50
+ * session's local resolver (`ctx.kernel.hwaccel`) which probes the
51
+ * host and picks. No more orchestrator round-trip — decoder addon
52
+ * is self-sufficient for this setting as of phase 2d.
53
+ */
54
+ private resolveHwAccelPref;
55
+ /**
56
+ * Re-run the platform probe on this host and persist the detected
57
+ * backend as `probedBestHwaccel`. The operator's `hwaccel` setting
58
+ * is intentionally left alone — the probe only updates the hint.
59
+ */
60
+ reprobeHwaccel(): Promise<{
61
+ backend: string;
62
+ }>;
63
+ supportsCodec(input: {
64
+ codec: string;
65
+ }): Promise<boolean>;
66
+ getInfo(): Promise<{
67
+ id: string;
68
+ name: string;
69
+ isPullMode?: boolean;
70
+ priority?: number;
71
+ }>;
72
+ createSession(config: CapDecoderSessionConfig): Promise<{
73
+ sessionId: string;
74
+ nodeId: string;
75
+ }>;
76
+ destroySession(input: {
77
+ sessionId: string;
78
+ }): Promise<void>;
79
+ listActiveSessions(): Promise<readonly {
80
+ sessionId: string;
81
+ codec: string;
82
+ outputFormat: string;
83
+ createdAtMs: number;
84
+ }[]>;
85
+ pushPacket(input: {
86
+ sessionId: string;
87
+ packet: CapEncodedPacket;
88
+ }): Promise<void>;
89
+ openStream(input: {
90
+ sessionId: string;
91
+ url: string;
92
+ }): Promise<void>;
93
+ pullFrames(input: {
94
+ sessionId: string;
95
+ maxCount: number;
96
+ }): Promise<CapDecodedFrame[]>;
97
+ updateConfig(input: {
98
+ sessionId: string;
99
+ config: Partial<CapDecoderSessionConfig>;
100
+ }): Promise<void>;
101
+ getStats(input: {
102
+ sessionId: string;
103
+ }): Promise<DecoderStats>;
104
+ protected onShutdown(): Promise<void>;
105
+ }
106
+
107
+ /**
108
+ * NodeAvDecoderSession — IDecoderSession using node-av **push mode**.
109
+ *
110
+ * Receives raw H.264/H.265 Annex-B packets from the StreamBroker and decodes
111
+ * them in-process using the node-av low-level API:
112
+ *
113
+ * CodecParser → splits raw Annex-B bytes into proper AVPackets
114
+ * CodecContext → software decode (sendPacket / receiveFrame)
115
+ * SoftwareScaleContext → scale + convert to target format
116
+ *
117
+ * Output format depends on config.outputFormat:
118
+ * 'jpeg' → scale to RGB24, encode JPEG via sharp (for detection pipeline)
119
+ * 'gray' → scale to GRAY8 raw buffer (for motion detection only)
120
+ *
121
+ * No child process, no duplicate RTSP connection.
122
+ */
123
+
124
+ type HwAccelPref = 'auto' | 'none' | HwAccelBackend;
125
+ interface NodeAvDecoderSessionOptions {
126
+ /** Addon-level hwaccel preference — per-agent. Default `'auto'`. */
127
+ readonly hwaccel?: HwAccelPref;
128
+ /**
129
+ * Kernel hwaccel resolver — typically `ctx.kernel.hwaccel` passed
130
+ * from the addon. When present, `hwaccel: 'auto'` calls
131
+ * `resolver.resolve(null)` to get the platform-ordered backend
132
+ * list; when absent, `'auto'` degrades to software (the session
133
+ * cannot probe on its own because it lives in a package that must
134
+ * not depend on `@camstack/kernel`).
135
+ */
136
+ readonly hwaccelResolver?: IKernelHwAccel;
137
+ }
138
+ declare class NodeAvDecoderSession implements IDecoderSession {
139
+ private config;
140
+ private readonly logger;
141
+ private frameCallbacks;
142
+ private destroyed;
143
+ private parser;
144
+ private codecCtx;
145
+ private scaler;
146
+ private avPacket;
147
+ private avFrame;
148
+ private dstFrame;
149
+ private EAGAIN;
150
+ private PIX_FMT_GRAY8;
151
+ private PIX_FMT_RGB24;
152
+ private SWS_FAST_BILINEAR;
153
+ /**
154
+ * Decoder output mode. Drives both the scaler's destination pixel
155
+ * format and whether sharp runs the JPEG encode at the end:
156
+ *
157
+ * - `'jpeg'` — scaler→RGB24 → sharp encode → emit JPEG bytes
158
+ * - `'rgb'` — scaler→RGB24 → emit raw RGB24 (no sharp)
159
+ * - `'gray'` — scaler→GRAY8 → emit raw GRAY8 (no sharp)
160
+ *
161
+ * The broker holds the policy decision on which mode to request based
162
+ * on its active subscribers; on-the-fly conversion (e.g. RGB→JPEG for
163
+ * a WebRTC consumer that joined while detection holds the decoder in
164
+ * RGB mode) happens broker-side via the per-frame conversion cache.
165
+ */
166
+ private outputMode;
167
+ private sharpFn;
168
+ /**
169
+ * Backpressure for the sharp JPEG encode pipeline. The broker
170
+ * currently creates sessions with `maxFps: 0` (unlimited) and relies
171
+ * on per-subscriber throttling, so without a bound the
172
+ * fire-and-forget `sharp(...).toBuffer()` chain would accumulate
173
+ * unboundedly whenever sharp falls behind the decoder. Cap at
174
+ * `MAX_JPEG_INFLIGHT` pending encodes per session — any frame that
175
+ * arrives while the cap is saturated is dropped and counted.
176
+ */
177
+ private static readonly MAX_JPEG_INFLIGHT;
178
+ private jpegEncodeInFlight;
179
+ /**
180
+ * Map a `DecoderSessionConfig.outputFormat` value to one of the three
181
+ * native scaler/encoder modes the session understands. The cap-level
182
+ * format vocabulary is broader (it accepts `bgr`, `yuv420`) than what
183
+ * libav's scaler is wired for here — anything else degrades to RGB
184
+ * (the canonical raw mode) and the broker is expected to convert
185
+ * downstream if a subscriber needs a different shape.
186
+ */
187
+ private static resolveOutputMode;
188
+ private initialized;
189
+ private initializing;
190
+ private scalerInitializing;
191
+ /**
192
+ * Monotonic counter incremented by `updateConfig` whenever the
193
+ * scaler + dstFrame get invalidated (e.g. output format toggle).
194
+ * `initScaler` captures the current value at entry and aborts — or
195
+ * disposes the locally-built scaler — if the epoch moved while
196
+ * its async init was in flight. Without this, a toggle racing an
197
+ * in-flight init could leave two scalers allocated natively while
198
+ * `this.scaler` only holds a reference to one → libav leak.
199
+ */
200
+ private scalerEpoch;
201
+ /**
202
+ * One-shot guard for the "first frame" diagnostic log + raw frame
203
+ * dump. Setting this synchronously inside `emitDecodedFrame`
204
+ * prevents re-entry — without it we were using `outputFrames === 0`
205
+ * which stays true until the async sharp encode callback runs, so
206
+ * several decoded frames could trigger the dump before the first
207
+ * JPEG landed.
208
+ */
209
+ private firstFrameLogged;
210
+ private outWidth;
211
+ private outHeight;
212
+ private lastEmitTime;
213
+ private minIntervalMs;
214
+ private inputPackets;
215
+ private outputFrames;
216
+ private droppedFrames;
217
+ private totalDecodeTimeMs;
218
+ private startTime;
219
+ private readonly hwaccelPref;
220
+ private readonly hwaccelResolver;
221
+ /** The backend that actually initialised successfully — `'none'` = software fallback. */
222
+ private activeHwAccel;
223
+ private hwDevice;
224
+ private swTransferFrame;
225
+ constructor(config: DecoderSessionConfig, logger?: IScopedLogger, options?: NodeAvDecoderSessionOptions);
226
+ /**
227
+ * Resolve the backend preference list and try each one against
228
+ * node-av's HW context APIs. The first backend whose
229
+ * `HardwareDeviceContext.create()` succeeds gets attached to
230
+ * `codecCtx.hwDeviceCtx` + its hw pixel format registered via
231
+ * `setHardwarePixelFormat`. On any failure, falls through to the
232
+ * next backend; if all fail, returns with `activeHwAccel='none'`
233
+ * and the decoder runs in software on the same context.
234
+ */
235
+ private tryAttachHwAccel;
236
+ /**
237
+ * Download a HW frame (format == hw pix fmt) into a SW frame so the
238
+ * rest of the pipeline (scaler, JPEG encoder, grayscale passthrough)
239
+ * handles it identically to the pure-software path. Uses the sync
240
+ * variant so the synchronous receive loop below doesn't need to be
241
+ * async-ified. Returns `null` on transfer failure, meaning the
242
+ * caller should drop the frame.
243
+ */
244
+ private transferHwFrame;
245
+ /**
246
+ * Initialize the decoder pipeline on the first keyframe.
247
+ * After this returns, all hot-path methods are fully synchronous (except JPEG encode).
248
+ */
249
+ private initDecoder;
250
+ /**
251
+ * Initialize the scaler after the first frame tells us the actual
252
+ * dimensions. Output pixel format: RGB24 for JPEG encoding, GRAY8
253
+ * for raw motion.
254
+ *
255
+ * Builds `scaler` + `dstFrame` on local variables and publishes
256
+ * them onto `this` in a single atomic step at the end. Captures
257
+ * `scalerEpoch` at entry; if `updateConfig` invalidated the scaler
258
+ * while this init was in flight (epoch mismatch), the locally
259
+ * built pair is disposed and discarded so the later init wins.
260
+ * Without the local-first approach, partial state (scaler set,
261
+ * dstFrame still null) could be observed by a concurrent
262
+ * `emitDecodedFrame` call.
263
+ */
264
+ private initScaler;
265
+ pushPacket(packet: EncodedPacket): void;
266
+ private decodeRawData;
267
+ private decodePacket;
268
+ private emitDecodedFrame;
269
+ /**
270
+ * Extract packed pixel buffer from a decoded frame.
271
+ * FFmpeg's av_frame_get_buffer() may pad each row to alignment (32/64 bytes).
272
+ * Sharp and WASM consumers expect tightly-packed rows (stride = width * channels).
273
+ * If linesize matches expected stride, return the buffer directly (zero-copy).
274
+ */
275
+ private extractPackedBuffer;
276
+ /**
277
+ * Encode RGB24 raw buffer as JPEG and emit.
278
+ *
279
+ * Drops the frame (and counts it) when `MAX_JPEG_INFLIGHT` encodes
280
+ * are already pending — prevents unbounded growth of the
281
+ * fire-and-forget promise chain when sharp cannot keep up with the
282
+ * decode rate.
283
+ */
284
+ private encodeAndEmitJpeg;
285
+ private emitRawFrame;
286
+ onFrame(callback: (frame: DecodedFrame) => void): Unsubscribe;
287
+ updateConfig(update: Partial<DecoderSessionConfig>): void;
288
+ destroy(): Promise<void>;
289
+ getStats(): DecoderStats;
290
+ get isPullMode(): boolean;
291
+ }
292
+
293
+ export { DecoderNodeAvAddon, NodeAvDecoderSession };
@@ -0,0 +1,293 @@
1
+ import * as _camstack_types from '@camstack/types';
2
+ import { BaseAddon, DecoderHwAccelConfig, IDecoderCapProvider, ProviderRegistration, FrameFormat, DecoderStats, IDecoderSession, DecoderSessionConfig, IScopedLogger, HwAccelBackend, IKernelHwAccel, EncodedPacket, DecodedFrame, Unsubscribe } from '@camstack/types';
3
+
4
+ /**
5
+ * Phase 2d of the pipeline-settings migration — decoder addon now
6
+ * owns its own `hwaccel` choice + `probedBestHwaccel` hint. Sessions
7
+ * resolve the effective backend from this addon's global settings
8
+ * instead of the orchestrator's legacy `AgentPipelineSettings.hwaccel`.
9
+ * Shared config shape + UI options live in `@camstack/types`
10
+ * (`DecoderHwAccelConfig` / `HWACCEL_OPTIONS`) so every decoder addon
11
+ * speaks the same vocabulary.
12
+ */
13
+ /** Cap-compatible frame shape — format must match DecodedFrameSchema enum values. */
14
+ type CapDecodedFrame = {
15
+ data: Uint8Array<ArrayBuffer>;
16
+ width: number;
17
+ height: number;
18
+ format: FrameFormat;
19
+ timestamp: number;
20
+ };
21
+ /** Cap-compatible session config — matches DecoderSessionConfigSchema output type. */
22
+ type CapDecoderSessionConfig = {
23
+ codec: string;
24
+ maxFps: number;
25
+ outputFormat: FrameFormat;
26
+ scale: number;
27
+ width?: number;
28
+ height?: number;
29
+ };
30
+ /** Cap-compatible encoded packet — data is Uint8Array matching EncodedPacketSchema. */
31
+ type CapEncodedPacket = {
32
+ type: 'video' | 'audio';
33
+ data: Uint8Array<ArrayBuffer>;
34
+ pts: number;
35
+ dts: number;
36
+ keyframe: boolean;
37
+ codec: string;
38
+ };
39
+ declare class DecoderNodeAvAddon extends BaseAddon<DecoderHwAccelConfig> implements IDecoderCapProvider {
40
+ private readonly sessions;
41
+ private readonly frameBuffers;
42
+ private readonly unsubscribers;
43
+ private readonly sessionMeta;
44
+ constructor();
45
+ protected globalSettingsSchema(): _camstack_types.ConfigUISchema;
46
+ protected onInitialize(): Promise<ProviderRegistration[]>;
47
+ /**
48
+ * Resolve the effective hwaccel backend for a new session. Reads
49
+ * this addon's own `hwaccel` setting first. `'auto'` defers to the
50
+ * session's local resolver (`ctx.kernel.hwaccel`) which probes the
51
+ * host and picks. No more orchestrator round-trip — decoder addon
52
+ * is self-sufficient for this setting as of phase 2d.
53
+ */
54
+ private resolveHwAccelPref;
55
+ /**
56
+ * Re-run the platform probe on this host and persist the detected
57
+ * backend as `probedBestHwaccel`. The operator's `hwaccel` setting
58
+ * is intentionally left alone — the probe only updates the hint.
59
+ */
60
+ reprobeHwaccel(): Promise<{
61
+ backend: string;
62
+ }>;
63
+ supportsCodec(input: {
64
+ codec: string;
65
+ }): Promise<boolean>;
66
+ getInfo(): Promise<{
67
+ id: string;
68
+ name: string;
69
+ isPullMode?: boolean;
70
+ priority?: number;
71
+ }>;
72
+ createSession(config: CapDecoderSessionConfig): Promise<{
73
+ sessionId: string;
74
+ nodeId: string;
75
+ }>;
76
+ destroySession(input: {
77
+ sessionId: string;
78
+ }): Promise<void>;
79
+ listActiveSessions(): Promise<readonly {
80
+ sessionId: string;
81
+ codec: string;
82
+ outputFormat: string;
83
+ createdAtMs: number;
84
+ }[]>;
85
+ pushPacket(input: {
86
+ sessionId: string;
87
+ packet: CapEncodedPacket;
88
+ }): Promise<void>;
89
+ openStream(input: {
90
+ sessionId: string;
91
+ url: string;
92
+ }): Promise<void>;
93
+ pullFrames(input: {
94
+ sessionId: string;
95
+ maxCount: number;
96
+ }): Promise<CapDecodedFrame[]>;
97
+ updateConfig(input: {
98
+ sessionId: string;
99
+ config: Partial<CapDecoderSessionConfig>;
100
+ }): Promise<void>;
101
+ getStats(input: {
102
+ sessionId: string;
103
+ }): Promise<DecoderStats>;
104
+ protected onShutdown(): Promise<void>;
105
+ }
106
+
107
+ /**
108
+ * NodeAvDecoderSession — IDecoderSession using node-av **push mode**.
109
+ *
110
+ * Receives raw H.264/H.265 Annex-B packets from the StreamBroker and decodes
111
+ * them in-process using the node-av low-level API:
112
+ *
113
+ * CodecParser → splits raw Annex-B bytes into proper AVPackets
114
+ * CodecContext → software decode (sendPacket / receiveFrame)
115
+ * SoftwareScaleContext → scale + convert to target format
116
+ *
117
+ * Output format depends on config.outputFormat:
118
+ * 'jpeg' → scale to RGB24, encode JPEG via sharp (for detection pipeline)
119
+ * 'gray' → scale to GRAY8 raw buffer (for motion detection only)
120
+ *
121
+ * No child process, no duplicate RTSP connection.
122
+ */
123
+
124
+ type HwAccelPref = 'auto' | 'none' | HwAccelBackend;
125
+ interface NodeAvDecoderSessionOptions {
126
+ /** Addon-level hwaccel preference — per-agent. Default `'auto'`. */
127
+ readonly hwaccel?: HwAccelPref;
128
+ /**
129
+ * Kernel hwaccel resolver — typically `ctx.kernel.hwaccel` passed
130
+ * from the addon. When present, `hwaccel: 'auto'` calls
131
+ * `resolver.resolve(null)` to get the platform-ordered backend
132
+ * list; when absent, `'auto'` degrades to software (the session
133
+ * cannot probe on its own because it lives in a package that must
134
+ * not depend on `@camstack/kernel`).
135
+ */
136
+ readonly hwaccelResolver?: IKernelHwAccel;
137
+ }
138
+ declare class NodeAvDecoderSession implements IDecoderSession {
139
+ private config;
140
+ private readonly logger;
141
+ private frameCallbacks;
142
+ private destroyed;
143
+ private parser;
144
+ private codecCtx;
145
+ private scaler;
146
+ private avPacket;
147
+ private avFrame;
148
+ private dstFrame;
149
+ private EAGAIN;
150
+ private PIX_FMT_GRAY8;
151
+ private PIX_FMT_RGB24;
152
+ private SWS_FAST_BILINEAR;
153
+ /**
154
+ * Decoder output mode. Drives both the scaler's destination pixel
155
+ * format and whether sharp runs the JPEG encode at the end:
156
+ *
157
+ * - `'jpeg'` — scaler→RGB24 → sharp encode → emit JPEG bytes
158
+ * - `'rgb'` — scaler→RGB24 → emit raw RGB24 (no sharp)
159
+ * - `'gray'` — scaler→GRAY8 → emit raw GRAY8 (no sharp)
160
+ *
161
+ * The broker holds the policy decision on which mode to request based
162
+ * on its active subscribers; on-the-fly conversion (e.g. RGB→JPEG for
163
+ * a WebRTC consumer that joined while detection holds the decoder in
164
+ * RGB mode) happens broker-side via the per-frame conversion cache.
165
+ */
166
+ private outputMode;
167
+ private sharpFn;
168
+ /**
169
+ * Backpressure for the sharp JPEG encode pipeline. The broker
170
+ * currently creates sessions with `maxFps: 0` (unlimited) and relies
171
+ * on per-subscriber throttling, so without a bound the
172
+ * fire-and-forget `sharp(...).toBuffer()` chain would accumulate
173
+ * unboundedly whenever sharp falls behind the decoder. Cap at
174
+ * `MAX_JPEG_INFLIGHT` pending encodes per session — any frame that
175
+ * arrives while the cap is saturated is dropped and counted.
176
+ */
177
+ private static readonly MAX_JPEG_INFLIGHT;
178
+ private jpegEncodeInFlight;
179
+ /**
180
+ * Map a `DecoderSessionConfig.outputFormat` value to one of the three
181
+ * native scaler/encoder modes the session understands. The cap-level
182
+ * format vocabulary is broader (it accepts `bgr`, `yuv420`) than what
183
+ * libav's scaler is wired for here — anything else degrades to RGB
184
+ * (the canonical raw mode) and the broker is expected to convert
185
+ * downstream if a subscriber needs a different shape.
186
+ */
187
+ private static resolveOutputMode;
188
+ private initialized;
189
+ private initializing;
190
+ private scalerInitializing;
191
+ /**
192
+ * Monotonic counter incremented by `updateConfig` whenever the
193
+ * scaler + dstFrame get invalidated (e.g. output format toggle).
194
+ * `initScaler` captures the current value at entry and aborts — or
195
+ * disposes the locally-built scaler — if the epoch moved while
196
+ * its async init was in flight. Without this, a toggle racing an
197
+ * in-flight init could leave two scalers allocated natively while
198
+ * `this.scaler` only holds a reference to one → libav leak.
199
+ */
200
+ private scalerEpoch;
201
+ /**
202
+ * One-shot guard for the "first frame" diagnostic log + raw frame
203
+ * dump. Setting this synchronously inside `emitDecodedFrame`
204
+ * prevents re-entry — without it we were using `outputFrames === 0`
205
+ * which stays true until the async sharp encode callback runs, so
206
+ * several decoded frames could trigger the dump before the first
207
+ * JPEG landed.
208
+ */
209
+ private firstFrameLogged;
210
+ private outWidth;
211
+ private outHeight;
212
+ private lastEmitTime;
213
+ private minIntervalMs;
214
+ private inputPackets;
215
+ private outputFrames;
216
+ private droppedFrames;
217
+ private totalDecodeTimeMs;
218
+ private startTime;
219
+ private readonly hwaccelPref;
220
+ private readonly hwaccelResolver;
221
+ /** The backend that actually initialised successfully — `'none'` = software fallback. */
222
+ private activeHwAccel;
223
+ private hwDevice;
224
+ private swTransferFrame;
225
+ constructor(config: DecoderSessionConfig, logger?: IScopedLogger, options?: NodeAvDecoderSessionOptions);
226
+ /**
227
+ * Resolve the backend preference list and try each one against
228
+ * node-av's HW context APIs. The first backend whose
229
+ * `HardwareDeviceContext.create()` succeeds gets attached to
230
+ * `codecCtx.hwDeviceCtx` + its hw pixel format registered via
231
+ * `setHardwarePixelFormat`. On any failure, falls through to the
232
+ * next backend; if all fail, returns with `activeHwAccel='none'`
233
+ * and the decoder runs in software on the same context.
234
+ */
235
+ private tryAttachHwAccel;
236
+ /**
237
+ * Download a HW frame (format == hw pix fmt) into a SW frame so the
238
+ * rest of the pipeline (scaler, JPEG encoder, grayscale passthrough)
239
+ * handles it identically to the pure-software path. Uses the sync
240
+ * variant so the synchronous receive loop below doesn't need to be
241
+ * async-ified. Returns `null` on transfer failure, meaning the
242
+ * caller should drop the frame.
243
+ */
244
+ private transferHwFrame;
245
+ /**
246
+ * Initialize the decoder pipeline on the first keyframe.
247
+ * After this returns, all hot-path methods are fully synchronous (except JPEG encode).
248
+ */
249
+ private initDecoder;
250
+ /**
251
+ * Initialize the scaler after the first frame tells us the actual
252
+ * dimensions. Output pixel format: RGB24 for JPEG encoding, GRAY8
253
+ * for raw motion.
254
+ *
255
+ * Builds `scaler` + `dstFrame` on local variables and publishes
256
+ * them onto `this` in a single atomic step at the end. Captures
257
+ * `scalerEpoch` at entry; if `updateConfig` invalidated the scaler
258
+ * while this init was in flight (epoch mismatch), the locally
259
+ * built pair is disposed and discarded so the later init wins.
260
+ * Without the local-first approach, partial state (scaler set,
261
+ * dstFrame still null) could be observed by a concurrent
262
+ * `emitDecodedFrame` call.
263
+ */
264
+ private initScaler;
265
+ pushPacket(packet: EncodedPacket): void;
266
+ private decodeRawData;
267
+ private decodePacket;
268
+ private emitDecodedFrame;
269
+ /**
270
+ * Extract packed pixel buffer from a decoded frame.
271
+ * FFmpeg's av_frame_get_buffer() may pad each row to alignment (32/64 bytes).
272
+ * Sharp and WASM consumers expect tightly-packed rows (stride = width * channels).
273
+ * If linesize matches expected stride, return the buffer directly (zero-copy).
274
+ */
275
+ private extractPackedBuffer;
276
+ /**
277
+ * Encode RGB24 raw buffer as JPEG and emit.
278
+ *
279
+ * Drops the frame (and counts it) when `MAX_JPEG_INFLIGHT` encodes
280
+ * are already pending — prevents unbounded growth of the
281
+ * fire-and-forget promise chain when sharp cannot keep up with the
282
+ * decode rate.
283
+ */
284
+ private encodeAndEmitJpeg;
285
+ private emitRawFrame;
286
+ onFrame(callback: (frame: DecodedFrame) => void): Unsubscribe;
287
+ updateConfig(update: Partial<DecoderSessionConfig>): void;
288
+ destroy(): Promise<void>;
289
+ getStats(): DecoderStats;
290
+ get isPullMode(): boolean;
291
+ }
292
+
293
+ export { DecoderNodeAvAddon, NodeAvDecoderSession };