フレーム レート

フレームレート API を使用すると、アプリは想定するフレームレートを Android プラットフォームに通知でき、フレームレート API は Android 11(API レベル 30)以降をターゲットとするアプリで利用できるようになります。従来、ほとんどのデバイスが 1 つのディスプレイ リフレッシュ レート(一般に 60 Hz)にのみ対応していましたが、これは変わってきています。多くのデバイスで、90 Hz や 120 Hz などの追加のリフレッシュ レートがサポートされています。シームレスなリフレッシュ レートの切り替えをサポートしているデバイスもあれば、画面が短時間(通常は 1 秒)表示されるものもあります。

API の主な目的は、サポートされているすべてのディスプレイ リフレッシュ レートをアプリがもっと活用できるようにすることです。たとえば、setFrameRate() を呼び出して 24 Hz の動画を再生するアプリでは、デバイスがディスプレイのリフレッシュ レートを 60 Hz から 120 Hz に変更する可能性があります。この新しいリフレッシュ レートにより、24 Hz の動画をスムーズに再生できるようになります。60 Hz のディスプレイで同じ動画を再生する場合のような 3:2 のプルダウンは必要ありません。これは、ユーザー エクスペリエンスの改善に貢献します。

基本的な使用方法

Android では、サーフェスにアクセスして操作する複数の方法を公開しているため、setFrameRate() API には複数のバージョンがあります。各バージョンの API は同じパラメータを取り、他の API と同じように動作します。

アプリは、setFrameRate() を安全に呼び出すために、実際にサポートされているディスプレイの更新レートを考慮する必要はありません。このレートは Display.getSupportedModes() を呼び出すことで取得できます。たとえば、デバイスが 60 Hz のみをサポートしている場合でも、アプリが優先するフレームレートで setFrameRate() を呼び出します。アプリのフレームレートに適したデバイスがない場合は、現在のディスプレイのリフレッシュ レートが維持されます。

setFrameRate() の呼び出しによってディスプレイのリフレッシュ レートが変わるかどうかを確認するには、DisplayManager.registerDisplayListener() または AChoreographer_registerRefreshRateCallback() を呼び出してディスプレイの変更通知に登録します。

setFrameRate() を呼び出すときは、整数に丸めずに正確なフレームレートを渡すことをおすすめします。たとえば、29.97 Hz で録画された動画をレンダリングする場合は、30 に丸めずに 29.97 を渡します。

動画アプリの場合は、setFrameRate() に渡される互換性パラメータを Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE に設定して、アプリがプルダウンを使用して一致しないディスプレイの更新レートに適応することを Android プラットフォームに追加ヒントとして伝える必要があります(これによりジャダーが発生します)。

場合によっては、動画サーフェスはフレームの送信を停止しますが、しばらくの間画面に表示されたままになります。一般的なシナリオとして、再生が動画の終わりに達した場合や、ユーザーが再生を一時停止した場合などがあります。このような場合は、フレームレート パラメータを 0 に設定して setFrameRate() を呼び出し、サーフェスのフレームレート設定をデフォルト値に戻します。サーフェスを破棄する場合や、ユーザーが別のアプリに切り替えたためにサーフェスが非表示になった場合は、このようにフレームレート設定を消去する必要はありません。サーフェスが使用されずに表示されたままになっている場合にのみ、フレームレート設定を消去します。

シームレスでないフレームレートの切り替え

一部のデバイスでは、リフレッシュ レートを切り替えると、画面が 1 ~ 2 秒間黒くなるなど、視覚的な中断が生じることがあります。これは通常、セットトップ ボックス、テレビパネルなどのデバイスで発生します。このような視覚的な割り込みを避けるため、Android フレームワークはデフォルトでは Surface.setFrameRate() API の呼び出し時にモードを切り替えません。

長い動画の最初と最後に視覚的な中断を好むユーザーもいます。これにより、ディスプレイの更新レートを動画のフレームレートに合わせることができ、映画の再生時の 3:2 プルダウン ジャダーなどのフレームレート変換アーティファクトを回避できます。

そのため、ユーザーとアプリの両方がオプトインした場合、シームレスでないリフレッシュ レートの切り替えを有効にできます。

映画などの長尺動画には、常に CHANGE_FRAME_RATE_ALWAYS を使用することをおすすめします。これは、動画のフレームレートを合わせるメリットが、リフレッシュ レートの変更時に発生する中断よりも優先されるためです。

その他の推奨事項

一般的なシナリオでは、以下の推奨事項に従ってください。

複数のサーフェス

Android プラットフォームは、フレームレート設定が異なる複数のサーフェスがあるシナリオを正しく処理するように設計されています。アプリにフレームレートの異なる複数のサーフェスがある場合は、各サーフェスの正しいフレームレートで setFrameRate() を呼び出します。デバイスで分割画面モードまたはピクチャー イン ピクチャー モードを使用して複数のアプリを同時に実行している場合でも、各アプリは独自のサーフェスに対して setFrameRate() を安全に呼び出すことができます。

プラットフォームがアプリのフレームレートを変更しない

アプリが setFrameRate() の呼び出しで指定したフレームレートをデバイスがサポートしていても、デバイスがディスプレイをそのリフレッシュ レートに切り替えない場合があります。たとえば、優先度の高いサーフェスには異なるフレームレート設定が適用されている場合や、デバイスがバッテリー セーバー モードになっている場合(バッテリーを節約するためにディスプレイの更新レートに制限が設定されている場合)などです。デバイスが通常の状況でディスプレイの更新レートをアプリのフレームレート設定に切り替える場合でも、デバイスがディスプレイの更新レートをアプリのフレームレート設定に切り替えない場合でも、アプリは正しく動作する必要があります。

ディスプレイのリフレッシュ レートがアプリのフレームレートと一致しない場合にどのように応答するかは、アプリによって異なります。動画の場合、フレームレートはソース動画のフレームレートに固定され、動画コンテンツを表示するにはプルダウンが必要になります。ゲームは、優先するフレームレートを維持するのではなく、ディスプレイのリフレッシュ レートで実行しようとする場合があります。アプリは、プラットフォームの動作に基づいて setFrameRate() に渡す値を変更してはなりません。プラットフォームがアプリのリクエストに合わせて調整しない場合のアプリの処理方法に関係なく、アプリの優先フレームレートに設定したままにする必要があります。これにより、デバイスの状態が変化して追加のディスプレイの更新レートを使用できるようになった場合、プラットフォームにはアプリの優先フレームレートに切り替えるための正しい情報が用意されます。

アプリがディスプレイの更新レートで実行されない、または実行できない場合は、プレゼンテーション タイムスタンプを設定するプラットフォームのメカニズムのいずれかを使用して、フレームごとにプレゼンテーション タイムスタンプを指定する必要があります。

これらのタイムスタンプを使用すると、プラットフォームがアプリフレームを早すぎるタイミングで表示するのを防ぐことができ、不要なジャダーが発生しなくなります。フレーム表示タイムスタンプの正しい使用方法は少し複雑です。ゲームについては、ジッターを回避する方法についてフレーム ペーシング ガイドをご覧ください。また、Android フレーム ペーシング ライブラリの使用を検討してください。

場合によっては、プラットフォームがアプリが setFrameRate() で指定したフレームレートの倍数に切り替わることがあります。たとえば、アプリが 60 Hz で setFrameRate() を呼び出すと、デバイスはディスプレイを 120 Hz に切り替える場合があります。このような事象が発生する理由の 1 つとして、別のアプリにフレームレートが 24 Hz のサーフェスがある場合が挙げられます。その場合、ディスプレイを 120 Hz で実行すると、60 Hz サーフェスと 24 Hz サーフェスの両方をプルダウンなしで実行できます。

ディスプレイがアプリのフレームレートの倍数で実行されている場合、アプリは不要な揺動を避けるために、各フレームの表示タイムスタンプを指定する必要があります。ゲームの場合、Android Frame Pacing ライブラリは、フレーム表示タイムスタンプを正しく設定するのに役立ちます。

setFrameRate() と preferredDisplayModeId

WindowManager.LayoutParams.preferredDisplayModeId は、アプリがフレームレートをプラットフォームに示すもう 1 つの方法です。アプリによっては、ディスプレイ解像度などの他のディスプレイ モード設定を変更するのではなく、ディスプレイのリフレッシュ レートのみを変更する場合があります。通常は、preferredDisplayModeId ではなく setFrameRate() を使用します。setFrameRate() 関数は、特定のフレームレートのモードを探すためにディスプレイ モードのリストを検索する必要がないため、使いやすい関数です。

setFrameRate() を使用すると、異なるフレームレートで実行されている複数のサーフェスがあるシナリオで、プラットフォームが互換性のあるフレームレートを選択できる機会が増えます。たとえば、Google Pixel 4 で 2 つのアプリが分割画面モードで実行されているとします。一方のアプリは 24 Hz の動画を再生し、もう一方はスクロール可能なリストをユーザーに表示しています。Pixel 4 は、60 Hz と 90 Hz の 2 つのディスプレイのリフレッシュ レートをサポートしています。preferredDisplayModeId API を使用すると、動画サーフェスは 60 Hz または 90 Hz のいずれかを選択する必要があります。24 Hz で setFrameRate() を呼び出すと、動画サーフェスはソース動画のフレームレートに関するより多くの情報をプラットフォームに提供します。これにより、プラットフォームはディスプレイのリフレッシュ レートに 90 Hz を選択できます。このシナリオでは 60 Hz よりも優れています。

ただし、次のような場合は setFrameRate() ではなく preferredDisplayModeId を使用する必要があります。

  • アプリが解像度やその他の表示モードの設定を変更する場合は、preferredDisplayModeId を使用します。
  • プラットフォームは、モードの切り替えが軽量で、ユーザーが気づく可能性が低い場合にのみ、setFrameRate() の呼び出しに応答してディスプレイ モードを切り替えます。Android TV デバイスなどで、負荷の高いモードの切り替えが必要であっても、アプリでディスプレイのリフレッシュ レートを切り替える場合は、preferredDisplayModeId を使用します。
  • アプリのフレームレートの倍数で実行されるディスプレイを処理できないアプリ(各フレームにプレゼンテーション タイムスタンプを設定する必要があるアプリ)は、preferredDisplayModeId を使用する必要があります。

setFrameRate() と preferredRefreshRate

WindowManager.LayoutParams#preferredRefreshRate は、アプリのウィンドウに優先フレームレートを設定します。このレートは、ウィンドウ内のすべてのサーフェスに適用されます。アプリは、setFrameRate() と同様に、デバイスでサポートされているリフレッシュ レートに関係なく、優先するフレームレートを指定する必要があります。これにより、アプリの目的のフレームレートをスケジューラに適切にヒントとして提供できます。

setFrameRate() を使用するサーフェスでは、preferredRefreshRate は無視されます。通常は、可能であれば setFrameRate() を使用してください。

preferredRefreshRate と preferredDisplayModeId

アプリで優先リフレッシュ レートのみを変更する場合は、preferredDisplayModeId ではなく preferredRefreshRate を使用することをおすすめします。

setFrameRate() の呼び出しを頻繁に行わない

setFrameRate() 呼び出しはパフォーマンスの面でそれほどコストがかかるものではありませんが、アプリではフレームごとに、または 1 秒間に複数回 setFrameRate() を呼び出さないようにする必要があります。setFrameRate() を呼び出すと、ディスプレイのリフレッシュ レートが変更される可能性があり、遷移中にフレーム ドロップが発生する可能性があります。正しいフレームレートを事前に把握し、setFrameRate() を 1 回呼び出す必要があります。

ゲームやその他の動画以外のアプリでの使用

動画は setFrameRate() API の主なユースケースですが、他のアプリにも使用できます。たとえば、(消費電力を抑えてプレイ セッションを長くするために)60 Hz を超えるレートで実行しないことを意図しているゲームは、Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT) を呼び出すことができます。これにより、デフォルトで 90 Hz で動作するデバイスは、ゲームがアクティブな間は 60 Hz で動作します。これにより、ディスプレイが 90 Hz で動作しているときにゲームが 60 Hz で動作した場合に発生するジャダーを回避できます。

Frame_RATE_COMPATIBILITY_FIXED_SOURCE の使用

FRAME_RATE_COMPATIBILITY_FIXED_SOURCE は動画アプリ専用です。動画以外の用途の場合は、FRAME_RATE_COMPATIBILITY_DEFAULT を使用します。

フレームレートを変更する戦略を選択する

  • 映画などの長時間の動画が表示されている場合は、setFrameRate(fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS) を呼び出すことを強くおすすめします。ここで、fps は動画のフレームレートです。
  • 動画の再生時間が数分以下になることが予想される場合は、CHANGE_FRAME_RATE_ALWAYS を指定して setFrameRate() を呼び出すアプリを使用しないことを強くおすすめします。

動画再生アプリの統合例

動画再生アプリに更新レートの切り替え機能を統合する場合は、次の手順をおすすめします。

  1. changeFrameRateStrategy を決定します。
    1. 映画などの長尺動画を再生する場合は、MATCH_CONTENT_FRAMERATE_ALWAYS を使用します。
    2. 映画の予告編などの短い動画を再生する場合は、CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS を使用します。
  2. changeFrameRateStrategyCHANGE_FRAME_RATE_ONLY_IF_SEAMLESS の場合は、ステップ 4 に進みます。
  3. 次の条件が両方とも満たされていることを確認して、非シームレスのリフレッシュ レートの切り替えが発生するかどうかを検出します。
    1. 現在のリフレッシュ レート(ここでは C)から動画のフレームレート(ここでは V とします)にシームレス モードを切り替えることはできません。これは、C と V が異なり、Display.getMode().getAlternativeRefreshRates に V の倍数が含まれていない場合に発生します。
    2. ユーザーがシームレスでないリフレッシュ レートの変更を有効にしている。これは、DisplayManager.getMatchContentFrameRateUserPreferenceMATCH_CONTENT_FRAMERATE_ALWAYS を返すかどうかを確認することで検出できます。
  4. シームレスに切り替える場合は、次の操作を行います。
    1. setFrameRate を呼び出して、fpsFRAME_RATE_COMPATIBILITY_FIXED_SOURCEchangeFrameRateStrategy を渡します。ここで、fps は動画のフレームレートです。
    2. 動画の再生を開始する
  5. 非シームレス モードの変更が予定されている場合は、次の操作を行います。
    1. UX を表示してユーザーに通知します。ユーザーがこの UX を終了し、ステップ 5.d の追加の遅延をスキップする方法を実装することをおすすめします。これは、切り替え時間が短いディスプレイでは、推奨される遅延時間が必要以上に長くなるためです。
    2. setFrameRate を呼び出して、fpsFRAME_RATE_COMPATIBILITY_FIXED_SOURCECHANGE_FRAME_RATE_ALWAYSfps は動画のフレームレート)を渡します。
    3. onDisplayChanged コールバックを待機します。
    4. モードの切り替えが完了するまで 2 秒待ちます。
    5. 動画の再生を開始

シームレス切り替えのみをサポートする疑似コードは次のとおりです。

SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
transaction.setFrameRate(surfaceControl,
    contentFrameRate,
    FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
    CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
transaction.apply();
beginPlayback();

上記のようなシームレス切り替えと非シームレス切り替えをサポートする擬似コードは次のとおりです。

SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
if (isSeamlessSwitch(contentFrameRate)) {
  transaction.setFrameRate(surfaceControl,
      contentFrameRate,
      FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
      CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
  transaction.apply();
  beginPlayback();
} else if (displayManager.getMatchContentFrameRateUserPreference()
      == MATCH_CONTENT_FRAMERATE_ALWAYS) {
  showRefreshRateSwitchUI();
  sleep(shortDelaySoUserSeesUi);
  displayManager.registerDisplayListener();
  transaction.setFrameRate(surfaceControl,
      contentFrameRate,
      FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
      CHANGE_FRAME_RATE_ALWAYS);
  transaction.apply();
  waitForOnDisplayChanged();
  sleep(twoSeconds);
  hideRefreshRateSwitchUI();
  beginPlayback();
}