G-MODE Engineers' Blog

株式会社ジー・モードのエンジニアブログ。
株式会社ジー・モードのエンジニアブログ。
  • rss
  • archive
  • Unity☓Androidアプリの発音遅延を解消する

    はじめまして。開発部の田辺です。
    クライアントアプリ開発をしております。

    2014年11月某日
    企画T氏:「Androidアプリの発音遅延なんとかならんですか?」
    田辺:「UnityならCRIのプラグイン使ってみたらどうですか?低遅延再生可能だそうですよ。」
    企画T氏:「大人の事情でそれは難しいです。察してください。」

    というやりとりから始まった今回の低レイテンシでの発音調査の件。デバイス叩く系の機能調査=面倒そうな匂いがプンプンするやつワクワクするお仕事です。

    1.とりあえずUnity☓Androidアプリでの現状把握

    そういえば、今まで音の遅延について気にしたことはなかったので、とりあえず現状把握。適当にサンプル作ってAndroid端末上でサウンドを鳴らしてみる。
    条件としてはこんな感じ。

    • サウンドファイルはResourcesからロード。wav形式。
    • サウンドファイルは44.1KHz / 16bit / モノラル。
    • サウンド設定は、Load into Memory&Best latencyにしておく。
    • 発音タイミングがわかりやすいように画面から指を離した時に発音させる。
    • 端末は au SOL22(Xperia UL) Android 4.2.2
    • Unityのバージョンは ver.4.5.5f1

    ▼サウンドファイルのImport Settings
    image
    image

    ▼Project Settings > Audio
    image

    確かに遅い。Unity曰く低レイテンシな設定なのに。自分の脳がおかしくなければワンテンポ遅れて発音していると認識。Unityエディタでは遅延はなし(のはず)。実際感覚なんで、説得力にかけるということもあり、鳴音要求出してから発音までの遅延時間を計測を試みる。数字は正しい。

    で、少々調べてみたところ、実際に鳴音状態については、どうもAudioSource.isPlaying一択らしい。Update()関数内だと正確な時間計測にならないだろうし、AudioSource.play()呼んだら、即時isPlayingが状態変更するだろうから無理かな思いつつも、一縷の望みにかけて確認。

    方法としては、簡単に

    1. AudioSource.Play()呼んだ時に時間取得。
    2. update()内でAudioSource.isPlaying監視。
    3. isPlayingがTrueになった時間との差分を取得。

    <結果>
    Unityエディタ:0.032232…
    Android端末 :0.032718…

    ・・・やっぱりダメでした!
    実際、発音の遅延計測方法あるかもしれませんが、とりあえず今回は他にもやることあるので遅延時間の数値化はスルー。 早速できるメンバーアピール失敗。すいません。

    2.iPhoneではどうなのよ?

    作ったプロジェクトをそのままiPhone(iPhone5)に転送してみる。

    …こちらは問題無し。即座に発音していると認識。ということは、Androidの発音そもそも遅いんじゃないか疑惑が…

    3.Android発音遅い疑惑!?

    ということで、早速サンプル作成に着手。そういえばAndroidネイティブ本格的にやるまえにUnity触ったので、MediaPlayer以外知らない。ついでに調べることにする。
    で、Android SDKには鳴音処理については3種類あるとのこと。

    1. SoundPool
      サウンドデータをオンメモリで扱う。再生遅延がほぼないとのこと。
      そこそこ制限はあるものの、SE再生目的などでは必要十分。
      登録したデータのIDのスタートが1から始まるので配列と組み合わせる場合は要注意。
      データの登録数上限256 / 登録ファイルの再生時間は10秒までの制限あり。
      データをアンロードしても登録数のカウントは減らないので、追加する場合はインスタンスを追加する必要あり。

      実装手順
      • SoundPooolインスタンス生成
      • データロード完了リスナ登録
      • データロード
      • ロード完了確認
    2. AudioTrack
      オンメモリなStaticとストレージから随時ロードしてくるStream再生両方対応。
      バッファに流し込むデータはサウンドファイルから波形データのみを抜き出す必要があるため、実装となるとやや面倒なやつ達成感あるやつ。(逆に波形データ加工とかする場合はこれを使うのが良さげ。)
      バッファへの書き込みはブロッキングモードのため、Steam形式では別スレッドでの書き込み必須。

      実装手順
      • AudioTrackインスタンス生成
      • イベントリスナ登録(Stream形式であれば必須)
      • データロード
      • バッファへの書き込み用スレッド生成
      • Staticの場合は、即時バッファにデータ書き込み。Streamの場合は再生後に、書き込み用スレッド開始。
    3. MediaPlayer
      万能プレイヤー。とりあえずサウンド再生するなら、これ使っておけ的なもの
      楽。これに尽きる。

      実装手順
      • MediaPlayerインスタンス生成
      • データロード

    で、それぞれ試した結果…

    (遅延大)MediaPlayer > AudioTrack > SoundPool(遅延小)の順番で遅延が少ない。

    • SoundPool:最速。ほぼ遅延なし。iPhone/Unityエディタ上での再生と同等のレイテンシ。
    • AudioTrack:Unity x Androidよりは低レイテンシな印象。
    • MediaPlayer:いつものUnity x Androidな遅延具合。

    SoundPool、iPhoneやUnityエディタ上で再生しているものと同等でほぼ遅延なし。ということは、AndroidではUnity側の鳴音処理が遅いと推察。MediaPlayerつかってる? 疑ってごめんよAndroid。

    検証結果から、UnityからSoundPool直接叩けば、Unity x Androidでも遅延なしに近い形で再生可能になるのではないかと想定。Androidプラグインをつくろう…。

    4.Androidプラグインをつくる

    ということで、先ほど作ったサンプルを元に、Android用のプラグイン作成。 Eclipseからエクスポート → Java → JarファイルでJarを吐き出すだけでOK。その際は、クラスファイルだけ入っていれば良いので、不要なものは含めない。

    image
    image

    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での発音遅延は、おそらくUnityのAndroid向けの鳴音処理が原因。
    • Android SDKのSoundPoolを使う限りでは、発音遅延はない。
    • Androidプラグインを作ってSoundPoolを使うとUnityでも遅延なしで発音可能。

    Unity ☓ Androidでの発音遅延でお悩みの方、一度Androidプラグイン(SoundPool)をお試しください。

    最後に、株式会社ジー・モードでは現在エンジニアを絶賛募集中です。 ご興味のある方は、ぜひ以下の採用ページをご覧ください!

    採用情報 | 株式会社ジー・モード

    • 12月 8, 2014 (10:30 am)
© 2014 G-MODE Engineers' Blog