SpeechKit框架语音命令应用:如何检测语音语句结束静音?
Hey there! I’ve run into this exact issue before when building voice-controlled apps—getting that Siri-like "user has finished speaking" detection is tricky with the default SFSpeechRecognizer setup, but there are a few reliable workarounds that get you pretty close. Let’s break this down based on your use case:
SFSpeechRecognizer doesn’t expose built-in silence detection out of the box, but we can combine its recognition updates with either timed result tracking or raw audio analysis to mimic Siri’s behavior.
1. Quick Fix: Track Interim Results + Silence Timeout
This method uses the recognition task’s interim result updates to monitor when the user stops speaking. We’ll set a timer that triggers if we don’t get a new result after a set threshold (e.g., 1.5 seconds).
Here’s how to modify your closure-based recognitionTask setup:
import Speech class VoiceCommandHandler { private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))! private var recognitionTask: SFSpeechRecognitionTask? private var lastResultReceivedDate: Date? private var silenceTimer: Timer? private let silenceThreshold: TimeInterval = 1.5 // Adjust based on testing func startListening() { guard !speechRecognizer.isAvailable else { print("Speech recognition unavailable") return } let request = SFSpeechAudioBufferRecognitionRequest() request.shouldReportPartialResults = true request.taskHint = .command // Tells the recognizer we're expecting short commands // Assume you've set up AVAudioEngine to feed audio to the request (omitted for brevity) recognitionTask = speechRecognizer.recognitionTask(with: request) { [weak self] result, error in guard let self = self else { return } // Reset timer and update last result time whenever we get new data self.silenceTimer?.invalidate() self.lastResultReceivedDate = Date() // Start a new timer to check for silence self.silenceTimer = Timer.scheduledTimer(withTimeInterval: self.silenceThreshold, repeats: false) { _ in request.endAudio() // Signal end of speech self.silenceTimer?.invalidate() } if let result = result { let transcript = result.bestTranscription.formattedString.lowercased() print("Current transcript: \(transcript)") if result.isFinal { // Handle your command logic here if transcript == "open setting" { DispatchQueue.main.async { // Navigate to Settings page // e.g., self.navigationController?.pushViewController(SettingsVC(), animated: true) } } self.silenceTimer?.invalidate() } } if error != nil { print("Recognition error: \(error!.localizedDescription)") self.silenceTimer?.invalidate() } } } }
2. More Reliable: Raw Audio Analysis with AVAudioEngine
For better accuracy (especially in noisy environments), we can use AVAudioEngine to directly monitor input volume. When the volume drops below a threshold for our silence duration, we end the recognition task.
This is closer to how Siri handles detection:
import Speech import AVFoundation class AdvancedVoiceCommandHandler: NSObject, SFSpeechRecognizerDelegate { private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))! private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest? private var recognitionTask: SFSpeechRecognitionTask? private let audioEngine = AVAudioEngine() private var silenceStartTime: Date? private let silenceThreshold: TimeInterval = 1.5 private let volumeThreshold: Float = -40 // Adjust based on your environment func startListening() { guard !audioEngine.isRunning else { return } do { // Configure audio session let audioSession = AVAudioSession.sharedInstance() try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers) try audioSession.setActive(true, options: .notifyOthersOnDeactivation) // Set up recognition request recognitionRequest = SFSpeechAudioBufferRecognitionRequest() guard let request = recognitionRequest else { fatalError("Could not create recognition request") } request.shouldReportPartialResults = true request.taskHint = .command // Tap into audio input to monitor volume let inputNode = audioEngine.inputNode let recordingFormat = inputNode.outputFormat(forBus: 0) inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { [weak self] buffer, _ in guard let self = self else { return } self.recognitionRequest?.append(buffer) // Calculate average volume of the audio buffer let volume = self.averagePower(for: buffer) if volume < self.volumeThreshold { // Start tracking silence duration if self.silenceStartTime == nil { self.silenceStartTime = Date() } else { let silenceDuration = Date().timeIntervalSince(self.silenceStartTime!) if silenceDuration >= self.silenceThreshold { self.stopListening() self.silenceStartTime = nil } } } else { // Reset silence tracker if user starts speaking again self.silenceStartTime = nil } } // Start audio engine and recognition task audioEngine.prepare() try audioEngine.start() recognitionTask = speechRecognizer.recognitionTask(with: request) { [weak self] result, error in guard let self = self else { return } if let result = result { let transcript = result.bestTranscription.formattedString.lowercased() if result.isFinal { if transcript == "open setting" { DispatchQueue.main.async { // Navigate to Settings page } } self.stopListening() } } if error != nil || (result?.isFinal ?? false) { self.audioEngine.stop() inputNode.removeTap(onBus: 0) self.recognitionRequest = nil self.recognitionTask = nil } } } catch { print("Error starting audio engine: \(error.localizedDescription)") } } func stopListening() { audioEngine.stop() recognitionRequest?.endAudio() recognitionTask?.finish() recognitionRequest = nil recognitionTask = nil } // Helper to calculate average audio power (volume) private func averagePower(for buffer: AVAudioPCMBuffer) -> Float { guard let channelData = buffer.floatChannelData else { return 0 } let channelDataArray = Array(UnsafeBufferPointer(start: channelData[0], count: Int(buffer.frameLength))) let sumOfSquares = channelDataArray.map { $0 * $0 }.reduce(0, +) let averagePower = sumOfSquares / Float(buffer.frameLength) return 10 * log10(averagePower) } }
Key Tips for Tuning
- Adjust
silenceThreshold(1.5s is a good starting point) based on how quickly your users speak commands. - Tweak
volumeThresholdfor your target environment—test in quiet and noisy spaces to find a balance. - Use
request.taskHint = .commandto help the recognizer prioritize short, actionable phrases over long speech.
内容的提问来源于stack exchange,提问作者Ramkrishna Sharma

