Skip to content

Mac meeting recording: system audio can start ahead of the microphone (A/V sync offset) #106

Description

@bisonbet

Summary

On native macOS meeting recordings (mic + system audio), the system-audio track can begin ahead of the microphone track, so the two are misaligned in the saved/mixed meeting. The gap equals the microphone's startup delay — usually small, but up to ~5s when the microphone health-check/recovery path runs.

Originally raised as a Codex review comment on PR #105 (AudioRecorderViewModel+MacEngine.swift). Deferred from the v2.2 review pass because a safe implementation needs on-device verification (see "Why deferred").

Root cause

When a meeting recording starts, system-audio capture begins immediately:

  • AudioRecorderViewModel+MacEngine.swift:~50MacSystemAudioCapture(outputURL:) then try await capture.start() starts writing the system track right away.
  • The microphone (AVAudioEngine tap) does not produce its first buffer until later; the first successful write is signaled via handleMacFirstSuccessfulWrite().
  • During finalization, the mix inserts both tracks at time zero, so any delay between "system capture started" and "mic produced its first buffer" shifts the system audio ahead of the mic in the output.

Proposed fix

Start system-audio capture paused, and release it from handleMacFirstSuccessfulWrite() so both tracks effectively begin at the same real moment.

The machinery already exists — MacSystemAudioCapture.setPaused(_:) (MacSystemAudioCapture.swift:116) drops samples while paused (didOutputSampleBuffer returns early when isPaused, ~line 181) and tracks pauseStartedAt / accumulatedPausedDuration to compensate timestamps. So this is mostly a start-ordering + handshake change, not new capture logic.

Alternative: preserve capture timestamps and align the two tracks during mixing instead of inserting both at t=0.

Why this was deferred (please read before implementing)

  1. The naive fix fails toward silence. If system capture waits for the mic's first buffer and that signal never arrives — a silent/failed mic, or the mic getting stuck in the ~5s health-check/recovery path (the exact scenario this fix targets) — the system track stays paused for the whole meeting and no remote-participant audio is captured at all. That is worse than the current sync offset. Any implementation should include a safety timeout that force-unpauses system capture after ~1–2s even if no mic buffer has arrived, so worst case degrades to today's behavior (a small offset) rather than to silence.

  2. Only verifiable on-device. This is an A/V-sync change; confirming it (a) actually aligns the tracks and (b) does not drop the system track requires recording a real meeting on a Mac (system audio playing through the app while speaking into the mic) and listening to the mixed output. It cannot be verified by a build or unit test.

  3. High blast radius. This lives in the Mac recording capture path — the most fragile area of the native-macOS migration — so a mistake risks regressing "recording captures system audio at all."

Acceptance criteria

  • Mic + system audio are time-aligned in a saved meeting recording, including when mic startup is delayed by the recovery path.
  • If the mic never produces a first buffer, system audio is still captured (safety-timeout fallback), not lost.
  • Verified by recording a real meeting on an Apple-silicon Mac and confirming alignment + both tracks present.

References

  • BisonNotes AI/BisonNotes AI/ViewModels/AudioRecorderViewModel+MacEngine.swift (system capture start)
  • BisonNotes AI/BisonNotes AI/ViewModels/MacSystemAudioCapture.swift (setPaused, pause/timestamp compensation)
  • handleMacFirstSuccessfulWrite() (first-mic-buffer signal)
  • PR V2.2 #105 Codex review comment: "Pause system capture until microphone audio starts"

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions