はじめに
マイクで取得したデータを、数値の配列にしたいだけなのに、苦労した話です。
調べるとScriptProcessorNodeを使ったものがいくつか出てきますが、非推奨っぽい。
AudioWorkletで代用してね、という話もあるが、これは扱いにくそう。
MediaRecorderからWav形式に変換できる方法もあるようだが、そこからうまいこと取り出すのも大変。
もっと簡単にできる方法があると思う。
結論
- MediaRecorderでMediaStreamからデータを取得し適当な音声ファイル形式に出力
- FileReaderにより音声ファイルをArrayBuffer形式に変換
- AudioContext.decodeAudioDataによりArrayBufferをAudioBuffer形式に変換
- AudioBufferからgetChannelDataなどにより生データの取得
※取得される値の詳細はAudioBufferを参照してください。WAV形式で保管される数値とは異なります。
※コードは後述
調べたこと
ScriptProcessorNode
次の方の記事に書いてあるような方法になります。
ただmozillaによると、本機能は廃止対象とのことです。なので長期使用を考える場合は、これを使わない方法が必要となります。
AudioWorklet
ScriptProcessorNodeの代替を調べると、AudioWorkletが出てきます。
次の方の記事が詳しいです。
む、難しいです。また、ファイル数が増えることや、実装行数が増えることもちょっといただけません。
私はただ、音声データの配列が欲しいだけなのです。
MediaRecorder
やはりこれで何とかしないと、とは思うのですが、うまく検索できませんでした。
Audio要素
Audio要素で再生するのは簡単ですね。
ふむ、ではここに格納されたデータを取得してうまいことできる方法は、と思ったのですが、見つけられませんでした。
decodeAudioData
音声ファイルをAudioBufferに変換するものだそうです。
音声ファイルはMediaRecorderで生成できるので、あとはAudioBufferから生データを取り出せれば、できます。
まとめ
手順は以下の通りです。
まず、MediaDevicesからMediaStreamを取得します。
let audioStream = null
navigator.mediaDevices.getUserMedia({audio: true}).then(stream => {
audioStream = stream
})
次に、MediaRecorderを作成します。
そしてEventListenerでデータの取得・変換を行うようにします。
chunks = []
const mediaRecorder = new MediaRecorder(audioStream, {
mimeType: 'audio/webm'
})
mediaRecorder.addEventListener('dataavailable', e => {
if (e.data.size > 0) {
chunks.push(e.data);
}
})
mediaRecorder.addEventListener('stop', e => {
const blob = new Blob(chunks)
const reader = new FileReader()
reader.readAsArrayBuffer(blob)
reader.onload = () => {
audioCtx.decodeAudioData(reader.result).then(buf => {
const b = buf.getChannelData(0)
console.log(b)
})
}
})
あとは適切なタイミングでMediaRecorderを開始・停止するだけでになります。
mediaRecorder.start()
// some process
mediaRecorder.stop()
完成コード
おまけ
そもそもなぜ必要だったかというと、
でマイクからの音声をデータとして扱えるようにするためです。
よかったら見てもらえると嬉しいです。