Choreographer 〜 AndroidでFPSを意識してみる話 〜

  • 56
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

挨拶

こんにちは、12/1を担当させて頂く@wasabeef_jpです。

DroidKaigi 2016Connpassが募集開始されましたね。
もう、結構埋まっているみたいですが、Android界隈がいっそう盛り上がって貰えたら嬉しいです(๑•̀ㅂ•́)و✧

さて、今年一発目なので、ふんわりした内容を書いていこうかと思います。
少し前にTaktというライブラリを作りましたので、その話をさせて下さい。

Takt

data_takt.png

何が出来るライブラリかというと「FPSを表示」することが出来るライブラリになります。
ゲームとかだとFPSが落ちないように意識するのが当たり前になっていますが
SNS系のアプリを作っている上で、そこまで意識していないことが多いのではないでしょうか。

重くなるようなアプリを作っていない場合は必要ないかもしれません。
私の場合、動画関連のAPIやOpenGLを利用する頻度が高かったため、これで簡単に可視化するようにしました。

takt_fps.gif

FPS (Frames Per Second)

そもそもFPS(Frames Per Second)とは、動画において

単位時間あたりに処理させるフレーム数(静止画像数、コマ数)である。
通常、1秒あたりの数値で表し、fps(英: frames per second=フレーム毎秒)という単位で表す。

のことを指します。

人間が認識できるFPSは、60fpsまでと言われています。
アクションゲームの中には、60fpsが採用されているものがありますね。
実は、映像業界でも標準となっているFPSでも映画は24fpsだったりします。

FPSが違うと、こんなにも差があるように見えます。
fpsdemo1.gif
参考: https://twitter.com/McFunkypants/status/607916176912453632

VSync

Displayのフレッシュレートが60Hzの場合
1秒間に60frameしか表示できないので、FPSが80とか出てた場合は
1frameを表示し切る前に、GPUから次のframeが来てしまうので、画面が乱れたりします..
こういうのをティアリングって言います。

そこで、ティアリングが起こらないように、DisplayのとGPUのframeを合わせようとするわけです。
Displayがframeを表示しきってからGPUにframeを送ってもらうことをVSync垂直同期といいます。

Choreographer

The choreographer receives timing pulses (such as vertical synchronization) from the display subsystem then schedules work to occur as part of rendering the next display frame.

AndroidではFPSを算出してくれるAPIはありません。
ただ、前述のVSyncのタイミングをChoreographerを使うことによって
Choreographer.FrameCallbackの登録が可能になります。
※しかし、Android 4.1で追加されたAPIということに注意する必要があります。

実は、ValueAnimatorの内部で、Choreographerが使われています。
AndroidのAnimationの実行タイミングはVSyncを意識して作られているということが分かりますね。
ただ、4.0以下の場合は Handlerを利用しているため、厳密にはVSync同期にはなっていません。

また、Facebook/Reboundでも似たような処理が書かれています。

以下は、Taktの一部を抜粋したコードになりますが
Choreographerの使い方は簡単で、Choreographerのinstanceに対して、Callbackの登録・解除を行うだけで
VSyncのタイミングがdoFrameで返ってきます。

注意しなければいけないポイントとしては、1回の登録で1回分しか返ってこないため
計測し続けたい場合は、Choreographer.FrameCallback#doFrame(long)で、再登録する必要があります。

public class Metronome implements Choreographer.FrameCallback {

  private Choreographer choreographer;

  public Metronome() {
    choreographer = Choreographer.getInstance();
  }

  public void start() {
    choreographer.postFrameCallback(this);
  }

  public void stop() {
    choreographer.removeFrameCallback(this);
  }

  @Override public void doFrame(long frameTimeNanos) {
    Timber.i("Callback frameTimeNanos" + frameTimeNanos);
    choreographer.postFrameCallback(this);
  }
}

Setup

Dependencies

dependencies {
    compile 'jp.wasabeef:takt:1.0.2'
}

Simple

簡単に使う場合は、1行コードを追加するだけで可能です。

public class MyApplication extends Application {
  public void onCreate() {
    super.onCreate();
    Takt.stock(this).play();
  }
}

Advanced

FPSの表示位置、更新時間、テキスト色、テキストサイズなど細かく設定が可能です。

Takt.stock(this)
    .seat(Seat.BOTTOM_RIGHT)
    .interval(250)
    .color(Color.WHITE)
    .size(14f)
    .alpha(.5f)
    .listener(new Audience() {
      @Override public void heartbeat(double fps) {
        Timber.i("Excellent!" + fps + " fps");
        // Timber
        // jp.wasabeef.example.takt D/Excellent!﹕ 59.28853754940712 fps
        // jp.wasabeef.example.takt D/Excellent!﹕ 59.523809523809526 fps
        // jp.wasabeef.example.takt D/Excellent!﹕ 59.05511811023622 fps
        // jp.wasabeef.example.takt D/Excellent!﹕ 55.33596837944664 fps
        // jp.wasabeef.example.takt D/Excellent!﹕ 59.523809523809526 fps
      }
    })
    .play();
}

使ってみると、ざっくりではありますが、遅くなるタイミングわかったので意外と幸せになりました。

おしまい

この投稿は Android Advent Calendar 20151日目の記事です。