リアルタイム音声文字起こしスキル
ブラウザ アプリ(主に Code Apps)で、話している内容をその場で文字にしながら録音する機能を作る。
マイク ─▶ AudioContext ─▶ AudioWorklet(同一オリジン)─▶ 16 kHz PCM ─▶ Push ストリーム ─▶ Speech SDK ─(WebSocket)─▶ Azure AI Speech
└────▶ MediaRecorder(録音・opus) ▲ 短期トークン
Code Apps ─(カスタム コネクタ)─▶ トークン発行 Function(Managed Identity)────────────────────────────┘
| 判断 | 理由 |
|---|
| ブラウザから Speech へ直結する | 途中結果を 0.2〜0.4 秒間隔で受け取れる。サーバー中継は遅延と運用が増える |
| トークンはコネクタ経由の Function から受け取る | キーをブラウザに置かない(キー認証が無効なテナントでも動く)。コネクタ呼び出しは CSP の追加が不要 |
| 音声は自前で PCM にして Push ストリームで渡す | SDK にマイクを直接渡すと、Code Apps では音声が SDK に届かない |
リファレンス: 認証とロール / 遅延と精度 /
連続録音の区切り / 名乗りからの話者の特定 / MAI-Transcribe との組み合わせ / 異常系 / パラメータ
Step 0: 事前確認(会話の最初に 1 回だけ)
standard の共通契約に加え、1 回の AskUserQuestion で確認する。
| # | 質問 | 合格条件 |
|---|
| 1 | Speech リソースのサブスクリプション・リージョン・既存リソースの有無 | リージョンが決まり、カスタム サブドメインを付けられる |
| 2 | 環境の CSP に connect-src wss://<region>.stt.speech.microsoft.com を追加してよいか | CSP は環境全体に効く。変更の承認者が決まっている |
| 3 | ロール付与・コネクタ作成・DLP 分類の担当者 | Azure RBAC、Power Platform のコネクタ、DLP の担当が分かれて記録されている |
| 4 | 音声と文字起こしのデータ分類・保存期間・保存先 | 録音を扱ってよい根拠と保存先が決まっている |
| 5 | 利用端末と会場(マイク、ブラウザ、ほかに動かすアプリ) | 端末の負荷とマイクの経路(ミキサー等)が分かっている |
Step 1: Speech リソースと権限を用意する
- Speech リソース(
kind=SpeechServices)をカスタム サブドメイン付きで作る。キー認証が無効でもよい(Entra ID で認証する)
- 開発者とトークン発行 Function の Managed Identity に Foundry User を付ける。Managed Identity はリソース グループの範囲で付ける
(実測で数秒〜2 分で反映。リソース範囲は反映に約 12 分かかった。通らなかったロールは 認証とロール)
Step 2: 端末で往復検証する
アプリを作る前に、リソース・権限・リージョンが正しいことを端末だけで確かめる。
Speech SDK を入れたプロジェクトのフォルダーで実行する。
$env:SPEECH_TOKEN = (powershell -File .github/skills/realtime-speech/scripts/get_speech_token.ps1 -Raw)
node .github/skills/realtime-speech/scripts/verify_speech_roundtrip.mjs --out samples/question.wav --phrase <専門用語>
node .github/skills/realtime-speech/scripts/verify_push_stream.mjs --wav samples/question.wav
1 本目は音声合成で質問音声を作って認識する(作った WAV は後の疑似マイクに使う)。
2 本目はブラウザと同じ変換経路(48 kHz → 16 kHz → Push ストリーム)で認識する。両方で確定文が出れば次へ進む。
Step 3: トークン発行 Function とコネクタを用意する
構成は azure-infra のトークン ブローカー パターン に従い、実装はテンプレートから生成する。
python .github/skills/update-skills/scripts/scaffold_from_template.py `
--template .github/skills/realtime-speech/templates/speech-token-broker --target <出力先> --dry-run
生成後は scaffold.json の手順(ビルド → deploy_mcp_function.py)で Function をデプロイし、
コネクタと接続は custom-connector スキル の Step 1〜7 で作る
(deploy_connector.py --connector-dir <出力先>/connector → create_connection.py plan / apply / invoke --path /speech/token)。
コネクタのテンプレートは on-behalf-of が有効なので、接続はブラウザ操作なしで、スクリプトを実行したサインイン ユーザーの権限で作られる。
Function はコネクタのトークン(scp=User.Read、appid=API アプリ)を信頼クライアントとして受け付ける(authorizeClaims())。
Step 4: Code Apps の CSP を設定する
python .github/skills/code-apps/scripts/configure_code_app_csp.py --directive Connect-Src --source wss://<region>.stt.speech.microsoft.com --apply
python .github/skills/code-apps/scripts/configure_code_app_csp.py --directive Connect-Src --source wss://<region>.stt.speech.microsoft.com --assert
worker-src や script-src に data: / blob: は追加しない(Step 5 の部品はどちらも使わない)。
Step 5: アドオンを生成してホストへ統合する
python .github/skills/update-skills/scripts/scaffold_from_template.py `
--template .github/skills/realtime-speech/templates/code-apps-addon --target <作業フォルダー> --dry-run
public/ と src/ をホストへ統合し、ホストで npm install microsoft-cognitiveservices-speech-sdk を実行する。
使い方は アドオンの README。守ること:
- 開始はボタンのクリックから呼ぶ(
AudioContext の開始にユーザー操作が要る)
- 前提(トークン発行コネクタ)が欠けているときは開始ボタンを無効にし、理由を表示する
- 認識器は必ず
createRecognizer() で作る(WebWorkerLoadType=off を設定する。外すと文字起こしが無言で止まる)
- 録音を止めずに続け、発言者ごとに区切って保存する場合は、認識器に送る PCM を
pcm-segmenter.ts に溜め、
区切りのフレーズの offset で切り出す(連続録音の区切り)
- 話者が番号と名前を名乗る場合(株主番号・会員番号など)は、番号を固定の変換だけで決めない。名簿の候補をブラウザで絞り、
生成 AI(構造化出力)に候補から選ばせ、確からしさ 0.8 以上だけ自動適用する(話者の特定)
- 精度を上げたい場合は、確定文ごとにその区間の録音を MAI-Transcribe で認識し直す(日本のリージョンは未提供のため東南アジア等。
置き換え/比較を選べるようにする。MAI-Transcribe)
Step 6: ホスト再現テストで確認する
code-apps のホスト再現テスト で、Step 2 の WAV を疑似マイクにして動かす。
複数の話者・長い台本で試すときは、台本を Windows の読み上げで WAV にする(クラウド不要)。
powershell -ExecutionPolicy Bypass -File .github/skills/realtime-speech/scripts/synthesize_script_wav.ps1 -Script <台本.json> -Out .mcp/tts/script.wav
- 正常系: 確定文と記録の保存まで進み、CSP 違反が 0 件
- 停止耐性: 送信開始 5 秒以降にメインスレッドを 0.8 秒止めても、停止中・停止後の文が正しく確定する
- 連続録音の場合: 区切りの順番と、切り出した録音の長さが区切りの
offset の差と一致する
Step 7: 実機で確認する
Power Apps で開き、DevTools の Console をログの接頭辞(例: [APP)で絞り込んで、次の順に出ることを確認する。
マイク取得 → AudioContext {"state":"running"} → 音声の取り込み方式 {"mode":"worklet"} → Speech トークンを取得 →
WebSocket 接続確立 → 発話開始を検知 → 最初の途中結果 → 確定 → 記録を保存しました
CSP 違反 が 1 件でも出たら 異常系 を見る。
検証チェックリスト