2017/07/17 (2017年07月 のアーカイブ)
WebAudio APIでの過大振幅信号処理の闇
とある WebAudio を使ったアプリで思うような音が出ない現象があり、少し調べた所信号がレベルオーバーした時の挙動がブラウザによって違うのが原因のようです。WebAudio API では通常、音声信号を -1.0 ~ +1.0 の範囲の値で扱います。これは単なる数値データですのでオーディオグラフの処理の途中段階ではどのような値になっても良いのですが、最終的に AudioContext の destination に接続する際には -1.0~+1.0 が 0dBFS つまりフルスイングの信号となります。
問題はこの範囲外のデータを生成してしまった時にその値がどう扱われるかなのですが、WebAudio API のスペックでは今の所規定されておらず実装依存になっています。実際にはクリップしてしまったり強制的に振幅を調整するなど、ブラウザや OS によって処理の仕方がバラバラで場合によってはまずい事になる可能性があります。
ざっとまとめたのが次の表です。
OS | Browser | Description |
---|---|---|
Windows | Chrome | 長い時定数でコンプレッションがかかり振幅が強制的に調整される |
Firefox | 短い時定数でコンプレッションがかかり振幅が強制的に調整される | |
Edge | destinationに接続された時点で信号が -1.0 ~ +1.0 の範囲にクリップされる | |
Mac(内蔵スピーカー) | Chrome | そのままMacの音量調整を通して出力可能な範囲で出力される |
Firefox | そのままMacの音量調整を通して出力可能な範囲で出力される | |
Safari | destinationに接続された時点で信号が -1.0 ~ +1.0 の範囲にクリップされる | |
Mac(USB-I/F) | Chrome | destinationに接続された時点で信号が -1.0 ~ +1.0 の範囲にクリップされる |
Firefox | destinationに接続された時点で信号が -1.0 ~ +1.0 の範囲にクリップされる | |
Safari | destinationに接続された時点で信号が -1.0 ~ +1.0 の範囲にクリップされる |
Edge と Safari の処理が素直と言えば素直なのですが、WebAudio API の処理グラフから destination に出力される時点で信号が範囲を超えているとハードクリップされ、歪みが発生します。アプリとしてはこの Edge、Safari で歪みが発生しないようにオーディオグラフ内で適切に信号の振幅を管理するのが本来の姿だと思います。
Mac での Chrome、Firefox の場合は、内蔵スピーカーか USB 接続オーディオI/Fかによって挙動が異なるようです。USB 接続のオーディオI/Fの場合は Safari や Windows の Edge 同様に destination に接続された出力が -1.0~+1.0 の範囲にクリッピングされ、それ以上の振幅の信号は歪みが発生します。しかしどういうわけか内蔵スピーカーを選択している場合はクリッピングされず、信号の振幅が大きくてもそれに合わせて Mac のボリュームを下げていると歪みが発生しません。もちろんこの状態で Mac のボリュームを上げていくと歪みが発生しますが、これはアナログ回路的な出力振幅の限界のようです。
Windows の Chrome および Firefox の場合はまた少し違った挙動があります。出力信号が範囲を超えている場合は自動的にコンプレッションがかかり、ピークがクリップしないように自動的に調整されるようです。Windows でだけ起こる現象のため、OS 側のカーネルミキサー等が関わっているのかも知れませんが、Chrome と Firefox では過大信号がなくなった後の復帰時間、いわゆるコンプレッサーのリリースタイムが明らかに異なるので、どこでこの処理が行われているのかは良くわかりません。
と言う事で、とにかく気を付けないといけないのは、Windows で Chrome / Firefox を使っている場合、また Mac で内蔵スピーカーを使っている場合には、信号の振幅が -1.0 ~ +1.0 を超えていても歪まないので気が付かない可能性があるという事です。いい感じに鳴っていると思ってもブラウザや再生環境が変わるとバリバリに歪んでいるという事が起こるかも知れません。
割とやりがちなのが下の図のように複数のオシレータを並列に繋いでしまった時のピークの最大値をちゃんと考えていない、とかですね。そもそもオシレータの出力は -1.0~+1.0 のフルスイング信号で、並列接続した場合には単純に加算されますので、ワーストケースではピークが重なるとオシレータの数だけ出力ピークが上昇します。
なお、ポリフォニックシンセのような同時発音数が多い音を扱う場合は全てのサウンドソースのピークが重なるようなワーストケースを考えると1音あたりの音量が取れなくなってしまいますのである程度のヘッドルームを作りつつワーストケースではソフトサチュレーションさせたり、ゲーム系の音処理のような場合には明示的に DynamicsCompressor を使ってピークを抑えたりするのが一般的かと思います。
Posted by g200kg : 2017/07/17 03:51:01