speech-recognition

speech-recognition

熱門

使用 Apple 的 Speech 框架將語音轉錄為文字。適用於實作使用 AVAudioEngine 的即時麥克風轉錄、辨識錄製的音訊檔案、處理語音與麥克風授權、選擇裝置端或伺服器端 SFSpeechRecognizer 行為,以及在 iOS 26+ 上採用 SpeechAnalyzer、SpeechTranscriber、DictationTranscriber、AssetInventory 和非同步結果串流。

936星標
47分支
更新於 2026/7/15
SKILL.md
唯讀
名稱
speech-recognition
描述

使用 Apple 的 Speech 框架將語音轉錄為文字。適用於實作使用 AVAudioEngine 的即時麥克風轉錄、辨識錄製的音訊檔案、處理語音與麥克風授權、選擇裝置端或伺服器端 SFSpeechRecognizer 行為,以及在 iOS 26+ 上採用 SpeechAnalyzer、SpeechTranscriber、DictationTranscriber、AssetInventory 和非同步結果串流。

語音辨識

將即時與預先錄製的音訊轉錄為文字,使用 Apple 的 Speech 框架。
涵蓋 SpeechAnalyzer / SpeechTranscriber(iOS 26+)和
SFSpeechRecognizer(iOS 10+)的備援指引。

範圍界定: 此技能用於語音轉文字辨識、語音授權、麥克風擷取管線與結果處理。文字分析、轉錄後的語言識別、情感分析、嵌入與翻譯請交給 natural-language;音訊播放 UI 請交給 avkit;轉錄稿的摘要或生成請交給 apple-on-device-ai

目錄

SpeechAnalyzer 策略(iOS 26+)

對於現代 iOS 26+ 的語音分析,尤其是長篇錄音、即時轉錄、時間索引轉錄稿與完全裝置端流程,請使用 SpeechAnalyzer。若部署目標為 iOS 10+、需要伺服器端語言覆蓋率,或使用現有的回呼/委派實作,則保留 SFSpeechRecognizer

實作 iOS 26+ 轉錄管線、模型資產處理、揮發性結果或檔案/緩衝區範例時,請參閱 SpeechAnalyzer 模式

SpeechAnalyzer 設定檢查清單

  1. 選擇模組:
    • SpeechTranscriber:用於較新的通用裝置端模型。
    • DictationTranscriber:當目前裝置或語言不支援 SpeechTranscriber,且可接受聽寫相容支援時使用。
    • SpeechDetector:僅在搭配轉錄器使用,且語音活動偵測值得準確度/功耗取捨時使用。
  2. 建立 session 前檢查支援:
    • SpeechTranscriber.isAvailable
    • SpeechTranscriber.supportedLocale(equivalentTo:)
    • 顯示語言選擇時檢查 SpeechTranscriber.installedLocales / supportedLocales
  3. 選擇有文件記載的預設:
    • .transcription:用於基本準確轉錄。
    • .progressiveTranscription:用於即時 UI 更新。
    • .timeIndexedProgressiveTranscription:當播放反白需要 audioTimeRange 時使用。
  4. 使用 AssetInventory.assetInstallationRequest 安裝所需資產。
  5. 在產生 AnalyzerInput 之前,將即時音訊緩衝區轉換為 SpeechAnalyzer.bestAvailableAudioFormat(compatibleWith:)
  6. 在獨立的任務中消費模組結果的 AsyncSequence
  7. 明確結束:使用 finalizeAndFinish(through:)finalizeAndFinishThroughEndOfInput()cancelAndFinishNow()

不要使用 offlineTranscription 預設;Apple 並未記載此項。結束 AsyncStream 輸入序列並不會結束分析器 session。

SFSpeechRecognizer 設定

使用語言建立辨識器

import Speech

// 預設語言(使用者目前語言)
let recognizer = SFSpeechRecognizer()

// 特定語言
let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))

// 檢查此語言是否支援辨識
guard let recognizer, recognizer.isAvailable else {
    print("語音辨識不可用")
    return
}

監控可用性變化

final class SpeechManager: NSObject, SFSpeechRecognizerDelegate {
    private let recognizer = SFSpeechRecognizer()!

    override init() {
        super.init()
        recognizer.delegate = self
    }

    func speechRecognizer(
        _ speechRecognizer: SFSpeechRecognizer,
        availabilityDidChange available: Bool
    ) {
        // 更新 UI — 不可用時停用錄製按鈕
    }
}

授權

在開始即時轉錄前,同時請求語音辨識與麥克風權限。在 Info.plist 中加入以下鍵:

  • NSSpeechRecognitionUsageDescription
  • NSMicrophoneUsageDescription
import Speech
import AVFoundation

func requestPermissions() async -> Bool {
    let speechStatus = await withCheckedContinuation { continuation in
        SFSpeechRecognizer.requestAuthorization { status in
            continuation.resume(returning: status)
        }
    }
    guard speechStatus == .authorized else { return false }

    let micStatus: Bool
    if #available(iOS 17, *) {
        micStatus = await AVAudioApplication.requestRecordPermission()
    } else {
        micStatus = await withCheckedContinuation { continuation in
            AVAudioSession.sharedInstance().requestRecordPermission { granted in
                continuation.resume(returning: granted)
            }
        }
    }
    return micStatus
}

即時麥克風轉錄

標準模式:AVAudioEngine 擷取麥克風音訊 → 緩衝區附加到 SFSpeechAudioBufferRecognitionRequest → 結果串流傳入。

import Speech
import AVFoundation

final class LiveTranscriber {
    private let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))!
    private let audioEngine = AVAudioEngine()
    private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
    private var recognitionTask: SFSpeechRecognitionTask?

    func startTranscribing() throws {
        // 取消進行中的任務
        recognitionTask?.cancel()
        recognitionTask = nil

        // 設定音訊 session
        let audioSession = AVAudioSession.sharedInstance()
        try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers)
        try audioSession.setActive(true, options: .notifyOthersOnDeactivation)

        // 建立請求
        let request = SFSpeechAudioBufferRecognitionRequest()
        request.shouldReportPartialResults = true
        self.recognitionRequest = request

        // 開始辨識任務
        recognitionTask = recognizer.recognitionTask(with: request) { result, error in
            if let result {
                let text = result.bestTranscription.formattedString
                print("轉錄:\(text)")

                if result.isFinal {
                    self.stopTranscribing()
                }
            }
            if let error {
                print("辨識錯誤:\(error)")
                self.stopTranscribing()
            }
        }

        // 安裝音訊 tap
        let inputNode = audioEngine.inputNode
        let recordingFormat = inputNode.outputFormat(forBus: 0)
        inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) {
            buffer, _ in
            request.append(buffer)
        }

        audioEngine.prepare()
        try audioEngine.start()
    }

    func stopTranscribing() {
        audioEngine.stop()
        audioEngine.inputNode.removeTap(onBus: 0)
        recognitionRequest?.endAudio()
        recognitionRequest = nil
        recognitionTask?.cancel()
        recognitionTask = nil
    }
}

預先錄製的音訊檔案辨識

使用 SFSpeechURLRecognitionRequest 處理磁碟上的音訊檔案:

func transcribeFile(at url: URL) async throws -> String {
    guard let recognizer = SFSpeechRecognizer(), recognizer.isAvailable else {
        throw SpeechError.unavailable
    }
    let request = SFSpeechURLRecognitionRequest(url: url)
    request.shouldReportPartialResults = false

    return try await withCheckedThrowingContinuation { continuation in
        var didResume = false
        recognizer.recognitionTask(with: request) { result, error in
            guard !didResume else { return }
            if let error {
                didResume = true
                continuation.resume(throwing: error)
            } else if let result, result.isFinal {
                didResume = true
                continuation.resume(
                    returning: result.bestTranscription.formattedString
                )
            }
        }
    }
}

裝置端 vs 伺服器端辨識

SFSpeechRecognizer 可在 iOS 13+ 上使用裝置端辨識,適用於支援的語言。若 supportsOnDeviceRecognition 為 false,則辨識器需要網路連線。requiresOnDeviceRecognition 僅在辨識器支援時才有效。

let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))!

// 檢查此語言是否支援裝置端
if recognizer.supportsOnDeviceRecognition {
    let request = SFSpeechAudioBufferRecognitionRequest()
    request.requiresOnDeviceRecognition = true  // 強制裝置端
}

SFSpeechRecognizer 請求可能不適合長時間擷取。Apple 記載語音辨識約有一分鐘的任務限制及其他服務限制。對於 iOS 26+ 的長時間錄音,建議使用 SpeechAnalyzer;否則在達到限制前分段或重新啟動辨識,並跨任務保留已確認的轉錄稿。

處理結果

部分結果 vs 最終結果

使用 shouldReportPartialResults 時,在 SFSpeechRecognitionResult.isFinal 確認前,替換顯示的部分轉錄稿。這與 SpeechTranscriber.Result.isFinal 不同,後者的揮發性屬性範圍必須由該範圍的最終結果取代。即時範例與 references/speechanalyzer-patterns.md 包含標準迴圈。

存取替代轉錄稿與信心度

recognizer.recognitionTask(with: request) { result, error in
    guard let result else { return }

    // 最佳轉錄
    let best = result.bestTranscription

    // 所有替代(依信心度降序排列)
    for transcription in result.transcriptions {
        for segment in transcription.segments {
            print("\(segment.substring): \(segment.confidence)")
        }
    }
}

加入標點符號(iOS 16+)

let request = SFSpeechAudioBufferRecognitionRequest()
request.addsPunctuation = true

上下文字串

改善領域特定詞彙的辨識:

let request = SFSpeechAudioBufferRecognitionRequest()
request.contextualStrings = ["SwiftUI", "Xcode", "CloudKit"]

常見錯誤

錯誤 修正
即時音訊請求僅需語音授權 同時要求語音與麥克風權限。
僅檢查一次辨識器可用性 觀察委派可用性變化與模型服務中斷。
最終/錯誤/輪替路徑執行不同的清理 使用單一 stopTranscribing() 負責引擎停止、tap 移除、endAudio 與任務取消。
對每個語言強制使用裝置端模式 檢查 supportsOnDeviceRecognition 並提供備援方案。
使用單一 SFSpeechRecognizer 任務進行長時間擷取 在 iOS 26+ 上優先使用 SpeechAnalyzer,或分段處理並保留已確認的轉錄稿。
將結束分析器輸入視為 session 完成 明確透過最後一個樣本結束,或取消並結束。
附加揮發性的 SpeechAnalyzer 結果 在最終結果確認前替換揮發性範圍。
在第一個任務結束前啟動第二個辨識任務 取消/結束當前任務並完成清理後再替換。

載入 references/speechanalyzer-patterns.md 以取得完整的分析器結束與揮發性結果程式碼。

審查清單

  • [ ] NSSpeechRecognitionUsageDescription 已加入 Info.plist
  • [ ] NSMicrophoneUsageDescription 已加入 Info.plist(若使用即時音訊)
  • [ ] 開始辨識前已請求授權
  • [ ] 已設定 SFSpeechRecognizerDelegate 以處理 availabilityDidChange
  • [ ] 辨識結束時已停止音訊引擎並移除 tap
  • [ ] 錄製完成時已呼叫 recognitionRequest.endAudio()
  • [ ] 開始新任務前已取消先前的 recognitionTask
  • [ ] 在要求裝置端模式前已檢查 supportsOnDeviceRecognition
  • [ ] 部分結果與最終結果(isFinal)分開處理
  • [ ] 已考量 SFSpeechRecognizer 的一分鐘/服務限制
  • [ ] 對於 iOS 26+:使用 SpeechAnalyzer 前已安裝 AssetInventory 資產
  • [ ] 對於 iOS 26+:已檢查 SpeechTranscriber.isAvailable 與語言支援
  • [ ] 對於 iOS 26+:即時緩衝區已轉換為分析器相容格式
  • [ ] 對於 iOS 26+:分析器 session 已明確結束或取消
  • [ ] 對於 iOS 26+:揮發性結果由最終結果取代,而非重複

參考資料