ブラウザで音を再生してみた

趣旨

 最近のWebブラウザは、Web Audio API というものが実装されていて音声再生もいろいろと自由に操作することができるようになっています。 ということでちょっと触ってみたので、メモ代わりにまとめてみようと思います。

HTMLで音声再生

 手始めにaudioタグを使って音声再生してみます。

<audio src="hogehoge.ogg" controls>

 以上で終わりです。

 終わってしまった…APIもなにも関係なく終わってしまいました。さすがHTML5。 しかしさすがにつまらないので今回は、ローカル環境(自分のパソコンの中)においてある音声ファイルを読み込んで再生するようにしてみます。

参考資料

「Web Audio API」で検索するといくつか見つかると思いますが、今回はこれを参考に進めます。
ユーザーから音声データを取得する
https://developers.google.com/web/fundamentals/media/recording-audio/?hl=ja

細かい解説などことはここでは触れません。資料と一緒に読み進めてください。

最初のサンプル

 最初は参考資料に載っているサンプルを丸写しして練習してみました。なんの工夫もなく資料そのまま丸写しです。
ボタンを押すとファイル選択ダイアログが開きます。
再生したい音声ファイルを選択すると、表示されているオーディオコントロールで音声を再生できるようになります。
inputタグでファイルを選択。選択結果をaudioタグのsrc要素に反映させています。
サンプル

マイクにアクセスする

 マイクの音を再生するだけのサンプル。実用性はありません。 これもほぼ資料からの丸写しですが、一部修正してあります。(後述)

 getUserMediaを実行するとブラウザがマイクにアクセスしようとします。 この際、ユーザーに許可を求めるメッセージが表示されます。 許可をするとaudio要素のscr属性にマイクが接続されます。


<audio id="player" controls></audio>
<script>
  var player = document.getElementById('player');

  //  マイクを audio タグに接続します。
  // マイクへのアクセスが成功したら実行されます。
  var handleSuccess = function(stream) {
    // stream : マイクから取得した MediaStream オブジェクト
    try {
      player.srcObject = stream;
    } catch (error) {
      // 古いブラウザ向けの実装
      player.src = URL.createObjectURL(stream);
    }
  };

  //  ユーザーにマイクの許可を求めます。
  // getUserMedia() を使用すると、マイクに直接アクセスできるようになります。
  // ユーザーが許可を出すと、 MediaStream オブジェクトを handleSuccess() へ引数として渡して実行します。
  navigator.mediaDevices.getUserMedia({ audio: true, video: false })
      .then(handleSuccess)
</script>
    

サンプル
 Chromeでこのページを見ている人の中にはマイクが使えていない方もいると思います。 httpsでアクセスしていない場合、安全のためChromeブラウザがマイクの使用をブロックしてしまいます。 対策としていくつかの方法があります。

  • URLの先頭にあるhttp://をhttps://と書き直してアクセスしなおす。
    いつのまにかこのサイトもSSL(https)対応になってました…。
  • サンプルをダウンロードしてhttpsのサーバーにアップロードしなおす。
  • サンプルをダウンロードしてローカルPC内で開く。
  • Edgeなど問題なく動作するブラウザもあるので、そちらを使う。

 お好みの方法で対処してください。

 さて、スクリプトの話しに戻ります。最近のブラウザだと、参考元の資料のままだと動作しなくなっているようだったので、一部修正しました。 例えばChromeだとURL.createObjectURL() が MediaStream を引数として受け付けなくなったようで、サンプルのままだとエラーを出します。 srcObjectプロパティを使用して、player.srcObject = streamと書く必要があるようです。

 実行するとマイクで拾った音声が、スピーカーから出てきます。実行するときはスピーカーにマイクを近づけすぎないように注意してください。ハウリングが起きてマイクやスピーカーを破損する恐れがあります。

マイクから音の数値データを取得

 ここからが本格的に Web Audio API を使ったものになります。サンプルは資料の丸写しにすこし書き足したものです。 audio要素を使わずに再生し、また音声データを数値で取得します。

 サンプルは、マイクから音声データを取得して、取得した値でバーグラフを描画します。マイクが音を拾うと、音の大きさに合わせてバーグラフが伸びたり縮んだりします。簡易的なレベルメーターのサンプルです。


<div id="barbox">
  <div id="bar"></div>
</div>

<script>
  // 環境依存対策
  window.AudioContext = window.AudioContext || window.webkitAudioContext;  

  var handleSuccess = function(stream) {
    // AudioContextを作成
    var audioctx = new AudioContext();

    // 音声の再生や編集が出来るようにする
    //  navigator.mediaDevices.getUserMedia インスタンスから
    //  MediaStreamAudioSourceNodeオブジェクトを生成。
    var source = audioctx.createMediaStreamSource(stream)

    // ダイレクトな音声処理ができるようにする
    //  ScriptProcessorNode オブジェクトを作成
    //  [引数]
    //  バッファサイズ、入力チャンネル数、出力チャンネル数
    var processor = audioctx.createScriptProcessor(1024,1,1);

    source.connect(processor);
    processor.connect(audioctx.destination);
    // スピーカーに出力
    source.connect(audioctx.destination);
    
    // 音声データの処理方法を記述
    //  バッファが一杯になると実行されるイベントリスナーのようです。
    //  ※ onaudiopcess は廃止予定とされています。
    processor.onaudioprocess = function(e){
      // PCMデータをFloat32Arrayで取得
      var amp = e.inputBuffer.getChannelData(0);
      // バーグラフで簡易表示
      // document.getElementById('bar').style.width = '' + (Math.max.apply(null, amp) * 800) + 'px';
      const reducer = (accumulator, currentValue) => accumulator + Math.abs(currentValue);
      document.getElementById('bar').style.width = '' + (amp.reduce(reducer) / amp.length * 800) + 'px';
    };
  };

  //  ユーザーにマイクの許可を求めます。
  // getUserMedia() を使用すると、マイクに直接アクセス出来るようになります。
  // ユーザーが許可を出すと、 MediaStream オブジェクトを handleSuccess() へ引数として渡して実行します。
  navigator.mediaDevices.getUserMedia({ audio: true, video: false })
      .then(handleSuccess)
</script>
    

サンプル
 細かい技術解説などは資料に任せるとして、ここでは書き換えた場所について説明します。

 まずはじめにAudioContextのインスタンス(実体)が必要です。この音声コンテキストは必ず必要で、音を操作するときの作業領域的なものという理解でいいと思います。

window.AudioContext = window.AudioContext || window.webkitAudioContext;  
var audioctx = new AudioContext();

 window.AudioContext || window.webkitAudioContextはブラウザによっては実装が異なるのでその差を埋めるための処理です。めんどくさいですね。そのうち統一されるんだと思います。

 Web Audio API を利用するときの基本的な流れは、一般的なオーディオ機器を組み立てるときと同じです。 楽器などの音源となるものから最終的な出口であるスピーカーまでをケーブルでつないで組み立てます。入力と出力の関係を間違えなければ大丈夫です。

 Web Audio APIではこのような機器(マイクやスピーカー)をノードと呼びます。 ノード同士を接続するには、ケーブルの代わりにconnectプロパティを使います。サンプルでは次のような接続になっています。

source(マイク)
  ├-> processor -> audioctx.destination
  └-> audioctx.destination(スピーカー)

※ 入力側 -> 出力側

 音源から最後のスピーカーまでがつながると、パソコンに繋がったスピーカーから音が出せる状態になります。

 またサンプルでは途中で分岐してprocessorにつなげ、音を数値化してバーグラフに反映しています。 processoronaudioprocessを定期的に呼び出して実行しています。 具体的にはcreateScriptProcessorで指定したバッファがいっぱいになると実行されるようです。 取得した値は、amp.reduce(reducer) / amp.lengthで絶対値の平均を算出して、バーグラフとして表示させています。 ここは単純にMath.max.apply(null, amp)で最大値を表示させたほうがわかりやすかったかもしれませんね。

参考資料としてこちらも上げておきます。
WEB SOUNDER - Web Audio API 解説
https://weblike-curtaincall.ssl-lolipop.jp/portfolio-web-sounder/webaudioapi-basic/overview

マイクの音をファイルに保存

 次はマイクの音を録音して、wavファイル形式でダウンロードできるようにしてみます。これも資料から丸写しして、自分がわかりやすいように一部を書き換えてあります。

 ページを開いてマイクの使用を許可すると録音が開始されます。 [Stop] ボタンをクリックして録音を終了すると、録音したデータをwav形式のファイルでダウンロードできます。


<a id="download">Download</a>
<button id="stop">Stop</button>

<script>
  const downloadLink = document.getElementById('download');
  const stopButton   = document.getElementById('stop');

  // マイクの使用許可が出たら実行
  let handleSuccess = function(stream) {
    // 波形データのバッファ
    const recordedChunks = [];
    // 記録する際に使用する MIME タイプ
    const options = {mimeType: 'video/webm;codecs=vp9'};
    const mediaRecorder = new MediaRecorder(stream, options);  

    // Stop ボタンをクリック
    stopButton.addEventListener('click', function() {
        // 記録停止
        mediaRecorder.stop();
    });

    // 波形データをバッファに追記
    // mediaRecorder.stop()が実行されるとこのイベントが発生します。
    mediaRecorder.addEventListener('dataavailable', function(e) {
      if (e.data.size > 0) {
        // 波形データの Blob を出力先変数に追加
        recordedChunks.push(e.data);
      }
    });

    // downloadボタンクリックでダウンロードできるようにする
    // 記録が停止されたら実行される
    mediaRecorder.addEventListener('stop', function() {
      // downdloadボタンに録音データを接続
      downloadLink.href     = URL.createObjectURL(new Blob(recordedChunks));
      downloadLink.download = 'test.wav';
    });

    // 記録開始
    mediaRecorder.start();
  };

  //  ユーザーにマイクの許可を求めます。
  navigator.mediaDevices.getUserMedia({ audio: true, video: false })
      .then(handleSuccess)
</script>
    

サンプル
 mediaRecorderでメディアデータを記録できます。 記録が終わったら結果を変数に追記。 createObjectURLでダウンロードできる形にします。 動いているのを見るとなんとも簡単そうに見えてしまいますね。
参考資料としてこちらもどうぞ。
MediaRecorder API ( https://developer.mozilla.org/ja/docs/Web/API/MediaRecorder_API )

まだつづくよ

 これでWeb Audio APIの基本的な使い方と、ありがちな入出力ができるようになりました。 ここまでわかるとマイクの音を加工してダウンロードさせたり、音に合わせて映像を動かしたりができそうな気がして夢が膨らみますね。 実際にやろうとするとまだまだ大変なんでしょうけど…。

というわけで、次回は音の加工です。

関連記事

  1. ブラウザで音を作ってみた 音を作る  前回は音声ファイルやマイクなど外部から音のデータ...
  2. 音を可視化してみた 音声ファイルの読み込み(再)  最初に戻って音声ファイルを再...
  3. howler.jsを使ってみた howler.js入門  Web Audio APIに挑戦し...
  4. 音を可視化してみた2 音を分析 その2  前回、音声を周波数分析してカラーマップ表...