@camera.ui/common 0.0.38 → 0.0.39
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/camera/codec-hardware.d.ts +162 -43
- package/dist/camera/codec-hardware.js +612 -348
- package/dist/camera/codec-hardware.js.map +1 -1
- package/dist/camera/ffmpeg-process.js +1 -1
- package/dist/camera/ffmpeg-process.js.map +1 -1
- package/dist/camera/ffmpeg.d.ts +19 -2
- package/dist/camera/ffmpeg.js +56 -29
- package/dist/camera/ffmpeg.js.map +1 -1
- package/dist/camera/utils.d.ts +16 -9
- package/dist/camera/utils.js +316 -90
- package/dist/camera/utils.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/logger/index.d.ts +4 -4
- package/dist/logger/index.js +22 -27
- package/dist/logger/index.js.map +1 -1
- package/dist/logger/test.js +0 -15
- package/dist/logger/test.js.map +1 -1
- package/dist/net/index.js +4 -4
- package/dist/net/index.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/reactive.d.ts +11 -0
- package/dist/utils/reactive.js +32 -0
- package/dist/utils/reactive.js.map +1 -0
- package/dist/utils/utils.js +1 -0
- package/dist/utils/utils.js.map +1 -1
- package/package.json +15 -21
- package/dist/nats/index.d.ts +0 -3
- package/dist/nats/index.js +0 -4
- package/dist/nats/index.js.map +0 -1
- package/dist/nats/messageQueue.d.ts +0 -48
- package/dist/nats/messageQueue.js +0 -141
- package/dist/nats/messageQueue.js.map +0 -1
- package/dist/nats/nats.d.ts +0 -30
- package/dist/nats/nats.js +0 -85
- package/dist/nats/nats.js.map +0 -1
- package/dist/nats/packer.d.ts +0 -2
- package/dist/nats/packer.js +0 -17
- 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
|
-
//
|
|
5
|
-
static
|
|
6
|
-
static
|
|
7
|
-
static
|
|
8
|
-
static
|
|
9
|
-
static
|
|
10
|
-
static
|
|
11
|
-
static
|
|
12
|
-
static
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
static
|
|
19
|
-
static
|
|
20
|
-
static
|
|
21
|
-
static
|
|
22
|
-
static
|
|
23
|
-
static
|
|
24
|
-
static
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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.
|
|
36
|
-
FFMpegHardware.
|
|
37
|
-
FFMpegHardware.
|
|
38
|
-
FFMpegHardware.
|
|
39
|
-
FFMpegHardware.
|
|
40
|
-
FFMpegHardware.
|
|
41
|
-
FFMpegHardware.
|
|
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.
|
|
51
|
-
FFMpegHardware.
|
|
52
|
-
FFMpegHardware.
|
|
53
|
-
FFMpegHardware.
|
|
54
|
-
FFMpegHardware.
|
|
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
|
-
|
|
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.
|
|
127
|
+
this._userAgent = userAgent;
|
|
128
|
+
this._vaapiDeviceSelector = new VaapiDeviceSelector();
|
|
63
129
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
70
|
-
this.
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
103
|
-
args = this.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
240
|
+
// Test if this codec works
|
|
241
|
+
await this.runFFmpeg(args);
|
|
242
|
+
supportedCodecs.push(codec);
|
|
114
243
|
}
|
|
115
244
|
catch {
|
|
116
|
-
//
|
|
117
|
-
// Codec not supported, ignore
|
|
245
|
+
// Codec not supported, skip
|
|
118
246
|
}
|
|
119
247
|
}
|
|
120
|
-
this.
|
|
248
|
+
this.supportedCodecs = supportedCodecs;
|
|
121
249
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
case
|
|
133
|
-
|
|
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
|
|
137
|
-
|
|
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
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
|
146
|
-
case FFMpegHardware.VideoCodecM265:
|
|
350
|
+
case 'videotoolbox':
|
|
147
351
|
args.push('-hwaccel', 'videotoolbox', '-hwaccel_output_format', 'videotoolbox_vld');
|
|
148
352
|
break;
|
|
149
|
-
case
|
|
150
|
-
case FFMpegHardware.VideoCodecRK265:
|
|
353
|
+
case 'rkmpp':
|
|
151
354
|
args.push('-hwaccel', 'rkmpp', '-hwaccel_output_format', 'drm_prime');
|
|
152
355
|
break;
|
|
153
|
-
case
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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('-
|
|
178
|
-
if (arch() === 'arm64') {
|
|
179
|
-
args.push('-q:v', '90');
|
|
180
|
-
}
|
|
392
|
+
args.push('-realtime', '1');
|
|
181
393
|
break;
|
|
182
|
-
case
|
|
183
|
-
|
|
184
|
-
// For VAAPI
|
|
394
|
+
case 'opencl':
|
|
395
|
+
// OpenCL-specific arguments
|
|
185
396
|
break;
|
|
186
|
-
case
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// For NVIDIA NVENC
|
|
397
|
+
case 'vulkan':
|
|
398
|
+
// Vulkan-specific arguments
|
|
399
|
+
args.push('-quality', 'balanced');
|
|
190
400
|
break;
|
|
191
|
-
case
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
201
|
-
|
|
202
|
-
|
|
411
|
+
case 'qsv':
|
|
412
|
+
// QSV-specific arguments
|
|
413
|
+
args.push('-global_quality', '23');
|
|
414
|
+
args.push('-preset', 'medium');
|
|
203
415
|
break;
|
|
204
|
-
case
|
|
205
|
-
|
|
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
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
case
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
case
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
case
|
|
342
|
-
|
|
343
|
-
break;
|
|
344
|
-
case
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if (
|
|
359
|
-
return
|
|
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
|
-
|
|
568
|
+
return currentFilter ? `${currentFilter},${scalingFilter}` : scalingFilter;
|
|
370
569
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
376
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|