fix(recording): normalize native Windows mic loudness


This commit is contained in:
Wiii
2026-04-25 18:15:51 +07:00
committed by GitHub
parent 46f384d4d0
commit 97ba2a020c
3 changed files with 52 additions and 3 deletions
+26
View File
@@ -69,6 +69,32 @@ describe("getAudioSyncAdjustment", () => {
"[1:a]apad=pad_dur=120.000,aresample=async=1:first_pts=0,asetpts=PTS-STARTPTS[aout]",
]);
});
it("can add a small gain boost before resampling", () => {
const filterParts: string[] = [];
appendSyncedAudioFilter(
filterParts,
"[1:a]",
"aout",
getAudioSyncAdjustment(120, 120),
1.4,
);
expect(filterParts).toEqual([
"[1:a]volume=1.400,aresample=async=1:first_pts=0,asetpts=PTS-STARTPTS[aout]",
]);
});
it("can prepend mic normalization filters before sync handling", () => {
const filterParts: string[] = [];
appendSyncedAudioFilter(filterParts, "[1:a]", "aout", getAudioSyncAdjustment(120, 120), {
preFilters: ["loudnorm=I=-16:TP=-1.5:LRA=11"],
});
expect(filterParts).toEqual([
"[1:a]loudnorm=I=-16:TP=-1.5:LRA=11,aresample=async=1:first_pts=0,asetpts=PTS-STARTPTS[aout]",
]);
});
});
describe("applyRecordedAudioStartDelay", () => {
+13 -1
View File
@@ -102,8 +102,12 @@ export function appendSyncedAudioFilter(
inputLabel: string,
outputLabel: string,
adjustment: AudioSyncAdjustment,
options: number | { volumeMultiplier?: number; preFilters?: string[] } = 1,
) {
const filters: string[] = [];
const volumeMultiplier =
typeof options === "number" ? options : (options.volumeMultiplier ?? 1);
const preFilters = typeof options === "number" ? [] : (options.preFilters ?? []);
const filters: string[] = [...preFilters];
if (adjustment.mode === "delay" && adjustment.delayMs > 0) {
filters.push(`adelay=${adjustment.delayMs}|${adjustment.delayMs}`);
@@ -117,6 +121,14 @@ export function appendSyncedAudioFilter(
filters.push(`apad=pad_dur=${formatFfmpegSeconds(adjustment.durationDeltaMs)}`);
}
if (
Number.isFinite(volumeMultiplier) &&
volumeMultiplier > 0 &&
Math.abs(volumeMultiplier - 1) > 0.0005
) {
filters.push(`volume=${volumeMultiplier.toFixed(3)}`);
}
filters.push("aresample=async=1:first_pts=0", "asetpts=PTS-STARTPTS");
filterParts.push(`${inputLabel}${filters.join(",")}[${outputLabel}]`);
}
+13 -2
View File
@@ -33,6 +33,9 @@ import {
import { emitRecordingInterrupted } from "./events";
const execFileAsync = promisify(execFile);
// Match the browser path's "usable speech level" intent with a standard
// loudness pass on native Windows mic audio instead of a fixed tiny boost.
const WINDOWS_NATIVE_MIC_PRE_FILTERS = ["loudnorm=I=-16:TP=-1.5:LRA=11"];
export async function isNativeWindowsCaptureAvailable(): Promise<boolean> {
if (process.platform !== "win32") return false;
@@ -266,7 +269,9 @@ export async function muxNativeWindowsVideoWithAudio(
const micLabel = micPauseFilter ? "[mic_trimmed]" : "[2:a]";
appendSyncedAudioFilter(filterParts, systemLabel, "s", systemAdjustment);
appendSyncedAudioFilter(filterParts, micLabel, "m", micAdjustment);
appendSyncedAudioFilter(filterParts, micLabel, "m", micAdjustment, {
preFilters: WINDOWS_NATIVE_MIC_PRE_FILTERS,
});
filterParts.push("[s][m]amix=inputs=2:duration=longest:normalize=0[aout]");
await execFileAsync(
@@ -312,7 +317,13 @@ export async function muxNativeWindowsVideoWithAudio(
filterParts.push(pauseFilter);
}
const srcLabel = pauseFilter ? "[trimmed_audio]" : "[1:a]";
appendSyncedAudioFilter(filterParts, srcLabel, "aout", singleAdjustment);
appendSyncedAudioFilter(
filterParts,
srcLabel,
"aout",
singleAdjustment,
audioInputs[0] === "mic" ? { preFilters: WINDOWS_NATIVE_MIC_PRE_FILTERS } : 1,
);
await execFileAsync(
ffmpegPath,