@camera.ui/common 0.0.38 → 0.0.40

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.
Files changed (42) hide show
  1. package/dist/camera/codec-hardware.d.ts +162 -43
  2. package/dist/camera/codec-hardware.js +612 -348
  3. package/dist/camera/codec-hardware.js.map +1 -1
  4. package/dist/camera/ffmpeg-process.js +1 -1
  5. package/dist/camera/ffmpeg-process.js.map +1 -1
  6. package/dist/camera/ffmpeg.d.ts +19 -2
  7. package/dist/camera/ffmpeg.js +56 -29
  8. package/dist/camera/ffmpeg.js.map +1 -1
  9. package/dist/camera/utils.d.ts +16 -9
  10. package/dist/camera/utils.js +316 -90
  11. package/dist/camera/utils.js.map +1 -1
  12. package/dist/index.d.ts +0 -1
  13. package/dist/index.js +0 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/logger/index.d.ts +4 -4
  16. package/dist/logger/index.js +22 -27
  17. package/dist/logger/index.js.map +1 -1
  18. package/dist/logger/test.js +0 -15
  19. package/dist/logger/test.js.map +1 -1
  20. package/dist/net/index.js +4 -4
  21. package/dist/net/index.js.map +1 -1
  22. package/dist/utils/index.d.ts +1 -0
  23. package/dist/utils/index.js +1 -0
  24. package/dist/utils/index.js.map +1 -1
  25. package/dist/utils/reactive.d.ts +11 -0
  26. package/dist/utils/reactive.js +32 -0
  27. package/dist/utils/reactive.js.map +1 -0
  28. package/dist/utils/utils.js +1 -0
  29. package/dist/utils/utils.js.map +1 -1
  30. package/package.json +15 -21
  31. package/dist/nats/index.d.ts +0 -3
  32. package/dist/nats/index.js +0 -4
  33. package/dist/nats/index.js.map +0 -1
  34. package/dist/nats/messageQueue.d.ts +0 -48
  35. package/dist/nats/messageQueue.js +0 -141
  36. package/dist/nats/messageQueue.js.map +0 -1
  37. package/dist/nats/nats.d.ts +0 -30
  38. package/dist/nats/nats.js +0 -85
  39. package/dist/nats/nats.js.map +0 -1
  40. package/dist/nats/packer.d.ts +0 -2
  41. package/dist/nats/packer.js +0 -17
  42. package/dist/nats/packer.js.map +0 -1
@@ -1,388 +1,667 @@
1
- import { spawn } from 'node:child_process';
2
- import { arch } from 'node:os';
1
+ import { exec, spawn } from 'node:child_process';
2
+ import { arch, platform } from 'node:os';
3
+ import { promisify } from 'node:util';
4
+ const execAwait = promisify(exec);
5
+ /**
6
+ * Class to intelligently select the best VAAPI device
7
+ */
8
+ export class VaapiDeviceSelector {
9
+ _selectedGpu = null;
10
+ /**
11
+ * Get the best available VAAPI device
12
+ * @returns Path to the VAAPI device
13
+ */
14
+ async getSelectedGpu() {
15
+ if (this._selectedGpu) {
16
+ return this._selectedGpu;
17
+ }
18
+ // Default device if all else fails
19
+ const defaultDevice = '/dev/dri/renderD128';
20
+ // Check if /dev/dri exists
21
+ try {
22
+ const { stdout } = await execAwait('ls /dev/dri');
23
+ const devices = stdout.split('\n').filter((d) => d.startsWith('render'));
24
+ if (devices.length === 0) {
25
+ return defaultDevice;
26
+ }
27
+ if (devices.length === 1) {
28
+ this._selectedGpu = `/dev/dri/${devices[0]}`;
29
+ return this._selectedGpu;
30
+ }
31
+ // Test each device with vainfo
32
+ for (const device of devices) {
33
+ try {
34
+ await execAwait(`vainfo --display drm --device /dev/dri/${device}`);
35
+ this._selectedGpu = `/dev/dri/${device}`;
36
+ return this._selectedGpu;
37
+ }
38
+ catch {
39
+ // Continue to next device
40
+ }
41
+ }
42
+ return defaultDevice;
43
+ }
44
+ catch {
45
+ return defaultDevice;
46
+ }
47
+ }
48
+ }
49
+ /**
50
+ * Class to handle hardware-accelerated video encoding with FFmpeg
51
+ */
3
52
  export class FFMpegHardware {
4
- // H264 Codecs
5
- static VideoCodecN264 = { name: 'H264 NVENC', codecName: 'h264_nvenc' };
6
- static VideoCodecN264H = { name: 'H264 NVENC HQ profile', codecName: 'h264_nvenc' };
7
- static VideoCodecI264 = { name: 'H264 Intel Quick Sync Video (QSV)', codecName: 'h264_qsv' };
8
- static VideoCodecI264C = { name: 'H264 Intel Quick Sync Video (QSV) Compatibility profile', codecName: 'h264_qsv' };
9
- static VideoCodecM264 = { name: 'H264 VideoToolbox', codecName: 'h264_videotoolbox' };
10
- static VideoCodecV264 = { name: 'H264 VAAPI', codecName: 'h264_vaapi' };
11
- static VideoCodecO264 = { name: 'H264 OpenMAX (Raspberry Pi)', codecName: 'h264_omx' };
12
- static VideoCodecRK264 = { name: 'H264 Rockchip MPP', codecName: 'h264_rkmpp' };
13
- static VideoCodecJetson264 = { name: 'H264 Jetson NVMM', codecName: 'h264_nvmpi' };
14
- static VideoCodecV4L2M2M264 = { name: 'H264 V4L2M2M', codecName: 'h264_v4l2m2m' };
15
- static VideoCodecVulkan264 = { name: 'H264 Vulkan', codecName: 'h264_vulkan' };
16
- // H265 (HEVC) Codecs
17
- static VideoCodecN265 = { name: 'HEVC NVENC', codecName: 'hevc_nvenc' };
18
- static VideoCodecI265 = { name: 'HEVC Intel Quick Sync Video (QSV)', codecName: 'hevc_qsv' };
19
- static VideoCodecM265 = { name: 'HEVC VideoToolbox', codecName: 'hevc_videotoolbox' };
20
- static VideoCodecV265 = { name: 'HEVC VAAPI', codecName: 'hevc_vaapi' };
21
- static VideoCodecRK265 = { name: 'HEVC Rockchip MPP', codecName: 'hevc_rkmpp' };
22
- static VideoCodecJetson265 = { name: 'HEVC Jetson NVMM', codecName: 'hevc_nvmpi' };
23
- static VideoCodecV4L2M2M265 = { name: 'HEVC V4L2M2M', codecName: 'hevc_v4l2m2m' };
24
- static VideoCodecVulkan265 = { name: 'HEVC Vulkan', codecName: 'hevc_vulkan' };
25
- hwCodecSupport;
26
- minHeight = 360;
53
+ // H.264 Codecs
54
+ static VideoCodecLibx264 = { name: 'H264 Software', codecName: 'libx264', type: 'software' };
55
+ static VideoCodecNvenc264 = { name: 'H264 NVENC', codecName: 'h264_nvenc', type: 'hardware' };
56
+ static VideoCodecQsv264 = { name: 'H264 Intel Quick Sync Video (QSV)', codecName: 'h264_qsv', type: 'hardware' };
57
+ static VideoCodecVaapi264 = { name: 'H264 VAAPI', codecName: 'h264_vaapi', type: 'hardware' };
58
+ static VideoCodecAmf264 = { name: 'H264 AMD AMF', codecName: 'h264_amf', type: 'hardware' };
59
+ static VideoCodecToolbox264 = { name: 'H264 VideoToolbox', codecName: 'h264_videotoolbox', type: 'hardware' };
60
+ static VideoCodecOmx264 = { name: 'H264 OpenMAX (Raspberry Pi)', codecName: 'h264_omx', type: 'hardware' };
61
+ static VideoCodecRkmpp264 = { name: 'H264 Rockchip MPP', codecName: 'h264_rkmpp', type: 'hardware' };
62
+ static VideoCodecJetson264 = { name: 'H264 Jetson NVMM', codecName: 'h264_nvmpi', type: 'hardware' };
63
+ static VideoCodecV4L2M2M264 = { name: 'H264 V4L2M2M', codecName: 'h264_v4l2m2m', type: 'hardware' };
64
+ static VideoCodecVulkan264 = { name: 'H264 Vulkan', codecName: 'h264_vulkan', type: 'hardware' };
65
+ static VideoCodecOpenCL264 = { name: 'H264 OpenCL', codecName: 'h264_opencl', type: 'hardware' };
66
+ // H.265 (HEVC) Codecs
67
+ static VideoCodecLibx265 = { name: 'HEVC Software', codecName: 'libx265', type: 'software' };
68
+ static VideoCodecNvenc265 = { name: 'HEVC NVENC', codecName: 'hevc_nvenc', type: 'hardware' };
69
+ static VideoCodecQsv265 = { name: 'HEVC Intel Quick Sync Video (QSV)', codecName: 'hevc_qsv', type: 'hardware' };
70
+ static VideoCodecVaapi265 = { name: 'HEVC VAAPI', codecName: 'hevc_vaapi', type: 'hardware' };
71
+ static VideoCodecAmf265 = { name: 'HEVC AMD AMF', codecName: 'hevc_amf', type: 'hardware' };
72
+ static VideoCodecToolbox265 = { name: 'HEVC VideoToolbox', codecName: 'hevc_videotoolbox', type: 'hardware' };
73
+ static VideoCodecOmx265 = { name: 'HEVC OpenMAX (Raspberry Pi)', codecName: 'hevc_omx', type: 'hardware' };
74
+ static VideoCodecRkmpp265 = { name: 'HEVC Rockchip MPP', codecName: 'hevc_rkmpp', type: 'hardware' };
75
+ static VideoCodecJetson265 = { name: 'HEVC Jetson NVMM', codecName: 'hevc_nvmpi', type: 'hardware' };
76
+ static VideoCodecV4L2M2M265 = { name: 'HEVC V4L2M2M', codecName: 'hevc_v4l2m2m', type: 'hardware' };
77
+ static VideoCodecVulkan265 = { name: 'HEVC Vulkan', codecName: 'hevc_vulkan', type: 'hardware' };
78
+ static VideoCodecOpenCL265 = { name: 'HEVC OpenCL', codecName: 'hevc_opencl', type: 'hardware' };
79
+ /** List of supported hardware codecs on the current system */
80
+ supportedCodecs = [];
27
81
  scalerRegex = /scale=([-0-9]+):([-0-9]+)/;
28
82
  _ffmpegPath;
29
- ffmpegPathChanged = false;
30
- get ffmpegPath() {
31
- return this._ffmpegPath;
32
- }
83
+ _userAgent;
84
+ _vaapiDeviceSelector;
85
+ _ffmpegVersion = null;
86
+ /** Get all supported H.264 codecs */
33
87
  static get H264Codecs() {
34
88
  return [
35
- FFMpegHardware.VideoCodecN264,
36
- FFMpegHardware.VideoCodecN264H,
37
- FFMpegHardware.VideoCodecI264,
38
- FFMpegHardware.VideoCodecI264C,
39
- FFMpegHardware.VideoCodecM264,
40
- FFMpegHardware.VideoCodecV264,
41
- FFMpegHardware.VideoCodecO264,
42
- FFMpegHardware.VideoCodecRK264,
89
+ FFMpegHardware.VideoCodecNvenc264,
90
+ FFMpegHardware.VideoCodecVaapi264,
91
+ FFMpegHardware.VideoCodecQsv264,
92
+ FFMpegHardware.VideoCodecAmf264,
93
+ FFMpegHardware.VideoCodecToolbox264,
94
+ FFMpegHardware.VideoCodecOmx264,
95
+ FFMpegHardware.VideoCodecRkmpp264,
43
96
  FFMpegHardware.VideoCodecJetson264,
44
97
  FFMpegHardware.VideoCodecV4L2M2M264,
45
98
  FFMpegHardware.VideoCodecVulkan264,
99
+ FFMpegHardware.VideoCodecOpenCL264,
100
+ FFMpegHardware.VideoCodecLibx264, // Software fallback
46
101
  ];
47
102
  }
103
+ /** Get all supported H.265 codecs */
48
104
  static get H265Codecs() {
49
105
  return [
50
- FFMpegHardware.VideoCodecN265,
51
- FFMpegHardware.VideoCodecI265,
52
- FFMpegHardware.VideoCodecM265,
53
- FFMpegHardware.VideoCodecV265,
54
- FFMpegHardware.VideoCodecRK265,
106
+ FFMpegHardware.VideoCodecNvenc265,
107
+ FFMpegHardware.VideoCodecVaapi265,
108
+ FFMpegHardware.VideoCodecQsv265,
109
+ FFMpegHardware.VideoCodecAmf265,
110
+ FFMpegHardware.VideoCodecToolbox265,
111
+ FFMpegHardware.VideoCodecOmx265,
112
+ FFMpegHardware.VideoCodecRkmpp265,
55
113
  FFMpegHardware.VideoCodecJetson265,
56
114
  FFMpegHardware.VideoCodecV4L2M2M265,
57
115
  FFMpegHardware.VideoCodecVulkan265,
116
+ FFMpegHardware.VideoCodecOpenCL265,
117
+ FFMpegHardware.VideoCodecLibx265, // Software fallback
58
118
  ];
59
119
  }
60
- constructor(_ffmpegPath = 'ffmpeg') {
120
+ /**
121
+ * Create a new FFMpegHardware instance
122
+ * @param _ffmpegPath Path to the FFmpeg executable
123
+ * @param userAgent Custom user agent for FFmpeg requests
124
+ */
125
+ constructor(_ffmpegPath = 'ffmpeg', userAgent = 'FFmpeg Camera.UI') {
61
126
  this._ffmpegPath = _ffmpegPath;
62
- this.hwCodecSupport = [];
127
+ this._userAgent = userAgent;
128
+ this._vaapiDeviceSelector = new VaapiDeviceSelector();
63
129
  }
64
- setFfmpegPath(_ffmpegPath) {
65
- this._ffmpegPath = _ffmpegPath;
66
- if (this._ffmpegPath !== _ffmpegPath) {
67
- this.ffmpegPathChanged = true;
130
+ /**
131
+ * Get FFmpeg version information
132
+ * @returns Version object with major, minor, and patch numbers
133
+ */
134
+ async getFFmpegVersion() {
135
+ if (this._ffmpegVersion) {
136
+ return this._ffmpegVersion;
68
137
  }
69
- else {
70
- this.ffmpegPathChanged = false;
138
+ try {
139
+ const { stdout } = await execAwait(`${this._ffmpegPath} -version`);
140
+ const versionMatch = /ffmpeg version (\d+)\.(\d+)\.(\d+)/.exec(stdout);
141
+ if (versionMatch) {
142
+ this._ffmpegVersion = {
143
+ major: parseInt(versionMatch[1], 10),
144
+ minor: parseInt(versionMatch[2], 10),
145
+ patch: parseInt(versionMatch[3], 10),
146
+ };
147
+ return this._ffmpegVersion;
148
+ }
149
+ return { major: 0, minor: 0, patch: 0 };
150
+ }
151
+ catch {
152
+ return { major: 0, minor: 0, patch: 0 };
71
153
  }
72
154
  }
73
- async initHWSupport() {
74
- if (this.hwCodecSupport.length > 0 && !this.ffmpegPathChanged) {
75
- return;
155
+ /**
156
+ * Filter codecs based on current system architecture and OS
157
+ * @returns Filtered array of codecs that are likely to work on this system
158
+ */
159
+ getSystemCompatibleCodecs() {
160
+ const systemArch = arch();
161
+ const systemPlatform = platform();
162
+ // Start with all codecs
163
+ let potentialCodecs = [...FFMpegHardware.H264Codecs, ...FFMpegHardware.H265Codecs];
164
+ // First, filter out platform-exclusive codecs
165
+ // VideoToolbox is macOS only
166
+ if (systemPlatform !== 'darwin') {
167
+ potentialCodecs = potentialCodecs.filter((codec) => !codec.codecName.includes('videotoolbox'));
76
168
  }
77
- const hwCodecSupport = [];
78
- const codecs = [
79
- // H264 Codecs
80
- FFMpegHardware.VideoCodecN264H,
81
- FFMpegHardware.VideoCodecN264,
82
- FFMpegHardware.VideoCodecI264,
83
- FFMpegHardware.VideoCodecI264C,
84
- FFMpegHardware.VideoCodecV264,
85
- FFMpegHardware.VideoCodecM264,
86
- FFMpegHardware.VideoCodecO264,
87
- FFMpegHardware.VideoCodecRK264,
88
- FFMpegHardware.VideoCodecJetson264,
89
- FFMpegHardware.VideoCodecV4L2M2M264,
90
- FFMpegHardware.VideoCodecVulkan264,
91
- // H265 Codecs
92
- FFMpegHardware.VideoCodecN265,
93
- FFMpegHardware.VideoCodecI265,
94
- FFMpegHardware.VideoCodecM265,
95
- FFMpegHardware.VideoCodecV265,
96
- FFMpegHardware.VideoCodecRK265,
97
- FFMpegHardware.VideoCodecJetson265,
98
- FFMpegHardware.VideoCodecV4L2M2M265,
99
- FFMpegHardware.VideoCodecVulkan265,
100
- ];
169
+ // AMF is Windows only
170
+ if (systemPlatform !== 'win32') {
171
+ potentialCodecs = potentialCodecs.filter((codec) => !codec.codecName.includes('amf'));
172
+ }
173
+ // Jetson is ARM64 Linux only
174
+ if (!(systemPlatform === 'linux' && systemArch === 'arm64')) {
175
+ potentialCodecs = potentialCodecs.filter((codec) => !codec.codecName.includes('nvmpi'));
176
+ }
177
+ // Raspberry Pi OpenMAX is ARM Linux only
178
+ if (!(systemPlatform === 'linux' && (systemArch === 'arm' || systemArch === 'arm64'))) {
179
+ potentialCodecs = potentialCodecs.filter((codec) => !codec.codecName.includes('omx'));
180
+ }
181
+ // Rockchip MPP is ARM Linux only
182
+ if (!(systemPlatform === 'linux' && (systemArch === 'arm' || systemArch === 'arm64'))) {
183
+ potentialCodecs = potentialCodecs.filter((codec) => !codec.codecName.includes('rkmpp'));
184
+ }
185
+ // NVIDIA and Intel QSV are primarily x86/x64 only
186
+ if (systemArch !== 'x64' && systemArch !== 'x86_64') {
187
+ potentialCodecs = potentialCodecs.filter((codec) => !codec.codecName.includes('nvenc') && !codec.codecName.includes('qsv'));
188
+ }
189
+ // Now apply platform-specific inclusions
190
+ if (systemPlatform === 'darwin') {
191
+ // macOS supports VideoToolbox, potentially OpenCL and Vulkan
192
+ potentialCodecs = potentialCodecs.filter((codec) => codec.codecName.includes('videotoolbox') || codec.codecName.includes('opencl') || codec.codecName.includes('vulkan'));
193
+ }
194
+ else if (systemPlatform === 'win32') {
195
+ // Windows typically supports NVIDIA, Intel QSV, AMD AMF, Vulkan, and OpenCL
196
+ // All these codecs should still be in our list after the exclusion phase
197
+ }
198
+ else if (systemPlatform === 'linux') {
199
+ // Linux supports VAAPI, NVENC (on x64), V4L2M2M, and more
200
+ // We've already filtered platform-specific ones above
201
+ }
202
+ return potentialCodecs;
203
+ }
204
+ /**
205
+ * Initialize hardware acceleration support by testing specific codecs
206
+ * @param codecs List of codecs to test
207
+ */
208
+ async detectSupportedCodecsFromList(codecs) {
209
+ const supportedCodecs = [];
101
210
  for (const codec of codecs) {
102
- let args = ['-hide_banner', '-v', 'error'];
103
- args = this.hwDeviceInit(args, codec);
104
- args.push('-f', 'lavfi', '-i', 'color=c=red:s=1280x720', '-t', '0.1');
105
- args = args.concat(this.codecInit(codec));
106
- const videoFilter = this.hwMaxResFilter(codec, { width: 1280, height: 720 }, this.minHeight, false);
107
- if (videoFilter) {
108
- args.push('-vf', videoFilter);
211
+ // Create platform-specific test command
212
+ const args = await this.createPlatformSpecificProbeArgs(codec);
213
+ try {
214
+ // Test if this codec works
215
+ await this.runFFmpeg(args);
216
+ supportedCodecs.push(codec);
109
217
  }
110
- args.push('-f', 'null', '-');
218
+ catch {
219
+ // console.error(`Codec ${codec.name} failed:`, err);
220
+ // Codec not supported, skip
221
+ }
222
+ }
223
+ this.supportedCodecs = supportedCodecs;
224
+ }
225
+ /**
226
+ * Initialize hardware acceleration support by testing available codecs
227
+ */
228
+ async detectSupportedCodecs() {
229
+ // Skip detection if we already have results
230
+ if (this.supportedCodecs.length > 0) {
231
+ return;
232
+ }
233
+ const supportedCodecs = [];
234
+ // Get pre-filtered list of codecs that might work on this system
235
+ const potentialCodecs = this.getSystemCompatibleCodecs();
236
+ for (const codec of potentialCodecs) {
237
+ // Create platform-specific test command
238
+ const args = await this.createPlatformSpecificProbeArgs(codec);
111
239
  try {
112
- await this.executeFFmpeg(args);
113
- hwCodecSupport.push(codec);
240
+ // Test if this codec works
241
+ await this.runFFmpeg(args);
242
+ supportedCodecs.push(codec);
114
243
  }
115
244
  catch {
116
- // catch ENOENT ?
117
- // Codec not supported, ignore
245
+ // Codec not supported, skip
118
246
  }
119
247
  }
120
- this.hwCodecSupport = hwCodecSupport;
248
+ this.supportedCodecs = supportedCodecs;
121
249
  }
122
- hwDeviceInit(args, toCodec) {
123
- switch (toCodec) {
124
- case FFMpegHardware.VideoCodecO264:
125
- // OpenMAX requires no special initialization
126
- break;
127
- case FFMpegHardware.VideoCodecV4L2M2M264:
128
- case FFMpegHardware.VideoCodecV4L2M2M265:
129
- // V4L2M2M requires no special initialization
130
- break;
131
- case FFMpegHardware.VideoCodecN264:
132
- case FFMpegHardware.VideoCodecN264H:
133
- case FFMpegHardware.VideoCodecN265:
250
+ /**
251
+ * Create platform-specific probe arguments for testing codec support
252
+ * @param codec Codec to test
253
+ * @returns FFmpeg arguments for testing
254
+ */
255
+ async createPlatformSpecificProbeArgs(codec) {
256
+ const method = this.getHardwareMethod(codec);
257
+ const baseArgs = ['-hide_banner', '-v', 'error'];
258
+ const vaapiDevice = await this._vaapiDeviceSelector.getSelectedGpu();
259
+ switch (method) {
260
+ case 'vaapi':
261
+ return [
262
+ ...baseArgs,
263
+ '-hwaccel_flags',
264
+ 'allow_profile_mismatch',
265
+ '-init_hw_device',
266
+ `vaapi=va:${vaapiDevice}`,
267
+ '-f',
268
+ 'lavfi',
269
+ '-i',
270
+ 'testsrc2=size=1280x720',
271
+ '-t',
272
+ '0.1',
273
+ '-vf',
274
+ 'format=nv12,hwupload',
275
+ '-c:v',
276
+ codec.codecName,
277
+ '-f',
278
+ 'null',
279
+ '-',
280
+ ];
281
+ case 'cuda':
282
+ return [...baseArgs, '-init_hw_device', 'cuda', '-f', 'lavfi', '-i', 'testsrc2=size=1280x720', '-t', '0.1', '-c:v', codec.codecName, '-f', 'null', '-'];
283
+ case 'qsv':
284
+ return [
285
+ ...baseArgs,
286
+ '-init_hw_device',
287
+ `qsv=hw:${vaapiDevice}`,
288
+ '-filter_hw_device',
289
+ 'hw',
290
+ '-f',
291
+ 'lavfi',
292
+ '-i',
293
+ 'testsrc2=size=1280x720',
294
+ '-t',
295
+ '0.1',
296
+ '-vf',
297
+ 'format=nv12,hwupload=extra_hw_frames=64',
298
+ '-c:v',
299
+ codec.codecName,
300
+ '-f',
301
+ 'null',
302
+ '-',
303
+ ];
304
+ case 'videotoolbox':
305
+ return [...baseArgs, '-f', 'lavfi', '-i', 'testsrc2=size=1280x720', '-t', '0.1', '-c:v', codec.codecName, '-f', 'null', '-'];
306
+ case 'v4l2m2m':
307
+ return [...baseArgs, '-f', 'lavfi', '-i', 'testsrc2', '-t', '0.1', '-c:v', codec.codecName, '-f', 'null', '-'];
308
+ case 'rkmpp':
309
+ return [...baseArgs, '-f', 'lavfi', '-i', 'testsrc2', '-t', '0.1', '-c:v', codec.codecName, '-f', 'null', '-'];
310
+ case 'vulkan':
311
+ return [...baseArgs, '-init_hw_device', 'vulkan=vk:0', '-f', 'lavfi', '-i', 'testsrc2=size=1280x720', '-t', '0.1', '-c:v', codec.codecName, '-f', 'null', '-'];
312
+ case 'opencl':
313
+ return [...baseArgs, '-init_hw_device', 'opencl', '-f', 'lavfi', '-i', 'testsrc2=size=1280x720', '-t', '0.1', '-c:v', codec.codecName, '-f', 'null', '-'];
314
+ case 'amf':
315
+ // For AMD AMF (primarily Windows)
316
+ return [...baseArgs, '-f', 'lavfi', '-i', 'testsrc2=size=1280x720', '-t', '0.1', '-c:v', codec.codecName, '-f', 'null', '-'];
317
+ default:
318
+ // Fallback
319
+ const args = [...baseArgs];
320
+ args.push('-f', 'lavfi', '-i', 'color=c=red:s=1280x720', '-t', '0.1');
321
+ args.push('-c:v', codec.codecName);
322
+ args.push('-f', 'null', '-');
323
+ return args;
324
+ }
325
+ }
326
+ /**
327
+ * Set up hardware device initialization arguments
328
+ * @param args Existing FFmpeg arguments
329
+ * @param codec Target codec to set up
330
+ * @returns Updated arguments array
331
+ */
332
+ async setupHardwareDevice(args, codec) {
333
+ const method = this.getHardwareMethod(codec);
334
+ const vaapiDevice = await this._vaapiDeviceSelector.getSelectedGpu();
335
+ const version = await this.getFFmpegVersion();
336
+ switch (method) {
337
+ case 'cuda':
134
338
  args.push('-threads', '1', '-hwaccel_device', '0', '-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda');
135
339
  break;
136
- case FFMpegHardware.VideoCodecV264:
137
- case FFMpegHardware.VideoCodecV265:
138
- args.push('-vaapi_device', '/dev/dri/renderD128', '-hwaccel', 'vaapi', '-hwaccel_output_format', 'vaapi');
340
+ case 'vaapi':
341
+ args.push('-hwaccel_flags', 'allow_profile_mismatch', '-vaapi_device', vaapiDevice, '-hwaccel', 'vaapi', '-hwaccel_output_format', 'vaapi');
139
342
  break;
140
- case FFMpegHardware.VideoCodecI264:
141
- case FFMpegHardware.VideoCodecI264C:
142
- case FFMpegHardware.VideoCodecI265:
143
- args.push('-hwaccel', 'qsv', '-qsv_device', '/dev/dri/renderD128', '-hwaccel_output_format', 'qsv', '-init_hw_device', 'qsv=hw', '-filter_hw_device', 'hw');
343
+ case 'qsv':
344
+ args.push('-hwaccel', 'qsv', '-qsv_device', vaapiDevice, '-hwaccel_output_format', 'qsv', '-init_hw_device', `qsv=hw:${vaapiDevice}`, '-filter_hw_device', 'hw');
345
+ // Add dump_extra bsf for FFmpeg 6.1+
346
+ if (version.major >= 61) {
347
+ args.push('-bsf:v', 'dump_extra');
348
+ }
144
349
  break;
145
- case FFMpegHardware.VideoCodecM264:
146
- case FFMpegHardware.VideoCodecM265:
350
+ case 'videotoolbox':
147
351
  args.push('-hwaccel', 'videotoolbox', '-hwaccel_output_format', 'videotoolbox_vld');
148
352
  break;
149
- case FFMpegHardware.VideoCodecRK264:
150
- case FFMpegHardware.VideoCodecRK265:
353
+ case 'rkmpp':
151
354
  args.push('-hwaccel', 'rkmpp', '-hwaccel_output_format', 'drm_prime');
152
355
  break;
153
- case FFMpegHardware.VideoCodecJetson264:
154
- case FFMpegHardware.VideoCodecJetson265:
155
- // Jetson NVMM requires no special initialization
356
+ case 'v4l2m2m':
357
+ // No special initialization needed
358
+ break;
359
+ case 'opencl':
360
+ args.push('-init_hw_device', 'opencl', '-hwaccel', 'opencl', '-hwaccel_output_format', 'opencl');
156
361
  break;
157
- case FFMpegHardware.VideoCodecVulkan264:
158
- case FFMpegHardware.VideoCodecVulkan265:
159
- // args.push('-hwaccel', 'vulkan', '-init_hw_device', 'vulkan=gpu:0', '-filter_hw_device', 'gpu', '-hwaccel_output_format', 'vulkan');
362
+ case 'vulkan':
160
363
  args.push('-init_hw_device', 'vulkan=vk:0', '-hwaccel', 'vulkan', '-hwaccel_output_format', 'vulkan');
161
364
  break;
365
+ case 'amf':
366
+ // For AMD, usually using d3d11va for hardware decoding and AMF for encoding
367
+ // AMF doesn't need special initialization parameters like VAAPI or QSV do
368
+ if (platform() === 'win32') {
369
+ args.push('-hwaccel', 'd3d11va', '-hwaccel_output_format', 'd3d11');
370
+ }
371
+ break;
162
372
  default:
373
+ // Otherwise no initialization needed
163
374
  break;
164
375
  }
165
376
  return args;
166
377
  }
167
- codecInit(codec) {
168
- return ['-c:v', codec.codecName];
169
- }
170
- hwDeviceArgs(toCodec) {
378
+ /**
379
+ * Add codec-specific encoding parameters
380
+ * @param codec Target codec
381
+ * @returns Array of FFmpeg arguments for the codec
382
+ */
383
+ getCodecSpecificArgs(codec) {
171
384
  const args = [];
172
- switch (toCodec) {
173
- // https://github.com/FFmpeg/FFmpeg/blob/e907bf677a6bf50de2fe30c8674162bf26e4e83d/libavcodec/videotoolboxenc.c#L2879
174
- case FFMpegHardware.VideoCodecM264:
175
- case FFMpegHardware.VideoCodecM265:
385
+ const method = this.getHardwareMethod(codec);
386
+ // Common parameters that help with consistent streaming in HomeKit
387
+ args.push('-g', '30'); // GOP size of 30 (keyframe every 30 frames)
388
+ args.push('-bf', '0'); // Disable B-frames to reduce latency
389
+ switch (method) {
390
+ case 'videotoolbox':
176
391
  // For Apple devices (VideoToolbox)
177
- args.push('-allow_sw', '1', '-realtime', '1', '-coder', 'cabac');
178
- if (arch() === 'arm64') {
179
- args.push('-q:v', '90');
180
- }
392
+ args.push('-realtime', '1');
181
393
  break;
182
- case FFMpegHardware.VideoCodecV264:
183
- case FFMpegHardware.VideoCodecV265:
184
- // For VAAPI
394
+ case 'opencl':
395
+ // OpenCL-specific arguments
185
396
  break;
186
- case FFMpegHardware.VideoCodecN264:
187
- case FFMpegHardware.VideoCodecN264H:
188
- case FFMpegHardware.VideoCodecN265:
189
- // For NVIDIA NVENC
397
+ case 'vulkan':
398
+ // Vulkan-specific arguments
399
+ args.push('-quality', 'balanced');
190
400
  break;
191
- case FFMpegHardware.VideoCodecI264:
192
- case FFMpegHardware.VideoCodecI264C:
193
- case FFMpegHardware.VideoCodecI265:
194
- // For Intel Quick Sync Video (QSV)
401
+ case 'vaapi':
402
+ // VAAPI-specific encoding parameters
403
+ args.push('-rc_mode', 'CQP');
195
404
  break;
196
- case FFMpegHardware.VideoCodecRK264:
197
- case FFMpegHardware.VideoCodecRK265:
198
- // For Rockchip MPP
405
+ case 'cuda':
406
+ // NVENC-specific arguments for good balance of quality/speed
407
+ args.push('-threads', '1');
408
+ args.push('-rc', 'constqp', '-qp', '23');
409
+ args.push('-preset', 'p2', '-tune', 'll');
199
410
  break;
200
- case FFMpegHardware.VideoCodecJetson264:
201
- case FFMpegHardware.VideoCodecJetson265:
202
- // For NVIDIA Jetson
411
+ case 'qsv':
412
+ // QSV-specific arguments
413
+ args.push('-global_quality', '23');
414
+ args.push('-preset', 'medium');
203
415
  break;
204
- case FFMpegHardware.VideoCodecVulkan264:
205
- case FFMpegHardware.VideoCodecVulkan265:
416
+ case 'amf':
417
+ // AMD AMF-specific arguments
418
+ args.push('-quality', 'balanced');
419
+ args.push('-rc', 'cqp', '-qp_i', '23', '-qp_p', '23');
206
420
  break;
207
- case FFMpegHardware.VideoCodecO264:
208
- case FFMpegHardware.VideoCodecV4L2M2M264:
209
- case FFMpegHardware.VideoCodecV4L2M2M265:
210
- // For V4L2M2M (Raspberry Pi)
421
+ case 'rkmpp':
422
+ // Rockchip MPP specific arguments
423
+ args.push('-rc_mode', '1'); // CBR mode
211
424
  break;
212
- default:
213
- //
425
+ case 'v4l2m2m':
426
+ // V4L2 M2M specific arguments
427
+ // Most V4L2 implementations prefer CBR over CQP
428
+ args.push('-b:v', '4M');
214
429
  break;
215
430
  }
216
431
  return args;
217
432
  }
218
- hwFilterInit(toCodec, fullhw = true) {
219
- let videoFilter = '';
220
- switch (toCodec) {
221
- case FFMpegHardware.VideoCodecM264:
222
- case FFMpegHardware.VideoCodecM265:
223
- // For Apple devices (VideoToolbox)
224
- if (!fullhw) {
225
- videoFilter = 'format=nv12,hwupload';
226
- }
227
- break;
228
- case FFMpegHardware.VideoCodecV264:
229
- case FFMpegHardware.VideoCodecV265:
230
- // For VAAPI
231
- if (!fullhw) {
232
- videoFilter = 'format=nv12,hwupload';
233
- }
234
- break;
235
- case FFMpegHardware.VideoCodecN264:
236
- case FFMpegHardware.VideoCodecN264H:
237
- case FFMpegHardware.VideoCodecN265:
238
- // For NVIDIA NVENC
239
- if (!fullhw) {
240
- videoFilter = 'format=nv12,hwupload_cuda';
241
- }
242
- break;
243
- case FFMpegHardware.VideoCodecI264:
244
- case FFMpegHardware.VideoCodecI264C:
245
- case FFMpegHardware.VideoCodecI265:
246
- // For Intel Quick Sync Video (QSV)
247
- if (!fullhw) {
248
- videoFilter = 'format=nv12,hwupload=extra_hw_frames=64';
249
- }
250
- break;
251
- case FFMpegHardware.VideoCodecRK264:
252
- case FFMpegHardware.VideoCodecRK265:
253
- // For Rockchip MPP
254
- if (!fullhw) {
255
- videoFilter = 'format=drm_prime';
256
- }
257
- break;
258
- case FFMpegHardware.VideoCodecJetson264:
259
- case FFMpegHardware.VideoCodecJetson265:
260
- // For NVIDIA Jetson
261
- if (!fullhw) {
262
- videoFilter = 'format=nv12,hwupload';
263
- }
264
- break;
265
- case FFMpegHardware.VideoCodecVulkan264:
266
- case FFMpegHardware.VideoCodecVulkan265:
267
- videoFilter = 'hwupload';
268
- break;
269
- case FFMpegHardware.VideoCodecO264:
270
- case FFMpegHardware.VideoCodecV4L2M2M264:
271
- case FFMpegHardware.VideoCodecV4L2M2M265:
272
- // For V4L2M2M (Raspberry Pi)
273
- // No initial filter required
274
- break;
275
- default:
276
- // For software encoding or unrecognized codecs
277
- break;
433
+ /**
434
+ * Create complete hardware filter chain for scaling and processing
435
+ * @param codec Target codec
436
+ * @param requestedWidth Desired output width
437
+ * @param requestedHeight Desired output height
438
+ * @param keepOnHardware Whether to keep processing on hardware
439
+ * @param pixelFormat Optional specific pixel format for output
440
+ * @returns Complete filter string or null
441
+ */
442
+ createHardwareFilter(codec, requestedWidth, requestedHeight, keepOnHardware = true, pixelFormat) {
443
+ // Check if this is a software encoder
444
+ if (codec.type === 'software') {
445
+ return this.createSoftwareFilter(requestedWidth, requestedHeight, pixelFormat);
278
446
  }
279
- return videoFilter;
447
+ // The rest of the method remains the same for hardware encoders
448
+ // Build filter chain
449
+ let filterChain = this.createInitialFilter(codec, keepOnHardware);
450
+ // Add scaling filter if needed
451
+ if (requestedWidth && requestedHeight) {
452
+ const [targetWidth, targetHeight] = this.calculateOutputDimensions(requestedWidth, requestedHeight);
453
+ filterChain = this.applyScalingFilter(filterChain, codec, targetWidth, targetHeight);
454
+ }
455
+ // Always use finalizePipeline to handle the download path uniformly
456
+ // If pixelFormat is specified, pass it to get the right format conversion
457
+ return this.finalizePipeline(filterChain, codec, keepOnHardware, pixelFormat);
280
458
  }
281
- hwApplyScaleTemplate(sargs, toCodec, targetWidth, targetHeight) {
282
- let template = '';
283
- switch (toCodec) {
284
- case FFMpegHardware.VideoCodecM264:
285
- case FFMpegHardware.VideoCodecM265:
286
- template = 'scale_vt=$width:$height';
287
- break;
288
- case FFMpegHardware.VideoCodecV264:
289
- case FFMpegHardware.VideoCodecV265:
290
- template = 'scale_vaapi=w=$width:h=$height';
291
- break;
292
- case FFMpegHardware.VideoCodecN264:
293
- case FFMpegHardware.VideoCodecN264H:
294
- case FFMpegHardware.VideoCodecN265:
295
- template = 'scale_cuda=w=$width:h=$height';
296
- break;
297
- case FFMpegHardware.VideoCodecI264:
298
- case FFMpegHardware.VideoCodecI264C:
299
- case FFMpegHardware.VideoCodecI265:
300
- template = 'scale_qsv=w=$width:h=$height';
301
- break;
302
- case FFMpegHardware.VideoCodecRK264:
303
- case FFMpegHardware.VideoCodecRK265:
304
- template = 'scale_rkrga=w=$width:h=$height:format=yuv420p:force_original_aspect_ratio=0';
305
- break;
306
- case FFMpegHardware.VideoCodecJetson264:
307
- case FFMpegHardware.VideoCodecJetson265:
308
- template = 'scale_npp=$width:$height';
309
- break;
310
- case FFMpegHardware.VideoCodecV4L2M2M264:
311
- case FFMpegHardware.VideoCodecV4L2M2M265:
312
- template = 'scale=$width:$height';
313
- break;
314
- case FFMpegHardware.VideoCodecVulkan264:
315
- case FFMpegHardware.VideoCodecVulkan265:
316
- template = 'scale_vulkan=$width:$height';
317
- break;
459
+ /**
460
+ * Create filter chain for software encoders
461
+ * @param requestedWidth Desired output width
462
+ * @param requestedHeight Desired output height
463
+ * @param pixelFormat Optional specific pixel format for output
464
+ * @returns Filter string for software encoding
465
+ */
466
+ createSoftwareFilter(requestedWidth, requestedHeight, pixelFormat) {
467
+ let filterChain = '';
468
+ // Add scaling if needed
469
+ if (requestedWidth && requestedHeight) {
470
+ const [targetWidth, targetHeight] = this.calculateOutputDimensions(requestedWidth, requestedHeight);
471
+ filterChain = `scale=${targetWidth}:${targetHeight}`;
472
+ }
473
+ // Add format conversion if needed
474
+ if (pixelFormat) {
475
+ filterChain = filterChain ? `${filterChain},format=${pixelFormat}` : `format=${pixelFormat}`;
476
+ }
477
+ return filterChain;
478
+ }
479
+ /**
480
+ * Get initial filter setup for hardware pipeline
481
+ * @param codec Target codec
482
+ * @param keepOnHardware Whether to keep processing on hardware (true) or use hybrid pipeline (false)
483
+ * @returns Initial filter string
484
+ */
485
+ createInitialFilter(codec, keepOnHardware = true) {
486
+ // If keepOnHardware is true, we don't need to add hwupload filters
487
+ // as the input is already on hardware from setupHardwareDevice
488
+ if (keepOnHardware) {
489
+ return '';
490
+ }
491
+ const method = this.getHardwareMethod(codec);
492
+ switch (method) {
493
+ case 'videotoolbox':
494
+ case 'vaapi':
495
+ return 'format=nv12,hwupload';
496
+ case 'cuda':
497
+ return 'format=nv12,hwupload_cuda';
498
+ case 'qsv':
499
+ return 'format=nv12,hwupload=extra_hw_frames=64';
500
+ case 'rkmpp':
501
+ return 'format=drm_prime';
502
+ case 'opencl':
503
+ return 'format=nv12,hwupload_opencl';
504
+ case 'vulkan':
505
+ return 'hwupload';
506
+ case 'amf':
507
+ return 'format=nv12,hwupload';
318
508
  default:
319
- template = 'scale=$width:$height';
320
- break;
509
+ return '';
321
510
  }
322
- const replaced = sargs.replace(this.scalerRegex, template.replace('$width', targetWidth.toString()).replace('$height', targetHeight.toString()));
323
- return replaced;
324
511
  }
325
- hwApplyFullHWFilter(args, toCodec, fullhw) {
326
- if (!fullhw) {
327
- return args;
328
- }
329
- switch (toCodec) {
330
- case FFMpegHardware.VideoCodecM264:
331
- case FFMpegHardware.VideoCodecM265:
332
- case FFMpegHardware.VideoCodecV264:
333
- case FFMpegHardware.VideoCodecV265:
334
- case FFMpegHardware.VideoCodecN264:
335
- case FFMpegHardware.VideoCodecN264H:
336
- case FFMpegHardware.VideoCodecN265:
337
- case FFMpegHardware.VideoCodecI264:
338
- case FFMpegHardware.VideoCodecI264C:
339
- case FFMpegHardware.VideoCodecI265:
340
- case FFMpegHardware.VideoCodecJetson264:
341
- case FFMpegHardware.VideoCodecJetson265:
342
- args += ',hwdownload,format=nv12,format=yuv420p';
343
- break;
344
- case FFMpegHardware.VideoCodecRK264:
345
- case FFMpegHardware.VideoCodecRK265:
346
- args += ',hwmap=mode=read,format=yuv420p';
347
- break;
348
- case FFMpegHardware.VideoCodecVulkan264:
349
- case FFMpegHardware.VideoCodecVulkan265:
350
- args += ',hwdownload';
512
+ /**
513
+ * Apply hardware-specific scaling template
514
+ * @param currentFilter Current filter string
515
+ * @param codec Target codec
516
+ * @param width Target width
517
+ * @param height Target height
518
+ * @returns Updated filter string with hardware-specific scaling
519
+ */
520
+ applyScalingFilter(currentFilter, codec, width, height) {
521
+ let scaleTemplate = '';
522
+ const method = this.getHardwareMethod(codec);
523
+ // Select appropriate hardware scaler based on codec
524
+ switch (method) {
525
+ case 'videotoolbox':
526
+ scaleTemplate = 'scale_vt=$width:$height';
527
+ break;
528
+ case 'vaapi':
529
+ scaleTemplate = 'scale_vaapi=w=$width:h=$height';
530
+ break;
531
+ case 'cuda':
532
+ scaleTemplate = 'scale_cuda=w=$width:h=$height';
533
+ break;
534
+ case 'qsv':
535
+ scaleTemplate = 'vpp_qsv=w=$width:h=$height';
536
+ break;
537
+ case 'rkmpp':
538
+ scaleTemplate = 'scale_rkrga=w=$width:h=$height:format=yuv420p:force_original_aspect_ratio=0';
539
+ break;
540
+ case 'v4l2m2m':
541
+ scaleTemplate = 'scale=$width:$height';
542
+ break;
543
+ case 'opencl':
544
+ scaleTemplate = 'scale_opencl=w=$width:h=$height';
545
+ break;
546
+ case 'vulkan':
547
+ scaleTemplate = 'scale_vulkan=$width:$height';
548
+ break;
549
+ case 'amf':
550
+ // AMF doesn't have dedicated scaling filter, use standard scale
551
+ scaleTemplate = 'scale=$width:$height';
552
+ break;
553
+ case 'jetson':
554
+ // Jetson-specific scaling filter
555
+ scaleTemplate = 'scale_npp=$width:$height';
351
556
  break;
352
557
  default:
558
+ scaleTemplate = 'scale=$width:$height';
353
559
  break;
354
560
  }
355
- return args;
356
- }
357
- hwMaxResFilter(toCodec, vf, reqHeight, fullhw) {
358
- if (vf.width === 0 || vf.height === 0) {
359
- return null;
360
- }
361
- const [maxWidth, maxHeight] = this.hwCodecMaxRes(toCodec);
362
- const [targetWidth, targetHeight] = this.calculateTargetDimensions(vf, reqHeight, maxWidth, maxHeight);
363
- const scaleFilter = `scale=${targetWidth}:${targetHeight}`;
364
- let videoFilter = this.hwFilterInit(toCodec, fullhw);
365
- if (videoFilter) {
366
- videoFilter += `,${scaleFilter}`;
561
+ // Replace width and height in template
562
+ const scalingFilter = scaleTemplate.replace('$width', width.toString()).replace('$height', height.toString());
563
+ // Replace existing scale filter or add new one
564
+ if (currentFilter.match(this.scalerRegex)) {
565
+ return currentFilter.replace(this.scalerRegex, scalingFilter);
367
566
  }
368
567
  else {
369
- videoFilter = scaleFilter;
568
+ return currentFilter ? `${currentFilter},${scalingFilter}` : scalingFilter;
370
569
  }
371
- const match = videoFilter.match(this.scalerRegex);
372
- if (match) {
373
- videoFilter = this.hwApplyScaleTemplate(videoFilter, toCodec, targetWidth, targetHeight);
570
+ }
571
+ /**
572
+ * Add final filters to bring data back to CPU if needed and handle format conversion
573
+ * @param currentFilter Current filter string
574
+ * @param codec Target codec
575
+ * @param keepOnHardware Whether to keep processing on hardware
576
+ * @param pixelFormat Optional specific pixel format for output
577
+ * @returns Final filter chain
578
+ */
579
+ finalizePipeline(currentFilter, codec, keepOnHardware, pixelFormat) {
580
+ // If we want a full hardware pipeline with no pixel format conversion, return as is
581
+ if (keepOnHardware && !pixelFormat) {
582
+ return currentFilter;
374
583
  }
375
- videoFilter = this.hwApplyFullHWFilter(videoFilter, toCodec, fullhw);
376
- return videoFilter;
584
+ let finalFilter = currentFilter;
585
+ const method = this.getHardwareMethod(codec);
586
+ const formatsNeedingNV12 = ['yuv420p', 'yuvj420p', 'yuv444p', 'yuvj444p'];
587
+ const useGammaEq = false;
588
+ const createFilterPart = (base, needsGammaEq, targetFormat) => {
589
+ let filterPart = base;
590
+ // Add gamma equation if needed and appropriate
591
+ if (needsGammaEq && useGammaEq && base.includes('format=nv12')) {
592
+ filterPart += ',eq=gamma=1.4:gamma_weight=0.5';
593
+ }
594
+ // Add format conversion if needed
595
+ if (targetFormat && targetFormat !== 'nv12') {
596
+ filterPart += `,format=${targetFormat}`;
597
+ }
598
+ return filterPart;
599
+ };
600
+ // Handle each hardware method differently
601
+ switch (method) {
602
+ case 'v4l2m2m':
603
+ // Video4Linux2 M2M API doesn't require hwdownload operations
604
+ if (pixelFormat) {
605
+ finalFilter = finalFilter ? `${finalFilter},format=${pixelFormat}` : `format=${pixelFormat}`;
606
+ }
607
+ break;
608
+ case 'rkmpp':
609
+ if (pixelFormat || !keepOnHardware) {
610
+ const targetFormat = pixelFormat ?? 'yuv420p';
611
+ const filterPart = `hwmap=mode=read,format=${targetFormat}`;
612
+ finalFilter = finalFilter ? `${finalFilter},${filterPart}` : filterPart;
613
+ }
614
+ break;
615
+ case 'vaapi':
616
+ case 'cuda':
617
+ case 'qsv':
618
+ case 'amf':
619
+ case 'videotoolbox':
620
+ if (pixelFormat || !keepOnHardware) {
621
+ const needsNV12 = !pixelFormat || formatsNeedingNV12.includes(pixelFormat);
622
+ let baseFilter = 'hwdownload';
623
+ // Add nv12 format if needed as intermediate step
624
+ if (needsNV12) {
625
+ baseFilter += ',format=nv12';
626
+ }
627
+ // Create full filter part with gamma correction and final format if needed
628
+ const filterPart = createFilterPart(baseFilter, pixelFormat === 'yuv420p', pixelFormat);
629
+ // Combine with existing filter chain
630
+ finalFilter = finalFilter ? `${finalFilter},${filterPart}` : filterPart;
631
+ }
632
+ break;
633
+ case 'vulkan':
634
+ case 'opencl':
635
+ if (pixelFormat || !keepOnHardware) {
636
+ const targetFormat = pixelFormat ?? 'yuv420p';
637
+ const filterPart = `hwdownload,format=${targetFormat}`;
638
+ finalFilter = finalFilter ? `${finalFilter},${filterPart}` : filterPart;
639
+ }
640
+ break;
641
+ default:
642
+ // For any other methods, use a simple approach
643
+ if (pixelFormat || !keepOnHardware) {
644
+ const targetFormat = pixelFormat ?? 'yuv420p';
645
+ const filterPart = `hwdownload,format=${targetFormat}`;
646
+ finalFilter = finalFilter ? `${finalFilter},${filterPart}` : filterPart;
647
+ }
648
+ break;
649
+ }
650
+ return finalFilter;
377
651
  }
378
- getHwaccelMethod(codec) {
652
+ /**
653
+ * Determine the hardware acceleration method used by a codec
654
+ * @param codec Target codec
655
+ * @returns Hardware acceleration method
656
+ */
657
+ getHardwareMethod(codec) {
379
658
  if (codec.codecName.includes('vaapi')) {
380
659
  return 'vaapi';
381
660
  }
382
661
  else if (codec.codecName.includes('videotoolbox')) {
383
662
  return 'videotoolbox';
384
663
  }
385
- else if (codec.codecName.includes('cuda') || codec.codecName.includes('nvmpi')) {
664
+ else if (codec.codecName.includes('nvenc') || codec.codecName.includes('nvmpi')) {
386
665
  return 'cuda';
387
666
  }
388
667
  else if (codec.codecName.includes('qsv')) {
@@ -394,13 +673,31 @@ export class FFMpegHardware {
394
673
  else if (codec.codecName.includes('v4l2m2m')) {
395
674
  return 'v4l2m2m';
396
675
  }
676
+ else if (codec.codecName.includes('opencl')) {
677
+ return 'opencl';
678
+ }
679
+ else if (codec.codecName.includes('vulkan')) {
680
+ return 'vulkan';
681
+ }
682
+ else if (codec.codecName.includes('amf')) {
683
+ return 'amf';
684
+ }
685
+ else if (codec.codecName.includes('jetson')) {
686
+ return 'jetson';
687
+ }
397
688
  else {
398
689
  return 'auto';
399
690
  }
400
691
  }
401
- executeFFmpeg(args) {
692
+ /**
693
+ * Execute FFmpeg with given arguments
694
+ * @param args FFmpeg arguments
695
+ * @returns Promise that resolves when FFmpeg completes successfully
696
+ */
697
+ runFFmpeg(args) {
402
698
  return new Promise((resolve, reject) => {
403
699
  const options = {};
700
+ // console.log('Running FFmpeg:', this._ffmpegPath, args.join(' '));
404
701
  const ffmpeg = spawn(this._ffmpegPath, args, options);
405
702
  let stderr = '';
406
703
  ffmpeg.stderr?.on('data', (data) => {
@@ -419,50 +716,17 @@ export class FFMpegHardware {
419
716
  });
420
717
  });
421
718
  }
422
- calculateTargetDimensions(vf, reqHeight, maxWidth, maxHeight) {
423
- const aspectRatio = vf.width / vf.height;
424
- let targetHeight = Math.min(reqHeight, vf.height);
425
- let targetWidth = Math.round(targetHeight * aspectRatio);
426
- if (maxWidth > 0 && targetWidth > maxWidth) {
427
- targetWidth = maxWidth;
428
- targetHeight = Math.round(targetWidth / aspectRatio);
429
- }
430
- if (maxHeight > 0 && targetHeight > maxHeight) {
431
- targetHeight = maxHeight;
432
- targetWidth = Math.round(targetHeight * aspectRatio);
433
- }
434
- if (targetWidth % 2 !== 0) {
435
- targetWidth += 1;
436
- }
437
- if (targetHeight % 2 !== 0) {
438
- targetHeight += 1;
439
- }
719
+ /**
720
+ * Calculate output dimensions
721
+ * @param targetWidth Desired output width
722
+ * @param targetHeight Desired output height
723
+ * @returns [width, height] tuple with even dimensions
724
+ */
725
+ calculateOutputDimensions(targetWidth, targetHeight) {
726
+ // Ensure dimensions are even (required for some codecs)
727
+ targetWidth += targetWidth % 2;
728
+ targetHeight += targetHeight % 2;
440
729
  return [targetWidth, targetHeight];
441
730
  }
442
- hwCodecMaxRes(codec) {
443
- switch (codec) {
444
- case FFMpegHardware.VideoCodecO264:
445
- case FFMpegHardware.VideoCodecV4L2M2M264:
446
- case FFMpegHardware.VideoCodecV4L2M2M265:
447
- return [1920, 1080];
448
- case FFMpegHardware.VideoCodecN264:
449
- case FFMpegHardware.VideoCodecN264H:
450
- case FFMpegHardware.VideoCodecN265:
451
- case FFMpegHardware.VideoCodecI264:
452
- case FFMpegHardware.VideoCodecI264C:
453
- case FFMpegHardware.VideoCodecI265:
454
- case FFMpegHardware.VideoCodecM264:
455
- case FFMpegHardware.VideoCodecM265:
456
- return [4096, 4096];
457
- case FFMpegHardware.VideoCodecRK264:
458
- case FFMpegHardware.VideoCodecRK265:
459
- return [4096, 2304];
460
- case FFMpegHardware.VideoCodecJetson264:
461
- case FFMpegHardware.VideoCodecJetson265:
462
- return [3840, 2160];
463
- default:
464
- return [0, 0];
465
- }
466
- }
467
731
  }
468
732
  //# sourceMappingURL=codec-hardware.js.map