Skip to content

Meeting loses call audio for the rest of the recording when the system-audio tap switches to digital zeros mid-call (silence watch already disarmed) #1916

Description

@vlad22gor

About 18 minutes into a 51-minute Google Meet call, the system-audio tap started delivering pure digital zeros and kept doing so until the recording stopped. The mic track is fine, but the other side of the call is missing for the last ~32 minutes. There was no reconnect, no "Can't hear the call" warning, and the meeting saved with capture_quality: excellent, audio_gaps: 0.

Root cause (from reading main): after the tap hears signal, nothing watches for it going back to digital zeros.

  • In CoreAudioSystemAudioCapture, the stall check only fires when buffers stop arriving (now - lastBuffer > 3). Here buffers kept arriving; they were just zeros ("Zero-valued PCM is valid audio. Only absent callbacks trigger recovery").
  • SystemAudioSilenceWatch (added in Stop Boost Mic from sticking, and catch meetings that lose call audio #1796) catches "zeros while another app plays", but it is armed only on .start, .wake, .rebuild and .outputChange. In noteSilenceWatchBuffer the first buffer with signal sets silenceWatch = nil. The call had audio from the first seconds, so the watch was gone long before the dropout and was never re-armed: no wake, no output change, no rebuild.

Steps to reproduce
Intermittent, I could not reproduce it on demand. What happened, in order:

  1. Start recording a Google Meet call in Arc (Chromium). Output: Bluetooth headphones. Input: built-in mic.
  2. Share a window of another app in Meet (Claude desktop), then stop sharing.
  3. At the moment sharing stopped, the tap went to digital zeros for good.

Timeline (local time, from system_audio.m4a levels and log show):

Time Event
12:30:44 Recording starts
12:33:02 – 12:34:51 First screen share (another app window). Stopping it caused no problem
12:44:07 Second share starts (Claude desktop window)
12:48:59.345 replayd: Arc's SCStream stopCapture / stopAndInvalidateWithStreamData, then [ERROR] RPRecordingManager updateStream… no client found for streamID
12:48:59.5 system_audio.m4a switches from normal call audio (pauses sit at about −78 dBFS) to exact digital zero (−inf) until the end
12:49:00.114 coreaudiod: HALS_OverloadMessage: Overload possibly due to safety violation / Audio IO Overload
12:49:10 Transcripted app.jsonl: one "System audio silent" {"duration":"10s"} (AudioLevelMonitor). Nothing else from audio.system until stop
12:49:11 New share (browser tab), audio does not come back
13:21:32 Recording stopped. playback.m4a equals the mic track after 12:48:59

Another app kept playing audio the whole time (the Meet tab), so anotherProcessIsPlayingAudio should have been true, if the watch had been armed.

Expected behavior
If the tap has already heard signal and then delivers only exact digital zeros for several seconds while another process is playing audio, rebuild the tap once. If that doesn't help, show "Can't hear the call", the same way the start/wake watch already does.

Actual behavior
32 minutes of zeros, no recovery, no in-meeting warning, and the capture is reported as excellent.

Proposed fix (sketch, not compiled)
Re-arm the existing watch when a tap that has heard signal goes back to digital zeros. Bound the re-arms so a call where the far end is quiet can't cause repeated rebuilds. A rebuild during zeros costs nothing that was actually heard, as the comment in SystemAudioSilenceWatch already notes.

// SystemAudioSilenceWatch.Reason
/// The tap heard signal, then went back to digital zeros mid-recording.
case lostMidRecording

// SystemAudioSilenceWatch.armed(_:at:) — same parameters as .rebuild
case .start, .rebuild, .outputChange, .lostMidRecording:

// CoreAudioSystemAudioCapture
private var lastSignalAt: TimeInterval?
private var midRecordingRearms = 0
static let maxMidRecordingRearms = 3

// start(): reset both
lastSignalAt = nil
midRecordingRearms = 0

// deliverQueued(...), next to `if silenceWatch != nil { noteSilenceWatchBuffer(...) }`
if Self.containsSignal(buffer) { lastSignalAt = now }

// drain tick, just before `if silenceWatch != nil { checkSilenceWatch(at: now) }`
if silenceWatch == nil,
   let lastSignalAt,
   now - lastSignalAt >= SystemAudioSilenceWatch.silenceSeconds,
   midRecordingRearms < Self.maxMidRecordingRearms {
    midRecordingRearms += 1
    silenceWatch = .armed(.lostMidRecording, at: now)
    AppLogger.audioSystem.info("System audio went to digital silence after signal; watching")
}

After that, the existing flow applies: evaluate(otherAudioPlaying:) asks for one .reconnect (→ recover(.silentWhilePlaying)), then .reportUnheard after unheardReportSeconds. If signal returns on a new tap, didLosePlayback marks the loss. A unit test can cover it on the pure watch plus a fake hardwareHooks: signal buffers → zero buffers for more than 5 s with otherAudioIsPlaying == true → expect one reconnect.

Side note: capture_quality: excellent / audio_gaps: 0 should probably take isNotHearingPlayback / didLosePlayback into account, so this case is visible after the meeting too.

Area

  • Meetings

Environment

  • macOS version: 26.6.2 (25G83), Apple Silicon
  • Transcripted version/commit: 1.1.66 (release). Code references are to main @ c2bb706
  • Audio source: Google Meet in Arc (Chromium). Output: Bluetooth headphones. Input: built-in mic

Storage details

  • ~/Library/Application Support/Transcripted/

Diagnostics
I can share redacted app.jsonl / events.jsonl lines and log show excerpts for replayd / coreaudiod around 12:48:50–12:49:15 if useful. I'm not attaching audio or transcripts because they contain private meeting content.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions