Framerate

Mit der Frame Rate API können Apps die Android-Plattform über ihre beabsichtigte Framerate informieren. Sie ist in Apps verfügbar, die auf Android 11 (API-Level 30) oder höher ausgerichtet sind. Bisher wurde von den meisten Geräten nur eine einzige Displayaktualisierungsrate unterstützt, die in der Regel 60 Hz beträgt. Dies wurde jedoch geändert. Viele Geräte unterstützen jetzt zusätzliche Aktualisierungsfrequenzen wie 90 Hz oder 120 Hz. Einige Geräte unterstützen nahtlose Umstellung der Aktualisierungsrate, während bei anderen kurz ein schwarzer Bildschirm angezeigt wird, der in der Regel eine Sekunde lang angezeigt wird.

Der Hauptzweck der API besteht darin, dass Apps alle unterstützten Aktualisierungsraten für das Display besser nutzen können. Beispielsweise kann eine App, die ein 24-Hz-Video abspielt und setFrameRate() aufruft, dazu führen, dass das Gerät die Aktualisierungsrate des Displays von 60 Hz in 120 Hz ändert. Diese neue Aktualisierungsrate ermöglicht eine ruckelfreie Wiedergabe von 24-Hz-Videos, ohne dass ein 3:2-Pull-down erforderlich ist, wie es bei der Wiedergabe desselben Videos auf einem 60-Hz-Display erforderlich wäre. Dies verbessert die User Experience.

Grundlegende Verwendung

Android bietet verschiedene Möglichkeiten, auf Oberflächen zuzugreifen und diese zu steuern. Daher gibt es mehrere Versionen der setFrameRate() API. Jede Version der API verwendet dieselben Parameter und funktioniert wie die anderen:

Die App muss nicht die tatsächlich unterstützten Displayaktualisierungsraten berücksichtigen, die durch Aufrufen von Display.getSupportedModes() abgerufen werden können, um setFrameRate() sicher aufzurufen. Auch wenn das Gerät beispielsweise nur 60 Hz unterstützt, solltest du setFrameRate() mit der von deiner App bevorzugten Framerate aufrufen. Geräte, die nicht besser mit der Framerate der App übereinstimmen, behalten die aktuelle Displayaktualisierungsrate bei.

Wenn Sie sehen möchten, ob ein Aufruf von setFrameRate() zu einer Änderung der Displayaktualisierungsrate führt, registrieren Sie sich für Benachrichtigungen zu Displayänderungen. Rufen Sie dazu DisplayManager.registerDisplayListener() oder AChoreographer_registerRefreshRateCallback() auf.

Beim Aufrufen von setFrameRate() empfiehlt es sich, die genaue Framerate zu übergeben und nicht auf eine Ganzzahl zu runden. Beim Rendern eines mit 29,97 Hz aufgezeichneten Videos solltest du beispielsweise 29,97 übergeben, anstatt auf 30 zu runden.

Bei Video-Apps sollte der an setFrameRate() übergebene Kompatibilitätsparameter auf Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE gesetzt werden. So erhält die Android-Plattform einen zusätzlichen Hinweis, dass die App das Drop-down-Menü zur Anpassung an eine nicht übereinstimmende Aktualisierungsrate des Displays verwendet.

In einigen Fällen sendet die Videooberfläche keine Frames mehr, bleibt aber für einige Zeit auf dem Bildschirm sichtbar. Gängige Szenarien sind beispielsweise, wenn die Wiedergabe das Ende des Videos erreicht oder der Nutzer die Wiedergabe anhält. Rufe in diesen Fällen setFrameRate() auf, wobei der Framerate-Parameter auf 0 gesetzt ist, um die Framerate-Einstellung der Oberfläche auf den Standardwert zurückzusetzen. Das Löschen der Framerate-Einstellung ist nicht erforderlich, wenn die Oberfläche zerstört oder verborgen wird, weil der Nutzer zu einer anderen App wechselt. Die Framerate-Einstellung wird nur gelöscht, wenn die Oberfläche sichtbar bleibt, ohne dass sie verwendet wird.

Automatischer Wechsel der Framerate ohne nahtlose Übertragung

Auf einigen Geräten kann es beim Umschalten der Aktualisierungsrate zu visuellen Unterbrechungen wie einem schwarzen Bildschirm für ein oder zwei Sekunden kommen. Dies geschieht in der Regel bei Set-Top-Boxen, TV-Panels und ähnlichen Geräten. Standardmäßig wechselt das Android-Framework nicht den Modus, wenn die Surface.setFrameRate() API aufgerufen wird, um solche visuellen Unterbrechungen zu vermeiden.

Manche bevorzugen eine visuelle Unterbrechung am Anfang und am Ende von längeren Videos. Dadurch stimmt die Aktualisierungsrate der Anzeige mit der Framerate des Videos überein und es werden Framerate-Artefakte wie ein Pulldown-Ruckeln von 3:2 bei der Filmwiedergabe vermieden.

Daher können Schalter für die nicht nahtlose Aktualisierungsrate aktiviert werden, wenn sowohl der Nutzer als auch die Apps zustimmen:

Wir empfehlen, für Videos mit langer Laufzeit wie Filme immer CHANGE_FRAME_RATE_ALWAYS zu verwenden. Das liegt daran, dass der Vorteil eines Abgleichs der Framerate des Videos die Unterbrechung überwiegt, die beim Ändern der Aktualisierungsrate auftritt.

Weitere Empfehlungen

Beachten Sie die folgenden Empfehlungen für häufige Szenarien.

Mehrere Oberflächen

Die Android-Plattform wurde für den Umgang mit Szenarien entwickelt, in denen es mehrere Oberflächen mit unterschiedlichen Framerate-Einstellungen gibt. Wenn deine App mehrere Oberflächen mit unterschiedlichen Framerates hat, rufe setFrameRate() mit der richtigen Framerate für jede Oberfläche auf. Selbst wenn auf dem Gerät mehrere Apps gleichzeitig ausgeführt werden, kann jede App im Splitscreen- oder Bild-im-Bild-Modus bedenkenlos setFrameRate() für ihre eigenen Oberflächen aufrufen.

Die Plattform ändert sich nicht an die Framerate der App.

Auch wenn das Gerät die Framerate unterstützt, die von der App in einem Aufruf von setFrameRate() angegeben wird, kann es sein, dass das Display in manchen Fällen nicht auf diese Aktualisierungsrate umschaltet. Beispielsweise kann eine Oberfläche mit höherer Priorität eine andere Einstellung für die Framerate haben oder das Gerät befindet sich im Energiesparmodus (mit einer Einschränkung der Display-Aktualisierungsrate, um den Akku zu schonen). Die App muss auch dann ordnungsgemäß funktionieren, wenn das Gerät unter normalen Umständen die Display-Aktualisierungsrate nicht auf die Framerate-Einstellung der App umschaltet.

Die App entscheidet, wie reagiert wird, wenn die Display-Aktualisierungsrate nicht mit der App-Framerate übereinstimmt. Bei Videos ist die Framerate auf die des Quellvideos festgelegt und ein Drop-down-Menü ist erforderlich, um den Videoinhalt anzuzeigen. Ein Spiel kann stattdessen versuchen, mit der Display-Aktualisierungsrate zu laufen, anstatt bei der bevorzugten Frame-Rate zu bleiben. Die Anwendung sollte den Wert, den sie an setFrameRate() übergibt, nicht auf Grundlage der Funktionen der Plattform ändern. Er sollte auf die bevorzugte Framerate der App festgelegt bleiben, unabhängig davon, wie die App mit Fällen umgeht, in denen sich die Plattform nicht an die Anfrage der App anpasst. So hat die Plattform die richtigen Informationen, um zur bevorzugten Framerate der App zu wechseln, wenn sich die Gerätebedingungen ändern, um zusätzliche Display-Aktualisierungsraten verwenden zu können.

In Fällen, in denen die Anwendung nicht mit der Aktualisierungsrate des Bildschirms ausgeführt werden kann oder nicht kann, sollte die App für jeden Frame einen Zeitstempel für die Darstellung angeben. Dazu werden die entsprechenden Mechanismen der Plattform verwendet:

Die Verwendung dieser Zeitstempel verhindert, dass die Plattform einen App-Frame zu früh präsentiert, was zu einem unnötigen Ruckeln führen würde. Die korrekte Verwendung von Zeitstempeln für die Frame-Präsentation ist etwas kompliziert. Für Spiele finden Sie in unserem Leitfaden zur Taktung von Frames weitere Informationen dazu, wie Sie Ruckeln vermeiden und die Android Frame Pacing-Bibliothek verwenden können.

In einigen Fällen kann die Plattform zu einem Vielfachen der Framerate wechseln, die die App in setFrameRate() angegeben hat. Eine App kann beispielsweise setFrameRate() mit 60 Hz aufrufen und das Gerät kann die Anzeige auf 120 Hz umschalten. Ein Grund dafür kann sein, dass eine andere App eine Oberfläche mit einer Framerate von 24 Hz hat. In diesem Fall ermöglicht die Anzeige bei 120 Hz sowohl die 60-Hz-Oberfläche als auch die 24-Hz-Oberfläche, ohne dass ein Herunterziehen erforderlich ist.

Wenn die Anzeige mit einem Vielfachen der Framerate der App ausgeführt wird, sollte die App für jeden Frame einen Zeitstempel für die Darstellung angeben, um unnötiges Ruckeln zu vermeiden. Für Spiele ist die Android Frame Pacing-Bibliothek hilfreich, um die Zeitstempel der Frame-Präsentation richtig festzulegen.

setFrameRate() im Vergleich zu „preferredDisplayModeId“

WindowManager.LayoutParams.preferredDisplayModeId ist eine weitere Möglichkeit, wie Apps ihre Framerate an die Plattform angeben können. Einige Apps möchten nur die Displayaktualisierungsrate ändern, anstatt andere Einstellungen für den Anzeigemodus wie die Bildschirmauflösung zu ändern. Verwenden Sie im Allgemeinen setFrameRate() anstelle von preferredDisplayModeId. Die Funktion setFrameRate() ist einfacher zu verwenden, da die App nicht die Liste der Anzeigemodi durchsuchen muss, um einen Modus mit einer bestimmten Framerate zu finden.

setFrameRate() bietet der Plattform mehr Möglichkeiten, eine kompatible Framerate auszuwählen, wenn mehrere Oberflächen mit unterschiedlichen Framerates ausgeführt werden. Stellen wir uns zum Beispiel ein Szenario vor, bei dem zwei Apps im Splitscreen-Modus auf einem Pixel 4 ausgeführt werden, wobei eine App ein 24-Hz-Video abspielt und die andere dem Nutzer eine scrollbare Liste zeigt. Pixel 4 unterstützt zwei Displayaktualisierungsraten: 60 Hz und 90 Hz. Bei Verwendung der preferredDisplayModeId API muss die Videooberfläche entweder 60 Hz oder 90 Hz auswählen. Durch den Aufruf von setFrameRate() mit 24 Hz liefert die Videooberfläche der Plattform mehr Informationen über die Framerate des Quellvideos, sodass die Plattform 90 Hz für die Aktualisierungsrate des Displays auswählen kann, was in diesem Szenario besser als 60 Hz ist.

Es gibt jedoch Szenarien, in denen preferredDisplayModeId anstelle von setFrameRate() verwendet werden sollte, z. B.:

  • Wenn die App die Auflösung oder andere Einstellungen für den Anzeigemodus ändern möchte, verwende preferredDisplayModeId.
  • Die Plattform wechselt nur dann die Anzeigemodi als Reaktion auf einen Aufruf von setFrameRate(), wenn der Moduswechsel einfach ist und für den Nutzer unwahrscheinlich ist. Wenn die App die Aktualisierungsrate des Displays wechseln möchte, auch wenn ein intensiver Moduswechsel erforderlich ist (z. B. auf einem Android TV-Gerät), verwende preferredDisplayModeId.
  • Anwendungen, die die Anzeige nicht verarbeiten können, die mit einem Vielfachen der Framerate der Anwendung ausgeführt wird, wobei für jeden Frame ein Präsentationszeitstempel festgelegt werden muss, sollten preferredDisplayModeId verwenden.

„setFrameRate()“ im Vergleich zu „preferredRefreshRate“

Mit WindowManager.LayoutParams#preferredRefreshRate wird eine bevorzugte Framerate für das App-Fenster festgelegt. Diese gilt für alle Oberflächen im Fenster. Die App sollte unabhängig von den unterstützten Aktualisierungsraten des Geräts die bevorzugte Framerate angeben, ähnlich wie bei setFrameRate(). So erhält der Planer einen besseren Hinweis auf die von der App vorgesehene Framerate.

preferredRefreshRate wird bei Oberflächen, die setFrameRate() verwenden, ignoriert. Verwenden Sie nach Möglichkeit im Allgemeinen setFrameRate().

„präziseRefreshRate“ oder „preferredDisplayModeId“ im Vergleich

Wenn Apps nur die bevorzugte Aktualisierungsrate ändern möchten, sollte preferredRefreshRate statt preferredDisplayModeId verwendet werden.

Zu häufiges Aufrufen von „setFrameRate()“ vermeiden

Obwohl der setFrameRate()-Aufruf nicht sehr kostspielig in Bezug auf die Leistung ist, sollten Apps setFrameRate() nicht in jedem Frame oder mehrmals pro Sekunde aufrufen. Aufrufe von setFrameRate() führen wahrscheinlich zu einer Änderung der Displayaktualisierungsrate, was zu einem Frameverlust während des Übergangs führen kann. Du solltest die richtige Framerate vorab ermitteln und setFrameRate() einmal aufrufen.

Nutzung für Spiele oder andere Apps, die keine Videos sind

Videos sind zwar der primäre Anwendungsfall für die setFrameRate() API, können aber auch für andere Apps verwendet werden. Beispielsweise kann ein Spiel, das nicht über 60 Hz laufen soll, um den Stromverbrauch zu reduzieren und längere Spielsitzungen zu erreichen, Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT) aufrufen. Auf diese Weise läuft ein Gerät, das standardmäßig mit 90 Hz läuft, während das Spiel aktiv ist, bei 60 Hz. Dadurch wird das Ruckeln vermieden, das sonst auftreten würde, wenn das Spiel mit 60 Hz und das Display bei 90 Hz lief.

Nutzung von FRAME_RATE_COMPATIBILITY_FIXED_SOURCE

FRAME_RATE_COMPATIBILITY_FIXED_SOURCE ist nur für Video-Apps vorgesehen. Verwenden Sie FRAME_RATE_COMPATIBILITY_DEFAULT für andere Zwecke als Video.

Strategie zum Ändern der Framerate auswählen

  • Bei Apps mit langer Laufzeit wie Filmen empfehlen wir dringend, setFrameRate(fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS) aufzurufen, wobei fps die Framerate des Videos ist.
  • Wir raten dringend davon ab, Apps, die setFrameRate() mit CHANGE_FRAME_RATE_ALWAYS aufrufen, wenn die Videowiedergabe mehrere Minuten oder weniger dauert.

Beispielintegration für Apps zur Videowiedergabe

Wir empfehlen die folgenden Schritte für die Integration von Aktualisierungsraten-Schaltern in Apps zur Videowiedergabe:

  1. Entscheiden Sie sich für changeFrameRateStrategy:
    1. Bei der Wiedergabe eines Videos mit langer Laufzeit, z. B. einem Film, verwenden Sie MATCH_CONTENT_FRAMERATE_ALWAYS.
    2. Wenn ein kurzes Video wie ein Move-Trailer abgespielt wird, verwende CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS.
  2. Wenn changeFrameRateStrategy den Wert CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS hat, fahren Sie mit Schritt 4 fort.
  3. Um zu erkennen, ob ein Wechsel der nicht nahtlosen Aktualisierungsrate bevorsteht, prüfen Sie, ob diese beiden Punkte zutreffen:
    1. Ein nahtloser Moduswechsel ist von der aktuellen Aktualisierungsrate (noch C) zur Framerate des Videos (nun auch „V“) nicht möglich. Das ist der Fall, wenn C und V unterschiedlich sind und Display.getMode().getAlternativeRefreshRates kein Vielfaches von V enthält.
    2. Der Nutzer hat die Änderungen der Aktualisierungsrate ohne nahtlose Übertragung aktiviert. Sie können dies erkennen, indem Sie prüfen, ob DisplayManager.getMatchContentFrameRateUserPreference MATCH_CONTENT_FRAMERATE_ALWAYS zurückgibt.
  4. Wenn der Wechsel nahtlos erfolgen soll, gehen Sie so vor:
    1. Rufen Sie setFrameRate auf und übergeben Sie fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE und changeFrameRateStrategy, wobei fps die Framerate des Videos ist.
    2. Videowiedergabe starten
  5. Wenn eine Änderung des nicht nahtlosen Modus bevorsteht, gehen Sie so vor:
    1. UX zur Benachrichtigung der Nutzenden zeigen. Wir empfehlen, eine Möglichkeit für den Nutzer zu implementieren, diese UX zu schließen, und die zusätzliche Verzögerung in Schritt 5.d zu überspringen. Der Grund dafür ist, dass die empfohlene Verzögerung bei Bildschirmen, die schnellere Wechselzeiten aufweisen, größer als erforderlich ist.
    2. Rufen Sie setFrameRate auf und übergeben Sie fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE und CHANGE_FRAME_RATE_ALWAYS, wobei fps die Framerate des Videos ist.
    3. Warten Sie auf den onDisplayChanged-Callback.
    4. Warten Sie 2 Sekunden, bis der Moduswechsel abgeschlossen ist.
    5. Videowiedergabe starten

Der Pseudocode unterstützt nur einen nahtlosen Wechsel:

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

Der Pseudocode zur Unterstützung eines nahtlosen und nicht nahtlosen Wechsels, wie oben beschrieben, lautet wie folgt:

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();
}