Frame rate

A API de frame rate permite que os apps informem à plataforma Android a taxa de frame pretendida e está disponível em apps direcionados ao Android 11 (nível 30 da API) ou mais recente. Tradicionalmente, a maioria dos dispositivos era compatível com apenas uma única taxa de atualização da tela, normalmente 60 Hz, mas isso está mudando. Muitos dispositivos agora são compatíveis com taxas de atualização adicionais, como 90 Hz ou 120 Hz. Alguns dispositivos oferecem suporte a mudanças seamless na taxa de atualização, enquanto outros mostram brevemente uma tela preta, geralmente por um segundo.

O principal objetivo da API é permitir que os apps aproveitem melhor todas as taxas de atualização de exibição compatíveis. Por exemplo, um app que reproduz um vídeo de 24 Hz que chama setFrameRate() pode fazer com que o dispositivo mude a taxa de atualização da tela de 60 Hz para 120 Hz. Essa nova taxa de atualização permite a reprodução suave e sem tremulação de vídeos de 24 Hz, sem a necessidade de pulldown 3:2, como seria necessário para reproduzir o mesmo vídeo em uma tela de 60 Hz. Isso resulta em uma melhor experiência do usuário.

Uso básico

O Android expõe várias maneiras de acessar e controlar plataformas. Por isso, há várias versões da API setFrameRate(). Cada versão da API usa os mesmos parâmetros e funciona da mesma forma que as outras:

O app não precisa considerar as taxas de atualização de exibição compatíveis reais, que podem ser obtidas chamando Display.getSupportedModes(), para chamar setFrameRate() com segurança. Por exemplo, mesmo que o dispositivo só tenha suporte a 60 Hz, chame setFrameRate() com a taxa de frames preferida do app. Os dispositivos que não têm uma correspondência melhor para a taxa de frames do app vão permanecer com a taxa de atualização de tela atual.

Para saber se uma chamada para setFrameRate() resulta em uma mudança na taxa de atualização da tela, registre-se para receber notificações de mudança de tela chamando DisplayManager.registerDisplayListener() ou AChoreographer_registerRefreshRateCallback().

Ao chamar setFrameRate(), é melhor transmitir a taxa de frames exata em vez de arredondar para um número inteiro. Por exemplo, ao renderizar um vídeo gravado a 29,97 Hz, transmita 29,97 em vez de arredondar para 30.

Para apps de vídeo, o parâmetro de compatibilidade transmitido para setFrameRate() precisa ser definido como Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE para dar uma dica extra à plataforma Android de que o app vai usar o menu suspenso para se adaptar a uma taxa de atualização de tela incompatível (o que vai resultar em tremulação).

Em alguns casos, a superfície do vídeo para de enviar frames, mas permanece visível na tela por algum tempo. Os cenários comuns incluem quando a reprodução chega ao fim do vídeo ou quando o usuário pausa a reprodução. Nesses casos, chame setFrameRate() com o parâmetro de taxa de frames definido como 0 para limpar a configuração de taxa de frames da superfície de volta para o valor padrão. Limpar a configuração de taxa de frames como essa não é necessário ao destruir a superfície ou quando ela é oculta porque o usuário muda para outro app. Limpe a configuração de taxa de frames somente quando a superfície permanecer visível sem ser usada.

Troca de frame rate não perfeita

Em alguns dispositivos, a mudança de taxa de atualização pode ter interrupções visuais, como uma tela preta por um ou dois segundos. Isso geralmente ocorre em set-top boxes, painéis de TV e dispositivos semelhantes. Por padrão, o framework do Android não muda de modo quando a API Surface.setFrameRate() é chamada, para evitar interrupções visuais.

Alguns usuários preferem uma interrupção visual no início e no final de vídeos mais longos. Isso permite que a taxa de atualização da tela corresponda à taxa de frames do vídeo e evite artefatos de conversão de taxa de frames, como o tremor de 3:2 para reprodução de filmes.

Por esse motivo, as mudanças de taxa de atualização não perfeitas podem ser ativadas se o usuário e os apps ativarem:

Recomendamos que você sempre use CHANGE_FRAME_RATE_ALWAYS para vídeos longos, como filmes. Isso ocorre porque o benefício de corresponder à taxa de frames do vídeo é maior do que a interrupção que ocorre ao mudar a taxa de atualização.

Recomendações adicionais

Siga estas recomendações para cenários comuns.

Várias superfícies

A plataforma Android foi projetada para processar corretamente cenários em que há várias plataformas com diferentes configurações de taxa de frames. Quando o app tiver várias superfícies com taxas de frames diferentes, chame setFrameRate() com a taxa de frames correta para cada superfície. Mesmo que o dispositivo esteja executando vários apps ao mesmo tempo, usando a tela dividida ou o modo picture-in-picture, cada app pode chamar setFrameRate() com segurança para as próprias plataformas.

A plataforma não muda para a taxa de frames do app

Mesmo que o dispositivo ofereça suporte à taxa de frames especificada pelo app em uma chamada para setFrameRate(), há casos em que o dispositivo não alterna a tela para essa taxa de atualização. Por exemplo, uma superfície de prioridade mais alta pode ter uma configuração de taxa de frames diferente ou o dispositivo pode estar no modo de economia de bateria (definindo uma restrição na taxa de atualização da tela para preservar a bateria). O app ainda precisa funcionar corretamente quando o dispositivo não alterna a taxa de atualização da tela para a configuração de taxa de frames do app, mesmo que o dispositivo alterne em circunstâncias normais.

Cabe ao app decidir como responder quando a taxa de atualização da tela não corresponder à taxa de frames do app. Para vídeos, a taxa de frames é fixada na do vídeo de origem, e o menu suspenso será necessário para mostrar o conteúdo do vídeo. Em vez disso, um jogo pode tentar ser executado na taxa de atualização da tela em vez de permanecer com a taxa de frames preferida. O app não pode mudar o valor que é transmitido para setFrameRate() com base no que a plataforma faz. Ela precisa ser definida como a taxa de frames preferida do app, independentemente de como o app lida com casos em que a plataforma não se ajusta para corresponder à solicitação do app. Dessa forma, se as condições do dispositivo mudarem para permitir que outras taxas de atualização da tela sejam usadas, a plataforma terá as informações corretas para mudar para a taxa de quadros preferida do app.

Nos casos em que o app não pode ser executado na taxa de atualização da tela, ele precisa especificar carimbos de data/hora de apresentação para cada frame, usando um dos mecanismos da plataforma para definir carimbos de data/hora de apresentação:

O uso desses carimbos de data/hora impede que a plataforma apresente um frame do app muito cedo, o que resultaria em trepidação desnecessária. O uso correto de carimbos de data/hora de apresentação de frames é um pouco complicado. Para jogos, consulte nosso guia de frame pacing para mais informações sobre como evitar o judder e considere usar a biblioteca Frame Pacing do Android.

Em alguns casos, a plataforma pode mudar para um múltiplo da taxa de frames especificada pelo app em setFrameRate(). Por exemplo, um app pode chamar setFrameRate() com 60 Hz, e o dispositivo pode alternar a tela para 120 Hz. Isso pode acontecer se outro app tiver uma superfície com uma configuração de taxa de frames de 24 Hz. Nesse caso, executar a tela a 120 Hz permite que a superfície de 60 Hz e a de 24 Hz sejam executadas sem a necessidade de pulldown.

Quando a tela estiver executando em um múltiplo da taxa de frames do app, ele precisa especificar os carimbos de data/hora de apresentação para cada frame para evitar trepidações desnecessárias. Para jogos, a biblioteca Android Frame Pacing é útil para definir corretamente os carimbos de data/hora da apresentação de frames.

setFrameRate() x preferredDisplayModeId

WindowManager.LayoutParams.preferredDisplayModeId é outra maneira de os apps indicarem a taxa de frames para a plataforma. Alguns apps só querem mudar a taxa de atualização da tela, em vez de mudar outras configurações do modo de exibição, como a resolução. Em geral, use setFrameRate() em vez de preferredDisplayModeId. A função setFrameRate() é mais fácil de usar porque o app não precisa pesquisar na lista de modos de exibição para encontrar um modo com uma taxa de frames específica.

O setFrameRate() oferece à plataforma mais oportunidades de escolher uma taxa de frames compatível em cenários em que há várias plataformas em execução com taxas de frames diferentes. Por exemplo, considere um cenário em que dois apps estão em execução no modo de tela dividida em um Pixel 4, em que um app está reproduzindo um vídeo de 24 Hz e o outro está mostrando ao usuário uma lista rolável. O Pixel 4 oferece suporte a duas taxas de atualização da tela: 60 Hz e 90 Hz. Ao usar a API preferredDisplayModeId, a superfície de vídeo é forçada a escolher 60 Hz ou 90 Hz. Ao chamar setFrameRate() com 24 Hz, a superfície do vídeo fornece à plataforma mais informações sobre a taxa de frames do vídeo de origem, permitindo que a plataforma escolha 90 Hz para a taxa de atualização da tela, que é melhor do que 60 Hz nesse cenário.

No entanto, há cenários em que preferredDisplayModeId precisa ser usado em vez de setFrameRate(), como estes:

  • Se o app quiser mudar a resolução ou outras configurações do modo de exibição, use preferredDisplayModeId.
  • A plataforma só vai alternar os modos de exibição em resposta a uma chamada para setFrameRate() se a troca de modo for leve e não for perceptível para o usuário. Se o app preferir mudar a taxa de atualização da tela, mesmo que exija uma mudança de modo pesado (por exemplo, em um dispositivo Android TV), use preferredDisplayModeId.
  • Os apps que não conseguem processar a exibição em uma taxa de frames múltipla, o que exige a configuração de carimbos de data/hora de apresentação em cada frame, precisam usar preferredDisplayModeId.

setFrameRate() x preferredRefreshRate

WindowManager.LayoutParams#preferredRefreshRate define uma taxa de frames preferencial na janela do app, e a taxa é aplicável a todas as superfícies dentro da janela. O app precisa especificar a taxa de quadros preferida, independentemente das taxas de atualização aceitas pelo dispositivo, semelhante a setFrameRate(), para dar ao programador uma dica melhor da taxa de quadros pretendida do app.

preferredRefreshRate é ignorado para plataformas que usam setFrameRate(). Em geral, use setFrameRate(), se possível.

preferredRefreshRate x preferredDisplayModeId

Se os apps quiserem apenas mudar a taxa de atualização preferida, é melhor usar preferredRefreshRate em vez de preferredDisplayModeId.

Como evitar chamar setFrameRate() com muita frequência

Embora a chamada setFrameRate() não seja muito cara em termos de desempenho, os apps precisam evitar chamar setFrameRate() em cada frame ou várias vezes por segundo. As chamadas para setFrameRate() provavelmente resultarão em uma mudança na taxa de atualização da tela, o que pode resultar em uma queda de frames durante a transição. Você precisa descobrir a taxa de frames correta com antecedência e chamar setFrameRate() uma vez.

Uso para jogos ou outros apps que não sejam de vídeo

Embora o vídeo seja o caso de uso principal da API setFrameRate(), ela pode ser usada para outros apps. Por exemplo, um jogo que não pretende rodar acima de 60 Hz (para reduzir o uso de energia e ter sessões de jogo mais longas) pode chamar Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT). Dessa forma, um dispositivo que funciona a 90 Hz por padrão será executado a 60 Hz enquanto o jogo estiver ativo, o que evitará o tremor que ocorreria se o jogo fosse executado a 60 Hz enquanto a tela estivesse a 90 Hz.

Uso de FRAME_RATE_COMPATIBILITY_FIXED_SOURCE

FRAME_RATE_COMPATIBILITY_FIXED_SOURCE é destinado apenas a apps de vídeo. Para usos que não sejam de vídeo, use FRAME_RATE_COMPATIBILITY_DEFAULT.

Como escolher uma estratégia para mudar a taxa de frames

  • Recomendamos que os apps, ao exibir vídeos de longa duração, como filmes, chamem setFrameRate(fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS), em que fps é a taxa de frames do vídeo.
  • Não recomendamos que os apps chamem setFrameRate() com CHANGE_FRAME_RATE_ALWAYS quando você espera que a reprodução de vídeo dure vários minutos ou menos.

Exemplo de integração para apps de reprodução de vídeo

Recomendamos as seguintes etapas para integrar as mudanças de taxa de atualização em apps de reprodução de vídeo:

  1. Decida o changeFrameRateStrategy:
    1. Se você estiver reproduzindo um vídeo longo, como um filme, use MATCH_CONTENT_FRAMERATE_ALWAYS.
    2. Se você estiver reproduzindo um vídeo curto, como um trailer de filme, use CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS.
  2. Se o changeFrameRateStrategy for CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, vá para a etapa 4.
  3. Detecte se uma mudança de taxa de atualização não integrada está prestes a acontecer verificando se ambas as condições a seguir são verdadeiras:
    1. Não é possível alternar do modo uniforme da taxa de atualização atual (vamos chamar de C) para a taxa de quadros do vídeo (vamos chamar de V). Isso acontece se C e V forem diferentes e Display.getMode().getAlternativeRefreshRates não contiver um múltiplo de V.
    2. O usuário ativou as mudanças de taxa de atualização não integradas. Para detectar isso, verifique se DisplayManager.getMatchContentFrameRateUserPreference retorna MATCH_CONTENT_FRAMERATE_ALWAYS.
  4. Se a mudança for contínua, faça o seguinte:
    1. Chame setFrameRate e transmita fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE e changeFrameRateStrategy, em que fps é a taxa de frames do vídeo.
    2. Iniciar a reprodução de vídeo
  5. Se uma mudança de modo não integrada estiver prestes a acontecer, faça o seguinte:
    1. Mostre a UX para notificar o usuário. Recomendamos implementar uma maneira de o usuário dispensar essa UX e pular o atraso adicional na etapa 5.d. Isso ocorre porque o atraso recomendado é maior do que o necessário em telas que exibimos tempos de troca mais rápidos.
    2. Chame setFrameRate e transmita fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE e CHANGE_FRAME_RATE_ALWAYS, em que fps é a taxa de frames do vídeo.
    3. Aguarde o callback onDisplayChanged.
    4. Aguarde dois segundos para que a mudança de modo seja concluída.
    5. Iniciar a reprodução do vídeo

O pseudocódigo para somente oferecer suporte à troca perfeita é o seguinte:

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

O pseudocódigo para oferecer suporte à alternância perfeita e não perfeita, conforme descrito acima, é o seguinte:

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