WebAudioAPIとWebWorkerとの通信オーバーヘッド

Web Music Developers JPのアドベントカレンダー 21日目の記事です。

今日はWebAudioAPIのオーディオスレッドからWebWorkerを呼び出す際のオーバーヘッドについて書きます。ここでの内容はChrome限定で、Safariでは話が違ってきます。

オーディオスレッドとは?

まずは以下の実験を見てください。音量は絞ってますが音がなるのでご注意を。

  • startでカウンターが回り始めます。
  • alertでアラートダイアログが表示されます。

ここでおかしなことが起こります。ダイアログが表示されている間も音が鳴り、カウンターが回り続けてるんですね。Javascriptはシングルスレッドです。スレッドがダイアログによりブロックされているのに、いったい誰がカウンターを回しているのでしょう?

答えはオーディオスレッドです。具体的には下のようになっています。

var ctx = new webkitAudioContext(); 
var node = ctx.createJavaScriptNode(2048, 1, 1); 
node.onaudioprocess = function(){ 
  // この中がオーディオスレッド
}

WebAudioAPIにはこういう特殊なプロセスが存在しているようです。内部の実装が気になるところですね。ちなみにSafariではsetInterval()などでカウンターを回したときと同じような動作になります。

オーディオスレッドからのWebWorker呼び出し

では本題のオーディオスレッドとworkerの通信オーバーヘッドについて実験で計測してみましょう。

ここでの動作は上から順番に以下の通りです。

  1. onaudioprocess内で正弦波を合成する
  2. onaudioprocessからworkerにpostMessage()後、addEventListener()により正弦波を受け取る
  3. 2. のonaudioprocessの変わりにsetInterval()を使う

Chromeで実験するとレイテンシが3<1<2となります。つまりオーディオスレッドからworkerを呼ぶと、普通にworkerを呼ぶよりパフォーマンスが悪いようです。

WebAudioAPI仕様に関する議論

何でこんなことになるのでしょうか?興味深い議論をW3Cのメーリングリストで見つけたので抄訳して紹介しておきます。

a sample to use Web Worker with current JavaScriptAudioNode

JavaScriptAudioNodeとworkerを使ったサンプルを作ってみたよ。

Re: a sample to use Web Worker with current JavaScriptAudioNod

そのやり方は現状正しい。しかし良いパフォーマンスを得ようとすればworkerスレッドからコールバック関数(*1)を直接コールできるようにする必要がある。ここでの問題はメインスレッドからworkerスレッドを呼ぶという余計なスレッドホップにより遅延が発生してしまうことだ。

(*1) onaudioprocessに設定したコールバック関数

つまり現状はworkerスレッドからオーディオスレッドを呼べないのでパフォーマンス的にちょっとなぁ、と言うわけです。

それを実現しようという話もありました。

Re: Web Audio API and Web Workers

コールバック関数の代わりに、createJavaScriptProcessingNode関数にWeb Workerを食わせてみてはどうだろうか?

これはシンプルな解ですね。これが出来るようになると目の前がパーッと明るくなりそうです。

正直WebAudioはまだ実用の域に達していないと感じさせられる部分が多いのですが、その中でハックしていくのがWebAudioの面白みでもあると思います。何はともあれ今後の議論が楽しみですね。

以上、オーディオスレッドとWebWorkerにおける通信オーバーヘッドについてでした。同じWebkitでもSafariだと違う結果が得られるので、興味があれば試してみてください。