node-av 5.1.0 → 5.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.
@@ -1,271 +0,0 @@
1
- /**
2
- * Transcode Speed Benchmark Tests
3
- *
4
- * Compares transcoding performance between FFmpeg CLI and node-av
5
- * for various codec configurations.
6
- */
7
-
8
- import { dirname, join, resolve } from 'node:path';
9
- import { fileURLToPath } from 'node:url';
10
-
11
- import { Decoder, Demuxer, Encoder, HardwareContext, Muxer, pipeline } from '../../src/api/index.js';
12
- import { FF_ENCODER_LIBX264, FF_ENCODER_LIBX265 } from '../../src/constants/encoders.js';
13
- import { runner } from '../runner.js';
14
- import { FFmpegArgs } from '../utils/ffmpeg-cli.js';
15
-
16
- import type { BenchmarkConfig } from '../runner.js';
17
-
18
- const __filename = fileURLToPath(import.meta.url);
19
- const __dirname = dirname(__filename);
20
-
21
- // Default paths
22
- const testDataDir = resolve(__dirname, '../../testdata');
23
- const resultsDir = resolve(__dirname, '../results');
24
-
25
- /**
26
- * Default benchmark configuration
27
- */
28
- const defaultConfig = {
29
- iterations: 5,
30
- warmupIterations: 1,
31
- };
32
-
33
- /**
34
- * Software H.264 Transcode Benchmark
35
- */
36
- export async function benchmarkSWH264(inputFile?: string, outputFile?: string): Promise<void> {
37
- const input = inputFile ?? join(testDataDir, 'video.mp4');
38
- const output = outputFile ?? join(resultsDir, 'sw-h264-output.mp4');
39
-
40
- const config: BenchmarkConfig = {
41
- name: 'SW H.264 Transcode',
42
- description: 'Software H.264 encoding with libx264 (CPU)',
43
- category: 'transcode',
44
- inputFile: input,
45
- outputFile: output,
46
- ...defaultConfig,
47
- };
48
-
49
- const ffmpegOptions = {
50
- input,
51
- output,
52
- args: FFmpegArgs.swH264(23),
53
- };
54
-
55
- const nodeAVFn = async () => {
56
- await using demuxer = await Demuxer.open(input);
57
- const videoStream = demuxer.video();
58
- if (!videoStream) throw new Error('No video stream found');
59
-
60
- const decoder = await Decoder.create(videoStream);
61
- const encoder = await Encoder.create(FF_ENCODER_LIBX264, {
62
- decoder,
63
- options: { preset: 'medium', crf: 23 },
64
- });
65
-
66
- await using muxer = await Muxer.open(output);
67
-
68
- const control = pipeline(demuxer, decoder, encoder, muxer);
69
- await control.completion;
70
-
71
- // Get frame count from codec context
72
- const ctx = encoder.getCodecContext();
73
- return { framesProcessed: ctx?.frameNumber };
74
- };
75
-
76
- await runner.runBenchmark(config, ffmpegOptions, nodeAVFn);
77
- }
78
-
79
- /**
80
- * Software H.265/HEVC Transcode Benchmark
81
- */
82
- export async function benchmarkSWH265(inputFile?: string, outputFile?: string): Promise<void> {
83
- const input = inputFile ?? join(testDataDir, 'video.mp4');
84
- const output = outputFile ?? join(resultsDir, 'sw-h265-output.mp4');
85
-
86
- const config: BenchmarkConfig = {
87
- name: 'SW H.265 Transcode',
88
- description: 'Software H.265/HEVC encoding with libx265 (CPU)',
89
- category: 'transcode',
90
- inputFile: input,
91
- outputFile: output,
92
- ...defaultConfig,
93
- };
94
-
95
- const ffmpegOptions = {
96
- input,
97
- output,
98
- args: FFmpegArgs.swH265(28),
99
- };
100
-
101
- const nodeAVFn = async () => {
102
- await using demuxer = await Demuxer.open(input);
103
- const videoStream = demuxer.video();
104
- if (!videoStream) throw new Error('No video stream found');
105
-
106
- const decoder = await Decoder.create(videoStream);
107
- const encoder = await Encoder.create(FF_ENCODER_LIBX265, {
108
- decoder,
109
- options: { preset: 'medium', crf: 28 },
110
- });
111
-
112
- await using muxer = await Muxer.open(output);
113
-
114
- const control = pipeline(demuxer, decoder, encoder, muxer);
115
- await control.completion;
116
-
117
- // Get frame count from codec context
118
- const ctx = encoder.getCodecContext();
119
- return { framesProcessed: ctx?.frameNumber };
120
- };
121
-
122
- await runner.runBenchmark(config, ffmpegOptions, nodeAVFn);
123
- }
124
-
125
- /**
126
- * Hardware H.264 Transcode Benchmark (platform-dependent)
127
- */
128
- export async function benchmarkHWH264(inputFile?: string, outputFile?: string): Promise<void> {
129
- const input = inputFile ?? join(testDataDir, 'video.mp4');
130
- const output = outputFile ?? join(resultsDir, 'hw-h264-output.mp4');
131
-
132
- // Check hardware availability
133
- const hw = HardwareContext.auto();
134
- if (!hw) {
135
- console.log('⚠️ Skipping HW H.264 benchmark: No hardware acceleration available');
136
- return;
137
- }
138
-
139
- const hwEncoderCodec = hw.getEncoderCodec('h264');
140
- if (!hwEncoderCodec) {
141
- console.log('⚠️ Skipping HW H.264 benchmark: No hardware H.264 encoder available');
142
- hw.dispose();
143
- return;
144
- }
145
-
146
- // Determine FFmpeg hardware encoder based on platform
147
- let ffmpegArgs: string[];
148
- const platform = process.platform;
149
-
150
- if (platform === 'darwin') {
151
- ffmpegArgs = FFmpegArgs.hwH264VideoToolbox();
152
- } else if (platform === 'linux') {
153
- // Try VAAPI first, then NVENC
154
- ffmpegArgs = FFmpegArgs.hwH264Vaapi();
155
- } else if (platform === 'win32') {
156
- ffmpegArgs = FFmpegArgs.hwH264Nvenc();
157
- } else {
158
- console.log('⚠️ Skipping HW H.264 benchmark: Unknown platform');
159
- hw.dispose();
160
- return;
161
- }
162
-
163
- const config: BenchmarkConfig = {
164
- name: 'HW H.264 Transcode',
165
- description: `Hardware H.264 encoding (${hw.deviceTypeName})`,
166
- category: 'transcode',
167
- inputFile: input,
168
- outputFile: output,
169
- ...defaultConfig,
170
- };
171
-
172
- const ffmpegOptions = {
173
- input,
174
- output,
175
- args: ffmpegArgs,
176
- };
177
-
178
- const nodeAVFn = async () => {
179
- await using demuxer = await Demuxer.open(input);
180
- const videoStream = demuxer.video();
181
- if (!videoStream) throw new Error('No video stream found');
182
-
183
- const decoder = await Decoder.create(videoStream, { hardware: hw });
184
- const encoder = await Encoder.create(hwEncoderCodec, {
185
- decoder,
186
- bitrate: '2M',
187
- });
188
-
189
- await using muxer = await Muxer.open(output);
190
-
191
- const control = pipeline(demuxer, decoder, encoder, muxer);
192
- await control.completion;
193
-
194
- // Get frame count from codec context
195
- const ctx = encoder.getCodecContext();
196
- return { framesProcessed: ctx?.frameNumber };
197
- };
198
-
199
- try {
200
- await runner.runBenchmark(config, ffmpegOptions, nodeAVFn);
201
- } finally {
202
- hw.dispose();
203
- }
204
- }
205
-
206
- /**
207
- * Stream Copy (Remux) Benchmark
208
- */
209
- export async function benchmarkStreamCopy(inputFile?: string, outputFile?: string): Promise<void> {
210
- const input = inputFile ?? join(testDataDir, 'video.mp4');
211
- const output = outputFile ?? join(resultsDir, 'stream-copy-output.mp4');
212
-
213
- const config: BenchmarkConfig = {
214
- name: 'Stream Copy (Remux)',
215
- description: 'Copy streams without re-encoding',
216
- category: 'transcode',
217
- inputFile: input,
218
- outputFile: output,
219
- ...defaultConfig,
220
- };
221
-
222
- const ffmpegOptions = {
223
- input,
224
- output,
225
- args: FFmpegArgs.streamCopy(),
226
- };
227
-
228
- const nodeAVFn = async () => {
229
- await using demuxer = await Demuxer.open(input);
230
- await using muxer = await Muxer.open(output);
231
-
232
- // Add streams from demuxer to muxer (required for stream copy)
233
- const streamMap = new Map<number, number>();
234
- for (const stream of demuxer.streams) {
235
- const outIndex = muxer.addStream(stream);
236
- streamMap.set(stream.index, outIndex);
237
- }
238
-
239
- // Manual packet copy to count packets
240
- let packetCount = 0;
241
- for await (const packet of demuxer.packets()) {
242
- if (!packet) break;
243
- const outIndex = streamMap.get(packet.streamIndex);
244
- if (outIndex !== undefined) {
245
- await muxer.writePacket(packet, outIndex);
246
- packetCount++;
247
- }
248
- packet.free();
249
- }
250
-
251
- return { framesProcessed: packetCount };
252
- };
253
-
254
- await runner.runBenchmark(config, ffmpegOptions, nodeAVFn);
255
- }
256
-
257
- /**
258
- * Run all transcode benchmarks
259
- */
260
- export async function runAllTranscodeBenchmarks(inputFile?: string): Promise<void> {
261
- console.log('\n🎬 Running Transcode Speed Benchmarks\n');
262
- console.log('='.repeat(60));
263
-
264
- await benchmarkSWH264(inputFile);
265
- await benchmarkSWH265(inputFile);
266
- await benchmarkHWH264(inputFile);
267
- await benchmarkStreamCopy(inputFile);
268
-
269
- console.log('\n' + '='.repeat(60));
270
- console.log('Transcode benchmarks completed\n');
271
- }
@@ -1,264 +0,0 @@
1
- #!/usr/bin/env tsx
2
- /**
3
- * node-av Benchmark CLI
4
- *
5
- * Comprehensive benchmark suite comparing node-av with FFmpeg CLI.
6
- *
7
- * Usage:
8
- * npm run benchmark # Run all benchmarks
9
- * npm run benchmark -- --help # Show help
10
- * npm run benchmark -- transcode # Run only transcode benchmarks
11
- * npm run benchmark -- memory # Run only memory benchmarks
12
- * npm run benchmark -- latency # Run only latency benchmarks
13
- * npm run benchmark -- -i file1.mp4 -i file2.mp4 # Multiple inputs
14
- */
15
-
16
- import { existsSync, readdirSync } from 'node:fs';
17
- import { mkdir } from 'node:fs/promises';
18
- import { basename, dirname, join, resolve } from 'node:path';
19
- import { fileURLToPath } from 'node:url';
20
-
21
- import { measureAllLatencies } from './cases/latency.js';
22
- import { runAllMemoryBenchmarks } from './cases/memory.js';
23
- import { runAllTranscodeBenchmarks } from './cases/transcode.js';
24
- import { runner } from './runner.js';
25
- import { createInputFileInfo, getSystemInfo, saveResultsJSON, writeReport } from './utils/report.js';
26
-
27
- import type { BenchmarkReport, InputFileInfo } from './utils/report.js';
28
-
29
- const __filename = fileURLToPath(import.meta.url);
30
- const __dirname = dirname(__filename);
31
-
32
- // Default paths
33
- const testDataDir = resolve(__dirname, '../testdata');
34
- const resultsDir = resolve(__dirname, 'results');
35
- const defaultInputFile = join(testDataDir, 'video.mp4');
36
-
37
- /**
38
- * Parse command line arguments
39
- */
40
- function parseArgs(): { category?: string; inputFiles: string[]; help: boolean; noReport: boolean; iterations?: number; pattern?: string } {
41
- const args = process.argv.slice(2);
42
- let category: string | undefined;
43
- const inputFiles: string[] = [];
44
- let help = false;
45
- let noReport = false;
46
- let iterations: number | undefined;
47
- let pattern: string | undefined;
48
-
49
- for (let i = 0; i < args.length; i++) {
50
- const arg = args[i];
51
-
52
- if (arg === '--help' || arg === '-h') {
53
- help = true;
54
- } else if (arg === '--no-report') {
55
- noReport = true;
56
- } else if (arg === '--input' || arg === '-i') {
57
- inputFiles.push(args[++i]);
58
- } else if (arg === '--pattern' || arg === '-p') {
59
- pattern = args[++i];
60
- } else if (arg === '--iterations' || arg === '-n') {
61
- iterations = parseInt(args[++i], 10);
62
- } else if (!arg.startsWith('-')) {
63
- category = arg;
64
- }
65
- }
66
-
67
- return { category, inputFiles, help, noReport, iterations, pattern };
68
- }
69
-
70
- /**
71
- * Expand glob pattern to file list
72
- */
73
- function expandPattern(pattern: string): string[] {
74
- const dir = dirname(pattern);
75
- const filePattern = basename(pattern).replace('*', '.*');
76
- const regex = new RegExp(`^${filePattern}$`);
77
-
78
- if (!existsSync(dir)) return [];
79
-
80
- return readdirSync(dir)
81
- .filter((file) => regex.test(file))
82
- .map((file) => join(dir, file))
83
- .sort();
84
- }
85
-
86
- /**
87
- * Print help message
88
- */
89
- function printHelp(): void {
90
- console.log(`
91
- node-av Benchmark Suite
92
- ========================
93
-
94
- Usage:
95
- npm run benchmark [options] [category]
96
-
97
- Categories:
98
- transcode Run transcode speed benchmarks
99
- memory Run memory usage benchmarks
100
- latency Run latency benchmarks
101
- all Run all benchmarks (default)
102
-
103
- Options:
104
- -h, --help Show this help message
105
- -i, --input FILE Use custom input file (can be repeated for multiple files)
106
- -p, --pattern PAT Use glob pattern to match input files
107
- -n, --iterations N Number of iterations per benchmark (default: 5)
108
- --no-report Skip generating BENCHMARK.md report
109
-
110
- Examples:
111
- npm run benchmark # Run all benchmarks with default input
112
- npm run benchmark transcode # Run only transcode benchmarks
113
- npm run benchmark -- -i input.mp4 # Use custom input file
114
- npm run benchmark -- -i h264.mp4 -i hevc.mp4 # Multiple input files
115
- npm run benchmark -- -p "testdata/bbb-4k-*" # Match pattern
116
- npm run benchmark -- -n 10 # Run 10 iterations
117
- npm run benchmark -- --no-report # Skip report generation
118
- `);
119
- }
120
-
121
- /**
122
- * Main benchmark runner
123
- */
124
- async function main(): Promise<void> {
125
- const { category, inputFiles, help, noReport, pattern } = parseArgs();
126
-
127
- if (help) {
128
- printHelp();
129
- process.exit(0);
130
- }
131
-
132
- // Resolve input files
133
- let inputs: string[] = [];
134
-
135
- if (pattern) {
136
- inputs = expandPattern(pattern);
137
- if (inputs.length === 0) {
138
- console.error(`Error: No files matched pattern: ${pattern}`);
139
- process.exit(1);
140
- }
141
- } else if (inputFiles.length > 0) {
142
- inputs = inputFiles;
143
- } else {
144
- inputs = [defaultInputFile];
145
- }
146
-
147
- // Validate all input files exist
148
- for (const input of inputs) {
149
- if (!existsSync(input)) {
150
- console.error(`Error: Input file not found: ${input}`);
151
- console.error('Please provide a valid input file with --input or ensure testdata/video.mp4 exists.');
152
- process.exit(1);
153
- }
154
- }
155
-
156
- // Ensure results directory exists
157
- if (!existsSync(resultsDir)) {
158
- await mkdir(resultsDir, { recursive: true });
159
- }
160
-
161
- console.log(`
162
- ╔═══════════════════════════════════════════════════════════════╗
163
- ║ node-av Benchmark Suite ║
164
- ╚═══════════════════════════════════════════════════════════════╝
165
- `);
166
-
167
- // Print system info
168
- console.log('Gathering system information...\n');
169
- const systemInfo = await getSystemInfo();
170
-
171
- console.log(`System: ${systemInfo.os} ${systemInfo.osVersion} (${systemInfo.arch})`);
172
- console.log(`CPU: ${systemInfo.cpu} (${systemInfo.cpuCores} cores)`);
173
- console.log(`RAM: ${systemInfo.ram}`);
174
- if (systemInfo.gpu) {
175
- console.log(`GPU: ${systemInfo.gpu}`);
176
- }
177
- console.log(`Node.js: ${systemInfo.nodeVersion}`);
178
- console.log(`FFmpeg: ${systemInfo.ffmpegVersion}`);
179
- console.log(`node-av: ${systemInfo.nodeAVVersion}`);
180
- console.log(`Input files: ${inputs.length}`);
181
- for (const input of inputs) {
182
- console.log(` - ${basename(input)}`);
183
- }
184
-
185
- // Get input file info for all files
186
- const inputFileInfos: InputFileInfo[] = [];
187
- for (const input of inputs) {
188
- const info = await createInputFileInfo(input);
189
- inputFileInfos.push(info);
190
- console.log(`\n${basename(input)}:`);
191
- if (info.duration > 0) {
192
- console.log(` Duration: ${info.duration.toFixed(1)}s`);
193
- if (info.resolution) {
194
- console.log(` Resolution: ${info.resolution}`);
195
- }
196
- if (info.codec) {
197
- console.log(` Codec: ${info.codec}`);
198
- }
199
- if (info.fps) {
200
- console.log(` FPS: ${info.fps.toFixed(1)}`);
201
- }
202
- }
203
- }
204
-
205
- // Run benchmarks based on category for each input file
206
- const categoryLower = category?.toLowerCase() ?? 'all';
207
- let latencyMetrics;
208
-
209
- for (const input of inputs) {
210
- console.log(`\n${'='.repeat(60)}`);
211
- console.log(`📁 Benchmarking: ${basename(input)}`);
212
- console.log('='.repeat(60));
213
-
214
- switch (categoryLower) {
215
- case 'transcode':
216
- await runAllTranscodeBenchmarks(input);
217
- break;
218
- case 'memory':
219
- await runAllMemoryBenchmarks(input);
220
- break;
221
- case 'latency':
222
- latencyMetrics = await measureAllLatencies(input, 10);
223
- break;
224
- case 'all':
225
- default:
226
- await runAllTranscodeBenchmarks(input);
227
- await runAllMemoryBenchmarks(input);
228
- // Only measure latency for first file (it's input-independent)
229
- if (input === inputs[0]) {
230
- latencyMetrics = await measureAllLatencies(input, 10);
231
- }
232
- break;
233
- }
234
- }
235
-
236
- // Generate report if not disabled
237
- if (!noReport) {
238
- const results = runner.getResults();
239
-
240
- const report: BenchmarkReport = {
241
- systemInfo,
242
- inputFileInfos,
243
- transcodeResults: results.filter((r) => r.config.category === 'transcode'),
244
- memoryResults: results.filter((r) => r.config.category === 'memory'),
245
- latencyMetrics,
246
- timestamp: new Date().toISOString(),
247
- };
248
-
249
- await writeReport(report);
250
- saveResultsJSON(report);
251
- }
252
-
253
- console.log(`
254
- ╔═══════════════════════════════════════════════════════════════╗
255
- ║ Benchmarks Complete ║
256
- ╚═══════════════════════════════════════════════════════════════╝
257
- `);
258
- }
259
-
260
- // Run main
261
- main().catch((error) => {
262
- console.error('Benchmark failed:', error);
263
- process.exit(1);
264
- });
@@ -1,22 +0,0 @@
1
- /**
2
- * Regenerate BENCHMARK.md from existing results JSON
3
- */
4
- import { readFileSync, writeFileSync } from 'node:fs';
5
- import { dirname, resolve } from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
-
8
- import { generateReport } from './utils/report.js';
9
-
10
- import type { BenchmarkReport } from './utils/report.js';
11
-
12
- const __dirname = dirname(fileURLToPath(import.meta.url));
13
- const resultsPath = resolve(__dirname, 'results/benchmark-results.json');
14
- const outputPath = resolve(__dirname, '../BENCHMARK.md');
15
-
16
- const report: BenchmarkReport = JSON.parse(readFileSync(resultsPath, 'utf-8'));
17
- report.timestamp = new Date().toISOString();
18
-
19
- const markdown = await generateReport(report);
20
- writeFileSync(outputPath, markdown, 'utf-8');
21
-
22
- console.log('✓ BENCHMARK.md regenerated');
@@ -1,2 +0,0 @@
1
- # This directory stores benchmark results (JSON)
2
- # These files are gitignored to keep the repository clean