LoginSignup
43
34

More than 5 years have passed since last update.

getUserMediaで音声を拾いリアルタイムで波形を出力する

Last updated at Posted at 2018-01-02

概要

getUserMediaでブラウザから音声を拾い、画面に波形を出力します。

出力結果サンプル

人間の声を分析して色々やりたいと思い、第一歩としてこちらを取り組みました。

環境

Windows 10 Home 64Bit
Chrome 63

超大まかな前提

  • getUserMediaでマイクの音声を拾う
  • onAudioProcessで1024バイトずつバッファを溜めていく(今回はバッファの内容は使わないが、wave化してサーバに送信などする予定)
  • getChannelDataで返ってくるデータは生データなので、このままでは解析しにくい
  • 離散フーリエ変換を行うことで周波数帯ごとの解析がしやすくなる(スペクトラム分析)
  • audioContext.createAnalyser()でanalyserを作り、getByteFrequencyDataを利用すると上記フーリエ変換をした状態の値が取れる

ソースコード

voice_analyse.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>音声解析</title>
    <script
      src="https://code.jquery.com/jquery-3.2.1.js"></script>
  </head>
  <body>
    <button onclick="startRecording()">解析開始</button>
    <button onclick="endRecording()">解析終了</button>
    <hr>
    <canvas id="canvas" width="500" height="500"></canvas>
    <script src="voice_analyse.js"></script>
  </body>
</html>
voice_analyse.js
// クロスブラウザ定義
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;

// 変数定義
var localMediaStream = null;
var localScriptProcessor = null;
var audioContext = new AudioContext();
var bufferSize = 1024;
var audioData = []; // 録音データ
var recordingFlg = false;

// キャンバス
var canvas = document.getElementById('canvas');
var canvasContext = canvas.getContext('2d');

// 音声解析
var audioAnalyser = null;


// 録音バッファ作成(録音中自動で繰り返し呼び出される)
var onAudioProcess = function(e) {
    if (!recordingFlg) return;

    // 音声のバッファを作成
    var input = e.inputBuffer.getChannelData(0);
    var bufferData = new Float32Array(bufferSize);
    for (var i = 0; i < bufferSize; i++) {
        bufferData[i] = input[i];
    }
    audioData.push(bufferData);

    // 波形を解析
    analyseVoice();
};

// 解析用処理
var analyseVoice = function() {
    var fsDivN = audioContext.sampleRate / audioAnalyser.fftSize;
    var spectrums = new Uint8Array(audioAnalyser.frequencyBinCount);
    audioAnalyser.getByteFrequencyData(spectrums);
    canvasContext.clearRect(0, 0, canvas.width, canvas.height);

    canvasContext.beginPath();

    for (var i = 0, len = spectrums.length; i < len; i++) {
        //canvasにおさまるように線を描画
        var x = (i / len) * canvas.width;
        var y = (1 - (spectrums[i] / 255)) * canvas.height;
        if (i === 0) {
            canvasContext.moveTo(x, y);
        } else {
            canvasContext.lineTo(x, y);
        }
        var f = Math.floor(i * fsDivN);  // index -> frequency;

        // 500 Hz単位にy軸の線とラベル出力
        if ((f % 500) === 0) {
            var text = (f < 1000) ? (f + ' Hz') : ((f / 1000) + ' kHz');
            // Draw grid (X)
            canvasContext.fillRect(x, 0, 1, canvas.height);
            // Draw text (X)
            canvasContext.fillText(text, x, canvas.height);
        }
    }

    canvasContext.stroke();

    // x軸の線とラベル出力
    var textYs = ['1.00', '0.50', '0.00'];
    for (var i = 0, len = textYs.length; i < len; i++) {
        var text = textYs[i];
        var gy   = (1 - parseFloat(text)) * canvas.height;
        // Draw grid (Y)
        canvasContext.fillRect(0, gy, canvas.width, 1);
        // Draw text (Y)
        canvasContext.fillText(text, 0, gy);
    }
}


// 解析開始
var startRecording = function() {
    recordingFlg = true;
    navigator.getUserMedia({audio: true}, function(stream) {
        // 録音関連
        localMediaStream = stream;
        var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1);
        localScriptProcessor = scriptProcessor;
        var mediastreamsource = audioContext.createMediaStreamSource(stream);
        mediastreamsource.connect(scriptProcessor);
        scriptProcessor.onaudioprocess = onAudioProcess;
        scriptProcessor.connect(audioContext.destination);

        // 音声解析関連
        audioAnalyser = audioContext.createAnalyser();
        audioAnalyser.fftSize = 2048;
        frequencyData = new Uint8Array(audioAnalyser.frequencyBinCount);
        timeDomainData = new Uint8Array(audioAnalyser.frequencyBinCount);
        mediastreamsource.connect(audioAnalyser);
    },
    function(e) {
        console.log(e);
    });
};

// 解析終了
var endRecording = function() {
    recordingFlg = false;

    //audioDataをサーバに送信するなど終了処理
};

今後

人の声の特性をとらえ、声の録音を行ったあとに自動で音声をサーバに送るなどしたいです。

参考

録音部分

https://developers.google.com/web/fundamentals/media/recording-audio/?hl=ja
http://var.blog.jp/archives/62330155.html

音声解析部分

https://qiita.com/niisan-tokyo/items/764acfeec77d8092eb73
http://docolog.cocolog-nifty.com/papalog/2014/03/post-3de1.html
https://www.g200kg.com/jp/docs/webaudio/analyser.html
https://developer.mozilla.org/ja/docs/Web/API/AudioContext/createBufferSource
http://kagigotonet.hatenablog.com/entry/2014/09/16/124606
https://weblike-curtaincall.ssl-lolipop.jp/portfolio-web-sounder/webaudioapi-visualization/draw-wave

43
34
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
43
34