You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:

Core Problem & Solutions

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 volumeThreshold for your target environment—test in quiet and noisy spaces to find a balance.
  • Use request.taskHint = .command to help the recognizer prioritize short, actionable phrases over long speech.

内容的提问来源于stack exchange,提问作者Ramkrishna Sharma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:31:09