mcpField
S
speech-recognition

speech-recognition

github.com/dpearson2699开发1.2k3.7k 次安装13 周前更新
代码仓库

关于 speech-recognition

面向 Apple 应用开发者的 Speech 框架语音转文本指南,涵盖实时录音和文件转写、较新的 SpeechAnalyzer 流程,以及 SFSpeechRecognizer 回退方案。

主要功能

  1. 01麦克风实时转写
  2. 02音频文件转写
  3. 03iOS 26+ 的 SpeechAnalyzer 流程
  4. 04SFSpeechRecognizer 回退指引
  5. 05处理部分、最终及备选结果
  6. 06检查设备端识别支持情况

适用场景

  1. 01录音时实时显示转写内容
  2. 02转写已保存的音频录音
  3. 03在 iOS 26+ 构建长音频转写
  4. 04选择设备端或网络识别

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill speech-recognition

安装后在 Claude Code 中输入/speech-recognition确认已加载。

附带文件evals/evals.jsonreferences/speechanalyzer-patterns.md

详细介绍

语音识别

使用 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。
    • 如果当前设备或语言区域不支持 SpeechTranscriber,且可接受兼容听写的支持,则使用 DictationTranscriber。
    • 仅当语音活动检测的准确度/功耗权衡值得时,才将 SpeechDetector 与转录器配合使用。
  2. 创建会话前检查支持情况:
    • SpeechTranscriber.isAvailable
    • SpeechTranscriber.supportedLocale(equivalentTo:)
    • 显示语言选项时使用 SpeechTranscriber.installedLocales / supportedLocales。
  3. 选择有文档说明的预设:
    • 基本准确转录使用 .transcription。
    • 实时 UI 更新使用 .progressiveTranscription。
    • 播放高亮需要 audioTimeRange 时使用 .timeIndexedProgressiveTranscription。
  4. 使用 AssetInventory.assetInstallationRequest 安装所需资源。
  5. 在提供 AnalyzerInput 前,将实时音频缓冲区转换为 SpeechAnalyzer.bestAvailableAudioFormat(compatibleWith:)。
  6. 在单独的任务中,从模块结果的 AsyncSequence 中读取结果。
  7. 使用 finalizeAndFinish(through:)、 finalizeAndFinishThroughEndOfInput() 或 cancelAndFinishNow() 显式结束。

请勿使用 offlineTranscription 预设;Apple 并未为其提供文档说明。 结束 AsyncStream 输入序列并不会结束分析器会话。

SFSpeechRecognizer 设置

使用语言区域创建识别器
import Speech

// Default locale (user's current language)
let recognizer = SFSpeechRecognizer()

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

// Check if recognition is available for this locale
guard let recognizer, recognizer.isAvailable else {
    print("Speech recognition not available")
    return
}
监控可用性变化
final class SpeechManager: NSObject, SFSpeechRecognizerDelegate {
    private let recognizer = SFSpeechRecognizer()!

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

    func speechRecognizer(
        _ speechRecognizer: SFSpeechRecognizer,
        availabilityDidChange available: Bool
    ) {
        // Update UI — disable record button when unavailable
    }
}

授权

开始实时转录前,请同时请求语音识别和麦克风权限。在 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 {
        // Cancel any in-progress task
        recognitionTask?.cancel()
        recognitionTask = nil

        // Configure audio session
        let audioSession = AVAudioSession.sharedInstance()
        try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers)
        try audioSession.setActive(true, options: .notifyOthersOnDeactivation)

        // Create request
        let request = SFSpeechAudioBufferRecognitionRequest()
        request.shouldReportPartialResults = true
        self.recognitionRequest = request

        // Start recognition task
        recognitionTask = recognizer.recognitionTask(with: request) { result, error in
            if let result {
                let text = result.bestTranscription.formattedString
                print("Transcription: \(text)")

                if result.isFinal {
                    self.stopTranscribing()
                }
            }
            if let error {
                print("Recognition error: \(error)")
                self.stopTranscribing()
            }
        }

        // Install audio 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
                )
            }
        }
    }
}

设备端识别与服务端识别

SFSpeechRecognizer 可在 iOS 13+ 上为受支持的地区使用设备端识别。如果 supportsOnDeviceRecognition 为 false,则识别器需要网络连接。requiresOnDeviceRecognition 仅在识别器支持设备端识别时才会生效。

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

// Check if on-device is supported for this locale
if recognizer.supportsOnDeviceRecognition {
    let request = SFSpeechAudioBufferRecognitionRequest()
    request.requiresOnDeviceRecognition = true  // Force on-device
}

对于长时间录音,SFSpeechRecognizer 请求可能仍不太合适。Apple 文档指出,语音识别任务时长大约限制为一分钟,并且还有其他服务限制。对于 iOS 26+ 上的长时间录音,优先使用 SpeechAnalyzer;否则,应在达到限制前分段或重启识别,并在各任务间保留转录状态。

处理结果

部分结果与最终结果

启用 shouldReportPartialResults 后,在 SFSpeechRecognitionResult.isFinal 确认结果前,替换显示的部分转录文本。这与 SpeechTranscriber.Result.isFinal 不同:后者的易变属性范围必须由该范围的最终结果替换。实时示例和 references/speechanalyzer-patterns.md 包含规范的处理循环。

访问候选转录和置信度
recognizer.recognitionTask(with: request) { result, error in
    guard let result else { return }

    // Best transcription
    let best = result.bestTranscription

    // All alternatives (sorted by confidence, descending)
    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,或在保留已确认转录内容的同时滚动处理有时长限制的片段。
将结束 analyzer 输入视为会话完成明确处理完最后一个采样后再完成,或取消并完成。
追加易变的 SpeechAnalyzer 结果在最终结果确认前,替换易变范围。
第一个识别任务结束前就启动第二个任务取消并完成当前活动任务,清理完成后再替换。

加载 references/speechanalyzer-patterns.md,查看完整的 analyzer 完成处理和易变结果代码。

检查清单

  • Info.plist 中包含 NSSpeechRecognitionUsageDescription
  • Info.plist 中包含 NSMicrophoneUsageDescription(如使用实时音频)
  • 开始识别前请求授权
  • 设置 SFSpeechRecognizerDelegate 以处理 availabilityDidChange
  • 识别结束时停止音频引擎并移除 tap
  • 录音完成后调用 recognitionRequest.endAudio()
  • 开始新任务前取消之前的 recognitionTask
  • 要求使用设备端模式前检查 supportsOnDeviceRecognition
  • 将部分结果与最终(isFinal)结果分开处理
  • 考虑 SFSpeechRecognizer 的一分钟时长限制和服务限制
  • 对于 iOS 26+:使用 SpeechAnalyzer 前安装 AssetInventory 资源
  • 对于 iOS 26+:检查 SpeechTranscriber.isAvailable 和地区支持情况
  • 对于 iOS 26+:将实时缓冲区转换为 analyzer 兼容的格式
  • 对于 iOS 26+:明确完成或取消 analyzer 会话
  • 对于 iOS 26+:用最终结果替换易变结果,而非重复添加

参考资料

评论

支持 Markdown