getUserMediaで取得したMediaStreamをWeb Audio APIで処理してWebRTCに戻す方法を探る
使用したブラウザはGoogle Chrome Canaryです。
stableのChromeでもchrome:flagsで「getUserMedia() と Web Audio API の使用によるライブ音声入力を有効にします。」を有効にすれば試せますが、Canaryよりも不安定なようなのでCanaryで試す事をお勧めします。
まだ、Working DraftにもなっていないEditor's Draftが前提です。
CanaryはEditor's Draftの仕様を既に取り込んでいるようです。
WebRTCを使ったビデオチャットの大まかな流れ
MediaStreamをvideo要素に設定(ローカルの映像と音声)。
別途取得した、RTCPeerConnectionにMediaStreamを設定(送信用)。
ICEやSDPを使って相手の接続情報を取得しリモートに接続(ここ、超適当な説明)。
リモートのMediaStreamを取得し、video要素に設定(リモートの映像と音声)。
Web Audio APIの概念
数珠つなぎの処理は、分岐や合流も可能になっています。
米Yahoo!のPipesやMac OSX標準のQuartz Composerを思い浮かべると良いかもしれません。
音声処理の世界では一般的な考えらしいです。
AudioNodeにはローパスフィルタやダイナミックコンプレッサー等の一般的なものが最初から準備されています。
また、波形データをJavaScriptで弄る事ができるプログラマブルなAudioNodeもあります。
getUserMediaで取得したMediaStreamをWeb Audio APIで処理してWebRTCに戻すには
以下の2つがそれに該当します。
MediaStreamAudioSourceNode
MediaStreamを入力先にできるAudioNode。
AudioContext#createMediaStreamSource()で取得。
(Working Draftに載っています。)
MediaStreamAudioDestinationNode
MediaStreamを出力先にできるAudioNode。
AudioContext#createMediaStreamDestination()で取得。
MediaStreamAudioDestinationNode#streamからMediaStreamを取得可能。
(まだ、Editor's Draftにしか載っていません。)
今回は、ノイズ除去を行うためにローパスフィルタを使ってみました。
BiquadFilterNode
双2次フィルタを扱うためのAudioNode。
AudioContext#createBiquadFilter()で取得。
今回はノイズ除去用のローパスフィルタとして使用。
getUserMediaから取得したMediaStreamをWeb Audio APIに渡す
スピーカーに直接音を渡すAudioContext.destinationに接続すると、正常に音がWeb Audio APIに渡っているのが確認できます。
var mediastreamsource;
var lowpassfilter = audioContext.createBiquadFilter();
lowpassfilter.type = 0;
lowpassfilter.frequency.value = 440;
function initialize() {
navigator.webkitGetUserMedia(
{audio : true},
function(stream) {
mediastreamsource = audioContext.createMediaStreamSource(stream);
mediastreamsource.connect(lowpassfilter);
lowpassfilter.connect(audioContext.destination);
},
function(e) {
console.log(e);
}
);
}
window.addEventListener("load", initialize, false);
Web Audio APIからMediaStreamを取り出しaudio要素に設定
大音量のノイズが再生されることがあるので注意が必要です。
再読込やCanaryの再起動等を何度か繰り返していると、たまにマイクの音がスピーカーから出力されることがあります。
出力されるのは正常な音ではなくブチブチ途切れるような音ですが、それでもWeb Audio APIからMediaStreamを取り出す事ができました。
正常に動作しないのは、まだ実装途中で不安定だからなのでしょう。
var mediastreamsource;
var mediastreamdestination = audioContext.createMediaStreamDestination();
var lowpassfilter = audioContext.createBiquadFilter();
lowpassfilter.type = 0;
lowpassfilter.frequency.value = 440;
var audioElement;
function initialize() {
audioElement = document.getElementById("audio");
navigator.webkitGetUserMedia(
{audio : true},
function(stream) {
mediastreamsource = audioContext.createMediaStreamSource(stream);
mediastreamsource.connect(lowpassfilter);
lowpassfilter.connect(mediastreamdestination);
audioElement.src = webkitURL.createObjectURL(mediastreamdestination.stream);
audioElement.play();
},
function(e) {
console.log(e);
}
);
}
window.addEventListener("load", initialize, false);
まとめと所感
Working DraftにもなっていないEditor's Draftなので致し方ありませんが、まともに使えるようになるにはまだ時間がかかりそうです。
音声チャット時に音声処理ができるようになると、ノイズリダクションやノイズキャンセラの可能性が出てきます。
また、音声送信前にボイスチェンジャーのようなことができるようにもなるため、匿名性を保った状態でボイスチャットを行えるようになる等の応用が考えられます。
映像に関しての同じような仕様がないか調べると、MediaStream Processing APIというDraftの中にHTMLCanvasElement#canvasというものが見つかりました。
実環境上で、canvas要素にstream属性がないか調べてみましたが、見当たりませんでした。