Fréquence d'images

L'API de fréquence d'images permet aux applications d'informer la plate-forme Android de leur fréquence d'images prévue. Elle est disponible sur les applications qui ciblent Android 11 (niveau d'API 30) ou version ultérieure. Traditionnellement, la plupart des appareils n'acceptent qu'une seule fréquence d'actualisation de l'écran, généralement 60 Hz, mais cela a changé. De nombreux appareils prennent désormais en charge des fréquences d'actualisation supplémentaires, telles que 90 Hz ou 120 Hz. Certains appareils sont compatibles avec des commutateurs de fréquence d'actualisation fluides, tandis que d'autres affichent brièvement un écran noir, qui dure généralement une seconde.

L'objectif principal de l'API est de permettre aux applications de mieux exploiter toutes les fréquences d'actualisation de l'écran compatibles. Par exemple, une application qui lit une vidéo 24 Hz qui appelle setFrameRate() peut faire passer l'appareil de la fréquence d'actualisation de l'affichage de 60 Hz à 120 Hz. Cette nouvelle fréquence d'actualisation permet une lecture fluide et sans saccade des vidéos 24 Hz, sans qu'il soit nécessaire de faire défiler la page en 3:2, comme cela est nécessaire pour lire la même vidéo sur un écran 60 Hz. Cela se traduit par une meilleure expérience utilisateur.

Utilisation de base

Android propose plusieurs façons d'accéder aux surfaces et de les contrôler. Il existe donc plusieurs versions de l'API setFrameRate(). Chaque version de l'API utilise les mêmes paramètres et fonctionne de la même manière que les autres:

L'application n'a pas besoin de prendre en compte les fréquences d'actualisation de l'écran réelles compatibles, qui peuvent être obtenues en appelant Display.getSupportedModes(), afin d'appeler setFrameRate() de manière sécurisée. Par exemple, même si l'appareil n'est compatible qu'avec la fréquence de 60 Hz, appelez setFrameRate() avec la fréquence d'images préférée de votre application. Les appareils qui ne correspondent pas mieux à la fréquence d'images de l'application conserveront la fréquence d'actualisation actuelle de l'affichage.

Pour voir si un appel à setFrameRate() entraîne une modification de la fréquence d'actualisation de l'écran, inscrivez-vous aux notifications de changement d'affichage en appelant DisplayManager.registerDisplayListener() ou AChoreographer_registerRefreshRateCallback().

Lorsque vous appelez setFrameRate(), il est préférable de transmettre la fréquence d'images exacte plutôt que de l'arrondir à un nombre entier. Par exemple, lors du rendu d'une vidéo enregistrée à 29,97 Hz, transmettez 29,97 au lieu d'arrondir à 30.

Pour les applications vidéo, le paramètre de compatibilité transmis à setFrameRate() doit être défini sur Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE pour indiquer à la plate-forme Android que l'application utilisera un menu déroulant pour s'adapter à une fréquence d'actualisation de l'écran qui ne correspond pas (ce qui entraînera un saccades).

Dans certains cas, la surface de la vidéo cesse d'envoyer des images, mais reste visible à l'écran pendant un certain temps. C'est par exemple le cas lorsque la lecture atteint la fin d'une vidéo ou lorsque l'utilisateur met la lecture en pause. Dans ce cas, appelez setFrameRate() avec le paramètre de fréquence d'images défini sur 0 pour rétablir la valeur par défaut du paramètre de fréquence d'images de la surface. Il n'est pas nécessaire de supprimer le paramètre de fréquence d'images de cette manière lorsque vous détruirez la surface ou lorsque celle-ci est masquée parce que l'utilisateur passe à une autre application. Supprimez le paramètre de fréquence d'images uniquement lorsque la surface reste visible sans être utilisée.

Changement de fréquence d'images simplifié

Sur certains appareils, le changement de fréquence d'actualisation peut entraîner des interruptions visuelles, comme un écran noir pendant une seconde ou deux. Cela se produit généralement sur les boîtiers décodeurs, les panneaux TV et les appareils similaires. Par défaut, le framework Android ne change pas de mode lorsque l'API Surface.setFrameRate() est appelée, afin d'éviter de telles interruptions visuelles.

Certains utilisateurs préfèrent une interruption visuelle au début et à la fin des vidéos plus longues. Cela permet à la fréquence d'actualisation de l'écran de correspondre à la fréquence d'images de la vidéo et évite les artefacts de conversion de la fréquence d'images tels que le juddown Pull 3:2 pour la lecture du film.

Pour cette raison, il est possible d'activer des boutons de fréquence d'actualisation non fluides si l'utilisateur et les applications l'activent:

Nous vous recommandons de toujours utiliser CHANGE_FRAME_RATE_ALWAYS pour les vidéos de longue durée, comme les films. En effet, l'avantage d'une correspondance avec la fréquence d'images vidéo l'emporte sur l'interruption qui se produit lors de la modification de la fréquence d'actualisation.

Autres recommandations

Suivez ces recommandations pour les scénarios courants.

Surfaces multiples

La plate-forme Android est conçue pour gérer correctement les scénarios dans lesquels plusieurs surfaces ont des paramètres de fréquence d'images différents. Lorsque votre application comporte plusieurs surfaces avec différentes fréquences d'images, appelez setFrameRate() avec la fréquence d'images correcte pour chaque surface. Même si l'appareil exécute plusieurs applications à la fois, à l'aide de l'écran partagé ou du mode Picture-in-picture, chaque application peut appeler en toute sécurité setFrameRate() pour ses propres surfaces.

La plate-forme ne modifie pas la fréquence d'images de l'application.

Même si l'appareil est compatible avec la fréquence d'images spécifiée par l'application dans un appel à setFrameRate(), il peut arriver que l'appareil ne bascule pas l'affichage sur cette fréquence d'actualisation. Par exemple, une surface prioritaire peut avoir un paramètre de fréquence d'images différent ou l'appareil peut être en mode Économiseur de batterie (en définissant une restriction sur la fréquence d'actualisation de l'affichage pour préserver la batterie). L'application doit toujours fonctionner correctement lorsque l'appareil ne bascule pas la fréquence d'actualisation de l'écran sur le paramètre de fréquence d'images de l'application, même si l'appareil change dans des circonstances normales.

C'est à l'application de décider comment réagir lorsque la fréquence d'actualisation de l'écran ne correspond pas à la fréquence d'images de l'application. Pour la vidéo, la fréquence d'images est fixée à celle de la vidéo source. Un menu déroulant devra être utilisé pour afficher le contenu vidéo. Un jeu peut essayer de s'exécuter à la fréquence d'actualisation de l'écran plutôt que de conserver la fréquence d'images préférée. L'application ne doit pas modifier la valeur qu'elle transmet à setFrameRate() en fonction du comportement de la plate-forme. Elle doit rester définie sur la fréquence d'images préférée de l'application, quelle que soit la façon dont l'application gère les cas où la plate-forme ne s'ajuste pas pour répondre à la requête de l'application. Ainsi, si les conditions de l'appareil changent pour permettre l'utilisation de fréquences d'actualisation de l'écran supplémentaires, la plate-forme dispose des informations correctes pour basculer vers la fréquence d'images préférée de l'application.

Si l'application ne s'exécute pas ou ne peut pas s'exécuter à la fréquence d'actualisation de l'écran, elle doit spécifier des horodatages de présentation pour chaque image, à l'aide de l'un des mécanismes de la plate-forme pour définir les codes temporels de présentation:

L'utilisation de ces horodatages empêche la plate-forme de présenter trop tôt un frame d'application, ce qui entraînerait un gaspillage inutile. L'utilisation correcte des codes temporels de présentation de frames est un peu délicate. Pour les jeux, consultez notre guide du frame pacing pour savoir comment éviter les saccades et envisagez d'utiliser la bibliothèque Android Frame Pacing.

Dans certains cas, la plate-forme peut basculer vers un multiple de la fréquence d'images spécifiée par l'application dans setFrameRate(). Par exemple, une application peut appeler setFrameRate() avec 60 Hz, et l'appareil peut basculer l'affichage sur 120 Hz. Cela peut se produire si une autre application possède une surface avec un paramètre de fréquence d'images de 24 Hz. Dans ce cas, l'exécution de l'écran à 120 Hz permet à la surface 60 Hz et à la surface 24 Hz de fonctionner sans qu'il soit nécessaire de faire glisser l'écran.

Lorsque l'écran s'exécute à un multiple de la fréquence d'images de l'application, celle-ci doit spécifier des horodatages de présentation pour chaque image afin d'éviter les interférences inutiles. Pour les jeux, la bibliothèque Android Frame Pacing permet de définir correctement les codes temporels de présentation des frames.

setFrameRate() et preferredDisplayModeId

WindowManager.LayoutParams.preferredDisplayModeId est un autre moyen pour les applications d'indiquer leur fréquence d'images à la plate-forme. Certaines applications souhaitent uniquement modifier la fréquence d'actualisation de l'écran plutôt que de modifier d'autres paramètres de mode d'affichage, tels que la résolution d'affichage. En général, utilisez setFrameRate() au lieu de preferredDisplayModeId. La fonction setFrameRate() est plus facile à utiliser, car l'application n'a pas besoin de rechercher dans la liste des modes d'affichage pour trouver un mode avec une fréquence d'images spécifique.

setFrameRate() donne à la plate-forme plus de possibilités de choisir une fréquence d'images compatible dans les cas où plusieurs surfaces s'exécutent à des fréquences d'images différentes. Prenons l'exemple d'un scénario dans lequel deux applications s'exécutent en mode Écran partagé sur un Pixel 4, où une application lit une vidéo 24 Hz et l'autre montre à l'utilisateur une liste déroulante. Le Pixel 4 est compatible avec deux fréquences d'actualisation d'écran: 60 Hz et 90 Hz. Avec l'API preferredDisplayModeId, la surface vidéo est forcée de choisir 60 Hz ou 90 Hz. En appelant setFrameRate() avec 24 Hz, la surface vidéo donne à la plate-forme plus d'informations sur la fréquence d'images de la vidéo source. Elle peut ainsi choisir une fréquence d'actualisation de l'écran de 90 Hz, ce qui est préférable à 60 Hz dans ce scénario.

Toutefois, dans certains cas, preferredDisplayModeId doit être utilisé à la place de setFrameRate(), par exemple:

  • Si l'application souhaite modifier la résolution ou d'autres paramètres du mode d'affichage, utilisez preferredDisplayModeId.
  • La plate-forme ne change de mode d'affichage en réponse à un appel à setFrameRate() que si le changement de mode est léger et peu visible pour l'utilisateur. Si l'application préfère modifier la fréquence d'actualisation de l'écran même si elle nécessite un changement de mode lourd (par exemple, sur un appareil Android TV), utilisez preferredDisplayModeId.
  • Les applications qui ne peuvent pas gérer l'écran qui s'exécute à un multiple de la fréquence d'images de l'application, qui nécessite de définir un horodatage de présentation pour chaque image, doivent utiliser preferredDisplayModeId.

setFrameRate() et preferredRefreshRate

WindowManager.LayoutParams#preferredRefreshRate définit une fréquence d'images préférée pour la fenêtre de l'application. Cette fréquence s'applique à toutes les surfaces de la fenêtre. L'application doit spécifier sa fréquence d'images préférée, quelles que soient les fréquences d'actualisation compatibles avec l'appareil (comme setFrameRate()), afin de donner au programmeur un meilleur indice sur la fréquence d'images prévue par l'application.

preferredRefreshRate est ignoré pour les surfaces qui utilisent setFrameRate(). En règle générale, utilisez setFrameRate() si possible.

PreferredRefreshRate et PreferredDisplayModeId

Si les applications souhaitent uniquement modifier la fréquence d'actualisation préférée, il est préférable d'utiliser preferredRefreshRate plutôt que preferredDisplayModeId.

Éviter d'appeler trop souvent setFrameRate()

Bien que l'appel setFrameRate() ne soit pas très coûteux en termes de performances, les applications doivent éviter d'appeler setFrameRate() à chaque frame ou plusieurs fois par seconde. Les appels à setFrameRate() sont susceptibles de modifier la fréquence d'actualisation de l'affichage, ce qui peut entraîner une perte de frames pendant la transition. Vous devez déterminer la fréquence d'images correcte à l'avance et appeler setFrameRate() une fois.

Utilisation pour des jeux ou d'autres applications non vidéo

Bien que la vidéo soit le cas d'utilisation principal de l'API setFrameRate(), elle peut servir pour d'autres applications. Par exemple, un jeu qui ne prévoit pas de fonctionner à plus de 60 Hz (pour réduire la consommation d'énergie et prolonger les sessions de jeu) peut appeler Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT). De cette manière, un appareil qui s'exécute par défaut à 90 Hz fonctionne à 60 Hz lorsque le jeu est actif, ce qui évite les saccades qui se produiraient autrement si le jeu s'exécutait à 60 Hz alors que l'écran fonctionnait à 90 Hz.

Utilisation de IMAGE_RATE_COMPATIBILITY_FIXED_SOURCE

FRAME_RATE_COMPATIBILITY_FIXED_SOURCE ne concerne que les applications vidéo. Pour une utilisation non vidéo, utilisez FRAME_RATE_COMPATIBILITY_DEFAULT.

Choisir une stratégie pour modifier la fréquence d'images

  • Lors de l'affichage de vidéos de longue durée telles que des films, nous recommandons vivement aux applications d'appeler setFrameRate(FPS, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS), où FPS correspond à la fréquence d'images de la vidéo.
  • Nous vous déconseillons vivement aux applications qui appellent setFrameRate() avec CHANGE_FRAME_RATE_ALWAYS lorsque la lecture d'une vidéo devrait durer plusieurs minutes ou moins.

Exemple d'intégration pour les applications de lecture vidéo

Pour intégrer des boutons de fréquence d'actualisation dans les applications de lecture vidéo, nous vous recommandons de suivre la procédure ci-dessous:

  1. Définissez l'élément changeFrameRateStrategy :
    1. Si vous lisez une vidéo de longue durée, comme un film, utilisez MATCH_CONTENT_FRAMERATE_ALWAYS.
    2. Si vous lancez une vidéo courte (bande-annonce d'un mouvement, par exemple), utilisez CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS.
  2. Si changeFrameRateStrategy est CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, passez à l'étape 4.
  3. Déterminez si un changement de fréquence d'actualisation non fluide est sur le point de se produire en vérifiant que les deux faits suivants sont vrais :
    1. Le changement de mode fluide n'est pas possible entre la fréquence d'actualisation actuelle (appelons-la C) et la fréquence d'images de la vidéo (appelons-la V). Ce sera le cas si C et V sont différents et que Display.getMode().getAlternativeRefreshRates ne contient pas un multiple de V.
    2. L'utilisateur a accepté de modifier la fréquence d'actualisation sans accroc. Vous pouvez le détecter en vérifiant si DisplayManager.getMatchContentFrameRateUserPreference renvoie MATCH_CONTENT_FRAMERATE_ALWAYS.
  4. Si le changement se déroule sans accroc, procédez comme suit :
    1. Appelez setFrameRate et transmettez-lui fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE et changeFrameRateStrategy, où fps correspond à la fréquence d'images de la vidéo.
    2. Lancer la lecture de la vidéo
  5. Si un changement de mode fluide est sur le point de se produire, procédez comme suit :
    1. Afficher l'expérience utilisateur pour avertir l'utilisateur. Notez que nous vous recommandons de mettre en œuvre un moyen permettant à l'utilisateur d'ignorer cette expérience utilisateur et d'ignorer le délai supplémentaire indiqué à l'étape 5.d. En effet, le délai recommandé est plus long que nécessaire sur les écrans qui présentent des temps de basculement plus rapides.
    2. Appelez setFrameRate et transmettez-lui fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE et CHANGE_FRAME_RATE_ALWAYS, où fps correspond à la fréquence d'images de la vidéo.
    3. Attendez le rappel onDisplayChanged.
    4. Attendez deux secondes que le changement de mode soit terminé.
    5. Lancer la lecture de la vidéo

Le pseudo-code permettant uniquement un basculement fluide est le suivant:

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

Voici le pseudo-code permettant un basculement fluide et sans accroc comme décrit ci-dessus:

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