@Satoh_D no blog

大分にUターンしたので記念に。調べたこととか作ったこととか食べたこととか

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 devhttp://localhost:3000にアクセスし動作確認を行う

参考サイト

Vue.js&Nuxt.js超入門

Vue.js&Nuxt.js超入門