Fréquence d'images

L'API de fréquence d'images permet aux applications d'informer la plate-forme Android de la fréquence d'images souhaitée. Elle est disponible pour 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 acceptent désormais des fréquences d'actualisation supplémentaires, telles que 90 Hz ou 120 Hz. Certains appareils sont compatibles avec les changements de fréquence d'actualisation, 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 tirer parti de toutes les fréquences d'actualisation de l'affichage compatibles. Par exemple, une application qui lit une vidéo 24 Hz qui appelle setFrameRate() peut entraîner la modification de la fréquence d'actualisation de l'écran de 60 Hz à 120 Hz. Cette nouvelle fréquence d'actualisation permet une lecture fluide et sans saccades des vidéos 24 Hz, sans avoir à utiliser un menu déroulant 3:2 comme cela serait 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:

Pour appeler setFrameRate() en toute sécurité, l'application n'a pas besoin de prendre en compte les fréquences d'actualisation de l'écran réellement acceptées, que vous pouvez obtenir en appelant Display.getSupportedModes(). 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 à la fréquence d'images de l'application conservent la fréquence d'actualisation actuelle de l'affichage.

Pour voir si un appel à setFrameRate() modifie la fréquence d'actualisation de l'affichage, inscrivez-vous aux notifications de modification de l'écran 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 d'arrondir à un nombre entier. Par exemple, pour le rendu d'une vidéo enregistrée à 29,97 Hz, indiquez 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 afin d'indiquer à la plate-forme Android que l'application utilisera le menu déroulant pour s'adapter à une fréquence d'actualisation de l'affichage qui ne correspond pas (ce qui entraînera des saccades).

Dans certains cas, la surface de la vidéo cesse d'envoyer des images, mais reste visible à l'écran pendant un certain temps. Il peut s'agir, par exemple, de la fin de la vidéo ou de la mise en pause de la lecture par l'utilisateur. Dans ce cas, appelez setFrameRate() avec le paramètre de fréquence d'images défini sur 0 pour effacer le paramètre de fréquence d'images de la surface à la valeur par défaut. Il n'est pas nécessaire d'effacer le paramètre de fréquence d'images de ce type lorsque la surface est détruite ou lorsque celle-ci est masquée, car l'utilisateur passe à une autre application. Ne effacez le paramètre de fréquence d'images que lorsque la surface reste visible sans être utilisée.

Changement dynamique de la fréquence d'images

Sur certains appareils, le changement de la fréquence d'actualisation peut entraîner des interruptions visuelles, telles qu'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 d'éviter les artefacts de conversion de fréquence d'images tels que les saccades d'extraction en 3:2 pour la lecture du film.

Pour cette raison, les changements de fréquence d'actualisation fluides peuvent être activés si l'utilisateur et les applications acceptent à la fois:

Nous vous recommandons de toujours utiliser CHANGE_FRAME_RATE_ALWAYS pour les vidéos de longue durée, telles que 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 les recommandations ci-dessous pour les scénarios courants.

Plusieurs surfaces

La plate-forme Android est conçue pour gérer correctement les cas où plusieurs surfaces présentent 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, à l'aide du mode Écran partagé ou Picture-in-picture, chaque application peut appeler setFrameRate() en toute sécurité 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'écran 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 (en limitant la fréquence d'actualisation de l'écran pour préserver la batterie). L'application doit toujours fonctionner correctement lorsque l'appareil ne fait pas passer la fréquence d'actualisation de l'affichage au paramètre de fréquence d'images de l'application, même si l'appareil change de mode dans des circonstances normales.

Il appartient à l'application de décider comment réagir lorsque la fréquence d'actualisation de l'affichage ne correspond pas à la fréquence d'images de l'application. Pour les vidéos, la fréquence d'images est fixe à celle de la vidéo source. Un menu déroulant est nécessaire pour afficher le contenu vidéo. À la place, un jeu peut choisir de s'exécuter à la fréquence d'actualisation de l'affichage 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 elle gère les cas où la plate-forme ne s'ajuste pas à 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 appropriées pour passer à la fréquence d'images préférée de l'application.

Si l'application ne peut pas ou ne peut pas s'exécuter à la fréquence d'actualisation de l'affichage, 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 horodatages de présentation:

L'utilisation de ces codes temporels empêche la plate-forme de présenter une image d'application trop tôt, ce qui entraînerait des saccades inutiles. L'utilisation correcte des horodatages de présentation de trame est un peu délicate. Pour les jeux, consultez notre guide concernant le frame pacing afin d'é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 une fréquence de 60 Hz et l'appareil peut faire passer l'écran à 120 Hz. Cela peut se produire si une autre application a une surface avec un paramètre de fréquence d'images de 24 Hz. Dans ce cas, une fréquence de 120 Hz permet aux surfaces de 60 Hz et de 24 Hz de fonctionner sans qu'il soit nécessaire d'effectuer un défilement.

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 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() et preferenceDisplayModeId

WindowManager.LayoutParams.preferredDisplayModeId est un autre moyen permettant aux applications d'indiquer leur fréquence d'images à la plate-forme. Certaines applications ne souhaitent modifier que la fréquence d'actualisation de l'affichage plutôt que de modifier d'autres paramètres de mode d'affichage, tels que 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 rechercher dans la liste des modes d'affichage pour trouver un mode avec une fréquence d'images spécifique.

setFrameRate() permet à la plate-forme de choisir une fréquence d'images compatible dans les cas où plusieurs surfaces s'exécutent à différentes fréquences d'images. 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 présente à l'utilisateur 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 doit choisir entre 60 Hz ou 90 Hz. En appelant setFrameRate() avec une fréquence de 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'affichage, qui est meilleure que 60 Hz dans ce scénario.

Toutefois, dans certains cas, preferredDisplayModeId doit être utilisé au lieu 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 changera de mode d'affichage en réponse à un appel à setFrameRate() que si le changement de mode est léger et peu être visible pour l'utilisateur. Si l'application préfère modifier la fréquence d'actualisation de l'affichage 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'affichage de l'écran à un multiple de la fréquence d'images de l'application, ce qui nécessite de définir des horodatages 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 dans la fenêtre de l'application. La 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 prises en charge par l'appareil (comme setFrameRate()) afin de fournir au planificateur 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 si possible setFrameRate().

PreferredRefreshRate et preferenceDisplayModeId

Si les applications ne souhaitent modifier que la fréquence d'actualisation souhaité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 à l'avance la fréquence d'images appropriée 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 prévoit pas de dépasser 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 fonctionnant à 90 Hz par défaut s'exécutera à la place à 60 Hz lorsque le jeu est actif, ce qui évitera les saccades qui se produiraient autrement si le jeu fonctionnait à 60 Hz tandis 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 autre que vidéo, utilisez FRAME_RATE_COMPATIBILITY_DEFAULT.

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

  • Lorsque vous affichez des vidéos de longue durée, comme des films, nous vous recommandons vivement 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 d'appeler setFrameRate() avec CHANGE_FRAME_RATE_ALWAYS lorsque vous prévoyez que la lecture des vidéos durera plusieurs minutes ou moins.

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

Nous vous recommandons de procéder comme suit pour intégrer les boutons de fréquence d'actualisation dans les applications de lecture vidéo:

  1. Déterminez le changeFrameRateStrategy :
    1. Si vous lisez une vidéo de longue durée, telle qu'un film, utilisez MATCH_CONTENT_FRAMERATE_ALWAYS.
    2. Si vous regardez une vidéo courte, telle qu'une bande-annonce animée, utilisez CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS.
  2. Si changeFrameRateStrategy correspond à CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, passez à l'étape 4.
  3. Pour détecter si un changement de fréquence d'actualisation non fluide est sur le point de se produire, vérifiez que ces deux faits sont vrais :
    1. Il n'est pas possible de passer de la fréquence d'actualisation actuelle (appelée C) à la fréquence d'images de la vidéo (appelée V). Cela se produira si C et V sont différents et que Display.getMode().getAlternativeRefreshRates ne contient pas de multiple de V.
    2. L'utilisateur a activé les modifications fluides de la fréquence d'actualisation. Pour le détecter, vérifiez si DisplayManager.getMatchContentFrameRateUserPreference renvoie MATCH_CONTENT_FRAMERATE_ALWAYS.
  4. Si le changement se déroule sans heurts, 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 place un moyen permettant à l'utilisateur d'ignorer cette expérience utilisateur et d'ignorer le délai supplémentaire de l'étape 5.d. En effet, le délai recommandé est supérieur au délai nécessaire sur les écrans qui présentent 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 correspond à 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 terminé.
    5. Lancer la lecture de la vidéo

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

Le pseudo-code permettant une commutation fluide et fluide, tel que décrit ci-dessus, se présente comme suit:

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