Frequenza fotogrammi

L'API frame rate consente alle app di comunicare alla piattaforma Android la frequenza frame prevista ed è disponibile per le app destinate ad Android 11 (livello API 30) o versioni successive. Tradizionalmente, la maggior parte dei dispositivi supportava un'unica frequenza di aggiornamento del display, in genere 60 Hz, ma la situazione è cambiata. Molti dispositivi ora supportano frequenze di aggiornamento aggiuntive, come 90 Hz o 120 Hz. Alcuni dispositivi supportano interruttori di frequenza di aggiornamento senza interruzioni, mentre altri mostrano brevemente uno schermo nero, che solitamente dura un secondo.

Lo scopo principale dell'API è consentire alle app di sfruttare meglio tutte le frequenze di aggiornamento del display supportate. Ad esempio, un'app che riproduce un video a 24 Hz che chiama setFrameRate() potrebbe comportare la modifica della frequenza di aggiornamento del display da parte del dispositivo da 60 Hz a 120 Hz. Questa nuova frequenza di aggiornamento consente una riproduzione fluida dei video a 24 Hz senza interruzioni, senza bisogno del pulldown 3:2, perché sarebbe necessario riprodurre lo stesso video su un display a 60 Hz. Ciò si traduce in una migliore esperienza utente.

Utilizzo di base

Android mostra diversi modi per accedere alle piattaforme e controllarle, perciò esistono diverse versioni dell'API setFrameRate(). Ogni versione dell'API utilizza gli stessi parametri e funziona come le altre:

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

Per verificare se una chiamata al numero setFrameRate() comporta una modifica della frequenza di aggiornamento del display, registrati per ricevere le notifiche relative alle modifiche del display chiamando il numero DisplayManager.registerDisplayListener() o AChoreographer_registerRefreshRateCallback().

Quando chiami setFrameRate(), è meglio passare l'esatta frequenza fotogrammi anziché arrotondare a un numero intero. Ad esempio, per il rendering di un video registrato a 29,97 Hz, passi in 29,97 anziché arrotondare 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 (che provocherà interruzioni).

In alcuni scenari, la piattaforma video smetterà di inviare frame, ma rimarrà visibile sullo schermo per un po' di tempo. Gli scenari comuni includono quando la riproduzione raggiunge la fine del video o quando l'utente mette in pausa la riproduzione. In questi casi, richiama setFrameRate() con il parametro della frequenza fotogrammi impostato su 0 per annullare l'impostazione della frequenza fotogrammi della superficie sul valore predefinito. La cancellazione dell'impostazione della frequenza fotogrammi come questa non è necessaria quando si distrugge la superficie o quando questa è nascosta perché l'utente passa a un'altra app. Cancella l'impostazione della frequenza fotogrammi solo quando la superficie rimane visibile senza essere utilizzata.

Interruttore di frequenza fotogrammi senza interruzioni

Su alcuni dispositivi, il passaggio dalla frequenza di aggiornamento potrebbe causare interruzioni visive come 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 deve corrispondere alla frequenza fotogrammi del video ed evitare artefatti di conversione della frequenza fotogrammi come il Judder pulldown 3:2 per la riproduzione dei filmati.

Per questo motivo, è possibile abilitare le opzioni della frequenza di aggiornamento senza interruzioni se l'utente e le app 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ù piattaforme

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

La piattaforma non cambia la frequenza fotogrammi dell'app

Anche se il dispositivo supporta la frequenza fotogrammi specificata dall'app in una chiamata a setFrameRate(), in alcuni casi il dispositivo non passa a quella frequenza di aggiornamento per il display. Ad esempio, una piattaforma con priorità più alta potrebbe avere un'impostazione di frequenza fotogrammi diversa oppure il dispositivo potrebbe essere in modalità di risparmio energetico (impostando una limitazione sulla frequenza di aggiornamento del display per risparmiare batteria). L'app deve comunque funzionare correttamente quando il dispositivo non imposta la frequenza di aggiornamento del display sull'impostazione della frequenza fotogrammi dell'app, anche se in circostanze normali.

Spetta all'app decidere come rispondere quando la frequenza di aggiornamento del display non corrisponde a quella dei fotogrammi dell'app. Per i video, la frequenza fotogrammi è fissata a quella del video sorgente e sarà necessario un menu a discesa per mostrare i contenuti video. Un gioco potrebbe scegliere di eseguire la stessa frequenza di aggiornamento del display anziché mantenere la frequenza fotogrammi preferita. L'app non deve modificare il valore che trasmette a setFrameRate() in base alle attività della piattaforma. Deve rimanere impostato sulla frequenza fotogrammi preferita dell'app, indipendentemente da come l'app gestisce i casi in cui la piattaforma non si adatta 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 fotogrammi preferita dell'app.

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

L'utilizzo di questi timestamp impedisce alla piattaforma di presentare troppo prima il frame dell'app, il che comporterebbe una confusione inutile. Un uso corretto dei timestamp di presentazione dei frame è un po' complicato. Per i giochi, consulta la nostra guida al pacing dei frame per ulteriori informazioni su come evitare interferenze e valuta la possibilità di utilizzare la libreria del pacing dei frame Android.

In alcuni casi, la piattaforma potrebbe passare a un multiplo della frequenza frame specificata dall'app in setFrameRate(). Ad esempio, un'app potrebbe chiamare setFrameRate() a 60 Hz e il dispositivo potrebbe impostare il display a 120 Hz. Questo potrebbe verificarsi se un'altra app ha una piattaforma con un'impostazione della frequenza fotogrammi 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 pulldown.

Quando il display è in esecuzione su un multiplo della frequenza fotogrammi dell'app, l'app deve specificare i timestamp della presentazione per ogni frame per evitare sovraccarichi inutili. Per i giochi, la libreria di pacing dei frame Android è utile per impostare correttamente i timestamp di presentazione del frame.

setFrameRate() rispetto a preferredDisplayModeId

WindowManager.LayoutParams.preferredDisplayModeId è un altro modo in cui le app possono indicare la frequenza fotogrammi alla piattaforma. Alcune app vogliono modificare solo la frequenza di aggiornamento del display anziché cambiare 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 fotogrammi specifica.

setFrameRate() offre alla piattaforma maggiori opportunità di scegliere una frequenza fotogrammi compatibile in scenari in cui ci sono più piattaforme in esecuzione a frequenze fotogrammi diverse. Ad esempio, prendiamo in considerazione uno scenario in cui due app vengono eseguite in modalità schermo diviso su Pixel 4, dove su un'app viene riprodotto un video a 24 Hz e nell'altra viene mostrato all'utente un elenco scorrevole. Pixel 4 supporta due frequenze di aggiornamento del display: 60 Hz e 90 Hz. Utilizzando l'API preferredDisplayModeId, la piattaforma video deve scegliere tra 60 Hz o 90 Hz. Chiamando setFrameRate() con 24 Hz, la piattaforma video fornisce alla piattaforma maggiori informazioni sulla frequenza fotogrammi del video sorgente, consentendo alla piattaforma di scegliere 90 Hz come frequenza di aggiornamento del display, che è migliore di 60 Hz in questo scenario.

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

  • Se l'app vuole cambiare la risoluzione o altre impostazioni della modalità di visualizzazione, utilizza preferredDisplayModeId.
  • La piattaforma passerà a una modalità di visualizzazione diversa in risposta a una chiamata a setFrameRate() solo se l'interruttore della modalità è leggero e poco visibile all'utente. Se l'app preferisce cambiare la frequenza di aggiornamento del display anche se richiede un'opzione di modalità Intensa (ad esempio su un dispositivo Android TV), usa preferredDisplayModeId.
  • Le app che non sono in grado di gestire il display e vengono eseguite con un multiplo della frequenza fotogrammi dell'app, che richiede l'impostazione di timestamp della presentazione su ogni frame, dovrebbero usare l'istruzione preferredDisplayModeId.

setFrameRate() rispetto a preferredrefreshRate

WindowManager.LayoutParams#preferredRefreshRate imposta una frequenza fotogrammi preferita nella finestra dell'app, che si applica a tutte le piattaforme all'interno della finestra. L'app deve specificare la sua frequenza fotogrammi preferita indipendentemente dalle frequenze di aggiornamento supportate dal dispositivo, in modo simile a setFrameRate(), per dare allo scheduler un'idea più chiara della frequenza fotogrammi prevista per l'app.

preferredRefreshRate viene ignorato per le piattaforme che usano setFrameRate(). Se possibile, in uso generico setFrameRate().

preferredUpdateRate rispetto a preferredDisplayModeId

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

Evitare di chiamare troppo spesso setFrameRate()

Anche se la chiamata setFrameRate() non è molto costosa in termini di prestazioni, le app dovrebbero evitare di chiamare setFrameRate() a ogni frame o più volte al secondo. Le chiamate a setFrameRate() potrebbero comportare una modifica della frequenza di aggiornamento del display, che potrebbe comportare il calo di frame durante la transizione. Dovresti stabilire 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 per l'API setFrameRate(), può essere utilizzato per altre app. Ad esempio, un gioco per cui non è previsto un'esecuzione superiore a 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 verrà eseguito a 60 Hz mentre il gioco è attivo, il che eviterà gli effetti fastidiosi che si verificherebbero se il gioco venisse eseguito a 60 Hz mentre il display venisse eseguito a 90 Hz.

Utilizzo di FRAME_RATE_COMPATIBILITY_FIXED_SOURCE

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

Scegliere una strategia per modificare la frequenza fotogrammi

  • Per la visualizzazione di video di lunga durata come i film, consigliamo vivamente alle app di chiamare setFrameRate(f/s, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS), dove f/s è la frequenza fotogrammi del video.
  • Sconsigliamo vivamente di utilizzare app che chiamano setFrameRate() con CHANGE_FRAME_RATE_ALWAYS se prevedi che la riproduzione del video duri diversi minuti o meno.

Esempio di integrazione per le app di riproduzione video

Per integrare i sensori della frequenza di aggiornamento nelle app per la riproduzione di video ti consigliamo di procedere nel seguente modo:

  1. Decidi quali changeFrameRateStrategy:
    1. Se riproduci un video di lunga durata come un film, utilizza MATCH_CONTENT_FRAMERATE_ALWAYS
    2. Se riproduci un breve video, ad esempio il trailer di un film, usa 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 interruttore di frequenza di aggiornamento senza interruzioni verificando che entrambi i fatti sono veri:
    1. Non è possibile passare dalla frequenza di aggiornamento corrente (che chiamiamo C) alla frequenza fotogrammi del video (ovvero la chiamiamo V). Questo si verifica se C e V sono diverse e Display.getMode().getAlternativeRefreshRates non contiene un multiplo di V.
    2. L'utente ha attivato le modifiche della frequenza di aggiornamento senza interruzioni. Puoi rilevare questo problema controllando se DisplayManager.getMatchContentFrameRateUserPreference restituisce MATCH_CONTENT_FRAMERATE_ALWAYS
  4. Se il passaggio avverrà:
    1. Chiama setFrameRate e trasmetti il valore fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE e changeFrameRateStrategy, dove fps è la frequenza fotogrammi del video.
    2. Avvia la riproduzione del video
  5. Se sta per verificarsi un cambio di modalità senza interruzioni:
    1. Mostra UX per inviare una notifica all'utente. Tieni presente che ti consigliamo di implementare per l'utente un modo per ignorare questa UX e saltare il ritardo aggiuntivo indicato nel passaggio 5.d. Questo perché il ritardo consigliato è superiore al necessario sui display con tempi di commutazione più rapidi.
    2. Chiama setFrameRate e trasmetti il valore fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE e CHANGE_FRAME_RATE_ALWAYS, dove fps rappresenta la frequenza fotogrammi del video.
    3. Attendi il callback onDisplayChanged.
    4. Attendi 2 secondi per il completamento del cambio della modalità.
    5. Avvia la riproduzione del video

Lo pseudocodice per supportare solo il passaggio continuo è il seguente:

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 una commutazione fluida e senza 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();
}