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. Tradizionalmente, la maggior parte dei dispositivi supportava una sola frequenza di aggiornamento del display, tipicamente 60 Hz, ma la situazione sta cambiando. 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. Questa nuova frequenza di aggiornamento consente una riproduzione fluida e senza sfarfallio dei video a 24 Hz, senza bisogno del pulldown 3:2 necessario per riprodurre lo stesso video su un display a 60 Hz. Ciò si traduce in un'esperienza utente migliore.

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 verificare se una chiamata a setFrameRate() comporta una modifica della frequenza di aggiornamento del display, registrati per ricevere notifiche di modifica del display chiamando DisplayManager.registerDisplayListener() o AChoreographer_registerRefreshRateCallback().

Quando chiami setFrameRate(), è meglio passare la frequenza frame esatta anziché arrotondarla a un numero intero. Ad esempio, quando esegui il rendering di un video registrato a 29,97 Hz, specifica 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 tremolio).

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 passaggio da una frequenza di aggiornamento all'altra potrebbe comportare interruzioni visive, ad esempio un'oscuramento dello schermo 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 della 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 che viene passato a setFrameRate() in base alle azioni della 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 in modo da 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.

Se l'app non funziona o non può funzionare alla frequenza di aggiornamento del display, deve specificare i timestamp di 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 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 potrebbe passare a un multiplo della frequenza frame 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 in 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à di scegliere una frequenza frame compatibile in scenari in cui sono presenti più piattaforme che funzionano a frequenze frame 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 piattaforma video è costretta a scegliere 60 Hz o 90 Hz. Chiamando setFrameRate() a 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, use preferredDisplayModeId.
  • La piattaforma cambierà le modalità di visualizzazione solo in risposta a una chiamata asetFrameRate() se il passaggio di modalità è leggero e difficilmente rilevabile dall'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 frame preferita indipendentemente dalle frequenze di aggiornamento supportate dal dispositivo, in modo simile a setFrameRate(), per fornire allo scheduler un'idea migliore della frequenza frame prevista dell'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 deve funzionare a più di 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.

Esempio di integrazione per le app di riproduzione video

Per integrare i pulsanti di attivazione/disattivazione della frequenza di aggiornamento nelle app di riproduzione video, ti consigliamo di seguire 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, 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 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 frame 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 uniforme. Puoi rilevare questo problema controllando se DisplayManager.getMatchContentFrameRateUserPreference restituisce MATCH_CONTENT_FRAMERATE_ALWAYS
  4. Se il passaggio sarà senza interruzioni:
    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 verificarsi un cambio di modalità non senza interruzioni, procedi nel seguente modo:
    1. Mostra l'interfaccia utente per informare l'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 di 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 pseudocodice 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();
}