Files
sst/Sources/SstCore/AudioRecorder.swift
T
2026-10-01 09:50:14 +02:00

74 lines
2.4 KiB
Swift

import AVFoundation
public final class AudioRecorder: AudioRecording {
public enum RecorderError: Error, LocalizedError {
case converterUnavailable
public var errorDescription: String? {
"Kon de microfoon niet omzetten naar 16 kHz mono."
}
}
private let engine = AVAudioEngine()
private let lock = NSLock()
private var buffer: [Float] = []
private let targetFormat = AVAudioFormat(
commonFormat: .pcmFormatFloat32,
sampleRate: 16_000,
channels: 1,
interleaved: false
)!
public init() {}
public func start() throws {
lock.withLock { buffer.removeAll(keepingCapacity: true) }
let input = engine.inputNode
let inputFormat = input.outputFormat(forBus: 0)
guard let converter = AVAudioConverter(from: inputFormat, to: targetFormat) else {
throw RecorderError.converterUnavailable
}
let ratio = targetFormat.sampleRate / inputFormat.sampleRate
input.removeTap(onBus: 0)
input.installTap(onBus: 0, bufferSize: 4096, format: inputFormat) { [weak self] pcm, _ in
guard let self else { return }
let capacity = AVAudioFrameCount(Double(pcm.frameLength) * ratio) + 1
guard let out = AVAudioPCMBuffer(pcmFormat: self.targetFormat, frameCapacity: capacity) else { return }
var consumed = false
var error: NSError?
converter.convert(to: out, error: &error) { _, status in
if consumed {
status.pointee = .noDataNow
return nil
}
consumed = true
status.pointee = .haveData
return pcm
}
guard error == nil, let channel = out.floatChannelData?[0] else { return }
let samples = Array(UnsafeBufferPointer(start: channel, count: Int(out.frameLength)))
self.lock.withLock { self.buffer.append(contentsOf: samples) }
}
engine.prepare()
do {
try engine.start()
} catch {
input.removeTap(onBus: 0)
throw error
}
}
public func stop() -> [Float] {
engine.inputNode.removeTap(onBus: 0)
engine.stop()
return lock.withLock {
let samples = buffer
buffer = []
return samples
}
}
}