Velocidad de fotogramas

La API de velocidad de fotogramas permite que las apps informen a la plataforma de Android su velocidad de fotogramas prevista y está disponible en apps orientadas a 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 sin interrupciones, 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 admitidas. Por ejemplo, una app que reproduce un video de 24 Hz que 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 la reproducción fluida y sin interrupciones de videos de 24 Hz, sin necesidad de una reducción 3:2, como sería necesario para reproducir el mismo video en una pantalla de 60 Hz. Esto da como resultado una mejor experiencia una experiencia fluida a los desarrolladores.

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 la con los mismos parámetros y funciona igual que los demás:

La app no necesita considerar las tasas de actualización de pantalla compatibles 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 velocidad de fotogramas que prefiera tu app. Los dispositivos que no tengan una mejor concordancia con la velocidad de fotogramas de la app permanecerán como la frecuencia de actualización actual de la pantalla.

Para ver si una llamada a setFrameRate() genera un cambio en la actualización de la pantalla frecuencia, registra para recibir notificaciones de cambio de pantalla llamando DisplayManager.registerDisplayListener() o AChoreographer_registerRefreshRateCallback().

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 dar una sugerencia adicional a la plataforma de Android de que la app usará el menú desplegable para adaptarse a una frecuencia de actualización de la pantalla que no coincide (lo que provocará un parpadeo).

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

Interruptor de velocidad de fotogramas ininterrumpido

En algunos dispositivos, el cambio de frecuencia de actualización puede tener interrupciones visuales, como una pantalla negra durante uno o dos segundos. Esto suele ocurrir 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 esas interrupciones visuales.

Algunos usuarios prefieren una interrupción visual al principio y el final de los videos más largos. Esto permite que coincida la frecuencia de actualización de la pantalla la velocidad de fotogramas del video y evitar artefactos de conversión de velocidad de fotogramas como 3:2 sacudida desplegable para reproducir la película.

Por este motivo, se pueden habilitar los interruptores de frecuencia de actualización no fluida si el usuario y las apps aceptan lo siguiente:

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.

Varias superficies

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

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

Incluso si el dispositivo admite la velocidad de fotogramas que especifica la app en una llamada a setFrameRate(), hay casos en los que el dispositivo no cambia la pantalla a esa frecuencia de actualización. Por ejemplo, una superficie de mayor prioridad puede tener un valor velocidad de fotogramas o que el dispositivo esté en modo ahorro de batería (configurar una en la frecuencia de actualización de la pantalla para conservar la batería). La app aún debe funcionar correctamente cuando el dispositivo no cambia la frecuencia de actualización de la pantalla al parámetro de configuración de la velocidad de fotogramas de la app, incluso si el dispositivo cambia en circunstancias normales.

Depende de la app 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 es fija y se corresponde con la del video de origen. Para mostrar el contenido de video, se requerirá un menú desplegable. En cambio, un juego puede intentar ejecutarse a la frecuencia de actualización de la pantalla en lugar de mantener su velocidad de fotogramas preferida. La app no debería cambiar su valor pase a setFrameRate() según lo que haga la plataforma. Debería permanecer fija según la velocidad de fotogramas preferida de la app, sin importar cómo la app maneja los casos en los que la plataforma no se ajusta para coincidir con la solicitud de la app. De esta manera, si las condiciones del dispositivo cambian para permitir que se usen frecuencias de actualización de pantalla adicionales, la plataforma tiene la información correcta para cambiar a la velocidad de fotogramas 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, esta debe especificar marcas de tiempo de presentación para cada fotograma con 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 temprano, lo que generaría un parpadeo innecesario. El uso correcto de las marcas de tiempo de presentación de tramas es un poco complicado. En el caso de los juegos, consulta nuestra guía de regulación de fotogramas para obtener más información sobre cómo evitar el parpadeo y considera usar la biblioteca de regulación de fotogramas de Android.

En algunos casos, la plataforma puede cambiar a un múltiplo de la velocidad de fotogramas de la app especificadas en setFrameRate(). Por ejemplo, una app puede llamar a setFrameRate() con 60 Hz, y el dispositivo puede cambiar la pantalla a 120 Hz. Una de las razones por las que esto podría sucede cuando otra app tiene una superficie con una configuración de velocidad 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 una bajada.

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 para evitar un parpadeo innecesario. En el caso de los juegos, la biblioteca de Android Frame Pacing es útil para configurar correctamente las marcas de tiempo de presentación de fotogramas.

setFrameRate() frente a PreferredDisplayModeId

WindowManager.LayoutParams.preferredDisplayModeId es otra forma en que las apps pueden indicar su velocidad de fotogramas a la plataforma. Algunos las apps solo quieren cambiar la frecuencia de actualización de la pantalla en lugar de cambiar otras del modo de visualización, como la resolución. En general, usa setFrameRate() en lugar de preferredDisplayModeId. El setFrameRate() es más fácil de usar porque la app no necesita buscar en la de modos de visualización para encontrar un modo con una velocidad 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, imagina una situación en la que se ejecutan dos apps 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 velocidad de fotogramas del video de origen, lo que le permite elegir 90 Hz para la frecuencia de actualización de la pantalla, que es mejor que 60 Hz en esta situación.

Sin embargo, hay situaciones en las que se debe usar preferredDisplayModeId. en lugar de setFrameRate(), como se muestra a continuación:

  • Si la app quiere cambiar la resolución o alguna otra configuración del modo de visualización, haz lo siguiente: usa preferredDisplayModeId.
  • La plataforma solo cambiará los modos de visualización en respuesta a una llamada a setFrameRate() si el interruptor de modo es ligero y es poco probable que lo sea perceptibles para el usuario. Si la app prefiere cambiar la frecuencia de actualización de la pantalla, incluso si requiere un interruptor de modo pesado (por ejemplo, en un dispositivo Android TV), usa preferredDisplayModeId.
  • Apps que no pueden controlar la pantalla ejecutándose en un múltiplo del fotograma de la app de presentación, que requiere configurar marcas de tiempo de presentación en cada fotograma, usa preferredDisplayModeId.

setFrameRate() frente a 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 compatibles del 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 usa setFrameRate() si es posible.

PreferredRefreshRate frente a PreferredDisplayModeId

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

Evita llamar a setFrameRate() con demasiada frecuencia

Aunque 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() generen un cambio en el la frecuencia de actualización de la pantalla, lo que puede provocar una caída de fotogramas durante la transición. Debes determinar la velocidad de fotogramas correcta de antemano y llamar setFrameRate() una vez.

Uso para juegos y otras 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 tiene la intención de ejecutarse a más de 60 Hz (para reducir el consumo de energía y lograr sesiones de juego más largas) puede llamar a Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT). En este Por eso, un dispositivo que funciona a 90 Hz de forma predeterminada lo hará a 60 Hz, mientras que el juego está activo, lo que evitará el sacudón que se generaría si el el juego se ejecutó a 60 Hz y la pantalla a 90 Hz.

Uso de FRAME_RATE_COMPATIBILITY_FIXED_SOURCE

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

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

  • Recomendamos que las aplicaciones, al mostrar videos de larga duración, como películas, llama a setFrameRate(FPS, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS) donde fps es la velocidad de fotogramas del video.
  • Te recomendamos que no uses las apps que llamen a setFrameRate() con CHANGE_FRAME_RATE_ALWAYS cuando esperas que la reproducción de un 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 cambios de frecuencia de actualización en las apps de reproducción de video:

  1. Decide el changeFrameRateStrategy:
    1. Si reproduces un video de larga duración, como una película, usa MATCH_CONTENT_FRAMERATE_ALWAYS.
    2. Si reproduces un video corto, como un avance en movimiento, usa CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS.
  2. Si el changeFrameRateStrategy es CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS , ve al paso 4.
  3. Detecta si está a punto de ocurrir un cambio de frecuencia de actualización no fluido mediante la que los dos hechos son verdaderos:
    1. No es posible cambiar del modo fluido a la frecuencia de actualización actual (llamémosla C) a la velocidad 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 aceptó los cambios de frecuencia de actualización no fluidos. Para detectar esto, verifica si DisplayManager.getMatchContentFrameRateUserPreference muestra MATCH_CONTENT_FRAMERATE_ALWAYS.
  4. Si el cambio va a ser fluido, haz lo siguiente:
    1. Llama a setFrameRate y pasarlo fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, y changeFrameRateStrategy, donde fps es la velocidad de fotogramas del video.
    2. Cómo comenzar a reproducir un video
  5. Si se va a producir un cambio de modo no fluido, haz lo siguiente:
    1. Mostrar UX para notificar al usuario Ten en cuenta que te recomendamos implementar una forma para que el usuario descarte esta UX y omita la demora adicional en el paso 5.d. Este es porque el retraso recomendado es mayor que el necesario en pantallas que presentan tiempos de cambio 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 de onDisplayChanged.
    4. Espera 2 segundos a que se complete el cambio de modo.
    5. Iniciar reproducción de video

El seudocódigo que solo admite el cambio fluido 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 que admite el cambio fluido y fluido, 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();
}