2016/05/13 (2016年05月 のアーカイブ)
Chrome:Web Audio APIのオートメーションの問題
今まで色々と Web Audio 関係のアプリを作ってきて、時々「あれ、おかしいなー」と思いつつずっと放置していた問題があったのですが一応原因らしきものにたどり着きました。症状としては Chrome で Web Audio アプリを長時間動作させるといつの間にか動作がおかしい / 止まっていたりすると言う感じになります。アプリの作りやマシンパワーで変わりますが、普通に音楽を鳴らすようなアプリだと起動してから1時間ほど経ってから症状が出たりしますのでなかなか因果関係が掴めませんでした。これはノードの各種パラメータを設定する AudioParam を時間軸で操作する setValueAtTime() 等のオートメーションメソッドを使うと徐々に全体の動作が重くなって行くのが原因です。
このあたりのBlinkのソースを読んだ事があるという@mohayonaoさんの話によれば、使い終わったオートメーションイベントが破棄されずに毎回新規イベントの挿入場所を効率の悪い探し方をしているそうなのでそのせいでしょう。
という事でテストプログラム :
var actx=new AudioContext();
var osc=actx.createOscillator();
var gain=actx.createGain();
gain.gain.value=0;
osc.connect(gain);
gain.connect(actx.destination);
osc.start(0);
function Trigger(){
var t=actx.currentTime;
gain.gain.setValueAtTime(0,t+0.1);
gain.gain.setValueAtTime(1,t+0.101);
gain.gain.setValueAtTime(0,t+0.102);
gain.gain.setValueAtTime(1,t+0.103);
gain.gain.setValueAtTime(0,t+0.104);
}
setInterval(Trigger,50);
という事でとりあえず今、これを回避するには
- オートメーションメソッドを使わない
- 使ったオートメーションを時々消してやる
- オートメーションを使う頻度と必要な連続稼働時間の兼ね合いで見なかった事にする
まだ不完全なものですが一応 2. の方向で多少楽になる関数を作りました。Polyfill的にするのはまた面倒なのであまり変更を必要としない簡単なHelper的なもので。まだ足りないメソッドもあります。
function Automation(ctx,param,v,t){
this.param = param;
this.cmdbuf = [];
this.Cancel = function(){
while(this.cmdbuf.length && ctx.currentTime > this.cmdbuf[0][2])
this.cmdbuf.shift();
};
this.Reque = function(){
this.param.cancelScheduledValues(0);
for(var i = 0,l = this.cmdbuf.length; i < l; ++i){
var buf = this.cmdbuf[i];
switch(buf[0]){
case 0:
this.param.setValueAtTime(buf[1],buf[2]);
break;
case 1:
this.param.linearRampToValueAtTime(buf[1],buf[2]);
break;
case 2:
this.param.exponentialRampToValueAtTime(buf[1],buf[2]);
break;
case 3:
this.param.setTargetAtTime(buf[1],buf[2],buf[3]);
break;
}
}
};
this.cancelScheduledValues = function(t){
this.param.cancelScheduledValues(t);
};
this.setValueAtTime = function(v,t){
this.Cancel();
this.cmdbuf.push([0,v,t]);
this.Reque();
};
this.linearRampToValueAtTime = function(v,t){
this.Cancel();
this.cmdbuf.push([1,v,t]);
this.Reque();
};
this.exponentialRampToValueAtTime = function(v,t){
this.Cancel();
this.cmdbuf.push([2,v,t]);
this.Reque();
};
this.setTargetAtTime = function(v,t,c){
this.Cancel();
this.cmdbuf.push([3,v,t,c]);
this.Reque();
};
}
var oscfreq = new Automation(audiocontext, osc.frequency);
oscfreq.setValueAtTime(440,t);
var actx=new AudioContext();
var osc=actx.createOscillator();
var gain=actx.createGain();
var gaingain=new Automation(actx,gain.gain);
gain.gain.value=0;
osc.connect(gain);
gain.connect(actx.destination);
osc.start(0);
function Trigger(){
var t=actx.currentTime;
gaingain.setValueAtTime(0,t+0.1);
gaingain.setValueAtTime(1,t+0.101);
gaingain.setValueAtTime(0,t+0.102);
gaingain.setValueAtTime(1,t+0.103);
gaingain.setValueAtTime(0,t+0.104);
}
setInterval(Trigger,50);
また、これそのものではないですが関連するバグレポートは既にChrominumのBugTrackerに既にあがっているようで、それが直れば一緒に解消されそうです。早く対処されると良いですね。
Posted by g200kg : 2016/05/13 12:18:48