fix(recording): reduce native mic noise amplification

This commit is contained in:
wiiiii123
2026-05-07 03:29:58 +07:00
parent 0814711d47
commit 115ecba7c5
5 changed files with 98 additions and 4 deletions
+30
View File
@@ -54,6 +54,7 @@ import {
buildNativeVideoAudioMuxArgs,
getExperimentalNvidiaCudaExportSkipReason,
getNvidiaCudaAudioExportSkipReason,
getNvidiaCudaAutoStallTimeoutMs,
hasNvidiaGpuDeviceInGpuInfo,
mapNvidiaCudaWrapperProgressPercentage,
muxExportedVideoAudioBuffer,
@@ -209,6 +210,35 @@ describe("getNvidiaCudaAudioExportSkipReason", () => {
});
});
describe("getNvidiaCudaAutoStallTimeoutMs", () => {
it("only applies the stall guard to packaged auto candidates by default", () => {
expect(getNvidiaCudaAutoStallTimeoutMs(false)).toBeNull();
expect(getNvidiaCudaAutoStallTimeoutMs(true)).toBe(120_000);
});
it("allows the CUDA auto stall guard to be disabled or tuned", () => {
const envName = "RECORDLY_NVIDIA_CUDA_AUTO_STALL_TIMEOUT_MS";
const originalValue = process.env[envName];
try {
process.env[envName] = "0";
expect(getNvidiaCudaAutoStallTimeoutMs(true)).toBeNull();
process.env[envName] = "5000";
expect(getNvidiaCudaAutoStallTimeoutMs(true)).toBe(10_000);
process.env[envName] = "45000";
expect(getNvidiaCudaAutoStallTimeoutMs(true)).toBe(45_000);
} finally {
if (originalValue === undefined) {
delete process.env[envName];
} else {
process.env[envName] = originalValue;
}
}
});
});
describe("hasNvidiaGpuDeviceInGpuInfo", () => {
it("detects NVIDIA GPUs by vendor id or device strings", () => {
expect(
+50 -1
View File
@@ -46,6 +46,8 @@ const NVIDIA_PCI_VENDOR_ID = 0x10de;
const NVIDIA_CUDA_EXPORT_ENV = "RECORDLY_EXPERIMENTAL_NVIDIA_CUDA_EXPORT";
const NVIDIA_CUDA_ALLOW_AUDIO_EXPORT_ENV = "RECORDLY_NVIDIA_CUDA_ALLOW_AUDIO_EXPORT";
const NVIDIA_CUDA_FORCE_VIDEO_ONLY_ENV = "RECORDLY_NVIDIA_CUDA_FORCE_VIDEO_ONLY";
const NVIDIA_CUDA_AUTO_STALL_TIMEOUT_ENV = "RECORDLY_NVIDIA_CUDA_AUTO_STALL_TIMEOUT_MS";
const DEFAULT_NVIDIA_CUDA_AUTO_STALL_TIMEOUT_MS = 120_000;
type ElectronGpuDeviceLike = {
vendorId?: number | string;
@@ -1577,6 +1579,26 @@ function isNvidiaCudaForceVideoOnlyEnabled() {
return process.env[NVIDIA_CUDA_FORCE_VIDEO_ONLY_ENV] === "1";
}
export function getNvidiaCudaAutoStallTimeoutMs(
autoCandidateActive = isPackagedNvidiaCudaExportAutoCandidateActive(),
) {
if (!autoCandidateActive) {
return null;
}
const rawValue = process.env[NVIDIA_CUDA_AUTO_STALL_TIMEOUT_ENV]?.trim();
if (rawValue === "0" || rawValue?.toLowerCase() === "off") {
return null;
}
const parsed = Number(rawValue);
if (Number.isFinite(parsed) && parsed > 0) {
return Math.max(10_000, Math.round(parsed));
}
return DEFAULT_NVIDIA_CUDA_AUTO_STALL_TIMEOUT_MS;
}
export async function getExperimentalNvidiaCudaExportSkipReason(
options: NativeStaticLayoutExportOptions,
) {
@@ -2231,6 +2253,7 @@ async function runExperimentalNvidiaCudaStaticLayoutExport(
const startedAt = getNowMs();
const startedAtIso = new Date().toISOString();
const timeoutMs = Math.max(20 * 60 * 1000, options.durationSec * 2000);
const stallTimeoutMs = getNvidiaCudaAutoStallTimeoutMs();
const ffmpegDirectory = path.dirname(ffmpegPath);
const pathKey = process.platform === "win32" ? "Path" : "PATH";
const env = {
@@ -2272,17 +2295,39 @@ async function runExperimentalNvidiaCudaStaticLayoutExport(
let stderr = "";
let stderrLineBuffer = "";
let lastProgressPercentage = 0;
let stallTimedOut = false;
let settled = false;
const timeout = setTimeout(() => {
if (settled) return;
child.kill("SIGKILL");
}, timeoutMs);
let stallTimeout: ReturnType<typeof setTimeout> | null = null;
const clearStallTimeout = () => {
if (stallTimeout) {
clearTimeout(stallTimeout);
stallTimeout = null;
}
};
const armStallTimeout = () => {
if (!stallTimeoutMs) {
return;
}
clearStallTimeout();
stallTimeout = setTimeout(() => {
if (settled) return;
stallTimedOut = true;
child.kill("SIGKILL");
}, stallTimeoutMs);
};
armStallTimeout();
child.stdout.on("data", (chunk: Buffer) => {
stdout += chunk.toString();
armStallTimeout();
});
child.stderr.on("data", (chunk: Buffer) => {
const text = chunk.toString();
armStallTimeout();
stderr += text;
stderrLineBuffer += text;
const lines = stderrLineBuffer.split(/\r?\n/);
@@ -2320,6 +2365,7 @@ async function runExperimentalNvidiaCudaStaticLayoutExport(
session.currentProcess = null;
}
clearTimeout(timeout);
clearStallTimeout();
powerGuard.release();
reject(error);
});
@@ -2330,6 +2376,7 @@ async function runExperimentalNvidiaCudaStaticLayoutExport(
session.currentProcess = null;
}
clearTimeout(timeout);
clearStallTimeout();
powerGuard.release();
if (session.terminating) {
@@ -2372,7 +2419,9 @@ async function runExperimentalNvidiaCudaStaticLayoutExport(
const suffix = signal ? ` (signal ${signal})` : "";
reject(
new Error(
stderr.trim() ||
(stallTimedOut && stallTimeoutMs
? `Experimental NVIDIA CUDA exporter stalled for ${stallTimeoutMs}ms without output`
: stderr.trim()) ||
stdout.trim() ||
`Experimental NVIDIA CUDA exporter exited with code ${code ?? "unknown"}${suffix}`,
),
@@ -0,0 +1,14 @@
import { describe, expect, it } from "vitest";
import { WINDOWS_NATIVE_MIC_PRE_FILTERS } from "./audioFilters";
describe("Windows native mic pre-filter policy", () => {
it("keeps repair filters without automatic gain or loudness normalization", () => {
expect(WINDOWS_NATIVE_MIC_PRE_FILTERS).toContain("adeclip=threshold=1");
expect(
WINDOWS_NATIVE_MIC_PRE_FILTERS.some((filter) =>
/(^|,)(loudnorm|dynaudnorm|volume)=/i.test(filter),
),
).toBe(false);
});
});
+3
View File
@@ -0,0 +1,3 @@
// Keep native mic capture dry by default. Automatic loudness normalization
// amplified wireless-headset noise and WASAPI discontinuities during beta tests.
export const WINDOWS_NATIVE_MIC_PRE_FILTERS = ["adeclip=threshold=1"];
+1 -3
View File
@@ -23,6 +23,7 @@ import {
} from "../state";
import type { AudioSyncAdjustment } from "../types";
import { moveFileWithOverwrite } from "../utils";
import { WINDOWS_NATIVE_MIC_PRE_FILTERS } from "./audioFilters";
import {
getCompanionAudioStartDelayMs,
getRecordingAudioMuxTimeoutMs,
@@ -32,9 +33,6 @@ import {
import { emitRecordingInterrupted } from "./events";
const execFileAsync = promisify(execFile);
// Repair rare clipped mic peaks before loudness normalization so headset/driver
// overloads do not get amplified into audible pops during mux/export.
const WINDOWS_NATIVE_MIC_PRE_FILTERS = ["adeclip=threshold=1", "loudnorm=I=-16:TP=-1.5:LRA=11"];
const MIN_NATIVE_WINDOWS_VIDEO_PAD_MS = 500;
export async function isNativeWindowsCaptureAvailable(): Promise<boolean> {