feat: AudioLevel helper and AudioRecorder.onLevel

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 15:00:40 +02:00
co-authored by Claude Sonnet 5.5
parent 2c4b096850
commit c8e16145e1
4 changed files with 78 additions and 3 deletions
+24
View File
@@ -0,0 +1,24 @@
import Foundation
public enum AudioLevel {
/// Reported for empty or silent buffers (log of zero would be -infinity).
public static let silenceFloorDbfs: Float = -120
public static func rmsDbfs(_ samples: [Float]) -> Float {
samples.withUnsafeBufferPointer { rmsDbfs($0) }
}
public static func rmsDbfs(_ samples: UnsafeBufferPointer<Float>) -> Float {
guard !samples.isEmpty else { return silenceFloorDbfs }
var sum: Float = 0
for sample in samples { sum += sample * sample }
let rms = (sum / Float(samples.count)).squareRoot()
guard rms > 0 else { return silenceFloorDbfs }
return max(silenceFloorDbfs, 20 * log10(rms))
}
/// VU reading for a dBFS level, where `referenceDbfs` is 0 VU.
public static func vu(fromDbfs dbfs: Float, referenceDbfs: Float = -20) -> Float {
dbfs - referenceDbfs
}
}
+8 -1
View File
@@ -12,6 +12,10 @@ public final class AudioRecorder: AudioRecording {
private let engine = AVAudioEngine()
private let lock = NSLock()
private var buffer: [Float] = []
/// RMS level (dBFS) of every delivered input buffer. Called on the audio thread; set before `start()`.
public var onLevel: ((Float) -> Void)?
private let targetFormat = AVAudioFormat(
commonFormat: .pcmFormatFloat32,
sampleRate: 16_000,
@@ -32,8 +36,11 @@ public final class AudioRecorder: AudioRecording {
let ratio = targetFormat.sampleRate / inputFormat.sampleRate
input.removeTap(onBus: 0)
input.installTap(onBus: 0, bufferSize: 4096, format: inputFormat) { [weak self] pcm, _ in
input.installTap(onBus: 0, bufferSize: 1024, format: inputFormat) { [weak self] pcm, _ in
guard let self else { return }
if let onLevel = self.onLevel, let channel = pcm.floatChannelData?[0] {
onLevel(AudioLevel.rmsDbfs(UnsafeBufferPointer(start: channel, count: Int(pcm.frameLength))))
}
let capacity = AVAudioFrameCount(Double(pcm.frameLength) * ratio) + 1
guard let out = AVAudioPCMBuffer(pcmFormat: self.targetFormat, frameCapacity: capacity) else { return }