mcpField
S
speech-recognition

speech-recognition

github.com/dpearson2699開発1.2k3.7k 回インストール13 週間前に更新
リポジトリ

speech-recognition について

Speech フレームワークで音声認識機能を実装する Apple アプリ開発者向けのガイドです。ライブ音声やファイルの文字起こし、SpeechAnalyzer の新しい処理フローと SFSpeechRecognizer の代替手段を扱います。

主な機能

  1. 01マイク音声のリアルタイム文字起こし
  2. 02音声ファイルの文字起こし
  3. 03iOS 26+ の SpeechAnalyzer フロー
  4. 04SFSpeechRecognizer の代替指針
  5. 05途中・確定・候補結果の処理
  6. 06オンデバイス認識の対応確認

活用シーン

  1. 01録音中の文字起こし表示
  2. 02保存済み音声の文字起こし
  3. 03iOS 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. ライブ音声バッファーを SpeechAnalyzer.bestAvailableAudioFormat(compatibleWith:) に変換してから、AnalyzerInput を渡します。
  6. 別のタスクで、モジュールの結果を AsyncSequence から取得します。
  7. finalizeAndFinish(through:)、 finalizeAndFinishThroughEndOfInput()、または cancelAndFinishNow() を明示的に呼び出して終了します。

Apple がドキュメント化していないため、offlineTranscription プリセットは使用しないでください。 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 は、音声認識におけるタスクの制限時間をおよそ 1 分とし、ほかにもサービスの制限があることを説明しています。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"]

よくある間違い

間違い修正方法
ライブ音声のリクエストで音声認識の許可のみを求める音声認識とマイクの両方の許可を求めます。
認識の利用可能性を一度だけ確認するデリゲートの利用可能性の変更を監視し、サービスが利用できなくなった場合に対応します。
最終処理、エラー処理、ロールオーバーで異なるクリーンアップを行うエンジンの停止、タップの削除、endAudio、タスクのキャンセルを行う単一の stopTranscribing() に処理を集約します。
すべてのロケールでデバイス上モードを強制するsupportsOnDeviceRecognition を確認し、フォールバックを用意します。
長時間録音に単一の SFSpeechRecognizer タスクを使用するiOS 26+ では SpeechAnalyzer を優先するか、確定済みの文字起こしを保持しながら、長さを制限したセグメントに分割します。
アナライザーへの入力を終了すればセッションも完了すると見なす最後のサンプルまで明示的に確定するか、キャンセルして終了します。
揮発性の SpeechAnalyzer 結果を追記する最終結果で確定されるまで、揮発性の範囲を置き換えます。
1 つ目の認識タスクが終了する前に 2 つ目を開始するアクティブなタスクをキャンセルまたは終了し、置き換える前にクリーンアップを完了します。

アナライザーの終了処理と揮発性結果のコード全体については、references/speechanalyzer-patterns.mdを読み込んでください。

レビューチェックリスト

  • NSSpeechRecognitionUsageDescription が Info.plist に設定されている
  • ライブ音声を使用する場合、NSMicrophoneUsageDescription が Info.plist に設定されている
  • 認識を開始する前に許可を求めている
  • SFSpeechRecognizerDelegate を設定し、availabilityDidChange を処理している
  • 認識の終了時にオーディオエンジンを停止し、タップを削除している
  • 録音の完了時に recognitionRequest.endAudio() を呼び出している
  • 新しい認識タスクを開始する前に、前の recognitionTask をキャンセルしている
  • デバイス上モードを必須にする前に supportsOnDeviceRecognition を確認している
  • 部分結果を確定結果(isFinal)とは別に処理している
  • SFSpeechRecognizer の 1 分間の制限とサービスの制限を考慮している
  • iOS 26+:SpeechAnalyzer を使用する前に AssetInventory のアセットをインストールしている
  • iOS 26+:SpeechTranscriber.isAvailable とロケールの対応状況を確認している
  • iOS 26+:ライブバッファーをアナライザーに対応した形式に変換している
  • iOS 26+:アナライザーセッションを明示的に終了またはキャンセルしている
  • iOS 26+:揮発性の結果を重複させず、確定結果で置き換えている

参考資料

コメント