Velocidad de fotogramas

La API de velocidad de fotogramas permite que las apps informen a la plataforma de Android sobre la velocidad de fotogramas deseada y está disponible en las apps segmentadas para Android 11 (nivel de API 30) o versiones posteriores. Tradicionalmente, la mayoría de los dispositivos solo admitían una frecuencia de actualización de la pantalla, que en general era de 60 Hz, pero esto cambió. Muchos dispositivos ahora admiten frecuencias de actualización adicionales, como 90 Hz o 120 Hz. Algunos dispositivos admiten cambios de frecuencia de actualización fluidos, mientras que otros muestran brevemente una pantalla negra, que suele durar un segundo.

El objetivo principal de la API es permitir que las apps aprovechen mejor todas las frecuencias de actualización de pantalla compatibles. Por ejemplo, una app que reproduce un video de 24 Hz y llama a setFrameRate() puede hacer que el dispositivo cambie la frecuencia de actualización de la pantalla de 60 Hz a 120 Hz. Esta nueva frecuencia de actualización permite una reproducción fluida y sin interrupciones de videos de 24 Hz, sin necesidad de un pulldown 3:2, como se requeriría para reproducir el mismo video en una pantalla de 60 Hz. Esto genera una mejor experiencia del usuario.

Uso básico

Android expone varias formas de acceder y controlar superficies, por lo que existen varias versiones de la API de setFrameRate(). Cada versión de la API toma los mismos parámetros y funciona igual que las demás:

La app no necesita tener en cuenta las frecuencias de actualización de pantalla admitidas reales, que se pueden obtener llamando a Display.getSupportedModes(), para llamar a setFrameRate() de forma segura. Por ejemplo, incluso si el dispositivo solo admite 60 Hz, llama a setFrameRate() con la frecuencia de fotogramas que prefiera tu app. Los dispositivos que no tengan una mejor coincidencia para la frecuencia de fotogramas de la app mantendrán la frecuencia de actualización de la pantalla actual.

Para ver si una llamada a setFrameRate() genera un cambio en la frecuencia de actualización de la pantalla, llama a DisplayManager.registerDisplayListener() o AChoreographer_registerRefreshRateCallback() para registrarte y recibir notificaciones sobre los cambios en la pantalla.

Cuando llames a setFrameRate(), es mejor pasar la velocidad de fotogramas exacta en lugar de redondearla a un número entero. Por ejemplo, cuando renderices un video grabado a 29.97 Hz, pasa 29.97 en lugar de redondear a 30.

En el caso de las apps de video, el parámetro de compatibilidad que se pasa a setFrameRate() debe establecerse en Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE para proporcionar una sugerencia adicional a la plataforma de Android de que la app usará la función de desplegable para adaptarse a una frecuencia de actualización de pantalla que no coincida (lo que generará vibraciones).

En algunos casos, la superficie de video dejará de enviar fotogramas, pero permanecerá visible en la pantalla durante un tiempo. Entre los casos comunes, se incluyen cuando la reproducción llega al final del video o cuando el usuario la pausa. En estos casos, llama a setFrameRate() con el parámetro de frecuencia de fotogramas establecido en 0 para borrar la configuración de frecuencia de fotogramas de la superficie y restablecer el valor predeterminado. No es necesario borrar el parámetro de configuración de la frecuencia de fotogramas de esta manera cuando se destruye la superficie o cuando se oculta porque el usuario cambia a otra app. Borra el parámetro de configuración de la frecuencia de fotogramas solo cuando la superficie permanezca visible sin usarse.

Cambio de velocidad de fotogramas no fluido

En algunos dispositivos, el cambio de frecuencia de actualización puede tener interrupciones visuales, como una pantalla negra durante uno o dos segundos. Por lo general, esto ocurre en decodificadores, paneles de TV y dispositivos similares. De forma predeterminada, el framework de Android no cambia de modo cuando se llama a la API de Surface.setFrameRate() para evitar interrupciones visuales.

Algunos usuarios prefieren una interrupción visual al principio y al final de los videos más largos. Esto permite que la frecuencia de actualización de la pantalla coincida con la frecuencia de fotogramas del video y evita artefactos de conversión de frecuencia de fotogramas, como el efecto de vibración de 3:2 pulldown para la reproducción de películas.

Por este motivo, se pueden habilitar los cambios de frecuencia de actualización no fluidos si tanto el usuario como las apps habilitan la opción:

Te recomendamos que siempre uses CHANGE_FRAME_RATE_ALWAYS para videos de larga duración, como películas. Esto se debe a que el beneficio de hacer coincidir la velocidad de fotogramas del video supera la interrupción que se produce cuando se cambia la frecuencia de actualización.

Recomendaciones adicionales

Sigue estas recomendaciones para situaciones comunes.

Múltiples superficies

La plataforma de Android está diseñada para controlar correctamente las situaciones en las que hay varias superficies con diferentes parámetros de configuración de la frecuencia de fotogramas. Cuando tu app tiene varias superficies con diferentes velocidades de fotogramas, llama a setFrameRate() con la velocidad de fotogramas correcta para cada superficie. Incluso si el dispositivo ejecuta varias apps a la vez, con el modo de pantalla dividida o de pantalla en pantalla, cada app puede llamar a setFrameRate() de forma segura para sus propias superficies.

La plataforma no cambia a la velocidad de fotogramas de la app

Incluso si el dispositivo admite la frecuencia de actualización que especifica la app en una llamada a setFrameRate(), hay casos en los que el dispositivo no cambiará la pantalla a esa frecuencia de actualización. Por ejemplo, una superficie de mayor prioridad puede tener un ajuste de velocidad de fotogramas diferente, o el dispositivo puede estar en modo de ahorro de batería (lo que establece una restricción en la frecuencia de actualización de la pantalla para conservar la batería). La app debe seguir funcionando correctamente cuando el dispositivo no cambie la frecuencia de actualización de la pantalla al parámetro de configuración de la frecuencia de fotogramas de la app, incluso si el dispositivo sí cambia en circunstancias normales.

La app debe decidir cómo responder cuando la frecuencia de actualización de la pantalla no coincide con la velocidad de fotogramas de la app. En el caso de los videos, la velocidad de fotogramas se fija en la del video fuente, y se requerirá un pulldown para mostrar el contenido de video. En cambio, un juego puede optar por intentar ejecutarse a la frecuencia de actualización de la pantalla en lugar de mantener su frecuencia de actualización preferida. La app no debe cambiar el valor que pasa a setFrameRate() en función de lo que hace la plataforma. Debe permanecer configurado en la velocidad de fotogramas preferida de la app, independientemente de cómo la app maneje los casos en los que la plataforma no se ajusta para coincidir con la solicitud de la app. De esa manera, si las condiciones del dispositivo cambian para permitir el uso de frecuencias de actualización de pantalla adicionales, la plataforma tendrá la información correcta para cambiar a la frecuencia de actualización preferida de la app.

En los casos en que la app no se ejecute o no pueda ejecutarse a la frecuencia de actualización de la pantalla, debe especificar marcas de tiempo de presentación para cada fotograma, utilizando uno de los mecanismos de la plataforma para establecer marcas de tiempo de presentación:

El uso de estas marcas de tiempo evita que la plataforma presente un fotograma de la app demasiado pronto, lo que generaría un temblor innecesario. El uso correcto de las marcas de tiempo de presentación de fotogramas es un poco complicado. En el caso de los juegos, consulta nuestra guía de regulación de marcos para obtener más información sobre cómo evitar el judder y considera usar la biblioteca de Frame Pacing de Android.

En algunos casos, la plataforma puede cambiar a un múltiplo de la velocidad de fotogramas que especificó la app en setFrameRate(). Por ejemplo, una app puede llamar a setFrameRate() con 60 Hz y el dispositivo puede cambiar la pantalla a 120 Hz. Una razón por la que esto podría suceder es que otra app tenga una superficie con un parámetro de configuración de frecuencia de fotogramas de 24 Hz. En ese caso, ejecutar la pantalla a 120 Hz permitirá que la superficie de 60 Hz y la de 24 Hz se ejecuten sin necesidad de pulldown.

Cuando la pantalla se ejecuta a un múltiplo de la velocidad de fotogramas de la app, esta debe especificar marcas de tiempo de presentación para cada fotograma y evitar así el temblor innecesario. En el caso de los juegos, la biblioteca de Android Frame Pacing es útil para establecer correctamente las marcas de tiempo de presentación de fotogramas.

setFrameRate() vs. preferredDisplayModeId

WindowManager.LayoutParams.preferredDisplayModeId es otra forma en que las apps pueden indicar su velocidad de fotogramas a la plataforma. Algunas apps solo quieren cambiar la frecuencia de actualización de la pantalla en lugar de cambiar otros parámetros de configuración del modo de visualización, como la resolución de la pantalla. En general, usa setFrameRate() en lugar de preferredDisplayModeId. La función setFrameRate() es más fácil de usar porque la app no necesita buscar en la lista de modos de visualización para encontrar uno con una frecuencia de fotogramas específica.

setFrameRate() le brinda a la plataforma más oportunidades para elegir una velocidad de fotogramas compatible en situaciones en las que hay varias plataformas que se ejecutan a diferentes velocidades de fotogramas. Por ejemplo, considera una situación en la que dos apps se ejecutan en modo de pantalla dividida en un Pixel 4, en la que una app reproduce un video de 24 Hz y la otra le muestra al usuario una lista desplazable. El Pixel 4 admite dos frecuencias de actualización de la pantalla: 60 Hz y 90 Hz. Con la API de preferredDisplayModeId, la superficie de video se ve obligada a elegir 60 Hz o 90 Hz. Cuando se llama a setFrameRate() con 24 Hz, la superficie de video le brinda a la plataforma más información sobre la frecuencia de fotogramas del video fuente, lo que le permite elegir 90 Hz para la frecuencia de actualización de la pantalla, que es mejor que 60 Hz en este caso.

Sin embargo, hay situaciones en las que se debe usar preferredDisplayModeId en lugar de setFrameRate(), como las siguientes:

  • Si la app quiere cambiar la resolución o la configuración de otro modo de pantalla, usa preferredDisplayModeId.
  • La plataforma solo cambiará los modos de visualización en respuesta a una llamada a setFrameRate() si el cambio de modo es ligero y es poco probable que el usuario lo note. Si la app prefiere cambiar la frecuencia de actualización de la pantalla, incluso si requiere un cambio de modo pesado (por ejemplo, en un dispositivo Android TV), usa preferredDisplayModeId.
  • Las apps que no pueden controlar la pantalla que se ejecuta en un múltiplo de la velocidad de fotogramas de la app, lo que requiere establecer marcas de tiempo de presentación en cada fotograma, deben usar preferredDisplayModeId.

setFrameRate() vs. preferredRefreshRate

WindowManager.LayoutParams#preferredRefreshRate establece una velocidad de fotogramas preferida en la ventana de la app, y la velocidad se aplica a todas las superficies dentro de la ventana. La app debe especificar su frecuencia de fotogramas preferida, independientemente de las frecuencias de actualización admitidas por el dispositivo, de manera similar a setFrameRate(), para darle al programador una mejor sugerencia de la frecuencia de fotogramas prevista de la app.

preferredRefreshRate se ignora para las plataformas que usan setFrameRate(). En general, usa setFrameRate() si es posible.

preferredRefreshRate vs. preferredDisplayModeId

Si las apps solo quieren cambiar la frecuencia de actualización preferida, es mejor usar preferredRefreshRate en lugar de preferredDisplayModeId.

Evita llamar a setFrameRate() con demasiada frecuencia

Si bien la llamada a setFrameRate() no es muy costosa en términos de rendimiento, las apps deben evitar llamar a setFrameRate() en cada fotograma o varias veces por segundo. Es probable que las llamadas a setFrameRate() provoquen un cambio en la frecuencia de actualización de la pantalla, lo que puede generar una pérdida de fotogramas durante la transición. Debes determinar la velocidad de fotogramas correcta con anticipación y llamar a setFrameRate() una vez.

Uso para juegos o apps que no son de video

Si bien el video es el caso de uso principal de la API de setFrameRate(), se puede usar para otras apps. Por ejemplo, un juego que no pretende ejecutarse a más de 60 Hz (para reducir el uso de energía y lograr sesiones de juego más largas) puede llamar a Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT). De esta manera, un dispositivo que se ejecuta a 90 Hz de forma predeterminada se ejecutará a 60 Hz mientras el juego esté activo, lo que evitará el efecto de vibración que se produciría si el juego se ejecutara a 60 Hz mientras la pantalla se ejecutara a 90 Hz.

Uso de FRAME_RATE_COMPATIBILITY_FIXED_SOURCE

FRAME_RATE_COMPATIBILITY_FIXED_SOURCE está diseñado solo para apps de video. Para el uso que no sea de video, usa FRAME_RATE_COMPATIBILITY_DEFAULT.

Cómo elegir una estrategia para cambiar la frecuencia de fotogramas

  • Te recomendamos que, cuando las apps muestren videos de larga duración, como películas, llamen a setFrameRate(fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS), donde fps es la velocidad de fotogramas del video.
  • Te recomendamos que las apps no llamen a setFrameRate() con CHANGE_FRAME_RATE_ALWAYS cuando esperes que la reproducción de video dure varios minutos o menos.

Ejemplo de integración para apps de reproducción de video

Te recomendamos que sigas estos pasos para integrar los interruptores de frecuencia de actualización en las apps de reproducción de video:

  1. Decide el changeFrameRateStrategy:
    1. Si se reproduce un video de larga duración, como una película, usa MATCH_CONTENT_FRAMERATE_ALWAYS.
    2. Si se reproduce un video corto, como un avance de película, usa CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS.
  2. Si el changeFrameRateStrategy es CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ve al paso 4.
  3. Para detectar si está a punto de producirse un cambio de frecuencia de actualización no fluido, verifica que ambas afirmaciones sean verdaderas:
    1. No es posible cambiar de modo sin problemas desde la frecuencia de actualización actual (llamémosla C) a la frecuencia de fotogramas del video (llamémosla V). Este será el caso si C y V son diferentes y Display.getMode().getAlternativeRefreshRates no contiene un múltiplo de V.
    2. El usuario habilitó los cambios de frecuencia de actualización no fluidos. Puedes detectar esto verificando si DisplayManager.getMatchContentFrameRateUserPreference devuelve MATCH_CONTENT_FRAMERATE_ALWAYS.
  4. Si el cambio será sin interrupciones, haz lo siguiente:
    1. Llama a setFrameRate y pásale fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE y changeFrameRateStrategy, donde fps es la velocidad de fotogramas del video.
    2. Comienza la reproducción del video
  5. Si está a punto de producirse un cambio de modo no fluido, haz lo siguiente:
    1. Mostrar la UX para notificar al usuario Ten en cuenta que te recomendamos que implementes una forma para que el usuario descarte esta UX y omita la demora adicional del paso 5d. Esto se debe a que la demora recomendada es mayor de lo necesario en las pantallas que muestran tiempos de conmutación más rápidos.
    2. Llama a setFrameRate y pásale fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE y CHANGE_FRAME_RATE_ALWAYS, donde fps es la velocidad de fotogramas del video.
    3. Espera la devolución de llamada onDisplayChanged.
    4. Espera 2 segundos para que se complete el cambio de modo.
    5. Comienza la reproducción del video

El pseudocódigo para admitir solo el cambio sin interrupciones es el siguiente:

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

El seudocódigo para admitir el cambio sin interrupciones y con interrupciones, como se describió anteriormente, es el siguiente:

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