import AVFoundation import CoreAudio import Foundation final class AudioMixerByMultiTrack: AudioMixer { private static let defaultSampleTime: AVAudioFramePosition = 0 weak var delegate: (any AudioMixerDelegate)? var settings = AudioMixerSettings.default { didSet { if let inSourceFormat, settings.invalidateOutputFormat(oldValue) { outputFormat = settings.makeOutputFormat(inSourceFormat) } for (id, trackSettings) in settings.tracks { tracks[id]?.settings = trackSettings try? mixerNode?.update(volume: trackSettings.volume, bus: id, scope: .input) } } } var inputFormats: [UInt8: AVAudioFormat] { return tracks.compactMapValues { $0.inputFormat } } private(set) var outputFormat: AVAudioFormat? { didSet { guard let outputFormat, outputFormat != oldValue else { return } for id in tracks.keys { buffers[id] = .init(outputFormat) tracks[id] = .init(id: id, outputFormat: outputFormat) tracks[id]?.delegate = self } } } private var inSourceFormat: CMFormatDescription? { didSet { guard inSourceFormat != oldValue else { return } outputFormat = settings.makeOutputFormat(inSourceFormat) } } private var tracks: [UInt8: AudioMixerTrack] = [:] { didSet { tryToSetupAudioNodes() } } private var anchor: AVAudioTime? private var buffers: [UInt8: AudioRingBuffer] = [:] { didSet { if logger.isEnabledFor(level: .trace) { logger.trace(buffers) } } } private var mixerNode: MixerNode? private var sampleTime: AVAudioFramePosition = AudioMixerByMultiTrack.defaultSampleTime private var outputNode: OutputNode? private let inputRenderCallback: AURenderCallback = { (inRefCon: UnsafeMutableRawPointer, _: UnsafeMutablePointer, _: UnsafePointer, inBusNumber: UInt32, inNumberFrames: UInt32, ioData: UnsafeMutablePointer?) in let audioMixer = Unmanaged.fromOpaque(inRefCon).takeUnretainedValue() let status = audioMixer.render(UInt8(inBusNumber), inNumberFrames: inNumberFrames, ioData: ioData) guard status == noErr else { audioMixer.delegate?.audioMixer(audioMixer, errorOccurred: .unableToProvideInputData) return noErr } return status } deinit { if let mixerNode = mixerNode { AudioOutputUnitStop(mixerNode.audioUnit) } if let outputNode = outputNode { AudioOutputUnitStop(outputNode.audioUnit) } } func append(_ track: UInt8, buffer: CMSampleBuffer) { if settings.mainTrack == track { inSourceFormat = buffer.formatDescription } self.track(for: track)?.append(buffer) } func append(_ track: UInt8, buffer: AVAudioPCMBuffer, when: AVAudioTime) { if settings.mainTrack == track { inSourceFormat = buffer.format.formatDescription } self.track(for: track)?.append(buffer, when: when) } private func tryToSetupAudioNodes() { do { try setupAudioNodes() } catch { logger.error(error) delegate?.audioMixer(self, errorOccurred: .failedToMix(error: error)) } } private func setupAudioNodes() throws { if let mixerNode { AudioOutputUnitStop(mixerNode.audioUnit) } if let outputNode { AudioOutputUnitStop(outputNode.audioUnit) } mixerNode = nil outputNode = nil guard let outputFormat else { return } sampleTime = Self.defaultSampleTime let mixerNode = try MixerNode(format: outputFormat) try mixerNode.update(busCount: tracks.count, scope: .input) let busCount = try mixerNode.busCount(scope: .input) for index in 0..?) -> OSStatus { guard let buffer = buffers[track] else { return noErr } if buffer.counts == 0 { guard let bufferList = UnsafeMutableAudioBufferListPointer(ioData) else { return noErr } for i in 0.. AudioMixerTrack? { if let track = tracks[id] { return track } guard let outputFormat else { return nil } let track = AudioMixerTrack(id: id, outputFormat: outputFormat) track.delegate = self if let trackSettings = settings.tracks[id] { track.settings = trackSettings } tracks[id] = track buffers[id] = .init(outputFormat) return track } } extension AudioMixerByMultiTrack: AudioMixerTrackDelegate { // MARK: AudioMixerTrackDelegate func track(_ track: AudioMixerTrack, didOutput audioPCMBuffer: AVAudioPCMBuffer, when: AVAudioTime) { delegate?.audioMixer(self, track: track.id, didInput: audioPCMBuffer, when: when) buffers[track.id]?.append(audioPCMBuffer, when: when) if settings.mainTrack == track.id { if sampleTime == Self.defaultSampleTime { sampleTime = when.sampleTime anchor = when } mix(numberOfFrames: audioPCMBuffer.frameLength) } } func track(_ track: AudioMixerTrack, errorOccurred error: AudioMixerError) { delegate?.audioMixer(self, errorOccurred: error) } }