フレームレート API を使用すると、アプリは予想されるフレームレートを Android プラットフォームに通知でき、フレームレート API は Android 11(API レベル 30)以降を対象とするアプリで利用できるようになります。従来、ほとんどのデバイスは 1 つのディスプレイ リフレッシュ レート(通常は 60 Hz)のみをサポートしていましたが、この状況は変化しています。現在、多くのデバイスが 90 Hz や 120 Hz などの追加のリフレッシュ レートをサポートしています。シームレスなリフレッシュ レートの切り替えをサポートするデバイスもあれば、通常は 1 秒間、黒い画面が短時間表示されるデバイスもあります。
この API の主な目的は、サポートされているすべてのディスプレイ リフレッシュ レートをアプリでより有効に活用できるようにすることです。たとえば、24 Hz の動画を再生するアプリが setFrameRate() を呼び出すと、デバイスはディスプレイのリフレッシュ レートを 60 Hz から 120 Hz に変更する可能性があります。この新しいリフレッシュ レートにより、24 Hz の動画をスムーズに、ジャダーなしで再生できます。60 Hz のディスプレイで同じ動画を再生する場合に必要な 3:2 プルダウンは必要ありません。これは、ユーザー エクスペリエンスの改善に貢献します。
基本的な使用方法
Android には、サーフェスにアクセスして制御する方法がいくつか用意されているため、setFrameRate() API にはいくつかのバージョンがあります。API の各バージョンは同じパラメータを受け取り、他のバージョンと同じように動作します。
Surface.setFrameRate()SurfaceControl.Transaction.setFrameRate()ANativeWindow_setFrameRate()ASurfaceTransaction_setFrameRate()
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 秒間画面が暗くなるなど、視覚的な中断が発生することがあります。通常、これはセットトップ ボックス、テレビパネル、および同様のデバイスで発生します。デフォルトでは、このような視覚的な中断を回避するため、Surface.setFrameRate() API が呼び出されても Android フレームワークはモードを切り替えません。
ユーザーによっては、長い動画の最初と最後に視覚的な中断があることを好む場合があります。これにより、ディスプレイのリフレッシュ レートを動画フレームレートに合わせ、映画の再生時に 3:2 プルダウン ジャダーなどのフレームレート変換アーティファクトを回避できます。
このため、ユーザーとアプリの両方がオプトインしている場合は、シームレスでないリフレッシュ レートの切り替えを有効にできます。
- ユーザー: オプトインするには、ユーザー設定の [コンテンツのフレームレートに合わせる] を有効にします。
- アプリ: オプトインするには、アプリは
CHANGE_FRAME_RATE_ALWAYSをsetFrameRate()に渡します。
映画などの長時間再生される動画には、常に CHANGE_FRAME_RATE_ALWAYS を使用することをおすすめします。これは、動画フレームレートを一致させるメリットが、リフレッシュ レートの変更時に発生する中断を上回るためです。
その他の推奨事項
一般的なシナリオについては、次の推奨事項に沿って対応してください。
複数のサーフェス
Android プラットフォームは、フレームレート設定が異なる複数のサーフェスが存在するシナリオを正しく処理するように設計されています。アプリにフレームレートの異なる複数のサーフェスがある場合は、各サーフェスに適切なフレームレートを指定して setFrameRate() を呼び出します。デバイスが分割画面モードまたはピクチャー イン ピクチャー モードを使用して複数のアプリを同時に実行している場合でも、各アプリは独自のサーフェスに対して setFrameRate() を安全に呼び出すことができます。
プラットフォームがアプリのフレームレートに切り替わらない
アプリが setFrameRate() の呼び出しで指定したフレームレートをデバイスがサポートしている場合でも、デバイスがディスプレイをそのリフレッシュ レートに切り替えないことがあります。たとえば、優先度の高いサーフェスで異なるフレームレートが設定されている場合や、デバイスがバッテリー セーバー モードになっている場合(バッテリーを節約するためにディスプレイのリフレッシュ レートに制限が設定されている場合)などです。デバイスがディスプレイのリフレッシュ レートをアプリのフレームレート設定に切り替えない場合でも、アプリは正常に動作する必要があります。デバイスが通常は切り替える場合でも同様です。
ディスプレイのリフレッシュ レートがアプリのフレームレートと一致しない場合の対応方法は、アプリによって異なります。動画の場合、フレームレートはソース動画のフレームレートに固定され、動画コンテンツを表示するにはプルダウンが必要になります。ゲームによっては、優先フレームレートを維持するのではなく、ディスプレイのリフレッシュ レートで実行しようとする場合があります。アプリは、プラットフォームの動作に基づいて setFrameRate() に渡す値を変更しないでください。プラットフォームがアプリのリクエストに合わせて調整しないケースをアプリがどのように処理するかにかかわらず、アプリの優先フレームレートに設定されたままにする必要があります。そうすることで、デバイスの状態が変化して追加のディスプレイ リフレッシュ レートを使用できるようになると、プラットフォームはアプリの優先フレームレートに切り替えるための正しい情報を得ることができます。
アプリがディスプレイのリフレッシュ レートで動作しない、または動作できない場合は、プレゼンテーション タイムスタンプを設定するためのプラットフォームのメカニズムのいずれかを使用して、各フレームのプレゼンテーション タイムスタンプを指定する必要があります。
これらのタイムスタンプを使用すると、プラットフォームがアプリフレームを早すぎるタイミングで表示しなくなり、不要なジャダーが発生しなくなります。フレーム プレゼンテーション タイムスタンプを正しく使用するのは少し複雑です。ゲームの場合は、フレーム ペーシング ガイドでジャダーを回避する方法を確認し、Android Frame Pacing ライブラリの使用を検討してください。
場合によっては、プラットフォームが、アプリが setFrameRate() で指定したフレームレートの倍数に切り替えることがあります。たとえば、アプリが 60 Hz で setFrameRate() を呼び出し、デバイスがディスプレイを 120 Hz に切り替えることがあります。この問題が発生する理由の 1 つは、別のアプリにフレームレート設定が 24 Hz のサーフェスがあることです。その場合、ディスプレイを 120 Hz で動作させると、60 Hz のサーフェスと 24 Hz のサーフェスの両方をプルダウンなしで動作させることができます。
ディスプレイがアプリのフレームレートの倍数で動作している場合、アプリは不要なジャダーを回避するために、各フレームのプレゼンテーション タイムスタンプを指定する必要があります。ゲームの場合、Android Frame Pacing ライブラリは、フレーム表示タイムスタンプを正しく設定するのに役立ちます。
setFrameRate() と preferredDisplayModeId
WindowManager.LayoutParams.preferredDisplayModeId は、アプリがプラットフォームにフレームレートを示すもう一つの方法です。アプリによっては、ディスプレイの解像度など、他のディスプレイ モードの設定を変更するのではなく、ディスプレイのリフレッシュ レートのみを変更したい場合があります。通常は、preferredDisplayModeId ではなく setFrameRate() を使用します。setFrameRate() 関数を使用すると、アプリでディスプレイ モードのリストを検索して特定のフレームレートのモードを見つける必要がないため、より簡単に使用できます。
setFrameRate() を使用すると、異なるフレームレートで動作する複数のサーフェスがあるシナリオで、プラットフォームが互換性のあるフレームレートを選択する機会が増えます。たとえば、Google Pixel 4 で 2 つのアプリが分割画面モードで実行されており、一方のアプリが 24 Hz の動画を再生し、もう一方のアプリがスクロール可能なリストをユーザーに表示しているシナリオを考えてみましょう。Google 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() 呼び出しはパフォーマンスの観点からそれほどコストがかかりませんが、アプリは setFrameRate() をフレームごとに呼び出したり、1 秒間に複数回呼び出したりしないようにする必要があります。setFrameRate() を呼び出すと、ディスプレイのリフレッシュ レートが変更される可能性が高く、移行中にフレーム落ちが発生する可能性があります。適切なフレームレートを事前に把握し、setFrameRate() を 1 回呼び出す必要があります。
ゲームやその他の動画以外のアプリでの使用
動画は setFrameRate() API の主なユースケースですが、他のアプリにも使用できます。たとえば、60 Hz を超えて動作しないようにするゲーム(電力使用量を削減し、プレイ セッションを長くするため)は、Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT) を呼び出すことができます。これにより、デフォルトで 90 Hz で動作するデバイスは、ゲームがアクティブな間は 60 Hz で動作します。これにより、ゲームが 60 Hz で動作し、ディスプレイが 90 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()を呼び出すことはおすすめしません。
動画再生アプリの統合例
動画再生アプリにリフレッシュ レートの切り替えを統合する場合は、次の手順をおすすめします。
changeFrameRateStrategyを決定します。- 映画などの長時間再生される動画を再生する場合は、
MATCH_CONTENT_FRAMERATE_ALWAYSを使用します。 - 映画の予告編などの短い動画を再生する場合は、
CHANGE_FRAME_RATE_ONLY_IF_SEAMLESSを使用します。
- 映画などの長時間再生される動画を再生する場合は、
changeFrameRateStrategyがCHANGE_FRAME_RATE_ONLY_IF_SEAMLESSの場合は、ステップ 4 に進みます。- 次の 2 つの条件が満たされていることを確認して、シームレスでないリフレッシュ レートの切り替えが発生しようとしているかどうかを検出します。
- 現在のリフレッシュ レート(C とします)から動画のフレームレート(V とします)へのシームレスなモード切り替えができない。これは、C と V が異なり、
Display.getMode().getAlternativeRefreshRatesに V の倍数が含まれていない場合に発生します。 - ユーザーがシームレスでないリフレッシュ レートの変更を有効にしている。これは、
DisplayManager.getMatchContentFrameRateUserPreferenceがMATCH_CONTENT_FRAMERATE_ALWAYSを返すかどうかを確認することで検出できます。
- 現在のリフレッシュ レート(C とします)から動画のフレームレート(V とします)へのシームレスなモード切り替えができない。これは、C と V が異なり、
- 切り替えがシームレスに行われる場合は、次の操作を行います。
setFrameRateを呼び出し、fps、FRAME_RATE_COMPATIBILITY_FIXED_SOURCE、changeFrameRateStrategyを渡します。ここで、fpsは動画のフレームレートです。- 動画の再生を開始する
- シームレスでないモード変更が発生しようとしている場合は、次の操作を行います。
- ユーザーに通知する UX を表示します。なお、ユーザーがこの UX を閉じて、ステップ 5.d の追加の遅延をスキップする方法を実装することをおすすめします。これは、切り替え時間が短いディスプレイでは、推奨される遅延が必要以上に長くなるためです。
setFrameRateを呼び出し、fps、FRAME_RATE_COMPATIBILITY_FIXED_SOURCE、CHANGE_FRAME_RATE_ALWAYSを渡します。ここで、fpsは動画のフレームレートです。onDisplayChangedコールバックを待ちます。- モードの切り替えが完了するまで 2 秒ほど待ちます。
- 動画の再生を開始する
シームレス切り替えのみをサポートする擬似コードは次のとおりです。
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();
}