Unityのサウンド再生負荷を軽減する複数のアプローチ

# サウンドの再生負荷はこわいよ

（* 2018/12/24：PC/Mac以外のビルド時にもHCA-MXが再生できるよう、サンプリングレート設定の項目を追記しました。）

Unityを使ってアクションゲームやシューターなど音がたくさん鳴るようなゲームを開発するとき、開発者が気にしなくてはいけないのは「再生時にかかるCPU負荷」です。

ほとんどのゲームにおいてサウンドは圧縮を用いているのですが、mp3やoggなどの圧縮データを再生する際にはCPUで展開処理を行う必要があります。PC版「タイタンフォール」ではCPU負荷をなるべく減らすため、全てのオーディオファイルを非圧縮のままゲームに組み込み、インストール容量48GBのうち35GBが非圧縮のオーディオデータになった、[という話もありました](https://www.eurogamer.net/articles/2014-03-11-why-the-pc-version-of-titanfall-is-a-48gb-install
)。

こうした展開にかかるCPU負荷は、特にスマートフォンにおいて大きな問題になりがちです。圧縮もしつつ、処理負荷を下げないといけないためです。

## 負荷軽減のアプローチ

サウンド再生の負荷軽減についてはいくつか方法が考えられます。

### 複数の音をwav段階でミックスする

リアルタイムでたくさん鳴らすのではなく、オフラインでミックスした波形データを作ってしまおうという考え方です。極端な例ですが、ゲーム中で「拍手」の効果音を鳴らそうとしたとき、手をたたく音をたくさん鳴らせばそのように聞こえるでしょう。しかし、CPU処理は100倍になります。

waveファイルの段階で「拍手」の音を1ファイルにしてしまえば、処理負荷は1回で済みます。

### 無駄な再生処理を省く

不必要に再生してしまっている部分を減らします。遠くで鳴っている効果音が小さく再生される、というのはほとんど聞き取れませんので、距離に応じてオフにするべきです。
また、ボタンの連打でSEが重なって鳴ったり、爆発音などが重なって鳴ったりする場合、音そのものもひび割れてしまい、悲惨な結果を生みます。

この考え方はスクリプトで作ることもできます。たとえばAudioSourceに再生処理をさせる前に、ゲームシステムからSE再生の命令を一度受け取りつつ、内部で同時再生数をカウントしておき、その上限を超えるものは再生しない...というシステムを組んで、この問題に対処することができます。ですが、こうした管理機構の開発は手間です。

本投稿で紹介するサウンドミドルウェアCRI ADX2には、こうした「再生数の上限設定」が可能なシステムが組み込まれており、スクリプト側で一切追加をしなくても、ツール側で音に対して「再生上限」の属性を埋め込んだり、音のカテゴリごとに再生上限数を決めたりすることができます。

### 再生負荷の軽い圧縮形式を使う

Unityではサウンドの圧縮形式をogg vorbisかADPCMを選択することができます。
oggは高圧縮率（10分の1程度まで目指せる）ですが、再生処理が重いです。
ADPCMは再生処理負荷が軽い分、圧縮率が低い(3.5分の1)ためデータ容量が増えます。そのうえ音質もよくないため、使える場面が限られます。
先にあげた例のようにwaveファイルのままで収録すれば展開処理をゼロにすることができますが、データ容量は巨大になります。

本投稿で紹介するCRI ADX2には「HCA-MX」という、圧縮率を高めつつ複数同時再生時の負荷を抑える特殊コーデックが入っていますので、これを使用する方法を解説します。

## ADX2のHCA-MXコーデックについて

ADX2には、ゲーム用に開発された圧縮コーデック「HCA」「HCA-MX」が搭載されています。「HCA」はoggやmp3と同等の圧縮率・品質のコーデックです。処理負荷もそれほど変わりませんが、CPUのスパイク（一時的に負荷がぐっと上がる）が無いように設計され、フレームレートの安定化に寄与します。また、メモリ使用量も小さく抑えるように考えられています。

「HCA-MX」はHCAの特殊モードと呼べるもので、1本だけ再生している際は処理負荷に変化はありませんが、多数再生した際に再生数 x 再生処理ではなく、徐々に再生数ごとの負荷が減っていきます。

![HCAMXの説明図.png](https://qiita-image-store.s3.amazonaws.com/0/19010/43115401-52a7-b8b9-4b22-37bc43a9873b.png)

これは、mp3 やoggなどの圧縮コーデックが1音ずつデコードしてからミックスダウンを行う処理であることに対し、HCA-MXは圧縮された状態のままミックス処理を行い、デコードを1回で終わらせる仕組みをとっているためです。
この処理アプローチに起因して、HCA-MXを選択した波形データは、ピッチの変更などいくつかのエフェクトが使用できなくなります。ですので、あまり音色を変えず、かつ大量に鳴ると予測される音については積極的に利用するとよいでしょう。

# ADX2で「発音数制御」と「同時再生に強いコーデック」を行う

それでは実際に、ADX2を使って負荷軽減対策をやってみましょう。

UnityのプロジェクトにADX2を導入する最短手順については、「[Unity 2018のサウンド機能をADX2で強化する](https://qiita.com/Takaaki_Ichijo/items/16e6501fc07f5b3b3377)」で解説していますので、はじめてADX2を導入する場合は参照してください。

## 発音数制御１　音ひとつに対し同時再生数上限を設ける
ADX2ではプログラムからリクエストする音の単位を「キュー」と呼んでいますが、このキューが再生リクエストされた場合の再生数上限を設定してみます。

CRI Atom Craftにおいては、キューを選択したときに下部に表示されるCueタブ内にこの設定があります。
「キューリミット」のチェックボックスをオンにして、最大同時再生数と「タイプ」を指定します。

![キューリミット(ADX2 LE).png](https://qiita-image-store.s3.amazonaws.com/0/19010/8d9dd4da-3519-b28a-93c8-92d460a10efe.png)

タイプには二種類あります。「先着優先」は、リクエスト数が上限を超えていた時、先にリクエストされた音が鳴り終わるまで再生をスキップするモードです。
1人のキャラクターのボイスを再生したいとき、リミットを１にして先着有線にすれば、声が重なる不自然な状況を回避できます。

「後着優先」はリクエスト数が上限を超えた時、すでに鳴っている音をキャンセルして再生する方法です。メニューのSEなと、音数を減らしつつ確実に鳴ってほしい音に適用するとよいでしょう。

![キューリミットの指定.png](https://qiita-image-store.s3.amazonaws.com/0/19010/4184ab62-99d8-8206-ad36-6911a2f5121b.png)

キューリミットを指定してから、F5キーでキューのプレビュー再生を行ってみます。F5を連打しても指定数以上再生されなくなったことがわかると思います。

これはすなわち、Unityのスクリプト側からこのキューの再生リクエストを投げまくっても指定数以上は音が同時に鳴らない、ということを示します。
スクリプトには一切手を加えず、多重再生の危険性がひとつ減りました。

## 発音数制御２　音の種類ごとに同時再生数上限を設ける

キューごとの再生上限数指定は、1つの音は多重に再生されなくなりますが、異なるキューが一斉に再生リクエストをかけられた場合はそのまま鳴ってしまします。

そこで、ADX2でカテゴリごとの同時再生数上限を設定してみましょう。

まずはAtom Craftの左上エリア、プロジェクト全体設定にある「カテゴリ」エリアにて、カテゴリ分けを作ってみます。

![カテゴリ設定のデフォルト.png](https://qiita-image-store.s3.amazonaws.com/0/19010/a1a9825b-c847-d4b4-6e49-dcb228119d58.png)

デフォルトの名前ではわかりにくいので、カテゴリグループ名（CategoryGroup_0）をゲームの場面に合わせた名前、カテゴリ（Category_0）を作成したいカテゴリ名に変更します。
カテゴリは、カテゴリグループ上で右クリックから増やすことができます。

<img width="304" alt="カテゴリの新規作成.png" src="https://qiita-image-store.s3.amazonaws.com/0/19010/5ea17fb6-89b8-6a88-bb05-e3742cb50402.png">

あとから変更もできますので、ひとまず以下のようなカテゴリを作ってみました。

![カテゴリの作成.png](https://qiita-image-store.s3.amazonaws.com/0/19010/f3af43d6-fd8c-9e3e-0074-17cce4d4bc22.png)

このカテゴリごとに同時再生数の制限をつけて、キューにカテゴリを指定することで、複数のキューが同時に再生リクエストを投げられた場合の発音数制御が可能になります。
キューのカテゴリ登録は、キューそのものをカテゴリへドラッグ＆ドロップするか、キュー側の設定で追加することができます。

![キューのカテゴリ登録.png](https://qiita-image-store.s3.amazonaws.com/0/19010/acf8d4f5-0eaa-80c2-6d8d-a0ded6d50de8.png)

さて、カテゴリで再生上限数を決めてみましょう。全体設定から「カテゴリ」を選ぶと、そのカテゴリに属するキュー一覧が確認できます。
カテゴリにも「キューリミット」を指定する項目があるので、チェックを入れて有効にします。

![カテゴリ側でキューリミットを指定する.png](https://qiita-image-store.s3.amazonaws.com/0/19010/4de06e1c-40a1-c288-d3f4-a5b788ebc752.png)

カテゴリに表示されているキューを選択してF5キーを押すとプレビュー再生できますので、連打して挙動を確かめましょう。
この場合は「後着優先」の処理になります。（先着有線だと超長いキューがカテゴリに入ってしまったときに再生リクエストが待たされるため）

これで、スクリプト側は変更しないまま、音の再生上限数を制御することができるようになりました。
ここで開設した「カテゴリ」は、カテゴリごとにエフェクトをかけたり、ボリューム調整も可能になっていますが、それはまた別の機会に解説します。

## 同時再生に強いコーデックの指定

前の節で説明したCRI ADX2の特徴である「HCA-MX」コーデックを使うためには、Atom Craft側の設定とUnity Editor側での設定が１カ所あります。

## Atom Craft側での設定
HCA-MXは圧縮形式なので、キュー単位ではなく波形ファイルごとに指定する形になります。
Atom Craft左下のマテリアルルートフォルダを選択し、圧縮形式を変更したいフォルダを選択します。

![マテリアルの一例.png](https://qiita-image-store.s3.amazonaws.com/0/19010/f3900cbc-dd7e-a87f-305b-cb4ec775d4ee.png)

圧縮形式は、波形ファイルごとの設定かマテリアルフォルダごとの一括設定が可能です。
HCA-MXを使用するとピッチシフトなどの一部のエフェクトが使用できなくなるため、たとえばボイスデータや一部のSEなど、ピッチシフトをしない波形データが入っているフォルダに対し、HCA-MXの使用を指定するとよいです。

（フォルダは右クリックから「新規オブジェクト」→「サブフォルダの作成」で作ることができます）

マテリアルフォルダを選択している状態で、ツール中央に表示されるプロパティリストの「エンコード[品質]」項目を確認します。
エンコーディングタイプの値をクリックし、メニューから「HCA-MX」を選択します。

![マテリアルフォルダでの圧縮設定変更.png](https://qiita-image-store.s3.amazonaws.com/0/19010/3ef2c4c7-afd3-2701-24f9-40ccd4131d69.png)

これで、このフォルダに入っている波形データはHCA-MX形式に変換されるようになりました。

次に、Atom Craft側のHCA-MXサンプリングレート設定を確認します。
HCA-MXを使用するためには出力時のサンプリングレート設定を統一する必要があります。
PC上ではデフォルト設定のままでも鳴るのですが、iOS/Androidビルド時には出力サンプリングレートのデフォルト値が異なるので、指定してあげる必要があります。
Atom Craftの「ターゲットコンフィグ - Public」を開いて、HCA-MXの設定を確認します。

![HCA-MX設定_v2.png](https://qiita-image-store.s3.amazonaws.com/0/19010/bde2082f-28be-a44f-c673-6084b8bbf1ea.png)

デフォルトでは32000Hzですので、この値をUnity Editor側にも反映します。

## Unity Editor側での設定

CriWareLibraryInitializerコンポーネントを含むシーンを開き、「Sampling Rate」プロパティにサンプリングレートを入力します。

![Initializse_v2_SetSamplingLate.png](https://qiita-image-store.s3.amazonaws.com/0/19010/0a66f169-c283-e53e-75f9-bfeaf60a0aa2.png)

Unity EditorでHCA-MXの再生が成功するけど、実機上で鳴らない...という場合は、この設定を見直してみましょう。

次に、HCA-MX形式の音声データが最大何音再生できるかバッファサイズを指定します。
プロパティの中から「HCA-MX Voice Pool Config」を開きます。

![UnityEditorMX設定_v2.png](https://qiita-image-store.s3.amazonaws.com/0/19010/7a6a6d06-fa34-8f23-fe9b-66e504232be3.png)

ひとまず、オンメモリ再生（圧縮データをメモリに乗せてから再生する）を最大20本程度に設定しておきましょう。
これで準備は完了です。

# まとめ

サウンド再生負荷を軽減するアプローチは複数あり、そのうち「無駄な再生処理をしない」という処理はスクリプトの工夫で何とかなります。
しかし、ogg vorbisのCPU負荷はどうにもなら無い部分があり、ある程度ゲームの規模が大きくなったらADX2のHCA-MXコーデックを試してみるとよいでしょう。

ADX2を導入することで、同時再生数上限もスクリプトを書かずに綺麗に管理することができます。
既存ツールの機能をうまく活用して、開発にかかる手間を軽減していきましょう！

# 追記
実際の処理負荷がどのくらい減るかは、端末の種類やゲームの内容によって全然違うのですが
Snapdragon660あたりのミドル端末でとりあえず計測してみようと思います。しばしお待ちを。