import Accelerate import AVFoundation import CoreAudio import CoreMedia import Foundation final class AudioRingBuffer { private static let bufferCounts: UInt32 = 16 private static let numSamples: UInt32 = 1024 var counts: Int { if tail <= head { return head - tail + skip } return Int(outputBuffer.frameLength) - tail + head + skip } private var head = 0 private var tail = 0 private var skip = 0 private var sampleTime: AVAudioFramePosition = 0 private var inputFormat: AVAudioFormat private var inputBuffer: AVAudioPCMBuffer private var outputBuffer: AVAudioPCMBuffer init?(_ inputFormat: AVAudioFormat, bufferCounts: UInt32 = AudioRingBuffer.bufferCounts) { guard let inputBuffer = AVAudioPCMBuffer(pcmFormat: inputFormat, frameCapacity: Self.numSamples) else { return nil } guard let outputBuffer = AVAudioPCMBuffer(pcmFormat: inputFormat, frameCapacity: Self.numSamples * bufferCounts) else { return nil } self.inputFormat = inputFormat self.inputBuffer = inputBuffer self.outputBuffer = outputBuffer self.outputBuffer.frameLength = self.outputBuffer.frameCapacity } func isDataAvailable(_ inNumberFrames: UInt32) -> Bool { return inNumberFrames <= counts } func append(_ sampleBuffer: CMSampleBuffer) { guard CMSampleBufferDataIsReady(sampleBuffer) else { return } let targetSampleTime: CMTimeValue if sampleBuffer.presentationTimeStamp.timescale == Int32(inputBuffer.format.sampleRate) { targetSampleTime = sampleBuffer.presentationTimeStamp.value } else { targetSampleTime = Int64(Double(sampleBuffer.presentationTimeStamp.value) * inputBuffer.format.sampleRate / Double(sampleBuffer.presentationTimeStamp.timescale)) } if sampleTime == 0 { sampleTime = targetSampleTime } if outputBuffer.frameLength < sampleBuffer.numSamples { skip += sampleBuffer.numSamples return } if inputBuffer.frameLength < sampleBuffer.numSamples { if let buffer = AVAudioPCMBuffer(pcmFormat: inputFormat, frameCapacity: AVAudioFrameCount(sampleBuffer.numSamples)) { self.inputBuffer = buffer } } inputBuffer.frameLength = AVAudioFrameCount(sampleBuffer.numSamples) let status = CMSampleBufferCopyPCMDataIntoAudioBufferList( sampleBuffer, at: 0, frameCount: Int32(sampleBuffer.numSamples), into: inputBuffer.mutableAudioBufferList ) if status == noErr && kLinearPCMFormatFlagIsBigEndian == ((sampleBuffer.formatDescription?.audioStreamBasicDescription?.mFormatFlags ?? 0) & kLinearPCMFormatFlagIsBigEndian) { if inputFormat.isInterleaved { switch inputFormat.commonFormat { case .pcmFormatInt16: let length = sampleBuffer.dataBuffer?.dataLength ?? 0 var image = vImage_Buffer(data: inputBuffer.mutableAudioBufferList[0].mBuffers.mData, height: 1, width: vImagePixelCount(length / 2), rowBytes: length) vImageByteSwap_Planar16U(&image, &image, vImage_Flags(kvImageNoFlags)) default: break } } } skip = max(Int(targetSampleTime - sampleTime), 0) sampleTime += Int64(skip) append(inputBuffer) } func append(_ audioPCMBuffer: AVAudioPCMBuffer, when: AVAudioTime) { if sampleTime == 0 { sampleTime = when.sampleTime } if inputBuffer.frameLength < audioPCMBuffer.frameLength { if let buffer = AVAudioPCMBuffer(pcmFormat: inputFormat, frameCapacity: audioPCMBuffer.frameCapacity) { self.inputBuffer = buffer } } inputBuffer.frameLength = audioPCMBuffer.frameLength _ = inputBuffer.copy(audioPCMBuffer) skip = Int(max(when.sampleTime - sampleTime, 0)) sampleTime += Int64(skip) append(inputBuffer) } func render(_ inNumberFrames: UInt32, ioData: UnsafeMutablePointer?, offset: Int = 0) -> OSStatus { if 0 < skip { let numSamples = min(Int(inNumberFrames), skip) guard let bufferList = UnsafeMutableAudioBufferListPointer(ioData) else { return -1 } if inputFormat.isInterleaved { let channelCount = Int(inputFormat.channelCount) switch inputFormat.commonFormat { case .pcmFormatInt16: bufferList[0].mData?.assumingMemoryBound(to: Int16.self).advanced(by: offset * channelCount).update(repeating: 0, count: numSamples) case .pcmFormatInt32: bufferList[0].mData?.assumingMemoryBound(to: Int32.self).advanced(by: offset * channelCount).update(repeating: 0, count: numSamples) case .pcmFormatFloat32: bufferList[0].mData?.assumingMemoryBound(to: Float32.self).advanced(by: offset * channelCount).update(repeating: 0, count: numSamples) default: break } } else { for i in 0..