Frequenza fotogrammi

L'API Frame Rate consente alle app di comunicare alla piattaforma Android il frame rate previsto ed è disponibile nelle app che hanno come target Android 11 (livello API 30) o versioni successive. Tradizionalmente, la maggior parte dei dispositivi supporta una sola frequenza di aggiornamento del display, in genere 60 Hz, ma la situazione sta cambiando. Molti dispositivi ora supportano frequenze di aggiornamento aggiuntive, come 90 Hz o 120 Hz. Alcuni dispositivi supportano il cambio della frequenza di aggiornamento senza interruzioni, mentre altri mostrano brevemente una schermata nera, di solito per un secondo.

Lo scopo principale dell'API è consentire alle app di sfruttare al 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 60 Hz a 120 Hz. Questa nuova frequenza di aggiornamento consente una riproduzione fluida e senza scatti dei video a 24 Hz, senza la necessità di un pulldown 3:2 come 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 superfici, quindi esistono diverse versioni dell'API setFrameRate(). Ogni versione dell'API accetta gli stessi parametri e funziona allo stesso modo delle altre:

L'app non deve considerare le frequenze di aggiornamento del display supportate effettive, 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 il frame rate preferito dalla tua app. I dispositivi che non hanno una corrispondenza migliore per la frequenza fotogrammi dell'app manterranno l'attuale frequenza di aggiornamento del display.

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

Quando chiami setFrameRate(), è meglio passare la frequenza dei fotogrammi esatta anziché arrotondarla a un numero intero. Ad esempio, durante il rendering di un video registrato a 29,97 Hz, inserisci 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 suggerimento aggiuntivo alla piattaforma Android che l'app utilizzerà il pull-down per adattarsi a una frequenza di aggiornamento del display non corrispondente (che comporterà un effetto judder).

In alcuni scenari, la superficie video smetterà di inviare frame, ma rimarrà visibile sullo schermo per un po' di tempo. Gli scenari comuni includono la riproduzione che raggiunge la fine del video o quando l'utente mette in pausa la riproduzione. In questi casi, chiama setFrameRate() con il parametro frame rate impostato su 0 per ripristinare l'impostazione del frame rate della superficie al valore predefinito. La cancellazione dell'impostazione del frame rate in questo modo non è necessaria quando la superficie viene eliminata o quando la superficie è nascosta perché l'utente passa a un'altra app. Cancella l'impostazione del frame rate solo quando la superficie rimane visibile senza essere utilizzata.

Cambio della frequenza fotogrammi non fluido

Su alcuni dispositivi, il cambio della frequenza di aggiornamento potrebbe causare interruzioni visive, ad esempio una schermata nera per uno o due secondi. Ciò si verifica in genere su set-top box, pannelli TV e dispositivi simili. Per impostazione predefinita, il framework Android non cambia modalità quando viene chiamata l'API Surface.setFrameRate(), per evitare 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 dei fotogrammi del video ed evita artefatti di conversione della frequenza dei fotogrammi, come il judder di pulldown 3:2 per la riproduzione di film.

Per questo motivo, i cambi di frequenza di aggiornamento non fluidi possono essere abilitati se sia l'utente che le app acconsentono:

Ti consigliamo di utilizzare sempre CHANGE_FRAME_RATE_ALWAYS per i video di lunga durata, come i film. Questo perché il vantaggio di abbinare la frequenza dei 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 frame rate diverse. Quando la tua app ha più superfici con frame rate diversi, chiama setFrameRate() con il frame rate corretto per ogni superficie. Anche se il dispositivo esegue 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 passa alla frequenza fotogrammi dell'app

Anche se il dispositivo supporta il frame rate specificato dall'app in una chiamata a setFrameRate(), in alcuni casi il dispositivo non imposta la frequenza di aggiornamento del display. Ad esempio, una superficie con priorità più alta potrebbe avere un'impostazione di frame rate diversa oppure il dispositivo potrebbe essere in modalità Risparmio energetico (impostando una limitazione alla 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 dei fotogrammi dell'app, anche se il dispositivo lo fa 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 a quella del video sorgente e sarà necessario il pulldown per mostrare i contenuti video. Un gioco potrebbe invece scegliere di provare a essere eseguito alla 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 azioni della piattaforma. Deve rimanere impostato sulla frequenza dei fotogrammi 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 dei fotogrammi preferita dell'app.

Nei casi in cui l'app non viene eseguita o non può essere eseguita alla frequenza di aggiornamento del display, l'app 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 presto, il che comporterebbe un judder non necessario. L'utilizzo corretto dei timestamp di presentazione dei frame è un po' complicato. Per i giochi, consulta la nostra guida al frame pacing per saperne di più su come evitare il judder e valuta la possibilità di utilizzare la libreria Android Frame Pacing.

In alcuni casi, la piattaforma potrebbe passare a un multiplo del frame rate specificato dall'app in setFrameRate(). Ad esempio, un'app potrebbe chiamare setFrameRate() con 60 Hz e il dispositivo potrebbe impostare il display a 120 Hz. Uno dei motivi per cui ciò potrebbe accadere è che un'altra app ha una superficie con un'impostazione della frequenza dei fotogrammi di 24 Hz. In questo caso, l'esecuzione del display a 120 Hz consentirà sia alla superficie a 60 Hz sia a quella a 24 Hz di funzionare senza pulldown.

Quando la visualizzazione viene eseguita a un multiplo del frame rate dell'app, l'app deve specificare i timestamp di presentazione per ogni frame per evitare un judder non necessario. 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 dei fotogrammi alla piattaforma. Alcune app vogliono solo modificare la frequenza di aggiornamento del display anziché altre impostazioni della modalità di visualizzazione, come la risoluzione del display. In generale, utilizza setFrameRate() al posto di 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 dei fotogrammi specifica.

setFrameRate() offre alla piattaforma maggiori opportunità di scegliere una frequenza dei fotogrammi compatibile in scenari in cui sono presenti più piattaforme che vengono eseguite a frequenze dei fotogrammi diverse. Ad esempio, considera uno scenario in cui due app vengono eseguite in modalità schermo diviso su Pixel 4, una 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. Utilizzando 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 maggiori informazioni sul frame rate del video sorgente, consentendole di scegliere 90 Hz per la frequenza di aggiornamento del display, che è migliore di 60 Hz in questo scenario.

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

  • Se l'app vuole modificare la risoluzione o altre impostazioni della modalità di visualizzazione, usa preferredDisplayModeId.
  • La piattaforma cambierà modalità di visualizzazione solo in risposta a una chiamata a setFrameRate() se il cambio di modalità è leggero e difficilmente percepibile 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 riescono a gestire la visualizzazione a un multiplo del frame rate 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 dei fotogrammi preferita nella finestra dell'app e la frequenza è applicabile a tutte le superfici all'interno della finestra. L'app deve specificare la frequenza dei fotogrammi preferita indipendentemente dalle frequenze di aggiornamento supportate dal dispositivo, in modo simile a setFrameRate(), per fornire allo scheduler un suggerimento migliore sulla frequenza dei fotogrammi prevista dall'app.

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

preferredRefreshRate vs preferredDisplayModeId

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

Evitare di chiamare setFrameRate() troppo spesso

Sebbene la chiamata setFrameRate() non sia molto costosa in termini di prestazioni, le app devono evitare di chiamare setFrameRate() ogni frame o più volte al secondo. Le chiamate a setFrameRate() probabilmente comporteranno una modifica della frequenza di aggiornamento del display, che potrebbe causare un calo di frame durante la transizione. Dovresti calcolare in anticipo il frame rate corretto e chiamare setFrameRate() una sola volta.

Utilizzo per giochi o altre app non video

Sebbene il video sia il caso d'uso principale dell'API setFrameRate(), può essere utilizzata per altre app. Ad esempio, un gioco che non intende superare i 60 Hz (per ridurre il consumo energetico 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à invece a 60 Hz mentre il gioco è attivo, il che eviterà il judder che si verificherebbe altrimenti 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 visualizzano video di lunga durata come film, di chiamare setFrameRate(fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS) dove fps è il frame rate del video.
  • Sconsigliamo vivamente alle app di chiamare setFrameRate() con CHANGE_FRAME_RATE_ALWAYS quando prevedi che la riproduzione video duri diversi minuti o meno.

Esempio di integrazione per app di riproduzione video

Ti consigliamo di seguire questi passaggi per integrare i cambi di frequenza di aggiornamento nelle app di riproduzione video:

  1. Decidi il changeFrameRateStrategy:
    1. Se riproduci un video di lunga durata, ad esempio un film, utilizza MATCH_CONTENT_FRAMERATE_ALWAYS.
    2. Se riproduci 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 cambio di frequenza di aggiornamento non uniforme controllando che entrambi i seguenti fatti siano veri:
    1. Il cambio di modalità senza interruzioni non è possibile dalla frequenza di aggiornamento attuale (chiamiamola C) alla frequenza dei fotogrammi del video (chiamiamola V). Ciò si verifica se C e V sono diversi e Display.getMode().getAlternativeRefreshRates non contiene un multiplo di V.
    2. L'utente ha attivato le modifiche non fluide della frequenza di aggiornamento. Puoi rilevarlo controllando se DisplayManager.getMatchContentFrameRateUserPreference restituisce MATCH_CONTENT_FRAMERATE_ALWAYS
  4. Se il passaggio deve essere senza interruzioni, procedi nel seguente modo:
    1. Chiama setFrameRate e passalo a fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, e changeFrameRateStrategy, dove fps è il frame rate del video.
    2. Avvia la riproduzione video
  5. Se sta per verificarsi un cambio di modalità non continuo, procedi nel seguente modo:
    1. Mostra l'UX per notificare l'utente. Tieni presente che ti consigliamo di implementare un modo per consentire all'utente di chiudere questa UX e saltare l'ulteriore ritardo nel passaggio 5.d. Questo perché il ritardo consigliato è maggiore del necessario sui display che mostrano tempi di commutazione più rapidi.
    2. Chiama setFrameRate e passalo a fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, e CHANGE_FRAME_RATE_ALWAYS, dove fps è il frame rate del video.
    3. Attendi la richiamata di onDisplayChanged.
    4. Attendi 2 secondi per il completamento del cambio di modalità.
    5. Avvia la riproduzione video

Lo pseudocodice per supportare solo il cambio di rete senza interruzioni è 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 il cambio fluido e non fluido 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();
}