import Accelerate import AVFoundation private let kAudioMixerTrack_frameCapacity: AVAudioFrameCount = 1024 protocol AudioMixerTrackDelegate: AnyObject { func track(_ track: AudioMixerTrack, didOutput audioPCMBuffer: AVAudioPCMBuffer, when: AVAudioTime) func track(_ track: AudioMixerTrack, errorOccurred error: AudioMixerError) } final class AudioMixerTrack { let id: UInt8 let outputFormat: AVAudioFormat weak var delegate: T? var settings: AudioMixerTrackSettings = .init() { didSet { settings.apply(audioConverter, oldValue: oldValue) } } var inputFormat: AVAudioFormat? { return audioConverter?.inputFormat } private var inSourceFormat: CMFormatDescription? { didSet { guard inSourceFormat != oldValue else { return } setUp(inSourceFormat) } } private var audioTime = AudioTime() private var ringBuffer: AudioRingBuffer? private var inputBuffer: AVAudioPCMBuffer? private var outputBuffer: AVAudioPCMBuffer? private var audioConverter: AVAudioConverter? { didSet { guard let audioConverter else { return } audioConverter.downmix = settings.downmix if let channelMap = settings.validatedChannelMap(audioConverter) { audioConverter.channelMap = channelMap.map { NSNumber(value: $0) } } else { switch audioConverter.outputFormat.channelCount { case 1: audioConverter.channelMap = [0] case 2: audioConverter.channelMap = (audioConverter.inputFormat.channelCount == 1) ? [0, 0] : [0, 1] default: break } } audioConverter.primeMethod = .normal } } init(id: UInt8, outputFormat: AVAudioFormat) { self.id = id self.outputFormat = outputFormat } func append(_ sampleBuffer: CMSampleBuffer) { inSourceFormat = sampleBuffer.formatDescription if !audioTime.hasAnchor { audioTime.anchor(sampleBuffer.presentationTimeStamp, sampleRate: outputFormat.sampleRate) } ringBuffer?.append(sampleBuffer) resample() } func append(_ audioBuffer: AVAudioPCMBuffer, when: AVAudioTime) { inSourceFormat = audioBuffer.format.formatDescription if !audioTime.hasAnchor { audioTime.anchor(when) } ringBuffer?.append(audioBuffer, when: when) resample() } @inline(__always) private func resample() { guard let outputBuffer, let inputBuffer, let ringBuffer else { return } var status: AVAudioConverterOutputStatus? = .endOfStream repeat { var error: NSError? status = audioConverter?.convert(to: outputBuffer, error: &error) { inNumberFrames, status in if inNumberFrames <= ringBuffer.counts { _ = ringBuffer.render(inNumberFrames, ioData: inputBuffer.mutableAudioBufferList) inputBuffer.frameLength = inNumberFrames status.pointee = .haveData return inputBuffer } else { status.pointee = .noDataNow return nil } } switch status { case .haveData: delegate?.track(self, didOutput: outputBuffer.muted(settings.isMuted), when: audioTime.at) audioTime.advanced(1024) case .error: if let error { delegate?.track(self, errorOccurred: .failedToConvert(error: error)) } default: break } } while(status == .haveData) } private func setUp(_ inSourceFormat: CMFormatDescription?) { guard let inputFormat = AVAudioUtil.makeAudioFormat(inSourceFormat) else { delegate?.track(self, errorOccurred: .failedToCreate(from: inputFormat, to: outputFormat)) return } ringBuffer = .init(inputFormat) inputBuffer = .init(pcmFormat: inputFormat, frameCapacity: kAudioMixerTrack_frameCapacity * 4) outputBuffer = .init(pcmFormat: outputFormat, frameCapacity: kAudioMixerTrack_frameCapacity) if logger.isEnabledFor(level: .info) { logger.info("inputFormat:", inputFormat, ", outputFormat:", outputFormat) } audioTime.reset() audioConverter = .init(from: inputFormat, to: outputFormat) } }