mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-25 07:16:02 +00:00
fix(recording): reduce native mic noise amplification
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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"];
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user