@camstack/addon-decoder-ffmpeg 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,195 @@
1
+ import * as _camstack_types from '@camstack/types';
2
+ import { BaseAddon, DecoderHwAccelConfig, IDecoderCapProvider, ProviderRegistration, FrameFormat, DecoderStats, IDecoderProvider, IScopedLogger, DecoderSessionConfig, IDecoderSession, HwAccelBackend, IKernelHwAccel, EncodedPacket, DecodedFrame, Unsubscribe } from '@camstack/types';
3
+
4
+ /** Cap-compatible frame shape — format must match DecodedFrameSchema enum values. */
5
+ type CapDecodedFrame = {
6
+ data: Uint8Array<ArrayBuffer>;
7
+ width: number;
8
+ height: number;
9
+ format: FrameFormat;
10
+ timestamp: number;
11
+ };
12
+ /** Cap-compatible session config — matches DecoderSessionConfigSchema output type. */
13
+ type CapDecoderSessionConfig = {
14
+ codec: string;
15
+ maxFps: number;
16
+ outputFormat: FrameFormat;
17
+ scale: number;
18
+ width?: number;
19
+ height?: number;
20
+ };
21
+ /** Cap-compatible encoded packet — data is Uint8Array matching EncodedPacketSchema. */
22
+ type CapEncodedPacket = {
23
+ type: 'video' | 'audio';
24
+ data: Uint8Array<ArrayBuffer>;
25
+ pts: number;
26
+ dts: number;
27
+ keyframe: boolean;
28
+ codec: string;
29
+ };
30
+ /**
31
+ * FFmpeg decoder addon — H264/HEVC/MJPEG decode via ffmpeg child
32
+ * process.
33
+ *
34
+ * Phase 2d of the pipeline-settings migration — ffmpeg decoder owns
35
+ * its own `hwaccel` choice + `probedBestHwaccel` hint. Sessions
36
+ * resolve the effective backend from this addon's global settings
37
+ * instead of the orchestrator's legacy `AgentPipelineSettings.hwaccel`.
38
+ * Session constructor still appends `-hwaccel <name>` to the ffmpeg
39
+ * argv as before.
40
+ *
41
+ * Implements the sessionId-based IDecoderCapProvider cap interface.
42
+ * Sessions are managed internally via a Map; frames are polled via
43
+ * RingBuffer.
44
+ */
45
+ declare class DecoderFfmpegAddon extends BaseAddon<DecoderHwAccelConfig> implements IDecoderCapProvider {
46
+ private readonly sessions;
47
+ private readonly frameBuffers;
48
+ private readonly unsubscribers;
49
+ private readonly sessionMeta;
50
+ constructor();
51
+ protected globalSettingsSchema(): _camstack_types.ConfigUISchema;
52
+ protected onInitialize(): Promise<ProviderRegistration[]>;
53
+ /**
54
+ * Resolve the effective hwaccel backend for a new session. Reads
55
+ * this addon's own `hwaccel` setting. `'auto'` defers to the
56
+ * session's local resolver (`ctx.kernel.hwaccel`).
57
+ */
58
+ private resolveHwAccelPref;
59
+ /**
60
+ * Re-run the platform probe on this host and persist the detected
61
+ * backend as `probedBestHwaccel`. Operator `hwaccel` setting is not
62
+ * touched — only the hint.
63
+ */
64
+ reprobeHwaccel(): Promise<{
65
+ backend: string;
66
+ }>;
67
+ supportsCodec(input: {
68
+ codec: string;
69
+ }): Promise<boolean>;
70
+ getInfo(): Promise<{
71
+ id: string;
72
+ name: string;
73
+ isPullMode?: boolean;
74
+ priority?: number;
75
+ }>;
76
+ createSession(config: CapDecoderSessionConfig): Promise<{
77
+ sessionId: string;
78
+ nodeId: string;
79
+ }>;
80
+ destroySession(input: {
81
+ sessionId: string;
82
+ }): Promise<void>;
83
+ listActiveSessions(): Promise<readonly {
84
+ sessionId: string;
85
+ codec: string;
86
+ outputFormat: string;
87
+ createdAtMs: number;
88
+ }[]>;
89
+ pushPacket(input: {
90
+ sessionId: string;
91
+ packet: CapEncodedPacket;
92
+ }): Promise<void>;
93
+ openStream(input: {
94
+ sessionId: string;
95
+ url: string;
96
+ }): Promise<void>;
97
+ pullFrames(input: {
98
+ sessionId: string;
99
+ maxCount: number;
100
+ }): Promise<CapDecodedFrame[]>;
101
+ updateConfig(input: {
102
+ sessionId: string;
103
+ config: Partial<CapDecoderSessionConfig>;
104
+ }): Promise<void>;
105
+ getStats(input: {
106
+ sessionId: string;
107
+ }): Promise<DecoderStats>;
108
+ protected onShutdown(): Promise<void>;
109
+ }
110
+
111
+ declare class FfmpegDecoderProvider implements IDecoderProvider {
112
+ readonly id = "ffmpeg";
113
+ readonly name = "FFmpeg Decoder";
114
+ readonly isPullMode = false;
115
+ /** Software decoder — used as fallback when hardware decoders are unavailable. */
116
+ readonly priority = 50;
117
+ private logger;
118
+ setLogger(logger: IScopedLogger): void;
119
+ supportsCodec(input: {
120
+ codec: string;
121
+ }): Promise<boolean>;
122
+ createSession(config: DecoderSessionConfig): Promise<IDecoderSession>;
123
+ }
124
+
125
+ type HwAccelPref = 'auto' | 'none' | HwAccelBackend;
126
+ interface FfmpegDecoderSessionOptions {
127
+ /** Addon-level hwaccel preference — per-agent. Default `'auto'`. */
128
+ readonly hwaccel?: HwAccelPref;
129
+ /** Kernel hwaccel resolver — `ctx.kernel.hwaccel` passed from the addon. */
130
+ readonly hwaccelResolver?: IKernelHwAccel;
131
+ }
132
+ declare class FfmpegDecoderSession implements IDecoderSession {
133
+ private config;
134
+ private frameDropper;
135
+ private process;
136
+ private frameCallbacks;
137
+ private outputBuffer;
138
+ private destroyed;
139
+ private readonly logger;
140
+ /** When openStream() is used, we read from RTSP directly (not push mode) */
141
+ private pullMode;
142
+ private cachedWidth;
143
+ private cachedHeight;
144
+ private inputPackets;
145
+ private outputFrames;
146
+ private droppedFrames;
147
+ private totalDecodeTimeMs;
148
+ private decodeCount;
149
+ private startTime;
150
+ private readonly hwaccelPref;
151
+ private readonly hwaccelResolver;
152
+ /** The backend we actually passed to ffmpeg `-hwaccel` — `'none'` = software. */
153
+ private activeHwAccel;
154
+ /**
155
+ * Backend resolution is async (calls into `ctx.kernel.hwaccel`), but
156
+ * `pushPacket` is sync — we kick the resolve off in the constructor
157
+ * and cache the result. By the time the first keyframe arrives
158
+ * (~30ms for RTSP), the resolver has completed (it's ultimately
159
+ * `os.platform()` + file checks → sub-ms). If `ensurePushProcess`
160
+ * fires before resolve settles, it skips hwaccel for that spawn;
161
+ * reconnect loop gets the flag on subsequent sessions.
162
+ */
163
+ private resolvedBackend;
164
+ constructor(config: DecoderSessionConfig, logger?: IScopedLogger, options?: FfmpegDecoderSessionOptions);
165
+ /**
166
+ * Resolve the preferred backend for this host and return the first
167
+ * hit, or `null` when software is requested / nothing available.
168
+ * FFmpeg CLI accepts the `-hwaccel <name>` identifier directly.
169
+ */
170
+ private resolveHwAccelBackend;
171
+ /**
172
+ * Open an RTSP stream directly — ffmpeg reads from the URL and outputs JPEG frames.
173
+ * This avoids the raw h264 pipe which loses SPS/PPS metadata on some cameras.
174
+ */
175
+ openStream(url: string): Promise<void>;
176
+ private ensurePushProcess;
177
+ private killFfmpeg;
178
+ private handleOutputData;
179
+ private emitFrame;
180
+ pushPacket(packet: EncodedPacket): void;
181
+ onFrame(callback: (frame: DecodedFrame) => void): Unsubscribe;
182
+ updateConfig(update: Partial<DecoderSessionConfig>): void;
183
+ destroy(): Promise<void>;
184
+ getStats(): DecoderStats;
185
+ }
186
+
187
+ declare class FrameDropper {
188
+ private intervalMs;
189
+ private lastPassedAt;
190
+ constructor(maxFps: number);
191
+ shouldKeep(): boolean;
192
+ setMaxFps(maxFps: number): void;
193
+ }
194
+
195
+ export { DecoderFfmpegAddon, FfmpegDecoderProvider, FfmpegDecoderSession, FrameDropper };
@@ -0,0 +1,195 @@
1
+ import * as _camstack_types from '@camstack/types';
2
+ import { BaseAddon, DecoderHwAccelConfig, IDecoderCapProvider, ProviderRegistration, FrameFormat, DecoderStats, IDecoderProvider, IScopedLogger, DecoderSessionConfig, IDecoderSession, HwAccelBackend, IKernelHwAccel, EncodedPacket, DecodedFrame, Unsubscribe } from '@camstack/types';
3
+
4
+ /** Cap-compatible frame shape — format must match DecodedFrameSchema enum values. */
5
+ type CapDecodedFrame = {
6
+ data: Uint8Array<ArrayBuffer>;
7
+ width: number;
8
+ height: number;
9
+ format: FrameFormat;
10
+ timestamp: number;
11
+ };
12
+ /** Cap-compatible session config — matches DecoderSessionConfigSchema output type. */
13
+ type CapDecoderSessionConfig = {
14
+ codec: string;
15
+ maxFps: number;
16
+ outputFormat: FrameFormat;
17
+ scale: number;
18
+ width?: number;
19
+ height?: number;
20
+ };
21
+ /** Cap-compatible encoded packet — data is Uint8Array matching EncodedPacketSchema. */
22
+ type CapEncodedPacket = {
23
+ type: 'video' | 'audio';
24
+ data: Uint8Array<ArrayBuffer>;
25
+ pts: number;
26
+ dts: number;
27
+ keyframe: boolean;
28
+ codec: string;
29
+ };
30
+ /**
31
+ * FFmpeg decoder addon — H264/HEVC/MJPEG decode via ffmpeg child
32
+ * process.
33
+ *
34
+ * Phase 2d of the pipeline-settings migration — ffmpeg decoder owns
35
+ * its own `hwaccel` choice + `probedBestHwaccel` hint. Sessions
36
+ * resolve the effective backend from this addon's global settings
37
+ * instead of the orchestrator's legacy `AgentPipelineSettings.hwaccel`.
38
+ * Session constructor still appends `-hwaccel <name>` to the ffmpeg
39
+ * argv as before.
40
+ *
41
+ * Implements the sessionId-based IDecoderCapProvider cap interface.
42
+ * Sessions are managed internally via a Map; frames are polled via
43
+ * RingBuffer.
44
+ */
45
+ declare class DecoderFfmpegAddon extends BaseAddon<DecoderHwAccelConfig> implements IDecoderCapProvider {
46
+ private readonly sessions;
47
+ private readonly frameBuffers;
48
+ private readonly unsubscribers;
49
+ private readonly sessionMeta;
50
+ constructor();
51
+ protected globalSettingsSchema(): _camstack_types.ConfigUISchema;
52
+ protected onInitialize(): Promise<ProviderRegistration[]>;
53
+ /**
54
+ * Resolve the effective hwaccel backend for a new session. Reads
55
+ * this addon's own `hwaccel` setting. `'auto'` defers to the
56
+ * session's local resolver (`ctx.kernel.hwaccel`).
57
+ */
58
+ private resolveHwAccelPref;
59
+ /**
60
+ * Re-run the platform probe on this host and persist the detected
61
+ * backend as `probedBestHwaccel`. Operator `hwaccel` setting is not
62
+ * touched — only the hint.
63
+ */
64
+ reprobeHwaccel(): Promise<{
65
+ backend: string;
66
+ }>;
67
+ supportsCodec(input: {
68
+ codec: string;
69
+ }): Promise<boolean>;
70
+ getInfo(): Promise<{
71
+ id: string;
72
+ name: string;
73
+ isPullMode?: boolean;
74
+ priority?: number;
75
+ }>;
76
+ createSession(config: CapDecoderSessionConfig): Promise<{
77
+ sessionId: string;
78
+ nodeId: string;
79
+ }>;
80
+ destroySession(input: {
81
+ sessionId: string;
82
+ }): Promise<void>;
83
+ listActiveSessions(): Promise<readonly {
84
+ sessionId: string;
85
+ codec: string;
86
+ outputFormat: string;
87
+ createdAtMs: number;
88
+ }[]>;
89
+ pushPacket(input: {
90
+ sessionId: string;
91
+ packet: CapEncodedPacket;
92
+ }): Promise<void>;
93
+ openStream(input: {
94
+ sessionId: string;
95
+ url: string;
96
+ }): Promise<void>;
97
+ pullFrames(input: {
98
+ sessionId: string;
99
+ maxCount: number;
100
+ }): Promise<CapDecodedFrame[]>;
101
+ updateConfig(input: {
102
+ sessionId: string;
103
+ config: Partial<CapDecoderSessionConfig>;
104
+ }): Promise<void>;
105
+ getStats(input: {
106
+ sessionId: string;
107
+ }): Promise<DecoderStats>;
108
+ protected onShutdown(): Promise<void>;
109
+ }
110
+
111
+ declare class FfmpegDecoderProvider implements IDecoderProvider {
112
+ readonly id = "ffmpeg";
113
+ readonly name = "FFmpeg Decoder";
114
+ readonly isPullMode = false;
115
+ /** Software decoder — used as fallback when hardware decoders are unavailable. */
116
+ readonly priority = 50;
117
+ private logger;
118
+ setLogger(logger: IScopedLogger): void;
119
+ supportsCodec(input: {
120
+ codec: string;
121
+ }): Promise<boolean>;
122
+ createSession(config: DecoderSessionConfig): Promise<IDecoderSession>;
123
+ }
124
+
125
+ type HwAccelPref = 'auto' | 'none' | HwAccelBackend;
126
+ interface FfmpegDecoderSessionOptions {
127
+ /** Addon-level hwaccel preference — per-agent. Default `'auto'`. */
128
+ readonly hwaccel?: HwAccelPref;
129
+ /** Kernel hwaccel resolver — `ctx.kernel.hwaccel` passed from the addon. */
130
+ readonly hwaccelResolver?: IKernelHwAccel;
131
+ }
132
+ declare class FfmpegDecoderSession implements IDecoderSession {
133
+ private config;
134
+ private frameDropper;
135
+ private process;
136
+ private frameCallbacks;
137
+ private outputBuffer;
138
+ private destroyed;
139
+ private readonly logger;
140
+ /** When openStream() is used, we read from RTSP directly (not push mode) */
141
+ private pullMode;
142
+ private cachedWidth;
143
+ private cachedHeight;
144
+ private inputPackets;
145
+ private outputFrames;
146
+ private droppedFrames;
147
+ private totalDecodeTimeMs;
148
+ private decodeCount;
149
+ private startTime;
150
+ private readonly hwaccelPref;
151
+ private readonly hwaccelResolver;
152
+ /** The backend we actually passed to ffmpeg `-hwaccel` — `'none'` = software. */
153
+ private activeHwAccel;
154
+ /**
155
+ * Backend resolution is async (calls into `ctx.kernel.hwaccel`), but
156
+ * `pushPacket` is sync — we kick the resolve off in the constructor
157
+ * and cache the result. By the time the first keyframe arrives
158
+ * (~30ms for RTSP), the resolver has completed (it's ultimately
159
+ * `os.platform()` + file checks → sub-ms). If `ensurePushProcess`
160
+ * fires before resolve settles, it skips hwaccel for that spawn;
161
+ * reconnect loop gets the flag on subsequent sessions.
162
+ */
163
+ private resolvedBackend;
164
+ constructor(config: DecoderSessionConfig, logger?: IScopedLogger, options?: FfmpegDecoderSessionOptions);
165
+ /**
166
+ * Resolve the preferred backend for this host and return the first
167
+ * hit, or `null` when software is requested / nothing available.
168
+ * FFmpeg CLI accepts the `-hwaccel <name>` identifier directly.
169
+ */
170
+ private resolveHwAccelBackend;
171
+ /**
172
+ * Open an RTSP stream directly — ffmpeg reads from the URL and outputs JPEG frames.
173
+ * This avoids the raw h264 pipe which loses SPS/PPS metadata on some cameras.
174
+ */
175
+ openStream(url: string): Promise<void>;
176
+ private ensurePushProcess;
177
+ private killFfmpeg;
178
+ private handleOutputData;
179
+ private emitFrame;
180
+ pushPacket(packet: EncodedPacket): void;
181
+ onFrame(callback: (frame: DecodedFrame) => void): Unsubscribe;
182
+ updateConfig(update: Partial<DecoderSessionConfig>): void;
183
+ destroy(): Promise<void>;
184
+ getStats(): DecoderStats;
185
+ }
186
+
187
+ declare class FrameDropper {
188
+ private intervalMs;
189
+ private lastPassedAt;
190
+ constructor(maxFps: number);
191
+ shouldKeep(): boolean;
192
+ setMaxFps(maxFps: number): void;
193
+ }
194
+
195
+ export { DecoderFfmpegAddon, FfmpegDecoderProvider, FfmpegDecoderSession, FrameDropper };