#javascript
#webkitSpeechRecognition
NCCDay 19

webkitSpeechRecgnitionを扱う

はじめに

音声認識のためのインターフェイスである webkitSpeechRecognition を扱った際に得た知見をまとめました。本記事では、常にマイクから音声を聞きながら、声が発せられてから無音になるまでの間に話された言葉を文字列として取得する方法について述べていきます。

構造を理解する

まずは以下のコードを実行します。 onなんとか 系が多いので、どのようなタイミングで発火するのかを確認しましょう。

main.js
var rec = new webkitSpeechRecognition();
rec.continuous = false;
rec.interimResults = false;
rec.lang = 'ja-JP';

rec.onresult = function(e) {
    console.log('on result');
    for (var i = e.resultIndex; i < e.results.length; ++i) {
        if (e.results[i].isFinal) console.log('Recognised: ' + e.results[i][0].transcript);
    }
    rec.stop();
}

rec.onstart = () => { console.log('on start') };
rec.onend = () => { console.log('on end') };

rec.onspeechstart = () => { console.log('on speech start') };
rec.onspeechend = () => { console.log('on speech end') };

rec.onosundstart = () => { console.log('on sound start') };
rec.onsoundend = () => { console.log('on sound end') };

rec.onaudiostart = () => { console.log('on audio start') };
rec.onaudioend = () => { console.log('on audio end') };

rec.start();

声を発した瞬間に on speech start と表示されましたので、 onspeechstart は声が発せられることがトリガーだと考えられます。その他についてもまとめてみましょう。

  • onstart
    • rec.start() がされた時
  • onend
    • rec.stop() がされた時
  • onspeechstart
    • 発声の始めを声が受け付けた時
  • onsoundstartonaudiostart
    • 不明
  • - onspeechendonsoundendonaudioendonresult
    • 発声が終了した時
    • 判別できなかったが、順序はいつも上記の順番

常に音声を取得し続ける

常に音声を取得し続けるためには発声が終了し、結果が出た後にまた rec.start() をする必要があります。

var rec = new webkitSpeechRecognition();
rec.continuous = false;
rec.interimResults = false;
rec.lang = 'ja-JP';

rec.onresult = function(e) {
    rec.stop();
    for (var i = e.resultIndex; i < e.results.length; ++i) {
        if (e.results[i].isFinal) console.log('Recognised: ' + e.results[i][0].transcript);
    }
}

rec.onend = () => { rec.start() };

rec.start();

上記のコードのように、 rec.onendrec.start() することで終了後に開始することができます。 rec.stop() の位置をfor文の下にすると上手く動作しなかったため注意してください。

おまけ - audioデータも取得する

MediaRecorder を使って文字列とaudioデータの両方を取得しましょう。ここではaudioデータはbase64に変換しています。

main.js
var onSuccess = function(stream) {
    var base64 = '';

    var mediaRecorder = new MediaRecorder(stream);
    var chunks = [];

    mediaRecorder.onstop = function(e) {
        var blob = new Blob(chunks, { 'type': 'audio/wav' })
        var reader = new window.FileReader();
        reader.readAsDataURL(blob);
        reader.onloadend = () => {
            base64 = reader.result;
        };
        chunks = [];
    }
    mediaRecorder.ondataavailable = (e) => { chunks.push(e.data) };

    var speechRecognition = new webkitSpeechRecognition();
    speechRecognition.continuous = false;
    speechRecognition.interimResults = false;
    speechRecognition.lang = 'ja-JP';

    speechRecognition.onresult = function(e) {
        speechRecognition.stop();
        for (var i = e.resultIndex; i < e.results.length; ++i) {
            if (e.results[i].isFinal) {
                console.log("Recognition : " + e.results[i][0].transcript);
                console.log("Base64 : " + base64);
            }
        }
    }

    speechRecognition.onspeechstart = () => { mediaRecorder.start() };
    speechRecognition.onaudioend = () => { mediaRecorder.stop() };
    speechRecognition.onend = () => { speechRecognition.start() };

    speechRecognition.start();
}
navigator.mediaDevices.getUserMedia({ audio: true }).then(onSuccess, null);

ブラウザに getUserMedia() が実装されていないことがあるので注意してください。また途中でエラーを吐くことがたまにあるため、原因がわかった方はコメント等で教えて頂けると幸いです。

おわりに

私はこの知見を初参加のハッカソン中に得ました。開発が始まって1時間しないうちにパソコンに向かって「おにぎり」と連呼してテストを行っていたので、周囲からは変な人と思われていたでしょう...。何はともあれハッカソンは無事終わりました。この記事によって知見が共有され、どこかで使われることがあるならばとても嬉しいです。