Skip to content

Instantly share code, notes, and snippets.

@jcavar
Created November 25, 2023 14:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcavar/79a6cf497fd6ec1295c47d20ada96f91 to your computer and use it in GitHub Desktop.
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
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
}
}
@jcavar
Copy link
Author

jcavar commented Nov 26, 2023

This is only happening on macOS and is not reproducible on iOS or Simulator.

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