Frame rate

A API de frame rate permite que os apps informem a plataforma Android sobre o frame rate pretendido e está disponível em apps direcionados ao Android 11 (API de nível 30) ou versões mais recentes. Tradicionalmente, a maioria dos dispositivos oferece suporte a apenas uma taxa de atualização de tela, geralmente 60 Hz, mas isso está mudando. Muitos dispositivos agora oferecem suporte a taxas de atualização adicionais, como 90 Hz ou 120 Hz. Alguns dispositivos oferecem suporte a alternâncias de taxa de atualização contínuas, enquanto outros mostram brevemente uma tela preta, que geralmente dura um segundo.

O objetivo principal da API é permitir que os apps aproveitem melhor todas as taxas de atualização de tela 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 trepidações de vídeos de 24 Hz, sem necessidade de um menu suspenso de 3:2, porque isso 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. Portanto, 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 reais de atualização de tela com suporte, que podem ser recebidas chamando Display.getSupportedModes() para chamar setFrameRate() com segurança. Por exemplo, mesmo que o dispositivo seja compatível apenas com 60 Hz, chame setFrameRate() com o frame rate que seu app preferir. Os dispositivos que não têm uma correspondência melhor para o frame rate do app vão continuar com a taxa de atualização da tela atual.

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

Ao chamar setFrameRate(), é melhor transmitir o frame rate exato 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 usará um menu suspenso para se adaptar a uma taxa de atualização da tela não correspondente, o que resulta 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. 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 frame rate definido como 0 para limpar a configuração de frame rate da plataforma de volta ao valor padrão. Não é necessário limpar a configuração de frame rate desta forma ao destruir a superfície ou quando ela está oculta porque o usuário alterna para outro app. Limpe a configuração de frame rate somente quando a superfície permanecer visível sem ser usada.

Chave de frame rate não contínua

Em alguns dispositivos, a alternância da taxa de atualização pode ter interrupções visuais, como uma tela preta por um ou dois segundos. Isso geralmente ocorre em conversores, painéis de TV e dispositivos semelhantes. Por padrão, o framework do Android não alterna entre os modos 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 ao frame rate do vídeo e evite artefatos de conversão de taxa de frames, como trepidação de 3:2 para reprodução de filmes.

Por esse motivo, as alternâncias não contínuas da taxa de atualização poderão ser ativadas se o usuário e os apps aceitarem:

Recomendamos que você sempre use o CHANGE_FRAME_RATE_ALWAYS para vídeos de longa duração, como filmes. Isso ocorre porque o benefício da correspondência do frame rate 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 plataformas

A Plataforma Android foi projetada para processar corretamente cenários em que há várias superfícies com diferentes configurações de frame rate. Quando o app tiver várias plataformas com diferentes frame rates, chame setFrameRate() com o frame rate correto para cada plataforma. Mesmo que o dispositivo execute 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 superfícies.

A plataforma não muda para o frame rate do app.

Mesmo que o dispositivo ofereça suporte ao frame rate especificado pelo app em uma chamada para setFrameRate(), há casos em que o dispositivo não vai mudar a tela para essa taxa de atualização. Por exemplo, uma superfície de prioridade mais alta pode ter uma configuração de frame rate diferente ou o dispositivo pode estar no modo de economia de bateria, definindo uma restrição na taxa de atualização da tela para economizar bateria. O app ainda vai funcionar corretamente quando o dispositivo não alternar a taxa de atualização da tela para a configuração de frame rate 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 à frame rate do app. Para vídeos, o frame rate é igual ao do vídeo de origem, e um menu suspenso é necessário para mostrar o conteúdo do vídeo. Um jogo pode tentar ser executado na taxa de atualização da tela em vez de permanecer com o frame rate preferido. O app não pode mudar o valor transmitido para setFrameRate() com base no que a plataforma faz. Ele precisa permanecer definido como o frame rate preferido do app, independente de como o app processa 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 o uso de taxas extras de atualização da tela, a plataforma vai ter as informações corretas para alternar para o frame rate preferido do app.

Nos casos em que o app não pode 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 os 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 ritmo de frames para mais informações sobre como evitar trepidações. Use a biblioteca Android Frame Pacing.

Em alguns casos, a plataforma pode alternar para um múltiplo do frame rate que o app especificado em setFrameRate(). Por exemplo, um app pode chamar setFrameRate() com 60 Hz, e o dispositivo pode alternar a tela para 120 Hz. Um motivo para isso acontecer é se outro app tiver uma plataforma com uma configuração de frame rate de 24 Hz. Nesse caso, executar a tela em 120 Hz permitirá que as superfícies de 60 e 24 Hz sejam executadas sem a necessidade de pull.

Quando a tela é executada em um múltiplo do frame rate do app, o app precisa especificar 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 o frame rate 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 um frame rate específico.

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

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

  • 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 chave for leve e improvável ser perceptível para o usuário. Se o app preferir mudar a taxa de atualização da tela, mesmo que exija uma chave de modo pesado (por exemplo, em um dispositivo Android TV), use preferredDisplayModeId.
  • Apps que não conseguem processar a tela em execução em um múltiplo do frame rate do app, 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 um frame rate preferencial na janela do app, e a taxa é aplicável a todas as superfícies da janela. O app precisa especificar o frame rate preferido, independente das taxas de atualização com suporte do dispositivo, de maneira semelhante a setFrameRate(), para dar ao programador uma dica melhor do frame rate pretendido pelo app.

O 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 preferencial, use preferredRefreshRate em vez de preferredDisplayModeId.

Evitando 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 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. É necessário descobrir o frame rate correto com antecedência e chamar 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(), ele pode ser usado para outros apps. Por exemplo, um jogo que pretende não ser executado além de 60 Hz (para reduzir o uso de energia e alcançar sessões mais longas) pode chamar Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT). Dessa forma, um dispositivo executado a 90 Hz por padrão será executado a 60 Hz enquanto o jogo estiver ativo, o que evitará a trepidação 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. Caso não seja de vídeo, use FRAME_RATE_COMPATIBILITY_DEFAULT.

Como escolher uma estratégia para mudar o frame rate

  • É altamente recomendável que os apps, ao exibir vídeos de longa duração, como filmes, chamem setFrameRate(QPS, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS), em que QPS é o frame rate do vídeo.
  • Não é recomendável que apps chamem setFrameRate() com CHANGE_FRAME_RATE_ALWAYS quando 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 da taxa de atualização a apps de reprodução de vídeo:

  1. Decida o changeFrameRateStrategy:
    1. Se estiver assistindo um vídeo de longa duração, como um filme, use MATCH_CONTENT_FRAMERATE_ALWAYS
    2. Ao reproduzir um vídeo curto, como um trailer com movimento, 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 uniforme da taxa de atualização está prestes a acontecer verificando se ambos os fatos são verdadeiros:
    1. Não é possível alternar o modo contínuo da taxa de atualização atual (chamamos C) para a frame rate do vídeo (vamos chamar de V). Esse será o caso se C e V forem diferentes e Display.getMode().getAlternativeRefreshRates não contiver um múltiplo de V.
    2. O usuário aceitou mudanças simples na taxa de atualização. Para detectar isso, verifique se DisplayManager.getMatchContentFrameRateUserPreference retorna MATCH_CONTENT_FRAMERATE_ALWAYS.
  4. Se a migração ocorrer sem problemas, faça o seguinte:
    1. Chame setFrameRate e transmita fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE e changeFrameRateStrategy, em que fps é o frame rate do vídeo.
    2. Iniciar a reprodução do vídeo
  5. Se uma mudança de modo não contínuo estiver prestes a acontecer, faça o seguinte:
    1. Mostrar a UX para notificar o usuário. Recomendamos que você implemente uma maneira para o usuário dispensar essa UX e pular o atraso extra na etapa 5.d. Isso ocorre porque nosso atraso recomendado é maior que o necessário em telas que exibem tempos de alternância mais rápidos.
    2. Chame setFrameRate e transmita fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE e CHANGE_FRAME_RATE_ALWAYS, em que fps é o frame rate 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 do vídeo

O pseudocódigo para oferecer suporte apenas à alternância contínua é 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 contínua e estável, 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();
}