From 5e5dd7166e507ce452146de86e939022428a51ba Mon Sep 17 00:00:00 2001 From: Alan Trebugeais Date: Sat, 9 May 2026 19:43:54 +0200 Subject: [PATCH] fix audio exporting with microphone and system sound etc --- electron/ipc/recording/windows.ts | 1 - src/lib/exporter/audioEncoder.ts | 38 ++++++++++++++++--------- src/lib/exporter/modernVideoExporter.ts | 13 ++++++++- src/lib/exporter/videoExporter.ts | 14 ++++++++- 4 files changed, 49 insertions(+), 17 deletions(-) diff --git a/electron/ipc/recording/windows.ts b/electron/ipc/recording/windows.ts index 26e15b07..5b41fc4d 100644 --- a/electron/ipc/recording/windows.ts +++ b/electron/ipc/recording/windows.ts @@ -24,7 +24,6 @@ import { import type { AudioSyncAdjustment } from "../types"; import { moveFileWithOverwrite } from "../utils"; import { - RECORDING_AUDIO_SIDECAR_DEBUG_ENV, shouldKeepRecordingAudioSidecars, WINDOWS_NATIVE_MIC_PRE_FILTERS, } from "./audioFilters"; diff --git a/src/lib/exporter/audioEncoder.ts b/src/lib/exporter/audioEncoder.ts index 9e077f25..0e342c76 100644 --- a/src/lib/exporter/audioEncoder.ts +++ b/src/lib/exporter/audioEncoder.ts @@ -25,6 +25,18 @@ const OFFLINE_ENCODE_CHUNK_FRAMES = 1024; const OFFLINE_CHUNK_DURATION_SEC = 30; const USER_AUDIO_NORMALIZE_GAIN = 1.35; +function resolveSourceTrackGain( + sourceAudioTrackSettings: SourceAudioTrackSettings | undefined, + trackId: "mic" | "system" | "mixed", +) { + const settings = sourceAudioTrackSettings?.[trackId]; + if (!settings) { + return 1; + } + const normalizeGain = settings.normalize ? USER_AUDIO_NORMALIZE_GAIN : 1; + return Math.max(0, Math.min(2, settings.volume * normalizeGain)); +} + interface TimelineSlice { sourceStartMs: number; sourceEndMs: number; @@ -607,17 +619,19 @@ export class AudioProcessor { sourceAudioFallbackPaths, audioRegions, sourceTrackGainById: { - mic: Math.max(0, Math.min(2, sourceAudioTrackSettings?.mic?.volume ?? 1)), - system: Math.max(0, Math.min(2, sourceAudioTrackSettings?.system?.volume ?? 1)), - mixed: Math.max(0, Math.min(2, sourceAudioTrackSettings?.mixed?.volume ?? 1)), + mic: resolveSourceTrackGain(sourceAudioTrackSettings, "mic"), + system: resolveSourceTrackGain(sourceAudioTrackSettings, "system"), + mixed: resolveSourceTrackGain(sourceAudioTrackSettings, "mixed"), }, embeddedGain: Math.max( 0, Math.min( 2, - sourceAudioTrackSettings?.mixed?.volume ?? - sourceAudioTrackSettings?.system?.volume ?? - 1, + sourceAudioTrackSettings?.mixed + ? resolveSourceTrackGain(sourceAudioTrackSettings, "mixed") + : sourceAudioTrackSettings?.system + ? resolveSourceTrackGain(sourceAudioTrackSettings, "system") + : 1, ), ), }); @@ -626,11 +640,7 @@ export class AudioProcessor { const mainBuffer = resolvedPlan.includeEmbeddedInExport ? await this.decodeAudioFromUrl(videoUrl) : null; - const mainBufferGainSettings = - sourceAudioTrackSettings?.mixed ?? sourceAudioTrackSettings?.system ?? null; - const mainBufferGain = mainBufferGainSettings - ? Math.max(0, Math.min(2, mainBufferGainSettings.volume)) - : 1; + const mainBufferGain = resolveSourceTrackGain(sourceAudioTrackSettings, "mixed"); const mainBufferEntry = mainBuffer ? { buffer: mainBuffer, gain: mainBufferGain } : null; if (this.cancelled) throw new Error("Export cancelled"); @@ -647,9 +657,9 @@ export class AudioProcessor { companionEntries.push({ buffer, - gain: Math.max( - 0, - Math.min(2, sourceAudioTrackSettings?.[getSourceTrackIdFromPath(audioPath)]?.volume ?? 1), + gain: resolveSourceTrackGain( + sourceAudioTrackSettings, + getSourceTrackIdFromPath(audioPath), ), startDelaySec: estimateCompanionAudioStartDelaySeconds( refDuration, diff --git a/src/lib/exporter/modernVideoExporter.ts b/src/lib/exporter/modernVideoExporter.ts index 424df4a0..3967d35a 100644 --- a/src/lib/exporter/modernVideoExporter.ts +++ b/src/lib/exporter/modernVideoExporter.ts @@ -169,6 +169,16 @@ type NativeAudioPlan = }; const FILTERGRAPH_FALLBACK_AUDIO_SAMPLE_RATE = 48_000; + +function hasNonDefaultSourceTrackSettings(sourceAudioTrackSettings?: SourceAudioTrackSettings) { + if (!sourceAudioTrackSettings) { + return false; + } + return Object.values(sourceAudioTrackSettings).some( + (settings) => + Math.abs((settings?.volume ?? 1) - 1) > 0.0005 || Boolean(settings?.normalize), + ); +} const MIN_NATIVE_STATIC_LAYOUT_SPEED = 0.25; const MAX_NATIVE_STATIC_LAYOUT_SPEED = 30; @@ -1184,7 +1194,8 @@ export class ModernVideoExporter { speedRegions.length > 0 || audioRegions.length > 0 || sourceAudioFallbackPaths.length > 1 || - hasTimedSourceAudioFallback + hasTimedSourceAudioFallback || + hasNonDefaultSourceTrackSettings(this.config.sourceAudioTrackSettings) ) { const sourceDurationMs = Math.max( 0, diff --git a/src/lib/exporter/videoExporter.ts b/src/lib/exporter/videoExporter.ts index 55b1b184..09dea96f 100644 --- a/src/lib/exporter/videoExporter.ts +++ b/src/lib/exporter/videoExporter.ts @@ -123,6 +123,16 @@ type NativeAudioPlan = const FILTERGRAPH_FALLBACK_AUDIO_SAMPLE_RATE = 48_000; +function hasNonDefaultSourceTrackSettings(sourceAudioTrackSettings?: SourceAudioTrackSettings) { + if (!sourceAudioTrackSettings) { + return false; + } + return Object.values(sourceAudioTrackSettings).some( + (settings) => + Math.abs((settings?.volume ?? 1) - 1) > 0.0005 || Boolean(settings?.normalize), + ); +} + export class VideoExporter { private config: VideoExporterConfig; private streamingDecoder: StreamingVideoDecoder | null = null; @@ -563,7 +573,8 @@ export class VideoExporter { speedRegions.length > 0 || audioRegions.length > 0 || sourceAudioFallbackPaths.length > 1 || - hasTimedSourceAudioFallback + hasTimedSourceAudioFallback || + hasNonDefaultSourceTrackSettings(this.config.sourceAudioTrackSettings) ) { const sourceDurationMs = Math.max( 0, @@ -947,6 +958,7 @@ export class VideoExporter { this.config.audioRegions, this.config.sourceAudioFallbackPaths, this.config.sourceAudioFallbackStartDelayMsByPath, + this.config.sourceAudioTrackSettings, ), "ffmpeg edited audio rendering", "audio",