iOS 26 SpeechAnalyzer をライブマイクに繋ぐ最小実装 — ドキュメントが書かない落とし穴

-- Views

June 23, 26

スライド概要

iOS 26 は SFSpeechRecognizer を SpeechAnalyzer + 合成可能モジュールに置き換えました。本資料は SpeechTranscriber をライブの AVAudioEngine マイクに繋ぐ最小実装(SwiftUI・オンデバイス)と、ドキュメントが書かない落とし穴をまとめたものです:オーディオバッファの形式変換が必須/言語モデルは初回利用時にダウンロード(システム共有アセット)/volatile と final の扱い分け/カスタム語彙は無い/SpeechAnalyzer は watchOS 非対応(system dictation にフォールバック)。warm start の初 volatile 結果までの実測は ~0.3–0.5s(iPhone 16e / iOS 26.5 / time-to-first-volatile-result、first-party 計測であり対照ベンチではありません)。

サンプルコード(MIT): https://github.com/simplememofast/ios26-speechanalyzer-live-mic
詳しい解説(原典): https://simplememofast.com/en/blog/ios26-speechanalyzer-live-mic
この実装が動いている例: https://simplememofast.com/voice-input/

— 株式会社ユリカ

profile-image

書く、送る、 メールとObsidianへ。 音声でもサッとメモ。ワンタップでメールへ届き、Obsidianにも自動保存。 最速の音声自動入力 ・ Obsidian連携 ・ Apple Watch対応 ・ Captio式のクイックメモ App Store からダウンロード https://simplememofast.com/

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

SOLO iOS GROWTH DIA RY · VOL. 7 iOS 26 の SpeechAnalyzer を ライブマイクに繋ぐ ドキュメントが書かない落とし穴 最小SwiftUIサンプル · オンデバイス · 出荷版iOS 26実機でビルド&実行(Xcode 26, Swift 6) 株式会社ユリカ / Simple Memo team

2.

TL ;D R 1つのオブジェクトから、オーケストレータ + モジュールへ • iOS 26 は SFSpeechRecognizer を SpeechAnalyzer + 合成可能モジュールに置き換え。 • 本資料: SpeechTranscriber を ライブの AVAudioEngine マイクに繋ぐ(SwiftUI・オンデバイス)。 • ドキュメントが飛ばす5つの罠: バッファ変換 / 初回モデルDL / volatile と final / カスタム語彙なし / watchOS非対応。 • 完全なサンプルは GitHub(MIT)。出荷版 iOS 26 実機でビルド&実行。 Solo iOS Growth Diary — Vol. 7 株式会社ユリカ · 1

3.

対象 想定読者 • SFSpeechRecognizer から移行する開発者。 • WWDCサンプルを試して「ビルドは通るが文字が出ない」状態になった人。 「ビルドは通るのに無音」で午後を溶かした人へ — その答えはバッファ変換のスライド。 Solo iOS Growth Diary — Vol. 7 株式会社ユリカ · 2

4.

なぜ なぜ SpeechAnalyzer が SFSpeechRecognizer を置き換えるのか • 旧: 1つのオブジェクトが セッション + 言語 + 認識 を抱えていた。 • 新: モジュールを attach する オーケストレータ。必要なものだけ合成する。 • 長め・会話的な音声に最適化。 • 対応ロケールでは完全オンデバイス。 • 設定で「音声入力 / Siri を有効化」する手順が不要。 Solo iOS Growth Diary — Vol. 7 株式会社ユリカ · 3

5.

メンタル モデル Analyzer + モジュール mic --> AVAudioEngine.installTap --> AVAudioConverter --> AnalyzerInput | SpeechAnalyzer([ SpeechTranscriber ]) | for try await result in transcriber.results result.text (AttributedString) / result.isFinal SpeechTranscriber=文字起こし。 SpeechDetector=音声区間検出。 DictationTranscriber=短文ディクテーション。 Solo iOS Growth Diary — Vol. 7 株式会社ユリカ · 4

6.

最小実装 · 1 / 3 権限 + transcriber と analyzer を組む // Info.plist: NSMicrophoneUsageDescription / NSSpeechRecognitionUsageDescription guard let locale = await SpeechTranscriber .supportedLocale(equivalentTo: .current) else { throw Failure.localeNotSupported } let transcriber = SpeechTranscriber( locale: locale, transcriptionOptions: [], reportingOptions: [.volatileResults], // 発話中の暫定結果 attributeOptions: []) // + .audioTimeRange で語単位タイミング let analyzer = SpeechAnalyzer(modules: [transcriber]) let analyzerFormat = await SpeechAnalyzer .bestAvailableAudioFormat(compatibleWith: [transcriber]) Solo iOS Growth Diary — Vol. 7 株式会社ユリカ · 5

7.
[beta]
最小実装 · 2 / 3

結果ループ → analyzer を開始
resultsTask = Task {
for try await result in transcriber.results {
let piece = String(result.text.characters)
// AttributedString
if result.isFinal { finalizedText += piece; volatileText = "" }
else
{ volatileText = piece }
}
}
let (sequence, builder) = AsyncStream<AnalyzerInput>.makeStream()
self.inputBuilder = builder
try await analyzer.start(inputSequence: sequence)

Solo iOS Growth Diary — Vol. 7

株式会社ユリカ · 6

8.

最小実装 · 3 / 3 マイクの tap let converter = AudioBufferConverter() // ローカルだけをキャプチャ let input = audioEngine.inputNode let micFormat = input.outputFormat(forBus: 0) input.installTap(onBus: 0, bufferSize: 4096, format: micFormat) { buffer, _ in // SpeechAnalyzer が要求する形式に変換してから yield guard let c = try? converter.convert(buffer, to: analyzerFormat) else { return } builder.yield(AnalyzerInput(buffer: c)) } audioEngine.prepare(); try audioEngine.start() 完全な SpeechSession / AudioBufferConverter / SwiftUIビューはリポジトリに。 Solo iOS Growth Diary — Vol. 7 株式会社ユリカ · 7

9.

落とし穴 01 オーディオバッファの変換は必須 • 入力ノードの形式(多くは48kHz・ハード依存)は SpeechAnalyzer.bestAvailableAudioFormat(compatibleWith:) と一致しないことが多い。 • 不一致 → ビルドは成功、文字起こしはゼロ。 • 「ビルドは通るが何も起きない」の最頻出原因。 • 対策: 全バッファをまず AVAudioConverter に通す。 Solo iOS Growth Diary — Vol. 7 株式会社ユリカ · 8

10.

落とし穴 02 モデルは初回利用時にDL — オフラインを処理 let installed = await Set( SpeechTranscriber.installedLocales.map { $0.identifier(.bcp47) }) if !installed.contains(locale.identifier(.bcp47)) { if let request = try await AssetInventory .assetInstallationRequest(supporting: [transcriber]) { try await request.downloadAndInstall() // request.progress でUI表示 } } • 言語モデル=システム共有アセット(アプリ容量に算入されない)。 • 初回かつ無ネットワークではDL不可 → .assetUnavailable を明示処理し、無音で失敗させない。 Solo iOS Growth Diary — Vol. 7 株式会社ユリカ · 9

11.

UXの要 volatile と finalized の結果 • reportingOptions: [.volatileResults] → 発話中の高速な暫定結果。 • result.isFinal → 確定テキスト。 • 定番UI: volatile は淡色表示、final が来たら置換。永続化は final のみ。 • result.text は AttributedString → attributeOptions: [.audioTimeRange] で語単位タイミング(ハイライト / シーク)。 Solo iOS Growth Diary — Vol. 7 株式会社ユリカ · 10

12.

出荷版 iOS 26 で計測 レイテンシ — 条件とセットで提示する ベータ期の報告 出荷版 iOS 26.5 14s+ ~0.3–0.5s 初結果 · iPhone 16 Pro · iOS 26.0 beta · Xcode beta 5( allocate / preheat 後も) 初 volatile 結果 · iPhone 16e(非Pro A18)· warm start(モデ ル導入済・locale allocated) first-party 計測(time-to-first-volatile-result)であり対照ベンチではない。初回起動はモデルDLを1度だけ含む — 別枠でプログレス表示。 Neural Engine は A18 ファミリ共通の16コア。 Solo iOS Growth Diary — Vol. 7 株式会社ユリカ · 11

13.

落とし穴 03 カスタム語彙は無い • SFSpeechRecognizer には既知語へ寄せる contextualStrings があった。 • SpeechAnalyzer は iOS 26.0 時点で相当機能なし。 • 固有名詞・専門用語が多い領域は、このギャップを今から見込む。 Solo iOS Growth Diary — Vol. 7 株式会社ユリカ · 12

14.

落とし穴 04 watchOS — SpeechAnalyzer は無い、でも音声入力はある • SpeechAnalyzer: iOS / iPadOS / macOS / visionOS / tvOS 26 — watchOS には無い。 • フォールバック: watchOS の system dictation → 確定テキストを返す(volatile / タイムレンジ / 独自tap は失うが、 音声入力は機能)。 // watchOS — system がディクテーションを処理しテキストを返す: TextFieldLink(prompt: Text("Speak or type")) { Image(systemName: "mic.fill") } onSubmit: { text in send(text) } 実際に出荷している分割: iPhone=SpeechAnalyzer、Watch=TextFieldLink。 Solo iOS Growth Diary — Vol. 7 株式会社ユリカ · 13

15.

落とし穴 05 Swift 6 並行性 + 可用性ゲート • マイクtapのクロージャは実時間オーディオスレッドで動く。 • ローカルだけをキャプチャ(AsyncStream.Continuation・target AVAudioFormat・直前生成のconverter)。 tap内で @MainActor を触らない。 • complete strict concurrency でビルド可。@unchecked Sendable は不要。 • iOS 26 未満も対応するなら @available(iOS 26.0, *) + if #available(iOS 26.0, *)。 Solo iOS Growth Diary — Vol. 7 株式会社ユリカ · 14

16.

移行 SFSpeechRecognizer → SpeechAnalyzer (iOS 26) SFSpeechRecognizer(旧) SpeechAnalyzer(iOS 26) 1オブジェクト: セッション + 認識 SpeechAnalyzer + 合成可能モジュール append(_:) でバッファ追加 AnalyzerInput(buffer:) を AsyncStream に partialResults フラグ reportingOptions: [.volatileResults] delegate / result handler for try await result in transcriber.results bestTranscription.formattedString String(result.text.characters) contextualStrings(カスタム語彙) 相当機能なし 設定でディクテーション有効化が必要 不要 watchOS で動作 watchOS 非対応(system dictation) Solo iOS Growth Diary — Vol. 7 株式会社ユリカ · 15

17.

リポジト リと採用箇所 サンプルを入手、解説を読む サンプル(MIT) github.com/simplememofast/ios26-speechanalyzer-live-mic SpeechSession + AudioBufferConverter + SwiftUIビュー。出荷版iOS 26でビルド(Xcode 26, Swift 6)。 詳しい解説(原典) simplememofast.com/en/blog/ios26-speechanalyzer-live-mic 採用箇所 このパイプライン(メール送信部を除く)は Simple Memo のオンデバイス音声入力に採用: simplememofast.com/voice-input/ © 2026 株式会社ユリカ · サンプルは MIT · 修正は PR 歓迎 Solo iOS Growth Diary — Vol. 7 株式会社ユリカ · 16