Frame rate

A API de frame rate permite que os apps informem à plataforma Android o frame rate pretendido 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 são compatíveis com trocas de taxa de atualização uniformes, 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 e 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 trepidaçã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 tela compatíveis, que podem ser obtidas chamando Display.getSupportedModes() para chamar setFrameRate() com segurança. Por exemplo, mesmo que o dispositivo ofereça suporte apenas a 60 Hz, chame setFrameRate() com a taxa de frames preferida do app. Os dispositivos que não tiverem uma correspondência melhor para a taxa de frames do app vão manter a taxa de atualização da 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 adicional à 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 trepidação).

Em alguns casos, a superfície de 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 e voltar ao valor padrão. Não é necessário limpar a configuração da taxa de frames dessa forma ao destruir a superfície ou quando ela fica oculta porque o usuário muda para outro app. Limpe a configuração da taxa de frames apenas quando a superfície permanecer visível sem ser usada.

Troca de frame rate não contínua

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

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

Por esse motivo, as trocas de taxa de atualização não contínuas podem ser ativadas se o usuário e os apps ativarem a opção:

Recomendamos que você sempre use CHANGE_FRAME_RATE_ALWAYS para vídeos mais longos, como filmes. Isso acontece porque o benefício de corresponder à taxa de frames do vídeo supera 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 superfícies 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 tela dividida ou modo picture-in-picture, cada app pode chamar setFrameRate() com segurança para as próprias superfícies.

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

Mesmo que o dispositivo seja compatível com a taxa de frames especificada pelo app em uma chamada para setFrameRate(), há casos em que o dispositivo não muda 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 muda a taxa de atualização da tela para a configuração de taxa de frames do app, mesmo que o dispositivo mude em circunstâncias normais.

Cabe ao app decidir como responder quando a taxa de atualização da tela não corresponde ao frame rate do app. Para vídeo, a taxa de frames é fixa na do vídeo de origem, e é necessário fazer um pull-down para mostrar o conteúdo. Um jogo pode tentar ser executado na taxa de atualização da tela em vez de manter a taxa de frames preferida. O app não pode mudar o valor que ele transmite para setFrameRate() com base no que a plataforma faz. Ele precisa permanecer definido na taxa de frames preferida do app, independente de como o app processa casos em que a plataforma não se ajusta para corresponder à solicitação do app. Assim, se as condições do dispositivo mudarem para permitir o uso de taxas de atualização de tela adicionais, a plataforma terá as informações corretas para mudar para a taxa de frames preferida do app.

Nos casos em que o app não é executado ou 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 esses carimbos:

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

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 mudar a tela para 120 Hz. Isso pode acontecer se outro app tiver uma superfície com uma configuração de taxa de atualização de 24 Hz. Nesse caso, executar a tela a 120 Hz permite que as superfícies de 60 Hz e 24 Hz sejam executadas sem necessidade de pulldown.

Quando a tela estiver sendo executada em um múltiplo da taxa de frames do app, ele precisará especificar carimbos de data/hora de apresentação para cada frame e evitar trepidação desnecessária. Para jogos, a biblioteca Android Frame Pacing é útil para definir corretamente carimbos de data/hora de 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 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.

setFrameRate() dá à plataforma mais oportunidades de escolher uma taxa de frames compatível em cenários com várias plataformas executadas em 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. 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 é compatível com duas taxas de atualização da tela: 60 Hz e 90 Hz. Usando 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 de vídeo oferece mais informações à plataforma sobre a taxa de frames do vídeo de origem, permitindo que ela escolha 90 Hz para a taxa de atualização da tela, o que é melhor do que 60 Hz nesse cenário.

No entanto, há cenários em que preferredDisplayModeId deve ser usado em vez de setFrameRate(), como os seguintes:

  • Se o app quiser mudar a resolução ou outras configurações de modo de exibição, use preferredDisplayModeId.
  • A plataforma só vai mudar os modos de exibição em resposta a uma chamada para setFrameRate() se a troca de modo for leve e improvável de ser percebida pelo usuário. Se o app preferir mudar a taxa de atualização da tela mesmo que isso exija uma troca de modo pesada (por exemplo, em um dispositivo Android TV), use preferredDisplayModeId.
  • Os apps que não conseguem processar a tela em uma taxa de atualização múltipla da taxa de frames do app, o que exige a definiçã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 preferida na janela do app, e a taxa é aplicável a todas as superfícies dentro da janela. O app precisa especificar a taxa de frames preferida, independente das taxas de atualização compatíveis com o dispositivo, semelhante a setFrameRate(), para dar ao programador uma dica melhor da taxa de frames 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.

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() a cada frame ou várias vezes por segundo. As chamadas para setFrameRate() provavelmente vão resultar em uma mudança na taxa de atualização da tela, o que pode causar uma queda de frames durante a transição. Descubra a taxa de frames correta com antecedência e chame setFrameRate() uma vez.

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

Embora o vídeo seja o principal caso de uso da API setFrameRate(), ela pode ser usada para outros apps. Por exemplo, um jogo que não pretende ser executado acima de 60 Hz (para reduzir o uso de energia e aumentar a duração das sessões de jogo) pode chamar Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT). Assim, um dispositivo que funciona a 90 Hz por padrão vai funcionar a 60 Hz enquanto o jogo estiver ativo. Isso evita o tremor que ocorreria se o jogo funcionasse a 60 Hz enquanto a tela funcionasse a 90 Hz.

Uso de FRAME_RATE_COMPATIBILITY_FIXED_SOURCE

O FRAME_RATE_COMPATIBILITY_FIXED_SOURCE é destinado apenas a apps de vídeo. Para uso não relacionado a 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 longos, 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 mudanças na 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 troca de taxa de atualização não contínua está prestes a acontecer verificando se estes dois fatos são verdadeiros:
    1. Não é possível fazer uma troca de modo sem problemas da taxa de atualização atual (vamos chamar de C) para a taxa de frames do vídeo (vamos chamar de V). Isso vai acontecer 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 contínuas. Para detectar isso, verifique se DisplayManager.getMatchContentFrameRateUserPreference retorna MATCH_CONTENT_FRAMERATE_ALWAYS.
  4. Se a troca for perfeita, 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 contínua estiver prestes a acontecer, faça o seguinte:
    1. Mostrar a UX para notificar o usuário. Recomendamos que você implemente uma maneira de o usuário dispensar essa UX e pular o atraso adicional na etapa 5.d. Isso acontece porque o atraso recomendado é maior do que o necessário em telas que exibem 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 troca de modo seja concluída.
    5. Iniciar a reprodução de vídeo

O pseudocódigo para oferecer suporte apenas à troca sem problemas é 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 à troca contínua e não contínua, 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();
}