使用 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+)
- SFSpeechRecognizer 設定
- 授權
- 即時麥克風轉錄
- 預先錄製的音訊檔案辨識
- 裝置端 vs 伺服器端辨識
- 處理結果
- 常見錯誤
- 審查清單
- 參考資料
SpeechAnalyzer 策略(iOS 26+)
對於現代 iOS 26+ 的語音分析,尤其是長篇錄音、即時轉錄、時間索引轉錄稿與完全裝置端流程,請使用 SpeechAnalyzer。若部署目標為 iOS 10+、需要伺服器端語言覆蓋率,或使用現有的回呼/委派實作,則保留 SFSpeechRecognizer。
實作 iOS 26+ 轉錄管線、模型資產處理、揮發性結果或檔案/緩衝區範例時,請參閱 SpeechAnalyzer 模式。
SpeechAnalyzer 設定檢查清單
- 選擇模組:
SpeechTranscriber:用於較新的通用裝置端模型。DictationTranscriber:當目前裝置或語言不支援SpeechTranscriber,且可接受聽寫相容支援時使用。SpeechDetector:僅在搭配轉錄器使用,且語音活動偵測值得準確度/功耗取捨時使用。
- 建立 session 前檢查支援:
SpeechTranscriber.isAvailableSpeechTranscriber.supportedLocale(equivalentTo:)- 顯示語言選擇時檢查
SpeechTranscriber.installedLocales/supportedLocales。
- 選擇有文件記載的預設:
.transcription:用於基本準確轉錄。.progressiveTranscription:用於即時 UI 更新。.timeIndexedProgressiveTranscription:當播放反白需要audioTimeRange時使用。
- 使用
AssetInventory.assetInstallationRequest安裝所需資產。 - 在產生
AnalyzerInput之前,將即時音訊緩衝區轉換為SpeechAnalyzer.bestAvailableAudioFormat(compatibleWith:)。 - 在獨立的任務中消費模組結果的
AsyncSequence。 - 明確結束:使用
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 中加入以下鍵:
NSSpeechRecognitionUsageDescriptionNSMicrophoneUsageDescription
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+:揮發性結果由最終結果取代,而非重複
參考資料
- Speech 框架
- SpeechAnalyzer
- SpeechTranscriber
- SpeechTranscriber.Preset
- DictationTranscriber
- SpeechDetector
- SFSpeechRecognizer
- SFSpeechAudioBufferRecognitionRequest
- SFSpeechURLRecognitionRequest
- SFSpeechRecognitionResult
- SFSpeechRecognitionRequest
- AssetInventory
- 請求使用語音辨識的權限
- 在即時音訊中辨識語音
- 使用 SpeechAnalyzer 為你的 App 帶來先進的語音轉文字功能




