スマホ実機でサウンドのスペクトル解析を見たい

f:id:hirasho0:20190823160703p:plain

こんにちは。技術部平山です。

今回は小ネタで、実機上にサウンドのスペクトル解析を表示するツールを作りました。 ソースコードはgithubに置いてあり、 持っていってテキトーなgameObjectにつければ動きます。 ImageやTextと同様、 UnityEngine.UI.Graphic の派生クラスとして作ってあるので、canvasの下流に配置してください。

これは何?

AudioListener、つまり出力される音をスペクトル解析して、 その周波数成分をグラフに出すものです。

以前の記事 で作った波形生成サンプル に組み込んだ動画をご覧ください

262Hzのドから順に、レミファソラシド、と鳴らし、最後にドミソシの和音を出しています。 出しているのは純粋な正弦波ですので、それぞれの音は細いトゲのような形で可視化されます。 縦軸は強さ(デシベル)、横軸が周波数(Hz)です。 右上のグラフの周波数成分が変化するのがおわかりでしょうか。 横軸は太い縦線が1000Hzで、縦線一本ごとに周波数が半分、 縦軸は太い横線が0dbで、横線一本ごとに6dbです。

なお、サウンドのフィルタリングの記事サンプル にも同じものを組み込んであります(冒頭の画面写真です)。 こちらは外部ファイルを読めるようにしたりと、実際に使うことを考えた拡張をしています。

動機

なんでこんなものを作ったかと言うと、「サウンドの圧縮の結果どれくらい周波数成分が崩れてるのか可視化したい!」 という要望があったからです。

高周波、つまり高い音ほど、多くのデータ量を必要とします。 音を圧縮する時には、高い音を削り落としてしまうのが常道でして、 その結果、周波数成分をグラフに出すと右の方が削れてきます。

もちろん、耳で聴いて問題なければそれで良いですし、 逆に、グラフで見て大丈夫そうでも耳で聴いてダメならダメなわけですが、 「明らかに削れてる」ケースは目で見た瞬間にわかりますから、 チェックの手間が多少はマシになります。

Unityの場合圧縮はインポート時に行われ、 サウンドアーティストが自分で圧縮設定をして圧縮したファイルを渡すことはできませんから、 最終的にどういう圧縮をされたかはUnityEditorか、 あるいはビルドした実機で見ないとわからないわけです。 これはテクスチャの圧縮に関しても同じ問題がありますね。

実際、とある音素材をいろんな設定で圧縮して見たのが以下です。

f:id:hirasho0:20190823160709p:plain

f:id:hirasho0:20190823160706p:plain

f:id:hirasho0:20190823160659p:plain

順に、Vorbisのquality=35、quality=1、mp3の64kbps、です。 mp3は別ソフト(Audacity)で圧縮して、 無圧縮PCMとしてインポートしたものです。

quality=35のvorbisでは16kHzを超えるところまでグラフが動きますが、 quality=1にすると15kHzから上は削れてしまいます。 そして、64kpbsのmp3だと8kHz以上はなくなってしまっています。 こうなるとかなりガッカリする音になります。

また、他の用途としては、 実行時にフィルタをかけた場合にかかり方を確認するのにも良いかと思います。

使い方詳細

コンポーネント一個なので、gameObjectにくっつけて終わりです (プレハブにして良ければ冒頭の写真のように数字を出したりできるのですが、 導入の楽さを優先して線だけでの描画としました)。 inspectorの設定はデフォルト設定で良ければ不要です。

一応inspectorの項目について述べますと、以下のような感じです。

f:id:hirasho0:20190823160657p:plain

  • Sample Count: 多いほど特に低音部のデータの精度が増えます。CPU負荷に問題なければこのままで良いと思います。
  • Bin Count: グラフの横解像度です。減らせばUIの描画負荷を減らせます。
  • Graph Center Frequency: 太い縦線を何Hzにするかです。デフォルトは1000。
  • Graph Min Frequency: Hzの最低値です。デフォルトは20。
  • Graph Max Frequency: Hzの最大値です。デフォルトは22000。
  • Graph Max Db: 縦軸の最大値(デシベル)です。デフォルトは6dbですが、0でもいい気はします。
  • Graph Max Db: 縦軸の最小値(デシベル)です。デフォルトは-78dbですが、-60くらいでもいい気はします。

なお、コンポーネントのenabledがtrueである間、 AudioListener.GetSupectrumData()の負荷と、 UIの更新負荷が丸ごとかかりますので、 不要な時に出さないよう注意が必要です。 canvasの頂点変更はタダでも重い処理であり、しかも結構な頂点数ですから、 間違っても売り物に入らないようにしておく必要があります。

実装

実装はAudioListener.GetSpectrumData()をUpdateごとに呼んで、 出てきたものを周波数ごとに分類してグラフを描く、というだけのものです。

GetSpectrumData()が詰めてくれるfloat配列の中身は、 直近の出力波形データをフーリエ変換したものです。 もし4096要素の配列を渡せば、 4096個の周波数成分について、それぞれの強さが得られます。

k番のデータが対応する周波数Fkは、 出力周波数(AudioSettings.outputSampleRate) をFoとして、

Fk = Fo * 0.5 * k / 4096

です。仮に出力周波数が44100Hzである場合、 0番はkが0なので、0Hz。 1番は44100 * 0.5 * 1 / 4096なので、5.38Hz。 一番上の4095番は、44100 * 0.5 * 4095 / 4096なので、22045Hz、となります。 44100に0.5を掛けて半分にしているのは、離散フーリエ変換の結果はサンプリング周波数(今の場合44100Hz) の半分以上の所は、半分以下の所を裏返した値になる、ということがわかっているからです。 下半分だけもらえれば十分なわけですね(最初実装した時には周波数が倍ズレていて、このせいでした)。 このあたりについては、 Unityのフォーラムに詳しい解説をしている人がいます

あとは、表示に使えるように分類を行います。 表示は周波数を対数的に表現しているので、 例えば1Hzから16Hzを4段階で表示するとしたら、

  • 1から2
  • 2から4
  • 4から8
  • 8から16

の4つに分類して合計を求め、これをグラフにします。 この「分類した一個一個」をよくbin(ビン)と呼び、ヒストグラムを作る処理では良く出てくる言葉です。 デフォルトでは512個のビンを用意しています。

なお、下の方のbinほど対応する元データの数が減るので、 低音のデータが欲しければGetSpectrumDataに大き目の配列を渡す必要があります。 ちなみに、inspectorのSample Countはこの配列のサイズです。

あと実装に必要な知識は、

  • UnityEngine.UI.Graphic.OnPopulateMeshで頂点を詰める方法
  • 対数変換に慣れ親しんでいること

といったところでしょうか。縦軸もデシベルですので、 GetSpectrumDataから出てきたものを対数変換してグラフの高さを求める必要があります。

終わりに

さて、こういうことができるとして、実際どう使うのがいいでしょうか。

音素材ファイルの数はゲームによっては千とか万になり、 いちいち音質のテストなんてしていられません。 おそらくは、最初にいくつかのファイルで圧縮設定ごとの音質を調べて、 種類ごとにパラメータを確定させ、 スクリプトで一括設定してもう個別には見ない、ということになるかと思います。 このツールはその「最初の設定を決める時」にしか使えないかもしれません。 ただ、BGMに限って言えばせいぜい数十でしょうから、 個別にチェックすることもできるかと思います。

なお、vorbisの標準ビットレートは112kbps(112kbit/s=14kB/s)でして、 それくらいの設定であれば「一般人には原音と区別がつかない」と言われています。 Unityの2019.1.3で試した限り、この112kbpsになるのは、 インポート時のquality設定が35の時です。 qualityを1にすると、45kbpsにまで減ります。 容量は半分以下になりますが、音質はかなり落ちます。 その間のどこで行くかは製品次第かと思います。

ちなみに、mp3は 同じ容量であればvorbisよりは劣化が気になります。 上で示したように、64kpbsあっても、45kpbsのVorbisよりも 音質が悪いのです。それがグラフにも現れています。 8kHz以上が削り落とされてしまった結果、 ずいぶんと遠くから聞こえてくる音のようになってしまうのです。 vorbisの場合64kbps(unityの設定では16)でも、 16kHz近くまで残っており、結構聞ける品質であるように思えます。

2019.1.3ではすでにmp3を選べなくなっているので、 普通にやればvorbisになるとは思いますが、 古いUnityを利用されている方はご注意ください。

また、インポートする元素材がそもそもmp3だった、 というようなことがあれば、当然音質は落ちます。 無圧縮のwavデータをUnityにインポートするのが理想ですが、 Unityのリポジトリが肥大化するのを嫌って、 圧縮してからインポートするケースもあるでしょう。 その場合は劣化を覚悟してやることになります。