Frequenza fotogrammi

L'API frequenza frame consente alle app di comunicare alla piattaforma Android la frequenza frame prevista ed è disponibile nelle app che hanno come target Android 11 (livello API 30) o versioni successive. In passato, la maggior parte dei dispositivi supportava una sola frequenza di aggiornamento del display, in genere 60 Hz, ma il comportamento è cambiato. Molti dispositivi ora supportano frequenze di aggiornamento aggiuntive, ad esempio 90 Hz o 120 Hz. Alcuni dispositivi supportano il passaggio senza interruzioni della frequenza di aggiornamento, mentre altri mostrano brevemente una schermata nera, in genere per un secondo.

Lo scopo principale dell'API è consentire alle app di sfruttare meglio tutti i tassi di aggiornamento del display supportati. Ad esempio, un'app che riproduce un video a 24 Hz che chiama setFrameRate() potrebbe causare la modifica della frequenza di aggiornamento del display da 60 Hz a 120 Hz. La nuova frequenza di aggiornamento consente una riproduzione fluida e senza sbalzi di video a 24 Hz, che non richiede il pulldown 3:2, in quanto sarebbe necessario per riprodurre lo stesso video su un display a 60 Hz. Ciò si traduce in una migliore esperienza utente.

Utilizzo di base

Android espone diversi modi per accedere e controllare le piattaforme, quindi esistono diverse versioni dell'API setFrameRate(). Ogni versione dell'API accetta gli stessi parametri e funziona come le altre:

L'app non deve considerare le frequenze di aggiornamento effettive supportate dal display, che possono essere ottenute chiamando Display.getSupportedModes(), per chiamare in sicurezza setFrameRate(). Ad esempio, anche se il dispositivo supporta solo 60 Hz, chiama setFrameRate() con la frequenza frame preferita dalla tua app. I dispositivi che non hanno una corrispondenza migliore per la frequenza fotogrammi dell'app rimarranno con la frequenza di aggiornamento del display corrente.

Per sapere se una chiamata al numero setFrameRate() determina una modifica della frequenza di aggiornamento del display, registrati per le notifiche di modifica del display chiamando il numero DisplayManager.registerDisplayListener() o AChoreographer_registerRefreshRateCallback().

Quando chiami setFrameRate(), è meglio indicare la frequenza fotogrammi esatta anziché arrotondare a un numero intero. Ad esempio, per il rendering di un video registrato a 29,97 Hz, utilizza 29,97 anziché arrotondarlo a 30.

Per le app video, il parametro di compatibilità passato a setFrameRate() deve essere impostato su Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE per fornire un ulteriore suggerimento alla piattaforma Android che l'app utilizzerà il pulldown per adattarsi a una frequenza di aggiornamento del display non corrispondente (con conseguente judder).

In alcuni casi, la superficie video smetterà di inviare frame, ma rimarrà visibile sullo schermo per un po' di tempo. Gli scenari più comuni sono quando la riproduzione arriva alla fine del video o quando l'utente mette in pausa la riproduzione. In questi casi, chiama setFrameRate() con il parametro frequenza frame impostato su 0 per ripristinare il valore predefinito per la frequenza frame della superficie. Cancellare l'impostazione della frequenza frame in questo modo non è necessario quando la superficie viene distrutta o nascosta perché l'utente passa a un'altra app. Cancella l'impostazione della frequenza frame solo quando la superficie rimane visibile senza essere utilizzata.

Cambio di frequenza fotogrammi non continuo

Su alcuni dispositivi, il cambio di frequenza di aggiornamento potrebbe presentare interruzioni visive, ad esempio uno schermo nero per uno o due secondi. Questo accade in genere su decoder, pannelli TV e dispositivi simili. Per impostazione predefinita, il framework Android non cambia modalità quando viene chiamata l'API Surface.setFrameRate() per evitare queste interruzioni visive.

Alcuni utenti preferiscono un'interruzione visiva all'inizio e alla fine dei video più lunghi. In questo modo, la frequenza di aggiornamento del display corrisponde alla frequenza fotogrammi del video ed è possibile evitare artefatti di conversione della frequenza fotogrammi, come il judder del pulldown 3:2 per la riproduzione di film.

Per questo motivo, i cambi di frequenza di aggiornamento non fluidi possono essere attivati se sia l'utente sia le app li attivano:

Ti consigliamo di utilizzare sempre CHANGE_FRAME_RATE_ALWAYS per i video di lunga durata, come i film. Questo perché il vantaggio della corrispondenza della frequenza fotogrammi del video supera l'interruzione che si verifica quando si modifica la frequenza di aggiornamento.

Altri consigli

Segui questi consigli per gli scenari comuni.

Più superfici

La piattaforma Android è progettata per gestire correttamente gli scenari in cui sono presenti più superfici con impostazioni di frequenza fotogrammi diverse. Quando la tua app ha più superfici con frequenze frame diverse, chiama setFrameRate() con la frequenza frame corretta per ogni superficie. Anche se sul dispositivo sono in esecuzione più app contemporaneamente, utilizzando la modalità schermo diviso o Picture in picture, ogni app può chiamare in sicurezzasetFrameRate() per le proprie piattaforme.

La piattaforma non cambia in base alla frequenza fotogrammi dell'app

Anche se il dispositivo supporta la frequenza frame specificata dall'app in una chiamata a setFrameRate(), in alcuni casi il dispositivo non imposta la frequenza di aggiornamento su quella specificata. Ad esempio, una superficie con priorità più elevata potrebbe avere un'impostazione diversa della frequenza frame o il dispositivo potrebbe essere in modalità Risparmio energetico (impostando una limitazione sulla frequenza di aggiornamento del display per preservare la batteria). L'app deve comunque funzionare correttamente quando il dispositivo non imposta la frequenza di aggiornamento del display sull'impostazione della frequenza frame dell'app, anche se il dispositivo esegue il passaggio in circostanze normali.

Spetta all'app decidere come rispondere quando la frequenza di aggiornamento del display non corrisponde alla frequenza fotogrammi dell'app. Per i video, la frequenza fotogrammi è fissa e corrisponde a quella del video di origine. Per visualizzare i contenuti video, sarà necessario un pulldown. Un gioco potrebbe invece scegliere di provare a funzionare alla frequenza di aggiornamento del display anziché mantenere la frequenza fotogrammi preferita. L'app non deve modificare il valore trasmesso in setFrameRate() in base alle operazioni svolte dalla piattaforma. Deve rimanere impostato sulla frequenza frame preferita dell'app, indipendentemente da come l'app gestisce i casi in cui la piattaforma non si adegua alla richiesta dell'app. In questo modo, se le condizioni del dispositivo cambiano per consentire l'utilizzo di frequenze di aggiornamento del display aggiuntive, la piattaforma dispone delle informazioni corrette per passare alla frequenza di frame preferita dell'app.

Nei casi in cui l'app non venga o non possa essere eseguita alla frequenza di aggiornamento del display, deve specificare i timestamp di presentazione per ogni frame utilizzando uno dei meccanismi della piattaforma per l'impostazione dei timestamp di presentazione:

L'utilizzo di questi timestamp impedisce alla piattaforma di presentare un frame dell'app troppo in anticipo, il che causerebbe un tremolio non necessario. L'utilizzo corretto dei timestamp di presentazione dei frame è un po' complicato. Per i giochi, consulta la nostra guida alla sincronizzazione dei fotogrammi per ulteriori informazioni su come evitare il tremolio e valuta la possibilità di utilizzare la libreria Android Frame Pacing.

In alcuni casi, la piattaforma può passare a un multiplo della frequenza fotogrammi specificata dall'app in setFrameRate(). Ad esempio, un'app potrebbe chiamare setFrameRate() con 60 Hz e il dispositivo potrebbe impostare il display su 120 Hz. Un motivo per cui questo potrebbe accadere è se un'altra app ha una superficie con un'impostazione della frequenza frame di 24 Hz. In questo caso, l'esecuzione del display a 120 Hz consentirà di utilizzare sia la superficie a 60 Hz sia la superficie a 24 Hz senza necessità di eseguire il pulldown.

Quando il display funziona a un multiplo della frequenza frame dell'app, l'app deve specificare i timestamp di presentazione per ogni frame per evitare tremolii non necessari. Per i giochi, la libreria Android Frame Pacing è utile per impostare correttamente i timestamp di presentazione dei frame.

setFrameRate() e preferredDisplayModeId

WindowManager.LayoutParams.preferredDisplayModeId è un altro modo con cui le app possono indicare la frequenza frame alla piattaforma. Alcune app vogliono modificare solo la frequenza di aggiornamento del display anziché altre impostazioni della modalità di visualizzazione, come la risoluzione del display. In generale, utilizza setFrameRate() anziché preferredDisplayModeId. La funzione setFrameRate() è più facile da usare perché l'app non deve cercare nell'elenco delle modalità di visualizzazione per trovare una modalità con una frequenza frame specifica.

setFrameRate() offre alla piattaforma più opportunità per scegliere una frequenza fotogrammi compatibile negli scenari in cui sono presenti più piattaforme in esecuzione con frequenze fotogrammi diverse. Ad esempio, considera uno scenario in cui due app sono in esecuzione in modalità schermo diviso su Pixel 4: un'app riproduce un video a 24 Hz e l'altra mostra all'utente un elenco scorrevole. Pixel 4 supporta due frequenze di aggiornamento del display: 60 Hz e 90 Hz. Se utilizzi l'API preferredDisplayModeId, la superficie video è costretta a scegliere 60 Hz o 90 Hz. Chiamando setFrameRate() con 24 Hz, la superficie video fornisce alla piattaforma più informazioni sulla frequenza fotogrammi del video di origine, consentendo alla piattaforma di scegliere 90 Hz per la frequenza di aggiornamento del display, che è migliore di 60 Hz in questo scenario.

Tuttavia, esistono scenari in cui è preferibile utilizzare preferredDisplayModeId anziché setFrameRate(), ad esempio:

  • Se l'app vuole modificare la risoluzione o altre impostazioni della modalità di visualizzazione, usa preferredDisplayModeId.
  • La piattaforma passerà da una modalità di visualizzazione all'altra solo in risposta a una chiamata a setFrameRate() se il cambio di modalità è leggero ed è improbabile che sia visibile all'utente. Se l'app preferisce cambiare la frequenza di aggiornamento del display anche se richiede un cambio di modalità pesante (ad esempio su un dispositivo Android TV), utilizza preferredDisplayModeId.
  • Le app che non sono in grado di gestire la visualizzazione a un multiplo della frequenza frame dell'app, il che richiede l'impostazione dei timestamp di presentazione su ogni frame, devono utilizzare preferredDisplayModeId.

setFrameRate() e preferredRefreshRate

WindowManager.LayoutParams#preferredRefreshRate imposta una frequenza fotogrammi preferita nella finestra dell'app e la frequenza è applicabile a tutte le piattaforme all'interno della finestra. L'app deve specificare la frequenza fotogrammi preferita indipendentemente dalle frequenze di aggiornamento supportate dal dispositivo, come setFrameRate(), per dare allo scheduler un suggerimento migliore sulla frequenza fotogrammi prevista dall'app.

preferredRefreshRate viene ignorato per le piattaforme che utilizzano setFrameRate(). In generale, se possibile, utilizza setFrameRate().

preferredRefreshRate e preferredDisplayModeId

Se le app vogliono solo modificare la frequenza di aggiornamento preferita, è preferibile utilizzare preferredRefreshRate anziché preferredDisplayModeId.

Evitare di chiamare setFrameRate() troppo spesso

Sebbene la chiamata a setFrameRate() non sia molto onerosa in termini di prestazioni, le app devono evitare di chiamare setFrameRate() ogni frame o più volte al secondo. Le chiamate a setFrameRate() potrebbero comportare una modifica della frequenza di aggiornamento del display, che potrebbe causare una perdita di frame durante la transizione. Devi calcolare in anticipo la frequenza fotogrammi corretta e chiamare setFrameRate() una volta.

Utilizzo per giochi o altre app non video

Sebbene i video siano il caso d'uso principale dell'API setFrameRate(), possono essere utilizzati anche per altre app. Ad esempio, un gioco che non intende superare i 60 Hz (per ridurre il consumo di energia e ottenere sessioni di gioco più lunghe) può chiamare Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT). In questo modo, un dispositivo che funziona a 90 Hz per impostazione predefinita funzionerà a 60 Hz mentre il gioco è attivo, evitando il tremolio che si verificherebbe se il gioco funzionasse a 60 Hz mentre il display funziona a 90 Hz.

Utilizzo di FRAME_RATE_COMPATIBILITY_FIXED_SOURCE

FRAME_RATE_COMPATIBILITY_FIXED_SOURCE è destinato solo alle app video. Per l'utilizzo non video, utilizza FRAME_RATE_COMPATIBILITY_DEFAULT.

Scegliere una strategia per modificare la frequenza fotogrammi

  • Consigliamo vivamente alle app, quando mostrano video di lunga durata come i film, di chiamare setFrameRate(fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS), dove fps è la frequenza dei fotogrammi del video.
  • Consigliamo vivamente di non utilizzare app che chiamano setFrameRate() con CHANGE_FRAME_RATE_ALWAYS quando prevedi che la riproduzione del video duri alcuni minuti o meno.

Integrazione di esempio per le app di riproduzione video

Per integrare i pulsanti di attivazione/disattivazione della frequenza di aggiornamento nelle app di riproduzione video, consigliamo i seguenti passaggi:

  1. Decidi il changeFrameRateStrategy:
    1. Se stai riproducendo un video lungo, ad esempio un film, utilizza MATCH_CONTENT_FRAMERATE_ALWAYS
    2. Se stai riproducendo un video breve, ad esempio il trailer di un film, utilizza CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
  2. Se changeFrameRateStrategy è CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS , vai al passaggio 4.
  3. Rileva se sta per verificarsi un passaggio a una frequenza di aggiornamento non uniforme controllando che entrambe le seguenti condizioni siano vere:
    1. Non è possibile passare dalla frequenza di aggiornamento corrente (chiamiamola C) alla frequenza dei fotogrammi del video (chiamiamola V) in modalità Seamless. Questo accade se C e V sono diversi e Display.getMode().getAlternativeRefreshRates non contiene un multiplo di V.
    2. L'utente ha attivato le modifiche alla frequenza di aggiornamento non continue. Puoi rilevare questo problema controllando se DisplayManager.getMatchContentFrameRateUserPreference restituisce MATCH_CONTENT_FRAMERATE_ALWAYS
  4. Se il passaggio non avverrà senza problemi:
    1. Chiama setFrameRate e passa fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE e changeFrameRateStrategy, dove fps è la frequenza dei fotogrammi del video.
    2. Avvia la riproduzione del video
  5. Se sta per essere effettuato un cambio di modalità senza soluzione di continuità:
    1. Mostra UX per inviare una notifica all'utente. Tieni presente che ti consigliamo di implementare un modo per consentire all'utente di ignorare questa esperienza utente e saltare il ritardo aggiuntivo nel passaggio 5.d. Questo accade perché il ritardo consigliato è superiore a quello necessario sui display con tempi di commutazione più rapidi.
    2. Chiama setFrameRate e passa fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE e CHANGE_FRAME_RATE_ALWAYS, dove fps è la frequenza dei fotogrammi del video.
    3. Attendi il callback di onDisplayChanged.
    4. Attendi 2 secondi per il completamento del passaggio alla modalità.
    5. Avvia la riproduzione del video

Il seguente è lo pseudocodice per solo supportare il passaggio senza interruzioni:

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

Lo pseudo-codice per supportare il passaggio senza interruzioni e con interruzioni come descritto sopra è il seguente:

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