51 lines
1.9 KiB
Swift
51 lines
1.9 KiB
Swift
@preconcurrency import AVFoundation
|
|
import Foundation
|
|
|
|
final class BufferConverter {
|
|
private final class Box<T>: @unchecked Sendable { var value: T; init(_ value: T) { self.value = value } }
|
|
enum ConverterError: Swift.Error {
|
|
case failedToCreateConverter
|
|
case failedToCreateConversionBuffer
|
|
case conversionFailed(NSError?)
|
|
}
|
|
|
|
private var converter: AVAudioConverter?
|
|
|
|
func convert(_ buffer: AVAudioPCMBuffer, to format: AVAudioFormat) throws -> AVAudioPCMBuffer {
|
|
let inputFormat = buffer.format
|
|
if inputFormat == format {
|
|
return buffer
|
|
}
|
|
if converter == nil || converter?.outputFormat != format {
|
|
converter = AVAudioConverter(from: inputFormat, to: format)
|
|
converter?.primeMethod = .none
|
|
}
|
|
guard let converter else { throw ConverterError.failedToCreateConverter }
|
|
|
|
let sampleRateRatio = converter.outputFormat.sampleRate / converter.inputFormat.sampleRate
|
|
let scaledInputFrameLength = Double(buffer.frameLength) * sampleRateRatio
|
|
let frameCapacity = AVAudioFrameCount(scaledInputFrameLength.rounded(.up))
|
|
guard let conversionBuffer = AVAudioPCMBuffer(pcmFormat: converter.outputFormat, frameCapacity: frameCapacity)
|
|
else {
|
|
throw ConverterError.failedToCreateConversionBuffer
|
|
}
|
|
|
|
var nsError: NSError?
|
|
let consumed = Box(false)
|
|
let inputBuffer = buffer
|
|
let status = converter.convert(to: conversionBuffer, error: &nsError) { _, statusPtr in
|
|
if consumed.value {
|
|
statusPtr.pointee = .noDataNow
|
|
return nil
|
|
}
|
|
consumed.value = true
|
|
statusPtr.pointee = .haveData
|
|
return inputBuffer
|
|
}
|
|
if status == .error {
|
|
throw ConverterError.conversionFailed(nsError)
|
|
}
|
|
return conversionBuffer
|
|
}
|
|
}
|