WebRTCで音声データを取得し音声のボリュームを表示する
ブラウザでマイクから取得した音声のボリュームを画面上で表示する必要があったので調べてみた
今回もNuxt.js
で書いてみる
前提
- yarn: v1.22.5
- Nuxt.js: v.2.14.6
ソースコード
<project-root>/pages/sample.vue
を作成
<template> <section class="section"> <div class="columns"> <div class="column"> <meter :value="volume" min="0" max="1" high="0.25"></meter> <span>{{ volume }}</span> </div> </div> <div class="buttons"> <button @click="getUserMedia">音声を取得する</button> </div> </section> </template> <script lang="ts"> /* eslint-disable no-console */ export default { data() { return { localstream: undefined, streamConst: { audio: true, // 録音する場合はaudio: trueとする video: false, }, audioContext: undefined, audioScript: undefined, mic: undefined, volume: 0, } }, mounted() {}, methods: { getLocalMediaStream(mediaStream: any) { this.localstream = mediaStream // 音声取得 // 音声コンテキストを作成 let audioContext = window.AudioContext this.audioContext = new audioContext() // 音声データを扱うためのオブジェクトを生成 // bufferSizeはOSごとに最適なサイズをあてる: https://weblike-curtaincall.ssl-lolipop.jp/portfolio-web-sounder/webaudioapi-basic/custom this.audioScript = this.audioContext.createScriptProcessor(2048, 1, 1) // audioContextで音声ストリームを扱えるようにする this.mic = this.audioContext.createMediaStreamSource(mediaStream) // それぞれをconnectで接続するしストリームの音声データを扱えるようにする this.mic.connect(this.audioScript) this.audioScript.connect(this.audioContext.destination) // イベントハンドラー this.audioScript.onaudioprocess = this.handleLocalOnAudioProcess }, handleLocalOnAudioProcess(event: any) { // via. https://tech.drecom.co.jp/web_audio_api_audiobuffer/ // サンプリング周波数と量子化ビット数を-1〜1の間で正規化した値を Float32Array で保存 [asin:B07X6F1C2P:detail] [asin:B01LYO6C1N:detail] const input = event.inputBuffer.getChannelData(0) // 入力ソースの音声データを取得 let sum = 0.0 for(let i = 0; i < input.length; ++i) { // inputBufferの値は-1〜1の間で正規化された数値であるため2乗することで正の数値に変更する sum += input[i] * input[i] } // sum / input.length: inputBufferから取得した音量の平均値を取得 // 平方根をを取ることで input[i] * input[i] の値をもとに戻す this.instant = Math.sqrt(sum / input.length) // 少数第3位を四捨五入し表示 this.instant = this.instant.toFixed(2) }, handleLocalMediaStreamError(error: string) { console.error(`navigator.getUserMedia error: ${error}`) }, getUserMedia() { console.log('getUserMedia') navigator.mediaDevices .getUserMedia(this.streamConst) .then(this.getLocalMediaStream) .catch(this.handleLocalMediaStreamError) }, }, } </script>
getUserMedia
でストリームの取得許可がとれたら音声データをゴニョゴニョするための準備を行う
流れとしては
- 音声コンテキストの作成:
new audioContext()
- 作成した音声コンテキストでストリームを扱えるように設定:
audioContext.createMediaStreamSource()
- 音声データを扱うためのオブジェクトを生成:
audioContext.createScriptProcessor()
- イベントハンドラの設定:
audioScript.onaudioprocess
getLocalMediaStream(mediaStream: any) { this.localstream = mediaStream let audioContext = window.AudioContext this.audioContext = new audioContext() this.audioScript = this.audioContext.createScriptProcessor(2048, 1, 1) this.mic = this.audioContext.createMediaStreamSource(mediaStream) this.mic.connect(this.audioScript) this.audioScript.connect(this.audioContext.destination) this.audioScript.onaudioprocess = this.handleLocalOnAudioProcess },
音量の可視化はhandleLocalOnAudioProcess()
で処理を行う
inputBuffer
には各入力チャンネルの音声データがFloat32Array
で格納されている。getChannelData(N)
で入力チャンネルを指定し音声データを取得する。今回は入力が1箇所しかないのでgetChannelData(0)
で取得。
取得した音声データの中身はサンプリング周波数
と量子化ビット数
を-1〜1
の間で正規化した値が入っている。
これらを正の数値に変更し平均値を作成し画面にボリュームとして表示する(for(let〜) - Math.sqrt()
の部分)
handleLocalOnAudioProcess(event: any) { const input = event.inputBuffer.getChannelData(0) // 入力ソースの音声データを取得 let sum = 0.0 for(let i = 0; i < input.length; ++i) { sum += input[i] * input[i] } this.instant = Math.sqrt(sum / input.length) this.instant = this.instant.toFixed(2) },
取得したボリュームはmeter要素
で可視化する
<meter :value="volume" min="0" max="1" high="0.25"></meter> <span>{{ volume }}</span>
最後にyarn dev
しhttp://localhost:3000
にアクセスし動作確認を行う