Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions Packages/ShowRecorderKit/Sources/Recording/Capture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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
Expand All @@ -235,11 +236,12 @@ final class Capture: Sendable {
for copy in 0..<rings.count where requested & (1 << copy) != 0 {
feeds[copy].markJoined(at: frame)
}
afterJoinRead?()
capturing.bitwiseOr(requested, ordering: .acquiringAndReleasing)
// A Copy stopped since its request was read: take it back out, so its bit can't outlive this block (#544).
let stopped = stoppedFeeds.load(ordering: .acquiring) & requested
if stopped != 0 { capturing.bitwiseAnd(~stopped, ordering: .acquiringAndReleasing) }
joinRequests.bitwiseAnd(~requested, ordering: .acquiringAndReleasing)
// A Copy stopped, or asked to join again, since its request was claimed: take it back out; a fresh request
// is served at the next block at the right frame (#544, #945).
let stale = (stoppedFeeds.load(ordering: .acquiring) | joinRequests.load(ordering: .acquiring)) & requested
if stale != 0 { capturing.bitwiseAnd(~stale, ordering: .acquiringAndReleasing) }
}
let mask = capturing.load(ordering: .acquiring) & ~stoppedFeeds.load(ordering: .acquiring)
noteInputGap(before: block, capturing: mask)
Expand Down
21 changes: 21 additions & 0 deletions Packages/ShowRecorderKit/Tests/RecordingTests/CopyJoinTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,27 @@ struct CopyJoinTests {
#expect(capture.feeds[1].ring.availableFrames == 480)
}

@Test("A Copy no longer fed and asked to join again while a join block is acting on its old request joins at the next block (#945)")
func stopAndRejoinDuringJoinBlock() {
let box = CaptureBox()
let capture = Capture(channelCount: 1, sampleRate: 48_000, hostTicksPerSecond: 48_000, afterJoinRead: {
guard let capture = box.capture else { return }
capture.stopFeeding(capture.feeds[1]) // the Drive fails
capture.attachMidTake(capture.feeds[1]) // and comes straight back
})
capture.startCapturing(copies: 1)
receive(capture)
capture.attachMidTake(capture.feeds[1])
box.capture = capture
receive(capture)
box.capture = nil
#expect(capture.feeds[1].ring.availableFrames == 0, "not fed on the block that served its old request")
receive(capture)
#expect(capture.feeds[1].joinedAt == 960)
#expect(capture.feeds[1].ring.availableFrames == 480)
#expect(capture.feeds[0].ring.availableFrames == 1_440)
}

/// Lets a Capture's own hook reach it.
final class CaptureBox: @unchecked Sendable {
var capture: Capture?
Expand Down
Loading