@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.
- package/dist/index.d.mts +195 -0
- package/dist/index.d.ts +195 -0
- package/dist/index.js +577 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +553 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +67 -0
package/dist/index.d.mts
ADDED
|
@@ -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 };
|
package/dist/index.d.ts
ADDED
|
@@ -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 };
|