Unityゲーム開発におけるAndroidのサウンド再生遅延対策

# まえがき：Androidでは音の再生が遅延する

Androidにおけるサウンド再生は、プログラム内で「音を鳴らす！」と命令してから実際にスピーカーから音が出るまで大きめの遅延が発生します。
10~30msぐらいの遅延なら気にならないのですが、端末によっては300ms (！)遅延するものもあります。
遅延が生じやすいこと、遅延具合が機種ごとにバラバラであることがゲーム開発者を苦しめています。

しかも、ながらく内部で「AudioTrack」と呼ばれる比較的高レイヤーのAPIを使っていたせいで、より遅いという状態が長く続いていました。
Unity 2019.1beta4ではOpenSLを使った実装に変わったそうです。

https://unity3d.com/jp/unity/beta/2019.1
これで万事解決...ならよかったのですが、

> Android: Use OpenSL instead of AudioTrack on more devices. This reduces audio input and output latency.

~~**on more devices.** というのがおそらく味噌でして、すべての端末はサポートされるかどうかは不明です。
どのみち某シリーズや某シリーズなどの端末は遅延が発生しうるものと思われます。~~

"on more devices."ということで、「OpenSL対応端末が増えた」という意味になります。以前はいくつかの端末でOpen SLの「Low_Latency」設定が無効になっていて対応できなかったそうなのですが、今後はハードウェア的に問題ない端末ならOpenSLを動かすようにしたそうです。たとえばGalaxy S7などはLow_Latencyが無効になっていたそうです。 ( @tsubaki_t1 さんコメントをありがとうございます！）

OpenSLを使っていようが他のAPIであろうが、Androidにおける低レイヤー層での音声再生はドライバやハードウェアによって挙動が異なり、バッファサイズを間違えると簡単にノイズまみれになってしまいます。

Pixel 3やXperiaシリーズなどはレイテンシーは大変良好なのですが、「高性能を謳っているのにレイテンシーは最悪」という端末はまだまだあります。というか、これからも生まれます....なぜ....。

Android Oreoからは「AAudio」という新しいオーディオ再生システムが搭載され、レイテンシーに改善があったらしいですが、ゲームの動作対象をOS 8.0以上に切ってしまえるのはもう少し未来になりそうです。

#### 2020/9/10追記：
製品版「ADX」と無償版「ADX LE」において、低遅延再生モードよりもさらに性能が高い機能「SonicSYNC」が利用可能となりました。
本ポストでは低遅延再生モードの利用方法について解説していますが、これはもう古いので、最新版ADXのライブラリを入手していただき「SonicSYNC」をご利用ください。

SonicSYNCとは
https://game.criware.jp/manual/unity_plugin/latest/contents/atom4u_keys_sonicsync.html

SonicSYNCは、CriWareInitializer の各プラットフォーム初期化コンフィグの「Enable SonicSYNC」チェックボックスを有効にすると利用できます。

#### 2020/6/18追記：
製品版「ADX2」において、CRIWARE Unity Plug-in 3.01.00から「AAudio」経由での再生にも対応しました。Android 8.0以降限定になりますが、後述のADX2の低遅延再生モード+AAudioの組み合わせでさらに再生遅延が改善されるようです。

UnityですぐAAudio再生が可能になる、というだけでも恩恵があります。気になる方はSDKを入手して比較検討するとよいでしょう。

https://criware.info/criware-unity-plug-in-3-01-00/


## CRI ADX2の低遅延再生モードを使用する
Androidにおいてなるべく遅延を解消したい、かつ対応端末すべてのネイティブライブラリを書くのは無理...という場合は、「CRI ADX2」の低遅延再生モードの利用が近道です。

CRI ADX2は、CRI・ミドルウェア社が販売している統合型オーディオミドルウェアです。ゲームに組み込むランタイムと、再生用のデータを作るオーサリングツールの2つで構成されています。
個人開発向けの無償版「ADX2 LE」と、法人向けの有償版「ADX2」があります。

統合型サウンドミドルウェア CRI ADX2 for Smartphone
https://game.criware.jp/products/adx2-smartphone/

無償版サウンドミドルウェア CRI ADX2 LE
https://game.criware.jp/products/adx2-le/

いくつかオーディオ向けのライブラリやミドルウェアがある中で、Android向けの改善策をサポートをしているのはADX2だけです。
そういう理由で、配信されているAndroidの音ゲーアプリでは、「CRIWARE」のロゴが出ているタイトルがたくさんある理由です。

# 低遅延再生モードの最短実装
UnityプロジェクトでADX2の性能テストをするための最短手順について説明します。
Unity Editorへの「ADX2 LE」導入手順については、こちらのチュートリアルをご覧ください。
ADX2で音を一つ鳴らすまでの手順が書かれています。

Unity 2018のサウンド機能をADX2で強化する
https://qiita.com/Takaaki_Ichijo/items/16e6501fc07f5b3b3377

こちらのQitta記事の続きで、１音再生してみた音をそのまま低遅延再生モードを適用してみましょう。

## 低遅延再生用のボイスプール数を指定
CRIWARE Library Initializerコンポーネント（スクリプトはCriWareInitializer.cs）にて、低遅延再生用のボイス再生数を設定します。

![Android低遅延音声再生の設定.png](https://qiita-image-store.s3.amazonaws.com/0/19010/fcfe9fd4-7d50-f1ce-3037-571d30b78bf8.png)

メモリ再生（圧縮音声ファイルをメモリにロードしてから再生）とストリーミング再生（ストレージから少しづつ圧縮音声ファイルをロードしながら再生）の2つの設定があります。

ストリーミング再生は読み出しで遅延が起きることがあるので、ほとんどの場合はメモリ再生で低遅延再生を利用します。
多重にならさない場合は、ここに「1」と記入すれば同時に１音だけが再生されます。
連打が想定される場合は、2または3を設定します。

ストリーミング再生で低遅延再生モードを使用したい場合は、ファイルをロードするためのハンドルがその分だけ必要になります。
Streaming Voicesに入れた数値の数分だけ、CriWareInitializerインスペクター上部のNumber of Lordersの数を増やしてください。
なお、低遅延再生のボイスプールの最大数はメモリ再生・ストリーム再生の合計で27までです。

## 低遅延再生用のAtomSourceを用意する
次に、低遅延再生モードを使用するCri Atom Sourceコンポーネントを用意します。
当該のAtom SourceのLow Latency Playbackにチェックを入れるだけです。

![低遅延再生時のAtomSource設定.png](https://qiita-image-store.s3.amazonaws.com/0/19010/82702520-55fe-93a5-1a62-da9d5c6bd070.png)

スクリプトでは、CriAtomSource.androidUseLowLatencyVoicePoolプロパティから操作できます。

以上で準備完了です。
シーンにボタンを配置し、AtomSourceのPlay関数を呼び出してください。
コツは、指を離したときにコールされるOnClickではなく、推した瞬間に呼ばれるOnPointerDownイベントで呼び出すことです。
Unity標準のAudioSource再生も同時に実行できますので、ボタンを並べてテストしてみましょう。
（設定でDisable Unity Audioを有効にしている場合はチェックを外しましょう）

## 諸注意
低遅延再生モードでは、いくつか機能制限があります。

* HCA-MXコーデックを使っていると効果がありません。
(負荷軽減コーデックHCA-MXについては [こちら](https://qiita.com/Takaaki_Ichijo/items/c0f196b1081b26ae77f2) )
* 低遅延再生の同時最大再生数はメモリ再生・ストリーム再生あわせて27まで。
* ピッチ調整やリバーブなどのエフェクト機能などは使えません。
* 低遅延再生モードで再生した音声データは、ADX2の「DSPバス」の処理を経由せずに出力されます。DSPバスを使ったエフェクトやボリューム調整の影響を受けません。

# サウンド再生遅延推測機能でタイミング調整をする
ADX2の低遅延再生モードを使ったとしても、Androidの音声再生遅延はチップセット、ドライバ、ハードウェアの問題ですので、ソフトウェアで可能な補正にも限界があります。
スマホ向けの音ゲーでは「音を鳴らす」タイミングのズレを予測してタップするタイミングでいい感じに音を鳴らすために、タイミングの補正機能がよく見られます。

ここでADX2のサウンド遅延推測の機能を使って端末ごと遅延を計測し、補正の参考値として使用します。
初めてアプリを起動したときに内部で1度だけ計測を行い、それをタイミング補正機能のデフォルト値として設定するとよいでしょう。

なお推測値は、アプリや端末の状況によって取得できる値にばらつきがあります。また、上記の「低遅延再生モード」を使っている音は推測の対象外です。

### 遅延時間を推測するタイミング
再生遅延時間の推測結果は、起動するたびにある程度の数値のばらつきが生じる場合があります。
ゲームで利用する場合は遅延時間を毎回推測しなおしてしまうとユーザーが混乱する可能性がありますので、一度推測値を記録したら以後は自動では更新しないようにするといいでしょう。

### 遅延推測のサンプルコード

遅延推測を行ってUI.Textに表示するサンプルコードです。

```Csharp 
using UnityEngine;
using UnityEngine.UI;
        
    public class RatencyTest: MonoBehaviour
    {
        public Text ratencyTestText;

        public void EstimateAudioLatency()
        {
            CriAtomExLatencyEstimator.InitializeModule();

            while (CriAtomExLatencyEstimator.GetCurrentInfo().status == CriAtomExLatencyEstimator.Status.Processing)
            {
                yield return null;
            }
    
            CriAtomExLatencyEstimator.EstimatorInfo info = CriAtomExLatencyEstimator.GetCurrentInfo();
    
            if (info.status == CriAtomExLatencyEstimator.Status.Done)
            {
                ratencyTestText.text = "Latency " + info.estimated_latency;
            }
            else
            {
                ratencyTestText.text = "Latency Error";
            }
    
            CriAtomExLatencyEstimator.FinalizeModule();
        }
    }

```


## ワイヤレスイヤフォンによる遅延に注意
Android Oreo以降、ワイヤレスイヤフォンはユーザーが自在にコーデックや伝送形式を変更できるという~~割と最悪な~~実装になっており、これらは大きな遅延の原因となります。
ユーザーに遅延調整の設定をさせる時は、「ワイヤレスイヤフォンは使わないでね」のようなダイアログで促すとよいでしょう。

## 低遅延再生モードで音にノイズがのる場合
冒頭で「多くの音ゲーに使われている」と紹介しましたが、使われているだけ多種多様な端末での使用実績があり鍛えられたライブラリになっています。
ただ、比較的新しい端末で音にノイズがのってしまう場合は、CRI社がまだテストや最適化を行っていない可能性があります。

ゲーム開発会社（法人）の場合はサポートへの問い合わせが可能ですので、サポートサイトで聞いてみましょう。
https://game.criware.jp/

個人開発者の場合は、下記のフェイスブックページで相談が可能です。
ADX2ユーザー助け合い所
https://www.facebook.com/groups/adx2userj/