はじめまして。開発部の田辺です。
クライアントアプリ開発をしております。
2014年11月某日
企画T氏:「Androidアプリの発音遅延なんとかならんですか?」
田辺:「UnityならCRIのプラグイン使ってみたらどうですか?低遅延再生可能だそうですよ。」
企画T氏:「大人の事情でそれは難しいです。察してください。」
というやりとりから始まった今回の低レイテンシでの発音調査の件。デバイス叩く系の機能調査=面倒そうな匂いがプンプンするやつワクワクするお仕事です。
1.とりあえずUnity☓Androidアプリでの現状把握
そういえば、今まで音の遅延について気にしたことはなかったので、とりあえず現状把握。適当にサンプル作ってAndroid端末上でサウンドを鳴らしてみる。
条件としてはこんな感じ。
▼サウンドファイルのImport Settings
▼Project Settings > Audio
確かに遅い。Unity曰く低レイテンシな設定なのに。自分の脳がおかしくなければワンテンポ遅れて発音していると認識。Unityエディタでは遅延はなし(のはず)。実際感覚なんで、説得力にかけるということもあり、鳴音要求出してから発音までの遅延時間を計測を試みる。数字は正しい。
で、少々調べてみたところ、実際に鳴音状態については、どうもAudioSource.isPlaying一択らしい。Update()関数内だと正確な時間計測にならないだろうし、AudioSource.play()呼んだら、即時isPlayingが状態変更するだろうから無理かな思いつつも、一縷の望みにかけて確認。
方法としては、簡単に
<結果>
Unityエディタ:0.032232…
Android端末 :0.032718…
・・・やっぱりダメでした!
実際、発音の遅延計測方法あるかもしれませんが、とりあえず今回は他にもやることあるので遅延時間の数値化はスルー。 早速できるメンバーアピール失敗。すいません。
2.iPhoneではどうなのよ?
作ったプロジェクトをそのままiPhone(iPhone5)に転送してみる。
…こちらは問題無し。即座に発音していると認識。ということは、Androidの発音そもそも遅いんじゃないか疑惑が…
3.Android発音遅い疑惑!?
ということで、早速サンプル作成に着手。そういえばAndroidネイティブ本格的にやるまえにUnity触ったので、MediaPlayer以外知らない。ついでに調べることにする。
で、Android SDKには鳴音処理については3種類あるとのこと。
SoundPool
サウンドデータをオンメモリで扱う。再生遅延がほぼないとのこと。
そこそこ制限はあるものの、SE再生目的などでは必要十分。
登録したデータのIDのスタートが1から始まるので配列と組み合わせる場合は要注意。
データの登録数上限256 / 登録ファイルの再生時間は10秒までの制限あり。
データをアンロードしても登録数のカウントは減らないので、追加する場合はインスタンスを追加する必要あり。
AudioTrack
オンメモリなStaticとストレージから随時ロードしてくるStream再生両方対応。
バッファに流し込むデータはサウンドファイルから波形データのみを抜き出す必要があるため、実装となるとやや面倒なやつ達成感あるやつ。(逆に波形データ加工とかする場合はこれを使うのが良さげ。)
バッファへの書き込みはブロッキングモードのため、Steam形式では別スレッドでの書き込み必須。
MediaPlayer
万能プレイヤー。とりあえずサウンド再生するなら、これ使っておけ的なもの
楽。これに尽きる。
で、それぞれ試した結果…
(遅延大)MediaPlayer > AudioTrack > SoundPool(遅延小)の順番で遅延が少ない。
SoundPool、iPhoneやUnityエディタ上で再生しているものと同等でほぼ遅延なし。ということは、AndroidではUnity側の鳴音処理が遅いと推察。MediaPlayerつかってる? 疑ってごめんよAndroid。
検証結果から、UnityからSoundPool直接叩けば、Unity x Androidでも遅延なしに近い形で再生可能になるのではないかと想定。Androidプラグインをつくろう…。
4.Androidプラグインをつくる
ということで、先ほど作ったサンプルを元に、Android用のプラグイン作成。 Eclipseからエクスポート → Java → JarファイルでJarを吐き出すだけでOK。その際は、クラスファイルだけ入っていれば良いので、不要なものは含めない。
5.プラグインからのサウンド処理呼び出し実装
作成したプラグイン(jar)の機能を使う場合、Unityのスクリプトとしてこんな感じに書けば良い。
とりあえず、今回は遅延なしのSoundPoolをターゲット。 プラグイン呼び出しコードはこんな感じ。
あとは、StreamingAssetsにサウンドファイルを入れてそのファイルパスをSoundPoolに渡してやるのみ。
Application.streamingAssetsPath を使って抜かりない実装した後、ビルドして端末に転送!
ゴールが見えてきた!
6.鳴らないSoundPool
おや?鳴らない。おかしいな。 パスは合ってるのにSoundPool側のロードで失敗している。常にIDが-1。
何度やっても結果は変わらない…。
あ… もしかしてStreamingAssetの中身って圧縮されたまま?(インストール時に展開されてないのか…)
とりあえず、StreamingAssetsから一回ファイルを取り出し後、アプリのデータ領域に書き出して、そこからロードする方式に変更。
パス指定にはApplication.persistentDataPathを使う。
再チャレンジ!
7.遅延解消!!!
わかりにくいですが、上からSoundPool / MediaPlayer / AudioTrack(Static) /AudioTrack(Stream) / AudioSource(Unity)の順です。
音がなりました!Unity☓Android端末でも遅延なしで!!!
オーダーしたT氏も結果には満足そうです。
ちなみに、AudioTrack / MediaPlayerについてもAndroidネイティブと同等のレイテンシであることを確認しております。プラグイン内機能の呼び出しによるオーバーヘッドによる影響は、あまり気にする必要はなさそうです。
まとめると、
Unity ☓ Androidでの発音遅延でお悩みの方、一度Androidプラグイン(SoundPool)をお試しください。
最後に、株式会社ジー・モードでは現在エンジニアを絶賛募集中です。 ご興味のある方は、ぜひ以下の採用ページをご覧ください!