ホーム > カテゴリ > HTML5・JavaScript >

Webカメラ、USBマイクのテストと導入、初期設定 [WebRTC/Web Audio API]

パソコンに接続されている「Webカメラ」または「USBマイク」の映像/音声のテストを行います。その他にデバイス/ブラウザの初期設定方法など。

テスト方法HTML5のWebRTC/Web Audio APIのJavaScriptコードで確認しますので映像、音声はサーバーに送信されません。全ての処理はクライアントサイドで行います。

※ココで使用しているJavaScriptコードはページ下部を参照してください。

1. 映像、音声のテスト

音声
映像

パソコンにマイクが接続されていなくても、デバイスで「ステレオミキサー」が有効になっている場合はマイクと認識されます。

※ステレオミキサーはパソコン内の音を拾います。

2. Webカメラ/USBマイクの導入

例としてロジクールの「HD ウェブカメラ C615」(内臓マイク付き)の導入を行います。※USBマイクも同様です。

パソコンにUSBケーブルを差すとプラグアンドプレイで自動的にデバイスドライバーの設定が行われます。Windows10の「デバイスマネージャ」を確認するとイメージングデバイスに「Logicool HD Webcam C615」。オーディオの入力および出力に「HD Webcam C615」が表示されます。

3. ブラウザでの初期設定

Webカメラを使用できるようにするには対象のWebサイトにアクセス後、ブラウザに表示される「カメラ、マイクの使用確認」を「許可」にします。

この時にブロックしてしまうと、カメラ、マイクを使用できません。

その場合は、Chromeだと[設定][詳細設定][コンテンツの設定]のカメラ、マイクのブロックを解除後に、ブラウザ上でF5キーを押してWebサイトを「最新の情報に更新する」と再度、確認ダイアログが表示されるようになります。

4. JavaScriptコード

WebRTC(Web Real-Time Communication)でカメラ、マイク接続。

Web Audio APIでマイクのハウリング対策を行っています。ハウリング対策はロジクールの「HD ウェブカメラ C615」用に調整しています。

また、USBマイク単体(サンワサプライ USBマイクロホン MM-MCU01BK)でテストすると出力音量が小さく感じます。

マイクの機種によってハウリングが異なりますので、ハウリング設定はユーザーが手動で選択できるようにした方が良いかもです。

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<table>
  <tr><th>音声</th><td><canvas id="myCanvas" width="300" height="200"></canvas></td></tr>
  <tr><th>映像</th><td><video id="myVideo" width="300" height="240"></video></td></tr>
</table>
<script type="text/javascript">
var video = document.getElementById('myVideo');
var tracks; 

var audioCtx; 
var audioSourceNode;      
var analyserNode;
var filter1,filter2,filter3;

var firstflg = true;
 
function play(){ 
  if(tracks) return;
  
  // メタデータがロードされたとき
  video.onloadedmetadata = function(){
    // 加工前音声の再生を無効化する(ハウリング対策)
    video.muted = true;
    video.play();
  };
   
  // 音声確認の棒グラフの表示及びハウリング対策
  function voice_processing(stream){
    var canvas = document.getElementById('myCanvas');
    
    // AudioContextの生成
    if(firstflg){
      // AudioContextの生成
      audioCtx =  new AudioContext(); 
      firstflg = false;  
    }
    // MediaElementAudioSourceNodeの生成       
    if(audioSourceNode){
      audioSourceNode.disconnect();
    }                           
    audioSourceNode = audioCtx.createMediaStreamSource(stream); 
 
    // AnalyserNodeの生成 
    // ※音声の時間と周波数を解析する
    if(analyserNode){
      analyserNode.disconnect();
    }            
    analyserNode = audioCtx.createAnalyser();   
    
    // FFT(高速フーリエ変換)の周波数領域
    analyserNode.fftSize = 256; 
    
    // fftSizeの1/2
    var bufferLength = analyserNode.frequencyBinCount;
    
    var dataArray = new Float32Array(bufferLength);                
    
    // *** ハウリング対策 ***
    
    if(filter1){
      filter1.disconnect();
    }  
    if(filter2){
      filter2.disconnect();
    }  
    if(filter3){
      filter3.disconnect();
    }                
        
    // ハイシェルフフィルタ
    // 8192Hz以下を通して、それ以外は減衰
    filter1 = audioCtx.createBiquadFilter();
    filter1.type='highshelf';
    filter1.frequency.value = 8192; // 周波数
    filter1.gain.value = -40;       // ゲイン(強さ)
   
    // ピーキングフィルタ
    // 0-500Hzを減衰       
    filter2 = audioCtx.createBiquadFilter();
    filter2.type='peaking';
    filter2.frequency.value = 250;  // 周波数
    filter2.gain.value = -40;       // ゲイン(強さ)
    
    // バンドパスフィルタ
    // 0-500Hzを通す。それ以外は減衰
    filter3 = audioCtx.createBiquadFilter();
    filter3.type='bandpass';
    filter3.frequency.value = 250; // 周波数(中央値)
    
    // フィルタの設定           
    audioSourceNode.connect(filter1);
    filter1.connect(filter2);
    filter2.connect(filter3);
    filter3.connect(analyserNode);    
    analyserNode.connect(audioCtx.destination); 
    
    var canvasCtx = canvas.getContext('2d');
    canvasCtx.clearRect(0, canvas.height/2, canvas.width, canvas.height);
  
    var draw = function() {
      // 次の再描画の前にアニメーションを更新
      requestAnimationFrame(draw);
 
      // 周波数データをFloat32Array配列にコピーする
      analyserNode.getFloatFrequencyData(dataArray);
 
      // 背景の描画
      canvasCtx.fillStyle = 'rgb(255, 255, 255)';
      canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
      
      canvasCtx.strokeRect(0, 0, canvas.width, canvas.height);
 
      // スペクトルの描画
      var barWidth = (canvas.width / bufferLength) * 2.5;
      var posX = 0;
      for (var i = 0; i < bufferLength; i++) {
        var barHeight =(dataArray[i]+140)  * 2;               
        canvasCtx.fillStyle = 'rgb(' + Math.floor(barHeight + 100) + ', 50, 50)';
        canvasCtx.fillRect(posX, canvas.height - barHeight / 2, barWidth, barHeight/ 2);
        posX += barWidth + 1;
      } 
    }
    
    draw();    
  }

  var ua = window.navigator.userAgent.toLowerCase();
  var chrome = (ua.indexOf('chrome') !== -1) && (ua.indexOf('edge') === -1)  && (ua.indexOf('opr') === -1);   
       
  // Chrome
  var constraints; 
  if(chrome){
    constraints = {                   
                    "mandatory": {
                      "googEchoCancellation" : false,
                      "googAutoGainControl"  : false,
                      "googNoiseSuppression" : false,
                      "googHighpassFilter"   : false                            
                     },
                    "optional": []
                  };
               
  // FireFox/Edge                  
  }else{
    constraints =  {                           
                     "echoCancellation"     : false,
                     "autoGainControl"      : false,        
                     "noiseSuppression"     : false
                   };
  
  }  
              
  // 将来的にウェブ標準予定  
  if("mediaDevices" in navigator && "getUserMedia" in navigator.mediaDevices ){    
    
    // マイク、カメラ
    navigator.mediaDevices.getUserMedia({
      video: true,
      audio: constraints
    }).then(function (stream) {
      
      tracks = stream.getTracks(); 
      video.srcObject = stream;
      voice_processing(stream);
      
    }).catch(function (err) {   
      // マイク   
      navigator.mediaDevices.getUserMedia({
        audio: constraints
      }).then(function (stream) {
        
        tracks = stream.getTracks(); 
        video.srcObject = stream;
        voice_processing(stream);
        
      }).catch(function (err) {      
        // カメラ   
        navigator.mediaDevices.getUserMedia({
          video: true
        }).then(function (stream) {
          
          tracks = stream.getTracks(); 
          video.srcObject = stream;
          
        }).catch(function (err) {      
          console.log(err);
        });
      });
    });
        
  // こちらが将来的にウェブ標準予定  
  }else{    
    
    console.log("USE: navigator.getUserMedia()");
    
    // カメラ、マイク
    navigator.getUserMedia(
    
        {video: true, audio: constraints},
        function(stream) { 
          tracks = stream.getTracks();          
          video.srcObject = stream;
          voice_processing(stream); 
        },
        
        // マイク
        function(err) {
           navigator.getUserMedia( 
            {audio: constraints},
            function(stream) { 
              tracks = stream.getTracks(); 
              video.srcObject = stream;
              voice_processing(stream); 
            },
            
            // カメラ
            function(err) {
               navigator.getUserMedia(
               
                {video: true},
                function(stream){ 
                  tracks = stream.getTracks();  
                  video.srcObject = stream;
                },
                
                function(err) {
                  console.log(err);               
                });         
            });
        }
    );
  }
}
 
function stop(){
  if(tracks){
    tracks.forEach(function(track) {
      track.stop();
    });
    tracks = null;
  }
}
 
play();  
</script>
<button onclick="play();">&nbsp;&nbsp;開 始&nbsp;&nbsp;</button>
<button onclick="stop();">&nbsp;&nbsp;停 止&nbsp;&nbsp;</button>
</body>
</html>

参考コード(MDN)

getUserMedia()のaudioでconstraintsの隠しコマンドを使用しています。この隠しコマンドではエコーキャンセラー、オートゲインコントロールなどをオフにしています。audioの通常の使い方は「audio:true」となります。

動作確認はChrome/Firefox/MS Edgeです。IE11は動作しません。
※スマートフォン(Android)でも動作するようです。(iPhoneは未確認)





関連記事



公開日:2019年02月05日 最終更新日:2019年02月16日
記事NO:02724