- CGEvent cghidEventTap 全局监听 Fn 键 - Apple Speech 流式语音识别(zh-CN 默认) - 实时音频 RMS 电平驱动波形动画 - NSPanel 胶囊浮动窗口 - 剪贴板 + Cmd+V 文本注入,CJK 输入法自动切换 - OpenAI 兼容 LLM 纠错(DeepSeek 预设) - LSUIElement 菜单栏运行 - SPM + Makefile 构建
67 lines
2.0 KiB
Swift
67 lines
2.0 KiB
Swift
import AVFAudio
|
||
import Foundation
|
||
|
||
/// 实时音频电平监测,通过 AVAudioEngine 获取麦克风 RMS 电平
|
||
final class AudioLevelMonitor {
|
||
|
||
private let engine = AVAudioEngine()
|
||
private var isRunning = false
|
||
|
||
/// RMS 电平回调,值范围约 0.0 ~ 1.0(正常语音),可能超过 1.0
|
||
var onLevelUpdate: ((Float) -> Void)?
|
||
|
||
func start() -> Bool {
|
||
guard !isRunning else { return true }
|
||
|
||
let inputNode = engine.inputNode
|
||
let format = inputNode.outputFormat(forBus: 0)
|
||
|
||
// 安装 tap 获取实时音频缓冲
|
||
inputNode.installTap(onBus: 0, bufferSize: 1024, format: format) { [weak self] buffer, _ in
|
||
self?.processAudioBuffer(buffer)
|
||
}
|
||
|
||
do {
|
||
try engine.start()
|
||
isRunning = true
|
||
print("[AudioLevel] Started")
|
||
return true
|
||
} catch {
|
||
print("[AudioLevel] Failed to start engine: \(error)")
|
||
return false
|
||
}
|
||
}
|
||
|
||
func stop() {
|
||
guard isRunning else { return }
|
||
engine.inputNode.removeTap(onBus: 0)
|
||
engine.stop()
|
||
isRunning = false
|
||
print("[AudioLevel] Stopped")
|
||
}
|
||
|
||
private func processAudioBuffer(_ buffer: AVAudioPCMBuffer) {
|
||
guard let channelData = buffer.floatChannelData else { return }
|
||
let frameLength = Int(buffer.frameLength)
|
||
let channelCount = Int(buffer.format.channelCount)
|
||
|
||
// 计算所有通道的 RMS
|
||
var sumSquares: Float = 0
|
||
for channel in 0..<channelCount {
|
||
let samples = channelData[channel]
|
||
for i in 0..<frameLength {
|
||
let sample = samples[i]
|
||
sumSquares += sample * sample
|
||
}
|
||
}
|
||
let rms = sqrt(sumSquares / Float(frameLength * channelCount))
|
||
|
||
// 放大约 15 倍,使正常语音峰值接近 1.0
|
||
let scaled = min(rms * 15.0, 1.5)
|
||
|
||
DispatchQueue.main.async { [weak self] in
|
||
self?.onLevelUpdate?(scaled)
|
||
}
|
||
}
|
||
}
|