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:
Surface.setFrameRate()
SurfaceControl.Transaction.setFrameRate()
ANativeWindow_setFrameRate()
ASurfaceTransaction_setFrameRate()
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 da 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 judder de puxada 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 aceitarem:
- Usuários: para ativar, os usuários podem ativar a configuração Match content frame rate.
- Apps: para ativar, os apps podem transmitir
CHANGE_FRAME_RATE_ALWAYS
parasetFrameRate()
.
Recomendamos que você sempre use CHANGE_FRAME_RATE_ALWAYS
para vídeos mais 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 tremores desnecessários. O uso correto dos 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 plataforma 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.
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
executando 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 de 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), usepreferredDisplayModeId
. - 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 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.
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()
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 mudanças de taxa de atualização em apps de reprodução de vídeo:
- Decida o
changeFrameRateStrategy
:- Se você estiver reproduzindo um vídeo longo, como um filme, use
MATCH_CONTENT_FRAMERATE_ALWAYS
. - Se você estiver reproduzindo um vídeo curto, como um trailer de filme, use
CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
.
- Se você estiver reproduzindo um vídeo longo, como um filme, use
- Se o
changeFrameRateStrategy
forCHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
, vá para a etapa 4. - 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:
- 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. - O usuário ativou as mudanças de taxa de atualização não integradas. Para detectar
isso, verifique se
DisplayManager.getMatchContentFrameRateUserPreference
retornaMATCH_CONTENT_FRAMERATE_ALWAYS
.
- 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
- Se a mudança for contínua, faça o seguinte:
- Chame
setFrameRate
e transmitafps
,FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
echangeFrameRateStrategy
, em quefps
é a taxa de frames do vídeo. - Iniciar a reprodução de vídeo
- Chame
- Se uma mudança de modo não integrada estiver prestes a acontecer, faça o seguinte:
- Mostre 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 ocorre porque nosso atraso recomendado é maior do que o necessário em telas que exibimos tempos de troca mais rápidos.
- Chame
setFrameRate
e transmitafps
,FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
eCHANGE_FRAME_RATE_ALWAYS
, em quefps
é a taxa de frames do vídeo. - Aguarde o callback
onDisplayChanged
. - Aguarde dois segundos para que a mudança de modo seja concluída.
- Iniciar a reprodução de 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 à troca 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();
}