diff --git a/Packages/ShowRecorderKit/Sources/Recording/Capture.swift b/Packages/ShowRecorderKit/Sources/Recording/Capture.swift index d28a5acb..1c2e1804 100644 --- a/Packages/ShowRecorderKit/Sources/Recording/Capture.swift +++ b/Packages/ShowRecorderKit/Sources/Recording/Capture.swift @@ -89,7 +89,8 @@ final class Capture: Sendable { private let hostClock: @Sendable () -> UInt64 /// A gap shorter than this (frames) is timing jitter, not lost input. private let gapThreshold: Int - /// Tests only: runs on the real-time thread between reading the join requests and acting on them (#544). Nil otherwise. + /// Tests only: runs on the real-time thread after a join block sets the join frames, before the Copies are added to + /// `capturing` (#544, #945). Nil otherwise. private let afterJoinRead: (@Sendable () -> Void)? /// The host clock's ticks per second on this machine. static let machTicksPerSecond: Double = { @@ -220,9 +221,9 @@ final class Capture: Sendable { func receive(_ block: AudioBlock) { meters.record(block) preRoll?.write(block) - let requested = joinRequests.load(ordering: .acquiring) + // A read first, so a block with nothing waiting writes nothing shared; the claim is one exchange (#945). + let requested = joinRequests.load(ordering: .relaxed) == 0 ? 0 : joinRequests.exchange(0, ordering: .acquiringAndReleasing) if requested != 0 { - afterJoinRead?() if preRollStart.load(ordering: .relaxed) == -2 { // The Pre-roll buffer already holds this block, so the live audio starts before it. let start = (preRoll?.totalWrittenFrames ?? block.frameCount) - block.frameCount @@ -235,11 +236,12 @@ final class Capture: Sendable { for copy in 0..