Fréquence d'images

L'API Frame Rate permet aux applications d'informer la plate-forme Android de la fréquence d'images prévue. Elle est disponible sur les applications qui ciblent Android 11 (niveau d'API 30) ou une version ultérieure. Traditionnellement, la plupart des appareils n'étaient compatibles qu'avec une seule fréquence d'actualisation de l'écran, généralement 60 Hz, mais cela a changé. De nombreux appareils sont désormais compatibles avec d'autres fréquences d'actualisation, comme 90 Hz ou 120 Hz. Certains appareils permettent de changer de fréquence d'actualisation de manière fluide, 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 lisant une vidéo 24 Hz qui appelle setFrameRate() peut entraîner le passage de la fréquence d'actualisation de l'affichage de l'appareil de 60 Hz à 120 Hz. Cette nouvelle fréquence d'actualisation permet une lecture fluide et sans saccades des vidéos 24 Hz, sans qu'il soit nécessaire d'effectuer une conversion 3:2, comme ce serait le cas pour lire la même vidéo sur un écran 60 Hz. Cela améliore l'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éellement acceptées, qui peuvent être obtenues en appelant Display.getSupportedModes(), pour appeler setFrameRate() de manière sécurisée. Par exemple, même si l'appareil n'est compatible qu'avec une fréquence de 60 Hz, appelez setFrameRate() avec la fréquence d'images que votre application préfère. Les appareils qui ne disposent pas d'une meilleure correspondance pour la fréquence d'images de l'application conserveront la fréquence d'actualisation de l'écran actuelle.

Pour voir si un appel à setFrameRate() entraîne une modification de la fréquence d'actualisation de l'écran, enregistrez-vous pour recevoir des notifications de modification de l'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 entier. Par exemple, lors de l'affichage d'une vidéo enregistrée à 29,97 Hz, transmettez 29,97 plutôt que 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 le pulldown pour s'adapter à une fréquence d'actualisation de l'écran non correspondante (ce qui entraînera des saccades).

Dans certains cas, la surface vidéo cesse d'envoyer des images, mais reste visible à l'écran pendant un certain temps. Par exemple, lorsque la lecture atteint la fin de la 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 d'effacer le paramètre de fréquence d'images de cette manière lorsque vous détruisez la surface ou lorsqu'elle est masquée parce que l'utilisateur passe à une autre application. Effacez le paramètre de fréquence d'images uniquement lorsque la surface reste visible sans être utilisée.

Changement de fréquence d'images non fluide

Sur certains appareils, le changement de fréquence d'actualisation peut entraîner des interruptions visuelles, comme un écran noir pendant une ou deux secondes. Cela se produit généralement sur les boîtiers décodeurs, les téléviseurs 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 vidéo et d'éviter les artefacts de conversion de la fréquence d'images, tels que les saccades de conversion 3:2 pour la lecture de films.

C'est pourquoi les changements de fréquence d'actualisation non fluides peuvent être activés si l'utilisateur et les applications l'acceptent :

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 de faire correspondre la fréquence d'images de la vidéo l'emporte sur l'interruption qui se produit lors du changement de la fréquence d'actualisation.

Autres recommandations

Suivez ces recommandations pour les scénarios courants.

Plusieurs surfaces

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 des fréquences d'images différentes, appelez setFrameRate() avec la fréquence d'images appropriée pour chaque surface. Même si l'appareil exécute plusieurs applications à la fois, en mode Écran partagé ou Picture-in-picture, chaque application peut appeler setFrameRate() en toute sécurité pour ses propres surfaces.

La plate-forme ne passe 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 qu'il ne bascule pas l'affichage sur cette fréquence d'actualisation. Par exemple, une surface de priorité plus élevée peut avoir un paramètre de fréquence d'images différent, ou l'appareil peut être en mode Économiseur de batterie (ce qui limite la fréquence d'actualisation de l'écran 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 bascule 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, et un pulldown sera nécessaire pour afficher le contenu vidéo. Un jeu peut choisir d'essayer de s'exécuter à la fréquence d'actualisation de l'écran plutôt que de conserver sa fréquence d'images préférée. L'application ne doit pas modifier la valeur qu'elle transmet à setFrameRate() en fonction de ce que fait 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 manière dont l'application gère les cas où la plate-forme ne s'ajuste pas pour correspondre à 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'affichage supplémentaires, la plate-forme dispose des informations correctes pour passer à la fréquence d'images préférée de l'application.

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

L'utilisation de ces codes temporels empêche la plate-forme de présenter un frame d'application trop tôt, ce qui entraînerait des saccades inutiles. L'utilisation correcte des codes temporels de présentation des images est un peu délicate. Pour les jeux, consultez notre guide sur le 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 passer à 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, par exemple, 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'affichage à 120 Hz permettra aux surfaces 60 Hz et 24 Hz de s'exécuter sans qu'un pulldown ne soit nécessaire.

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

setFrameRate() ou preferredDisplayModeId

WindowManager.LayoutParams.preferredDisplayModeId est un autre moyen pour les applications d'indiquer leur fréquence d'images à la plate-forme. Certaines applications ne souhaitent modifier que la fréquence d'actualisation de l'écran, et non d'autres paramètres du mode d'affichage, comme la résolution de l'écran. 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 parcourir la liste des modes d'affichage pour trouver un mode avec une fréquence d'images spécifique.

setFrameRate() donne à la plate-forme plus d'opportunités de choisir une fréquence d'images compatible dans les scénarios où plusieurs surfaces s'exécutent à des fréquences d'images différentes. Par exemple, imaginons que deux applications s'exécutent en mode Écran partagé sur un Pixel 4, l'une lisant une vidéo à 24 Hz et l'autre affichant une liste déroulante. Le Pixel 4 est compatible avec deux fréquences d'actualisation de l'affichage : 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 fournit à la plate-forme plus d'informations sur la fréquence d'images de la vidéo source, ce qui lui permet de choisir 90 Hz pour la fréquence d'actualisation de l'écran, ce qui est préférable à 60 Hz dans ce scénario.

Toutefois, dans certains scénarios, il est préférable d'utiliser preferredDisplayModeId plutôt que 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 susceptible d'être remarqué par l'utilisateur. Si l'application préfère changer la fréquence d'actualisation de l'écran même si cela 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'affichage à un multiple de leur fréquence d'images, ce qui nécessite de définir des codes temporels de présentation sur chaque image, doivent utiliser preferredDisplayModeId.

setFrameRate() et preferredRefreshRate

WindowManager.LayoutParams#preferredRefreshRate définit une fréquence d'images préférée dans la fenêtre de l'application, qui 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 acceptées par l'appareil, comme pour setFrameRate(), afin de donner au programmeur une meilleure indication de la fréquence d'images prévue de l'application.

preferredRefreshRate est ignoré pour les surfaces qui utilisent setFrameRate(). En général, 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 setFrameRate() trop fréquemment

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 d'entraîner une modification de la fréquence d'actualisation de l'affichage, ce qui peut entraîner une perte d'images pendant la transition. Vous devez déterminer la fréquence d'images appropriée à 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 principal cas d'utilisation de l'API setFrameRate(), elle peut être utilisée pour d'autres applications. Par exemple, un jeu qui ne doit pas s'exécuter à 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). Ainsi, un appareil qui fonctionne à 90 Hz par défaut fonctionnera à 60 Hz lorsque le jeu est actif, ce qui évitera les saccades qui se produiraient si le jeu fonctionnait à 60 Hz alors que l'écran fonctionnait à 90 Hz.

Utilisation de FRAME_RATE_COMPATIBILITY_FIXED_SOURCE

FRAME_RATE_COMPATIBILITY_FIXED_SOURCE n'est destiné qu'aux 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

  • Nous recommandons vivement aux applications, lorsqu'elles affichent des vidéos de longue durée telles que des films, d'appeler setFrameRate(fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS), où fps est la fréquence d'images de la vidéo.
  • Nous vous déconseillons vivement d'appeler setFrameRate() avec CHANGE_FRAME_RATE_ALWAYS lorsque vous vous attendez à ce que la lecture de la vidéo dure plusieurs minutes ou moins.

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

Nous vous recommandons de suivre les étapes suivantes pour intégrer les changements de fréquence d'actualisation dans les applications de lecture vidéo :

  1. Déterminez le changeFrameRateStrategy :
    1. Si vous lisez une vidéo longue, comme un film, utilisez MATCH_CONTENT_FRAMERATE_ALWAYS.
    2. Si vous lisez une vidéo courte, comme une bande-annonce de film, utilisez CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS.
  2. Si changeFrameRateStrategy est défini sur CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, passez à l'étape 4.
  3. Détectez si un changement de fréquence d'actualisation non fluide est sur le point de se produire en vérifiant que les deux conditions suivantes sont remplies :
    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 activé les changements de fréquence d'actualisation non fluides. Pour le détecter, vérifiez si DisplayManager.getMatchContentFrameRateUserPreference renvoie MATCH_CONTENT_FRAMERATE_ALWAYS.
  4. Si la transition doit être fluide, procédez comme suit :
    1. Appelez setFrameRate et transmettez-lui fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE et changeFrameRateStrategy, où fps est la fréquence d'images de la vidéo.
    2. Lancer la lecture de la vidéo
  5. Si un changement de mode non fluide est sur le point de se produire, procédez comme suit :
    1. Affichez l'UX pour avertir l'utilisateur. Notez que nous vous recommandons de mettre en place un moyen permettant à l'utilisateur d'ignorer cette expérience utilisateur et d'éviter le délai supplémentaire à l'étape 5.d. En effet, le délai que nous recommandons est plus long que nécessaire sur les écrans qui affichent des temps de commutation plus rapides.
    2. Appelez setFrameRate et transmettez-lui fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE et CHANGE_FRAME_RATE_ALWAYS, où fps est la fréquence d'images de la vidéo.
    3. Attendez le rappel onDisplayChanged.
    4. Patientez deux secondes, le temps que le changement de mode soit effectif.
    5. Lancer la lecture de la vidéo

Le pseudo-code permettant de uniquement prendre en charge le changement dynamique 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();

Le pseudo-code permettant de prendre en charge le changement dynamique et non dynamique, comme décrit ci-dessus, est le suivant :

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