API частоты кадров позволяет приложениям сообщать платформе Android предполагаемую частоту кадров и доступен в приложениях, ориентированных на Android 11 (уровень API 30) или выше. Традиционно большинство устройств поддерживали только одну частоту обновления дисплея, обычно 60 Гц, но это меняется. Многие устройства теперь поддерживают дополнительные частоты обновления, такие как 90 Гц или 120 Гц. Некоторые устройства поддерживают плавное переключение частоты обновления, в то время как другие на короткое время показывают черный экран, обычно длящийся секунду.
Основная цель API — дать возможность приложениям лучше использовать все поддерживаемые частоты обновления дисплея. Например, приложение, воспроизводящее видео 24 Гц, которое вызывает setFrameRate()
может привести к тому, что устройство изменит частоту обновления дисплея с 60 Гц на 120 Гц. Эта новая частота обновления обеспечивает плавное воспроизведение видео 24 Гц без дрожания, без необходимости в 3:2 pulldown, который потребовался бы для воспроизведения того же видео на дисплее 60 Гц. Это улучшает пользовательский опыт.
Основное использование
Android предоставляет несколько способов доступа и управления поверхностями, поэтому существует несколько версий API setFrameRate()
. Каждая версия API принимает те же параметры и работает так же, как и другие:
-
Surface.setFrameRate()
-
SurfaceControl.Transaction.setFrameRate()
-
ANativeWindow_setFrameRate()
-
ASurfaceTransaction_setFrameRate()
Приложению не нужно учитывать фактически поддерживаемые частоты обновления дисплея, которые можно получить, вызвав Display.getSupportedModes()
, чтобы безопасно вызвать setFrameRate()
. Например, даже если устройство поддерживает только 60 Гц, вызовите setFrameRate()
с частотой кадров, которую предпочитает ваше приложение. Устройства, которые не имеют лучшего соответствия частоте кадров приложения, останутся с текущей частотой обновления дисплея.
Чтобы узнать, приводит ли вызов setFrameRate()
к изменению частоты обновления дисплея, зарегистрируйтесь для получения уведомлений об изменении дисплея, вызвав DisplayManager.registerDisplayListener()
или AChoreographer_registerRefreshRateCallback()
.
При вызове setFrameRate()
лучше всего передавать точную частоту кадров, а не округлять ее до целого числа. Например, при рендеринге видео, записанного с частотой 29,97 Гц, передайте 29,97, а не округляйте до 30.
Для видеоприложений параметр совместимости, передаваемый в setFrameRate()
должен быть установлен на Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
, чтобы дать платформе Android дополнительную подсказку о том, что приложение будет использовать pulldown для адаптации к несоответствующей частоте обновления дисплея (что приведет к дрожанию).
В некоторых сценариях видеоповерхность перестанет отправлять кадры, но останется видимой на экране в течение некоторого времени. Обычные сценарии включают в себя, когда воспроизведение достигает конца видео или когда пользователь приостанавливает воспроизведение. В этих случаях вызовите setFrameRate()
с параметром частоты кадров, установленным на 0, чтобы очистить настройку частоты кадров поверхности обратно к значению по умолчанию. Очистка настройки частоты кадров таким образом не требуется при уничтожении поверхности или когда поверхность скрыта, потому что пользователь переключается на другое приложение. Очищайте настройку частоты кадров только тогда, когда поверхность остается видимой, не используясь.
Неплавное переключение частоты кадров
На некоторых устройствах переключение частоты обновления может иметь визуальные прерывания, такие как черный экран на секунду или две. Обычно это происходит на приставках, телевизионных панелях и подобных устройствах. По умолчанию фреймворк Android не переключает режимы при вызове API Surface.setFrameRate()
, чтобы избежать таких визуальных прерываний.
Некоторые пользователи предпочитают визуальное прерывание в начале и конце длинных видео. Это позволяет частоте обновления дисплея соответствовать частоте кадров видео и избегать артефактов преобразования частоты кадров, таких как дрожание 3:2 pulldown при воспроизведении фильмов.
По этой причине неплавные переключатели частоты обновления могут быть включены, если и пользователь, и приложения дадут на это согласие:
- Пользователи : чтобы принять участие, пользователи могут включить пользовательскую настройку «Соответствовать частоте кадров контента» .
- Приложения : чтобы включить эту функцию, приложения могут передать
CHANGE_FRAME_RATE_ALWAYS
вsetFrameRate()
.
Мы рекомендуем всегда использовать CHANGE_FRAME_RATE_ALWAYS
для продолжительных видео, таких как фильмы. Это связано с тем, что преимущество соответствия частоте кадров видео перевешивает прерывание, которое происходит при изменении частоты обновления.
Дополнительные рекомендации
Следуйте этим рекомендациям для распространенных сценариев.
Несколько поверхностей
Платформа Android разработана для корректной обработки сценариев, в которых есть несколько поверхностей с разными настройками частоты кадров. Если в вашем приложении есть несколько поверхностей с разной частотой кадров, вызовите setFrameRate()
с правильной частотой кадров для каждой поверхности. Даже если на устройстве одновременно запущено несколько приложений, используя режим разделенного экрана или «картинка в картинке», каждое приложение может безопасно вызывать setFrameRate()
для своих собственных поверхностей.
Платформа не меняет частоту кадров приложения
Даже если устройство поддерживает частоту кадров, указанную приложением в вызове setFrameRate()
, бывают случаи, когда устройство не переключает дисплей на эту частоту обновления. Например, поверхность с более высоким приоритетом может иметь другую настройку частоты кадров, или устройство может находиться в режиме экономии заряда батареи (устанавливая ограничение на частоту обновления дисплея для сохранения заряда батареи). Приложение должно работать правильно, когда устройство не переключает частоту обновления дисплея на настройку частоты кадров приложения, даже если устройство переключается при обычных обстоятельствах.
Приложение должно решить, как реагировать, когда частота обновления дисплея не соответствует частоте кадров приложения. Для видео частота кадров фиксируется на уровне исходного видео, и для отображения видеоконтента потребуется прокрутка. Вместо этого игра может попытаться запуститься с частотой обновления дисплея, а не оставаться с предпочтительной частотой кадров. Приложение не должно изменять значение, которое оно передает в setFrameRate()
в зависимости от того, что делает платформа. Оно должно оставаться установленным на предпочтительную частоту кадров приложения, независимо от того, как приложение обрабатывает случаи, когда платформа не подстраивается под запрос приложения. Таким образом, если условия устройства изменятся, чтобы разрешить использование дополнительных частот обновления дисплея, у платформы будет правильная информация для переключения на предпочтительную частоту кадров приложения.
В случаях, когда приложение не будет или не сможет работать с частотой обновления дисплея, оно должно указать временные метки представления для каждого кадра, используя один из механизмов платформы для установки временных меток представления:
Использование этих временных меток не позволяет платформе слишком рано представлять кадр приложения, что может привести к ненужному дрожанию. Правильное использование временных меток представления кадров немного сложно. Для игр см. наше руководство по темпу кадров для получения дополнительной информации о том, как избежать дрожания, и рассмотрите возможность использования библиотеки Android Frame Pacing .
В некоторых случаях платформа может переключиться на кратность частоты кадров, указанной приложением в setFrameRate()
. Например, приложение может вызвать setFrameRate()
с 60 Гц, а устройство может переключить дисплей на 120 Гц. Одной из причин, по которой это может произойти, является то, что другое приложение имеет поверхность с настройкой частоты кадров 24 Гц. В этом случае запуск дисплея на частоте 120 Гц позволит работать как поверхности 60 Гц, так и поверхности 24 Гц без необходимости понижения.
Когда дисплей работает с частотой, кратной частоте кадров приложения, приложение должно указывать временные метки представления для каждого кадра, чтобы избежать ненужного дрожания. Для игр библиотека Android Frame Pacing полезна для правильной установки временных меток представления кадров.
setFrameRate() против preferredDisplayModeId
WindowManager.LayoutParams.preferredDisplayModeId
— это еще один способ, которым приложения могут указывать свою частоту кадров платформе. Некоторые приложения хотят изменить только частоту обновления дисплея, а не другие настройки режима дисплея, например разрешение дисплея. В общем случае используйте setFrameRate()
вместо preferredDisplayModeId
. Функция setFrameRate()
проще в использовании, поскольку приложению не нужно искать в списке режимов дисплея режим с определенной частотой кадров.
setFrameRate()
дает платформе больше возможностей для выбора совместимой частоты кадров в сценариях, где есть несколько поверхностей, работающих с разной частотой кадров. Например, рассмотрим сценарий, в котором два приложения работают в режиме разделенного экрана на Pixel 4, где одно приложение воспроизводит видео с частотой 24 Гц, а другое показывает пользователю прокручиваемый список. Pixel 4 поддерживает две частоты обновления дисплея: 60 Гц и 90 Гц. Используя API preferredDisplayModeId
, видеоповерхность вынуждена выбирать либо 60 Гц, либо 90 Гц. Вызывая setFrameRate()
с 24 Гц, видеоповерхность дает платформе больше информации о частоте кадров исходного видео, позволяя платформе выбрать 90 Гц для частоты обновления дисплея, что лучше, чем 60 Гц в этом сценарии.
Однако существуют сценарии, в которых вместо setFrameRate()
следует использовать preferredDisplayModeId
, например следующие:
- Если приложение хочет изменить разрешение или другие настройки режима отображения, используйте
preferredDisplayModeId
. - Платформа будет переключать режимы отображения только в ответ на вызов
setFrameRate()
, если переключение режима легкое и вряд ли будет заметно пользователю. Если приложение предпочитает переключать частоту обновления дисплея, даже если для этого требуется тяжелое переключение режима (например, на устройстве Android TV), используйтеpreferredDisplayModeId
. - Приложения, которые не могут обрабатывать отображение, работающее с частотой, кратной частоте кадров приложения, что требует установки временных меток представления для каждого кадра, должны использовать
preferredDisplayModeId
.
setFrameRate() против preferredRefreshRate
WindowManager.LayoutParams#preferredRefreshRate
устанавливает предпочтительную частоту кадров в окне приложения, и эта частота применима ко всем поверхностям в пределах окна. Приложение должно указать предпочтительную частоту кадров независимо от поддерживаемых устройством частот обновления, аналогично setFrameRate()
, чтобы дать планировщику лучшее представление о предполагаемой частоте кадров приложения.
preferredRefreshRate
игнорируется для Surfaces, которые используют setFrameRate()
. В общем случае используйте setFrameRate()
если это возможно.
preferredRefreshRate против preferredDisplayModeId
Если приложения хотят изменить только предпочтительную частоту обновления, предпочтительнее использовать preferredRefreshRate
, а не preferredDisplayModeId
.
Избегайте слишком частого вызова setFrameRate()
Хотя вызов setFrameRate()
не очень затратен с точки зрения производительности, приложениям следует избегать вызова setFrameRate()
каждый кадр или несколько раз в секунду. Вызовы setFrameRate()
, скорее всего, приведут к изменению частоты обновления дисплея, что может привести к потере кадров во время перехода. Вам следует заранее выяснить правильную частоту кадров и вызвать setFrameRate()
один раз.
Использование для игр или других невидеоприложений
Хотя видео является основным вариантом использования API setFrameRate()
, его можно использовать и для других приложений. Например, игра, которая не должна работать на частоте выше 60 Гц (чтобы снизить энергопотребление и добиться более длительных игровых сеансов), может вызвать Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT)
. Таким образом, устройство, которое по умолчанию работает на частоте 90 Гц, вместо этого будет работать на частоте 60 Гц во время игры, что позволит избежать дрожания, которое в противном случае возникло бы, если бы игра работала на частоте 60 Гц, а дисплей — на частоте 90 Гц.
Использование FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
предназначен только для видеоприложений. Для не-видеоприложений используйте FRAME_RATE_COMPATIBILITY_DEFAULT
.
Выбор стратегии изменения частоты кадров
- Мы настоятельно рекомендуем приложениям при отображении продолжительных видео, таких как фильмы, вызывать
setFrameRate(
fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS)
, где fps — это частота кадров видео. - Мы настоятельно не рекомендуем приложениям вызывать
setFrameRate()
сCHANGE_FRAME_RATE_ALWAYS
если вы ожидаете, что воспроизведение видео продлится несколько минут или меньше.
Пример интеграции для приложений воспроизведения видео
Мы рекомендуем следующие шаги для интеграции переключателей частоты обновления в приложения для воспроизведения видео:
- Определите
changeFrameRateStrategy
:- При воспроизведении длительного видео, например фильма, используйте
MATCH_CONTENT_FRAMERATE_ALWAYS
- При воспроизведении короткого видео, например трейлера к фильму, используйте
CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
- При воспроизведении длительного видео, например фильма, используйте
- Если
changeFrameRateStrategy
имеет значениеCHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
, перейдите к шагу 4. - Определите, произойдет ли неплавное переключение частоты обновления, проверив, что оба следующих факта верны:
- Невозможно плавное переключение режима с текущей частоты обновления (назовем ее C) на частоту кадров видео (назовем ее V). Это будет иметь место, если C и V различны, а
Display.getMode().getAlternativeRefreshRates
не содержит кратного V. - Пользователь выбрал неплавные изменения частоты обновления. Вы можете определить это, проверив, возвращает ли
DisplayManager.getMatchContentFrameRateUserPreference
MATCH_CONTENT_FRAMERATE_ALWAYS
- Невозможно плавное переключение режима с текущей частоты обновления (назовем ее C) на частоту кадров видео (назовем ее V). Это будет иметь место, если C и V различны, а
- Если переключение будет плавным, выполните следующие действия:
- Вызовите
setFrameRate
и передайте емуfps
,FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
иchangeFrameRateStrategy
, гдеfps
— это частота кадров видео. - Начать воспроизведение видео
- Вызовите
- Если предстоит неплавная смена режима, выполните следующие действия:
- Показать UX для уведомления пользователя. Обратите внимание, что мы рекомендуем вам реализовать способ, позволяющий пользователю закрыть этот UX и пропустить дополнительную задержку на шаге 5.d. Это связано с тем, что рекомендуемая нами задержка больше необходимой на дисплеях, которые демонстрируют более быстрое время переключения.
- Вызовите
setFrameRate
и передайте емуfps
,FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
иCHANGE_FRAME_RATE_ALWAYS
, гдеfps
— это частота кадров видео. - Дождитесь обратного вызова
onDisplayChanged
. - Подождите 2 секунды, пока не завершится переключение режима.
- Начать воспроизведение видео
Псевдокод для поддержки только бесшовного переключения выглядит следующим образом:
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
transaction.setFrameRate(surfaceControl,
contentFrameRate,
FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
transaction.apply();
beginPlayback();
Псевдокод для поддержки бесшовного и не бесшовного переключения, описанного выше, выглядит следующим образом:
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();
}