Help us understand the problem. What is going on with this article?

WebRTCのMediaStreamをWebAudioAPIのFilterで加工する方法

More than 1 year has passed since last update.

概要

最近、SkyWayを使ったビデオチャットシステムを構築していたのだが、ハウリング対策として高音を低減して欲しいと言われた。JavaScript歴2ヶ月なのに無茶言うな

探すのに少し苦労したので、勉強も兼ねた覚え書きとして残しておこうと思う。

WebAudioAPIの基本概念

WebAudioAPIでは、マイクやスピーカーといった入出力先や、音の加工をするフィルターなどをNodeとして表現する。これらを接続(connect)していくことで、渡された順に処理されていく仕組みとなっている。
filter.png
今回はSourceとして、getUserMediaで取得したMediaStreamを使用する。

結果

最終的に、以下のようになった

apply_filter.js
navigator.mediaDevices.getUserMedia({
  audio: true,
  video: true,
}).then(stream => {
  // AudioContextを生成
  const audioContext = new AudioContext();

  // BiquadFilterを生成
  const biquadFilter = audioContext.createBiquadFilter();
  biquadFilter.type = 'highshelf';     // ハイシェルフフィルター
  biquadFilter.frequency.value = 1000; // 周波数閾値
  biquadFilter.gain.value = -50;       // Gain(強さ)

  // getUserMediaで取得したMediaStreamからMediaStreamAudioSourceNodeを生成
  const mediaStreamSource = audioContext.createMediaStreamSource(stream);

  // MediaStreamAudioSourceNodeをBiquadFilterNodeに、BiquadFilterNodeをAudioContext.destinationに接続する
  // AudioContext.destinationはブラウザの出力先を示しており、ここに音を流せばブラウザから出力される
  mediaStreamSource.connect(biquadFilter);
  biquadFilter.connect(audioContext.destination);

  // 最後にHTMLの<video>や<audio>のsrcObjectにstreamを渡す
  const video = document.querySelector('video');
  video.srcObject = stream;

  // HTML側で属性の指定をした場合は不要
  video.addEventListener('loadedmetadata', e => {
    video.muted = true; // streamの加工前音声を無効化
    video.play();
  });
});

HTML側でautoplay属性とmuted属性を指定した場合は、onloadedmetadataのコールバックを指定する必要はない。

video.html
<video autoplay muted></video>

ポイントは、加工前のSourceStreamの音声を再生しないことである。HTML側でmuted属性を付与するのが一番手っ取り早い。
試しにmutedじゃなくてもgetAudioTracksしてstopすればいいのでは?と思ったがダメだった。

failed.js
stream.getAudioTracks()[0].stop(); // 失敗例

SkyWayと組み合わせる際は、受け取った相手側でstreamにフィルターをかけるのが楽。

filter_skyway.js
const peer = new Peer({ key: 'your API key' });
peer.on('call', call => {
  // 事前にgetUserMediaで取得したMediaStreamを渡す
  call.answer(localStream);
  // 相手のMediaStreamを受け取ると発火
  call.on('stream', stream => {
    ApplyFilter(stream); // ここでフィルターをかける
  });
});

おまけ

複数のフィルターをかけたい場合は、かけたい順にconnectしていけばOK。

multi_filter.js
audioSource.connect(filter1);
filter1.connect(filter2);
filter2.connect(filter3);
filter3.connect(audioContext.destination);

参考文献

Web Audio API の基礎
BiquadFilterNode



ツッコミどころ等あったらコメントで教えて頂ければ幸いです。

108EAA0A
C++歴4年のC++er 使用言語:C/C++17/Java/C#/Python/PHP/Laravel/JS/TS/Node.js GoとReact勉強中
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away