Web Speech Recognition API を使ってブラウザオンリーで音声認識する

ちょっと前に、合成音声を発話させる Web Speech API を試したが、今回はその API 群の一つである、音声認識を行う Recognition API を試してみる。

サンプルを実装した

サンプルを実装してみたので、以下でお試しあれ。自分は Chrome v88 で動作検証した。

利用するには、ブラウザの設定で「マイク」へのアクセスを許可してやる必要がある。マイクを許可したら、適当に喋りかけてみて欲しい。うまく音声が認識できると、「認識結果」欄に聞き取れた言葉が出力される。

作り方・注意点

API の詳細は上のコードを見てもらうとして。本質だけ記しておく。

const rec = new webkitSpeechRecognition();
rec.continuous = false;  // `true` にすると連続して音声認識するが、各種イベントが発火しなくなる
rec.interimResults = false;  // `true` にすると認識途中の結果も返す
rec.lang = 'ja-JP';  // 言語指定
rec.maxAlternatives = 1;  // 結果候補の最大数 (デフォルト 1)

// 音声認識開始処理 (既に `start()` している時に実行するとエラーになるのでその対策として)
const startRec = () => {
  try {
    rec.start();
  }
  catch(error) {
    console.error('音声認識は既に開始されています', error);
  }
};
// デフォルトの End イベント
const defaultOnEnd = event => {
  console.log('on End', '終了', event);
  startRec();  // continuous 相当の処理のため、録音を再開する
};

// 各種イベントを定義する
rec.onstart       = event => console.log('on Start'       , '開始・何か喋ってください'        , event);
rec.onaudiostart  = event => console.log('on Audio Start' , '録音開始・何か喋ってください'    , event);
rec.onsoundstart  = event => console.log('on Sound Start' , '音声検出開始・何か喋ってください', event);
rec.onspeechstart = event => console.log('on Speech Start', '音声認識開始 … 聞き取っています', event);
rec.onspeechend   = event => console.log('on Speech End'  , '音声認識終了'                    , event);
rec.onsoundend    = event => console.log('on Sound End'   , '音声検出終了'                    , event);
rec.onaudioend    = event => console.log('on Audio End'   , '録音終了'                        , event);
rec.onresult      = event => {
  console.log('Result', '音声認識完了', event);
  rec.stop();  // continuous 相当の処理のためココで一時停止する
  
  // 最後に認識した言葉を出力する
  const transcript = event.results[event.results.length - 1][0].transcript;
  console.log('Result (音声認識完了) … Transcript : ', transcript);
};
rec.onend   = event => defaultOnEnd(event);
rec.onerror = event => {
  console.warn('Error', 'エラーが発生しました', event);
  if(event.error === 'not-allowed') {
    console.error('Error [not-allowed]', 'マイクが許可されていません', event);
    // `onerror` 後に `onend` が発火するため `start()` で再開させないようにする
    return rec.onend = onEndEvent => console.error('End', 'マイクが許可されていません', onEndEvent);
  }
  if(event.error === 'no-speech') {
    console.warn('Error [no-speech]', '無音状態・音声が聞き取れません・再開します…', event);
    rec.onend = onEndEvent => defaultOnEnd(onEndEvent);  // 念のためイベントを初期化する
    return rec.stop();
  };
  if(event.error === 'aborted') {
    console.warn('Error [aborted]', '音声認識が中止されました・再開します…', event);
    rec.onend = onEndEvent => defaultOnEnd(onEndEvent);
    return rec.stop();
  }
  // その他のエラー : 再開させないようにする https://wicg.github.io/speech-api/#speechreco-error
  rec.onend = onEndEvent => console.error('End', `その他のエラーが発生したため終了しました [${event.error}]`, onEndEvent);
};
rec.onnomatch = event => console.error('No Match', '音声認識できませんでした', event);

startRec();  // 初回音声認識開始

// 音声認識を中断する (`onaudioend` が発火する・`onresult` で結果が受け取れない)
//rec.abort();
// 音声認識を終了する (`onaudioend` が発火する・`onresult` で結果が受け取れる)
//rec.stop();

new webkitSpeechRecognition() して、初期設定したり、各種イベントでの制御を入れたりして、start() するだけ。

start() すると、onstartonaudiostart の順でイベントが発火し、待機状態になる。ハッキリしない音でも何かしら拾えば onsoundstart が発火し、ハッキリと言葉が聞こえたら onspeechstart が発火する。ココらへんは一応ログ出ししているが、このタイミングで何かやれることはないかな。

ひとしきり音声認識が終わると、onresult が発火する。この中から transcript プロパティを取得すると、音声認識した文言が取得できる。日本語の場合、単語で分かち書き (スペース区切り) され、漢字への変換なども行われた状態の文字列が取得できる。ここいらの仕組みはブラウザ内部の処理でありハッキリしないので、音声認識した結果を元に何か処理をしたい場合は、漢字・ひらがな・カタカナの表記を曖昧に認識できるように準備しておく必要があるだろう。

onresult の中で stop() を呼んでいるため、onspeechendonsoundendonend が発火する。自分が書いたコードでは onend 内で再度 start() をしているので、連続して音声認識が行われる。rec.continuous というモードがあるのだが、コレを使うとどうも上手く聞き取り続けられなかったので、自分で明示的に stop()start() を呼び出すようにした。

マイクがミュート状態だったりしてしばらく無音状態が続くと、onerror が発火するが、エラー種別が no-speech なのでそれ以外のエラーとは区別が付く。onnomatch は自分が試した限りでは発火したことがなかった。

以上

ブラウザオンリー、外部ライブラリも不要で、簡単に音声認識が実装できてしまった。イベントのハンドリングを原始的に行わないといけないので、その辺面倒臭さはあるが、以前試した Web Speech API と組み合わせてやれば、音声認識に対して合成音声で応答する、といったことも JS オンリーで実装できてしまうだろう。