A API de frame rate permite que os apps informem à plataforma Android o frame rate pretendido e está disponível em apps destinados ao Android 11 (API de nível 30) ou versões mais recentes. Tradicionalmente, a maioria dos dispositivos oferece suporte a apenas uma única 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 interruptores de taxa de atualização, enquanto outros mostram brevemente uma tela preta, geralmente com duração de 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ção de vídeos de 24 Hz, sem a necessidade de um menu suspenso de 3:2, que
é 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. 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:
Surface.setFrameRate()
SurfaceControl.Transaction.setFrameRate()
ANativeWindow_setFrameRate()
ASurfaceTransaction_setFrameRate()
O app não precisa considerar as taxas reais de atualização de tela com suporte,
que podem ser extraídas 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 o frame rate de sua preferência para o app.
Os dispositivos que não tiverem uma correspondência melhor para o frame rate do app vão continuar com
a taxa de atualização de tela atual.
Para conferir 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 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 resultará em trepidação).
Em alguns casos, a superfície do 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 para o valor padrão. Limpar a configuração de frame rate
dessa forma não é necessário 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.
Troca de frame rate ininterrupta
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 normalmente 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 final de vídeos mais longos. Isso permite que a taxa de atualização da tela corresponda ao frame rate do vídeo e evita artefatos de conversão de frame rate, como trepidações de 3:2 para a reprodução de filmes.
Por esse motivo, as chaves de taxa de atualização não perfeitas poderão ser ativadas se o usuário e os apps aceitarem:
- Usuários: para ativar, os usuários podem ativar a configuração Corresponder ao frame rate do conteúdo.
- Apps: para ativar, os apps podem transmitir
CHANGE_FRAME_RATE_ALWAYS
parasetFrameRate()
.
Recomendamos que você sempre use CHANGE_FRAME_RATE_ALWAYS
para vídeos de longa duração, como filmes. Isso ocorre porque o benefício de corresponder
ao 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 plataformas com diferentes configurações de frame rate. Quando o app tiver várias
plataformas com frame rates diferentes, chame setFrameRate()
com o frame rate
correto para cada uma delas. Mesmo que o dispositivo esteja executando vários apps ao
mesmo tempo, usando a tela dividida ou o modo picture-in-picture, cada app poderá chamar
setFrameRate()
com segurança para as próprias plataformas.
A plataforma não muda no 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 muda 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 precisa
funcionar corretamente quando o dispositivo não muda 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 ao frame rate do app. Para vídeos, o frame rate é fixo ao do
vídeo de origem, e um 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 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 outras taxas de atualização da tela, a
plataforma terá as informações corretas para mudar para o frame rate
preferido do app.
Nos casos em que o app não pode ou não 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 de app muito cedo, o que resultaria em trepidações desnecessárias. O uso correto de carimbos de data/hora da apresentação de frames é um pouco complicado. Para jogos, consulte nosso guia de ritmo de frames para mais informações sobre como evitar a trepidação e considere usar a biblioteca Android Frame Pacing.
Em alguns casos, a plataforma pode alternar para um múltiplo do frame rate do app
especificado em setFrameRate()
. Por exemplo, um app pode chamar setFrameRate()
com 60 Hz, e o dispositivo pode mudar a tela para 120 Hz. Um motivo para isso
acontecer é se outro app tiver uma superfície com uma configuração de frame rate de 24 Hz. Nesse
caso, executar a tela a 120 Hz vai permitir que as superfícies de 60 e
24 Hz sejam executadas sem a necessidade de botões suspensos.
Quando a tela é executada a 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
O 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.
setFrameRate()
oferece à plataforma mais oportunidades para escolher um frame rate
compatível em cenários em que há várias plataformas em execução em
frames diferentes. Por exemplo, considere um cenário em que dois apps estão
sendo executados no modo de tela dividida em um Pixel 4, em que um app exibe um vídeo de 24 Hz
e o outro mostra 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 do 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, existem 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ó alternará os modos de exibição em resposta a uma chamada para
setFrameRate()
se a chave 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 seja necessária uma chave de modo pesado (por exemplo, em um dispositivo Android TV), usepreferredDisplayModeId
. - Apps que não podem processar a tela em execução em um múltiplo do frame rate
do app, 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,
semelhante a setFrameRate()
, para dar ao programador uma dica melhor sobre o frame rate
pretendido do app.
O preferredRefreshRate
é ignorado para plataformas que usam setFrameRate()
. Em
geral, use setFrameRate()
, se possível.
PreferredRefreshRate x PreferredDisplayModeId
Se os apps só quiserem mudar a taxa de atualização preferencial, use
preferredRefreshRate
em vez de preferredDisplayModeId
.
Evitar chamar setFrameRate() com muita frequência
Embora a chamada de setFrameRate()
não seja muito cara em termos de desempenho,
os apps precisam evitar chamar setFrameRate()
em todos os frames 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.
Descubra o frame rate correto 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()
, 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 ter 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 fosse a 90 Hz.
Uso de FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
é destinado apenas a apps de vídeo. Para
uso que não seja de vídeo, use FRAME_RATE_COMPATIBILITY_DEFAULT
.
Como escolher uma estratégia para alterar 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 recomendamos apps que chamem
setFrameRate()
comCHANGE_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 alternâncias de taxa de atualização em apps de reprodução de vídeo:
- Decida a
changeFrameRateStrategy
:- Se estiver assistindo um vídeo de longa duração, como um filme, use
MATCH_CONTENT_FRAMERATE_ALWAYS
- Se estiver assistindo um vídeo curto, como um trailer com movimento, use
CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
- Se estiver assistindo um vídeo de longa duração, como um filme, use
- Se
changeFrameRateStrategy
forCHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
, vá para a etapa 4. - Para detectar se uma mudança não perfeita da taxa de atualização está prestes a acontecer, confira
se os dois fatos abaixo são verdadeiros:
- Não é possível alternar no modo contínuo da taxa de atualização atual (vamos
chamar de C) para o frame rate do vídeo (vamos chamar de V). Isso
vai acontecer se C e V forem diferentes e
Display.getMode().getAlternativeRefreshRates
não tiver um múltiplo de V. - O usuário ativou as mudanças constantes da taxa de atualização. Para detectar
isso, verifique se
DisplayManager.getMatchContentFrameRateUserPreference
retornaMATCH_CONTENT_FRAMERATE_ALWAYS
- Não é possível alternar no modo contínuo da taxa de atualização atual (vamos
chamar de C) para o frame rate do vídeo (vamos chamar de V). Isso
vai acontecer se C e V forem diferentes e
- Se a mudança for perfeita, faça o seguinte:
- Chame
setFrameRate
e transmitafps
,FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
echangeFrameRateStrategy
, em quefps
é o frame rate do vídeo. - Iniciar a reprodução do vídeo
- Chame
- Se uma mudança de modo não contínua estiver prestes a acontecer, faça o seguinte:
- Mostre 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 com tempos de troca mais rápidos.
- Chame
setFrameRate
e transmitafps
,FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
eCHANGE_FRAME_RATE_ALWAYS
, em quefps
é o frame rate do vídeo. - Aguarde o callback
onDisplayChanged
. - Aguarde dois segundos para que a mudança de modo seja concluída.
- 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 à comutação perfeita e não uniforme, 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();
}