はじめに
音声認識のためのインターフェイスである webkitSpeechRecognition
を扱った際に得た知見をまとめました。本記事では、常にマイクから音声を聞きながら、声が発せられてから無音になるまでの間に話された言葉を文字列として取得する方法について述べていきます。
構造を理解する
まずは以下のコードを実行します。 onなんとか
系が多いので、どのようなタイミングで発火するのかを確認しましょう。
const rec = new webkitSpeechRecognition()
rec.continuous = false
rec.interimResults = false
rec.lang = 'ja-JP'
rec.onresult = e => {
console.log('on result')
for (var i = e.resultIndex; i < e.results.length; i++) {
if (!e.results[i].isFinal) continue
const { transcript } = e.results[i][0]
console.log(`Recognised: ${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
- 発声の始めを声が受け付けた時
-
onsoundstart
、onaudiostart
- 不明
-
-
onspeechend
、onsoundend
、onaudioend
、onresult
- 発声が終了した時
- 判別できなかったが、順序はいつも上記の順番
-
常に音声を取得し続ける
常に音声を取得し続けるためには発声が終了し、結果が出た後にまた rec.start()
をする必要があります。
const rec = new webkitSpeechRecognition()
rec.continuous = false
rec.interimResults = false
rec.lang = 'ja-JP'
rec.onresult = e => {
rec.stop()
for (var i = e.resultIndex; i < e.results.length; i++) {
if (!e.results[i].isFinal) continue
const { transcript } = e.results[i][0]
console.log(`Recognised: ${transcript}`)
}
}
rec.onend = () => { rec.start() }
rec.start()
上記のコードのように、 rec.onend
で rec.start()
することで終了後に開始することができます。 rec.stop()
の位置をfor文の下にすると上手く動作しなかったため注意してください。
おまけ - audioデータも取得する
MediaRecorder を使って文字列とaudioデータの両方を取得しましょう。ここではaudioデータはbase64に変換しています。
const onSuccess = stream => {
let base64 = ''
let chunks = []
const mediaRecorder = new MediaRecorder(stream)
mediaRecorder.onstop = e => {
const blob = new Blob(chunks, { type: 'audio/wav' })
const reader = new window.FileReader()
reader.readAsDataURL(blob)
reader.onloadend = () => { base64 = reader.result }
chunks = []
}
mediaRecorder.ondataavailable = e => { chunks.push(e.data) }
const speechRecognition = new webkitSpeechRecognition()
speechRecognition.continuous = false
speechRecognition.interimResults = false
speechRecognition.lang = 'ja-JP'
speechRecognition.onresult = function (e) {
speechRecognition.stop()
for (let i = e.resultIndex; i < e.results.length; i++) {
if (!e.results[i].isFinal) continue
const { transcript } = e.results[i][0]
console.log(`Recognized: ${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)
ブラウザに getUserMedia()
が実装されていないことがあるので注意してください。また途中でエラーを吐くことがたまにあるため、原因がわかった方はコメント等で教えて頂けると幸いです。
おわりに
私はこの知見を初参加のハッカソン中に得ました。開発が始まって1時間しないうちにパソコンに向かって「おにぎり」と連呼してテストを行っていたので、周囲からは変な人と思われていたでしょう...。何はともあれハッカソンは無事終わりました。この記事によって知見が共有され、どこかで使われることがあるならばとても嬉しいです。