Created
November 25, 2023 14:35
-
-
Save jcavar/79a6cf497fd6ec1295c47d20ada96f91 to your computer and use it in GitHub Desktop.
Simple AVAudioEngine setup that generates TSan violation when adding nodes after engine has started
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import AudioUnit | |
import AVFAudio | |
@main | |
struct AllocOnOneUseOnAnotherApp: App { | |
let description = AudioComponentDescription( | |
componentType: kAudioUnitType_Effect, | |
componentSubType: 1768125554, | |
componentManufacturer: 1768125554, | |
componentFlags: AudioComponentFlags.sandboxSafe.rawValue, | |
componentFlagsMask: 0 | |
) | |
let engine = AVAudioEngine() | |
var body: some Scene { | |
WindowGroup { | |
Text("Tap for data race") | |
.onAppear { | |
AUAudioUnit.registerSubclass( | |
SampleAudioUnit.self, | |
as: description, | |
name: "SampleAudioUnit", | |
version: .max | |
) | |
attachNodes() | |
try! engine.start() | |
} | |
.onTapGesture { | |
attachNodes() | |
} | |
} | |
} | |
func attachNodes() { | |
let x = AVAudioUnit.instantiate(componentDescription: description) | |
engine.attach(x) | |
let player = AVAudioPlayerNode() | |
engine.attach(player) | |
engine.connect(player, to: x, format: nil) | |
engine.connect(x, to: engine.mainMixerNode, format: nil) | |
} | |
} | |
class SampleAudioUnit: AUAudioUnit { | |
private var startAt: Int64 = 1 | |
private var inputBusArray: AUAudioUnitBusArray! | |
private var outputBusArray: AUAudioUnitBusArray! | |
override var inputBusses: AUAudioUnitBusArray { inputBusArray } | |
override var outputBusses: AUAudioUnitBusArray { outputBusArray } | |
override init( | |
componentDescription: AudioComponentDescription, | |
options: AudioComponentInstantiationOptions = [] | |
) throws { | |
try super.init(componentDescription: componentDescription, options: options) | |
let format = AVAudioFormat(standardFormatWithSampleRate: 44_100, channels: 2)! | |
inputBusArray = AUAudioUnitBusArray( | |
audioUnit: self, | |
busType: .input, | |
busses: [try AUAudioUnitBus(format: format)] | |
) | |
outputBusArray = AUAudioUnitBusArray( | |
audioUnit: self, | |
busType: .output, | |
busses: [try AUAudioUnitBus(format: format)] | |
) | |
} | |
override var internalRenderBlock: AUInternalRenderBlock { | |
{ [unowned self] _, _, _, _, _, _, _ in | |
guard startAt == 1 else { return noErr } | |
return noErr | |
} | |
} | |
} | |
extension AVAudioUnit { | |
static func instantiate(componentDescription: AudioComponentDescription) -> AVAudioUnit { | |
let semaphore = DispatchSemaphore(value: 0) | |
var result: AVAudioUnit! | |
AVAudioUnit.instantiate(with: componentDescription) { avAudioUnit, _ in | |
guard let au = avAudioUnit else { fatalError("Unable to instantiate AVAudioUnit") } | |
result = au | |
semaphore.signal() | |
} | |
_ = semaphore.wait(wallTimeout: .distantFuture) | |
return result | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is only happening on macOS and is not reproducible on iOS or Simulator.