mirror of
https://github.com/HaishinKit/HaishinKit.swift.git
synced 2026-05-07 20:12:28 +00:00
128 lines
4.6 KiB
Swift
128 lines
4.6 KiB
Swift
import Accelerate
|
|
import AVFoundation
|
|
|
|
private let kAudioMixerTrack_frameCapacity: AVAudioFrameCount = 1024
|
|
|
|
protocol AudioMixerTrackDelegate: AnyObject {
|
|
func track(_ track: AudioMixerTrack<Self>, didOutput audioPCMBuffer: AVAudioPCMBuffer, when: AVAudioTime)
|
|
func track(_ track: AudioMixerTrack<Self>, errorOccurred error: AudioMixerError)
|
|
}
|
|
|
|
final class AudioMixerTrack<T: AudioMixerTrackDelegate> {
|
|
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)
|
|
}
|
|
}
|