Edited at

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


概要

最近、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



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