Azure Speech to Text で Microsoft Audio Stack(MAS)を有効にしたまま、8kHz・モノラルの WAV を PushStream から流すと SPXERR_RUNTIME_ERROR (0x1b) で落ちてしまう――そんなハマりポイントを丁寧に整理し、原因と対処法、そして設計時に押さえておきたいポイントをまとめます。PushStream と MAS を組み合わせる開発では「サンプルレート設計」が最重要です。
問題のパターンと現象の整理
まず、今回のトラブルをもう少し具体的に整理しておきます。
- 入力音声:モノラル・8kHz・16bit の Linear PCM(いわゆる Linear16)WAV
- 入力方法:
AudioInputStream.CreatePushStream()で PushStream を作成し、自前で PCM フレームを書き込む - 認識モード:連続認識(Continuous Recognition)
- MAS:有効(
AudioProcessingConstants.AUDIO_INPUT_PROCESSING_ENABLE_DEFAULTなど)
この状態で実行すると、認識が開始された直後に Canceled イベントが発生し、
ResultReason.Canceled
CancellationErrorCode: Error
HRESULT: 0x1b (SPXERR_RUNTIME_ERROR)
といった形でランタイムエラー扱いで終了してしまいます。
一方で、次のようなケースでは正常に動作します。
- MAS を無効にして、同じ 8kHz PushStream を送る
- 16kHz/32kHz/48kHz の音源を PushStream で送る
- PushStream を使わず、OS の既定マイク入力(
AudioConfig.FromDefaultMicrophoneInput())を使う - 複数チャンネル(ステレオなど)で、MAS が想定するフォーマットのデモ音源を使う
つまり、「PushStream+8kHz+MAS 有効」という組み合わせのときだけ落ちる、というのがポイントです。
Microsoft Audio Stack(MAS)と Azure Speech to Text の関係
ここで一度、Microsoft Audio Stack(MAS)が何をしているのかを整理します。
MAS は、Azure Speech SDK 内で利用できる音声前処理用のスタックで、主に次のような処理を担います。
- ノイズ抑圧(Noise Suppression)
- 自動ゲイン制御(AGC)
- エコーキャンセル(AEC)
- マイクアレイ用ビームフォーミング
特にマイクアレイ(会議室デバイスなど)を対象に設計されており、内部では「処理しやすいサンプルレート」「想定されたチャンネル構成」など、いくつかの前提条件を置いています。
Azure Speech to Text 自体は、16kHz 以上を強く推奨しているものの、SDK レベルでの最終入力は 8kHz テレフォニー音声も扱えます。しかし、
「Speech to Text が扱えるフォーマット」と、「MAS が内部処理に使えるフォーマット」は別物
である点が落とし穴です。PushStream で MAS を使うときは、この MAS 側の制約をすべてアプリが満たしてあげる必要があります。
原因:MAS が対応しているサンプルレートの制約
今回の現象の本質的な原因は、次の一点に集約されます。
- MAS は 16kHz 以上で、かつ 16kHz の整数倍(16k/32k/48k …)のサンプルレートのみ対応している
そのため、8kHz の PushStream を与えると、MAS 内部のフィルタや処理パイプライン初期化が失敗し、その結果として SDK 全体が SPXERR_RUNTIME_ERROR (0x1b) でキャンセルされます。
代表的なサンプルレートと MAS の対応を表にすると、イメージがつかみやすくなります。
| サンプルレート | 代表的な用途 | MAS での扱い(目安) | コメント |
|---|---|---|---|
| 8000 Hz | 電話(テレフォニー帯域) | 非対応 | 今回のケース。PushStream+MAS ではエラーになる |
| 16000 Hz | 音声認識向け標準 | 対応 | 最も無難で、推奨されるサンプルレート |
| 32000 Hz | 高音質通話など | 対応 | 高音質が欲しい場合の選択肢 |
| 48000 Hz | マイク・オーディオインターフェイスの標準 | 対応 | PC オーディオのデフォルトでよく使われる |
| 44100 Hz | CD 音質 | 非推奨/非対応の可能性 | 16kHz の整数倍ではないため、MAS では避けた方が無難 |
8kHz は「電話音声ではよくあるが、MAS から見ると想定外のフォーマット」という位置づけになります。
マイク入力で動くのに PushStream で失敗する理由
「同じ環境でマイク入力を使うと MAS 有効でも普通に動くのに、PushStream だとダメなのはなぜ?」という疑問も出てきます。
これは、OS/ドライバ側で次のような変換経路が存在するためです。
マイク & ドライバ
↓(OS のオーディオエンジンで 48kHz などに揃えられる)
Azure Speech SDK
↓
MAS(16kHz / 32kHz / 48kHz 前提)
↓
STT エンジン
つまり、マイク経由の場合は「MAS が処理しやすいフォーマットに揃えた上で」MAS に届きます。一方、PushStream は次のような経路になります。
アプリケーション(PCM を自分で読み出す)
↓(そのまま)
PushStream
↓(そのまま)
Azure Speech SDK
↓
MAS(ここで 8kHz を渡されて初期化に失敗)
PushStream では、アプリ側が「最終的に MAS に渡すフォーマット」を 100% 面倒を見る必要があるのです。
最短の解決策:16kHz 以上にアップサンプリングしてから PushStream に流す
実務上もっともシンプルで安全な解決策は、次の 3 つのステップです。
- 入力音声(8kHz WAV)を事前に 16kHz(または 32k/48k)へアップサンプリングする
- PushStream を 16kHz/16bit/モノラル PCM で作成し、その形式のフレームだけを書き込む
- MAS のオプションはデフォルトのまま(
AudioProcessingConstants.AUDIO_INPUT_PROCESSING_ENABLE_DEFAULTなど)で利用する
特に理由がなければ、16kHz/16bit/モノラル に統一しておくのがおすすめです。音声認識の精度と処理効率のバランスがよく、他の多くのサービスとも相性が良いフォーマットだからです。
C#:PushStream の形式を 16kHz に固定する
まずは PushStream を作る段階で、フォーマットを 16kHz に固定します。
var format = AudioStreamFormat.GetWaveFormatPCM(
16000, // sampleRateHz
(byte)16,// bitsPerSample
(byte)1 // channels (Mono)
);
using var pushStream = AudioInputStream.CreatePushStream(format);
この format に合わせて、以降 pushStream.Write に渡すバイト列も「16kHz/16bit/モノラルの PCM」でなければなりません。ここで形式が食い違うと、サンプルレートだけでなく、バイト数やチャンネル数の整合性でも問題が発生します。
8kHz WAV をオンザフライで 16kHz にリサンプルする(NAudio の例)
8kHz の WAV ファイルが入力として渡される場合、実装方針は大きく 2 つです。
- 事前バッチ変換:ファイルをあらかじめ 16kHz に変換して保存しておき、それを読み出す
- オンザフライ変換:ストリーミングしながら 8kHz → 16kHz にリサンプルし、そのまま PushStream に流す
ここでは後者の「オンザフライ変換」の例を NAudio を使って示します。
using var reader = new WaveFileReader("test.wav");
// 16kHz / 16bit / Mono へ変換
using var resampler = new MediaFoundationResampler(
reader,
new WaveFormat(16000, 16, 1))
{
ResamplerQuality = 60 // 品質と負荷のバランスを見て調整
};
var buffer = new byte[3200]; // 16kHz/16bit/Mono の約 100ms 分
int bytesRead;
while ((bytesRead = resampler.Read(buffer, 0, buffer.Length)) > 0)
{
// bytesRead 分だけ PushStream に書き込む
pushStream.Write(buffer, (uint)bytesRead);
}
// 認識終了を SDK に伝える
pushStream.Close();
16kHz・16bit・モノラル PCM の場合、1 秒あたりのバイト数は次のように計算できます。
- サンプルレート:16,000 サンプル/秒
- 1 サンプルあたり:16bit(2 バイト)
- 1 秒あたり:16,000 × 2 = 32,000 バイト
したがって、3,200 バイト ≒ 約 0.1 秒分 になり、100ms 単位で送ると、レイテンシと処理負荷のバランスが良い塩梅になります。
| サンプルレート | 1 秒あたりバイト数 (16bit/Mono) | 100ms あたりバイト数 |
|---|---|---|
| 8000 Hz | 16,000 バイト | 1,600 バイト |
| 16000 Hz | 32,000 バイト | 3,200 バイト |
| 48000 Hz | 96,000 バイト | 9,600 バイト |
この表をもとに、アプリ側で使いやすいフレームサイズ(例:100ms, 50ms など)を決めておくと、設計がスムーズになります。
MAS オプションの設定例(C#)
PushStream のフォーマットを 16kHz に揃えたら、MAS のオプションは基本的にそのままで構いません。C# のイメージコードは次のようになります。
var speechConfig = SpeechConfig.FromSubscription(subscriptionKey, region);
// MAS を有効化するオプション(例)
var audioProcessingOptions = AudioProcessingOptions.Create(
AudioProcessingConstants.AUDIO_INPUT_PROCESSING_ENABLE_DEFAULT,
PresetMicrophoneArrayGeometry.Mono
);
// 上で作成した pushStream を利用
using var audioConfig = AudioConfig.FromStreamInput(pushStream, audioProcessingOptions);
// 認識器を生成
using var recognizer = new SpeechRecognizer(speechConfig, audioConfig);
ポイントは、MAS の有効/無効ではなく、「PushStream のサンプルレートが MAS の前提を満たしているかどうか」です。ここさえクリアしていれば、MAS を積極的に活用して問題ありません。
どうしても 8kHz のまま使いたい場合の回避策
業務システムの中には、「上流から渡される音声は 8kHz 固定で、変えられない」「テレフォニー・ゲートウェイの仕様で 8kHz しか出力されない」といった制約を抱えるケースもあります。その場合の現実的な回避策は、
- MAS を無効化して、8kHz のまま STT に送る
という選択になります。
イメージコードは次の通りです。
var speechConfig = SpeechConfig.FromSubscription(subscriptionKey, region);
// MAS を無効化するオプション(例)
var audioProcessingOptions = AudioProcessingOptions.Create(
AudioProcessingConstants.AUDIO_INPUT_PROCESSING_DISABLE,
PresetMicrophoneArrayGeometry.Mono
);
var format8k = AudioStreamFormat.GetWaveFormatPCM(8000, (byte)16, (byte)1);
using var pushStream = AudioInputStream.CreatePushStream(format8k);
using var audioConfig = AudioConfig.FromStreamInput(pushStream, audioProcessingOptions);
using var recognizer = new SpeechRecognizer(speechConfig, audioConfig);
この場合でも、Azure 側の音声認識エンジンは 8kHz 音声を処理できますが、次のようなデメリットがあります。
- MAS によるノイズ抑圧やビームフォーミングなどの恩恵を受けられない
- 音声品質が低い場合、認識精度が 16kHz 音声より劣る可能性がある
とはいえ、「入力音声が電話回線由来で、そもそも 8kHz しか情報量がない」「サーバ側でノイズ抑圧済みの音声を受け取っている」といったケースでは、MAS 無効でも十分な精度が得られることも多いです。
結論としては、
- 音声品質を重視する、MAS の処理を活かしたい → 16kHz 以上にアップサンプリングし、MAS 有効で使う
- システム都合でどうしても 8kHz のまま → MAS を無効化して 8kHz のまま送る
という二択で検討するのが現実的です。
動作確認・トラブルシューティングのチェックリスト
PushStream+MAS 周りでトラブルシューティングを行う際に、最低限チェックしておきたい項目を一覧化します。
| チェック項目 | 確認内容 | NG の場合に起こりがちな症状 |
|---|---|---|
| PushStream のフォーマット | AudioStreamFormat が 16kHz/16bit/1ch になっているか | MAS 有効時に SPXERR_RUNTIME_ERROR (0x1b) で即終了 |
| PCM データの実体 | 実際に書き込んでいる PCM が、宣言したフォーマット(サンプルレート/ビット深度/チャンネル数)と一致しているか | 音が早送り/スローになる、雑音になる、途中でストップする |
| MAS の有効/無効 | 8kHz の場合は MAS を無効にしているか、または事前に 16kHz に変換しているか | 8kHz+MAS 有効のときだけエラーになる |
| ログ出力の有無 | Speech_LogFilename を設定して、SDK の詳細ログを確認しているか | 原因が「サンプルレート不整合」なのか「ネットワーク系」なのか切り分けできない |
| フレームサイズ | 100ms 程度のフレーム単位で Write しているか | 極端に大きい/小さいフレームでパフォーマンスが悪化、タイムアウトに近い挙動 |
ログで MAS 関連のメッセージを確認する
原因切り分けのためには、Speech SDK のログを有効にしておくと安心です。C# での設定例は次のようになります。
var speechConfig = SpeechConfig.FromSubscription(subscriptionKey, region);
// ログファイルを出力(パスは環境に合わせて変更)
speechConfig.SetProperty(
PropertyId.Speech_LogFilename,
"logs/speechsdk.log"
);
ログを出力したら、エディタで MAS や AudioProcessing、sample rate といったキーワードで検索してみてください。サンプルレート不整合や MAS の初期化失敗に関するヒントが残っていることがあります。
設計時に押さえておきたいサンプルレート戦略
今回のようなハマりを避けるためには、最初から「どのサンプルレートを使うか」をプロジェクト内で統一しておくのが重要です。
おすすめの基本方針
- Azure Speech SDK の入力は、原則 16kHz/16bit/モノラル PCM に統一する
- 外部システムから 8kHz 音声を受け取る場合は、エントリポイントで 16kHz にアップサンプリングする
- 録音経路が 48kHz の場合は、アプリ内部で 16kHz に落としてから STT に渡す(必要に応じて)
こうしておけば、
- MAS を有効にする/しないの切り替えを気にせずに済む
- Speech SDK 以外の音声処理ライブラリとの連携もしやすい
- デバッグ時に「この音声は何 Hz?」で悩むことが少なくなる
というメリットがあります。
PushStream とファイル入力のフォーマットを揃える
もう 1 つの小さなコツとして、
- PushStream で送る音声のフォーマット
- デバッグ用にディスクに保存する WAV ファイルのフォーマット
を同じにしておくと便利です。
たとえば、デバッグ時に PushStream に書き込んでいる PCM をそのまま WAV に保存しておけば、
- Audacity や他のツールで簡単に再生・可視化できる
- 「本当に 16kHz/Mono になっているか?」を目視で確認できる
- テスト用音源として再利用できる
といった効果が得られます。特にテレフォニー音声のように帯域やレベルが不安定なソースでは、実際の波形を確認しながら調整できるのは大きな安心材料になります。
まとめ:PushStream で MAS を使うならサンプルレート設計がカギ
Azure Speech to Text と Microsoft Audio Stack(MAS)を組み合わせて使う際に、モノラル 8kHz の PushStream だけが SPXERR_RUNTIME_ERROR (0x1b) で落ちるのは、
- MAS が 16kHz 以上かつ 16kHz の整数倍のサンプルレートのみを前提としている
- PushStream は OS の変換経路を介さず、アプリが指定したフォーマットがそのまま MAS に渡ってしまう
という 2 点が重なっているためです。
対処法としては、
- もっとも確実:事前に 16kHz 以上にアップサンプリングし、PushStream を 16kHz/16bit/Mono に固定した上で MAS を有効利用する
- どうしても 8kHz のまま使いたい:MAS を無効化して STT へ直接送る(ただし前処理の恩恵は受けられない)
という選択肢があります。
「マイク入力だと動くのに PushStream だと落ちる」という現象に遭遇した場合は、まず PushStream のサンプルレートと MAS の前提が噛み合っているかを疑ってみてください。サンプルレート設計を最初にきちんと決めておくことで、後からのデバッグコストを大幅に減らすことができます。

コメント