Частота кадров

API частоты кадров позволяет приложениям сообщать платформе Android о желаемой частоте кадров и доступен для приложений, ориентированных на Android 11 (уровень API 30) или выше. Традиционно большинство устройств поддерживали только одну частоту обновления экрана, обычно 60 Гц, но ситуация меняется. Многие устройства теперь поддерживают дополнительные частоты обновления, такие как 90 Гц или 120 Гц. Некоторые устройства поддерживают плавное переключение частоты обновления, в то время как другие кратковременно отображают черный экран, обычно на секунду.

Основная цель API — дать приложениям возможность лучше использовать все поддерживаемые частоты обновления экрана. Например, приложение, воспроизводящее видео с частотой 24 Гц и вызывающее функцию setFrameRate() может привести к изменению частоты обновления экрана с 60 Гц на 120 Гц. Эта новая частота обновления обеспечивает плавное воспроизведение видео с частотой 24 Гц без рывков, без необходимости использования преобразования 3:2, которое потребовалось бы для воспроизведения того же видео на экране с частотой 60 Гц. Это обеспечивает улучшенный пользовательский опыт.

Основное использование

Android предоставляет несколько способов доступа к поверхностям и управления ими, поэтому существует несколько версий API setFrameRate() . Каждая версия API принимает одни и те же параметры и работает так же, как и другие:

Приложению не нужно учитывать фактическую поддерживаемую частоту обновления экрана, которую можно получить, вызвав метод 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 для длительных видеороликов, таких как фильмы. Это связано с тем, что преимущество согласования частоты кадров видео перевешивает прерывания, возникающие при изменении частоты обновления.

Дополнительные рекомендации

Следуйте этим рекомендациям для распространенных ситуаций.

Множественные поверхности

Платформа Android разработана для корректной обработки сценариев, когда несколько поверхностей имеют разные настройки частоты кадров. Если ваше приложение использует несколько поверхностей с разной частотой кадров, вызовите setFrameRate() с правильной частотой кадров для каждой поверхности. Даже если на устройстве одновременно запущено несколько приложений, использующих режим разделенного экрана или «картинка в картинке», каждое приложение может безопасно вызывать setFrameRate() для своей собственной поверхности.

Платформа не переключается на частоту кадров приложения.

Даже если устройство поддерживает частоту кадров, указанную приложением в вызове setFrameRate() , бывают случаи, когда устройство не переключает дисплей на эту частоту обновления. Например, поверхность с более высоким приоритетом может иметь другую настройку частоты кадров, или устройство может находиться в режиме энергосбережения (устанавливая ограничение на частоту обновления дисплея для экономии заряда батареи). Приложение должно работать корректно, даже если устройство не переключает частоту обновления дисплея на заданную приложением частоту кадров, при обычных условиях переключения всё равно происходит.

Приложение само решает, как реагировать, когда частота обновления экрана не соответствует частоте кадров приложения. Для видео частота кадров фиксирована на уровне исходного видео, и для отображения видеоконтента потребуется функция pulldown. Игра может вместо этого попытаться работать с частотой обновления экрана, а не оставаться с предпочитаемой частотой кадров. Приложение не должно изменять значение, передаваемое в setFrameRate() в зависимости от действий платформы. Оно должно оставаться установленным на предпочитаемую приложением частоту кадров, независимо от того, как приложение обрабатывает случаи, когда платформа не подстраивается под запрос приложения. Таким образом, если условия устройства изменятся, позволяя использовать дополнительные частоты обновления экрана, платформа будет располагать необходимой информацией для переключения на предпочитаемую приложением частоту кадров.

В случаях, когда приложение не может или не может работать с частотой обновления экрана, оно должно указывать временные метки отображения для каждого кадра, используя один из механизмов платформы для установки временных меток отображения:

Использование этих временных меток предотвращает преждевременное отображение кадра приложения платформой, что может привести к ненужным рывкам. Правильное использование временных меток отображения кадров — довольно сложная задача. Для игр см. наше руководство по настройке частоты кадров для получения дополнительной информации о предотвращении рывков и рассмотрите возможность использования библиотеки Android Frame Pacing .

В некоторых случаях платформа может переключиться на частоту кадров, кратную той, которую приложение указало в setFrameRate() . Например, приложение может вызвать setFrameRate() с частотой 60 Гц, и устройство может переключить дисплей на 120 Гц. Одна из причин, по которой это может произойти, — если другое приложение использует Surface с частотой кадров 24 Гц. В этом случае работа дисплея на частоте 120 Гц позволит работать как с Surface 60 Гц, так и с Surface 24 Гц без необходимости использования функции pulldown.

Когда частота кадров дисплея кратна частоте кадров приложения, приложение должно указывать временные метки отображения для каждого кадра, чтобы избежать ненужных рывков. Для игр библиотека Android Frame Pacing полезна для правильной установки временных меток отображения кадров.

setFrameRate() против preferredDisplayModeId

WindowManager.LayoutParams.preferredDisplayModeId — это ещё один способ, с помощью которого приложения могут указывать платформе частоту кадров. Некоторые приложения хотят изменить только частоту обновления экрана, а не другие параметры режима отображения, такие как разрешение экрана. В целом, вместо preferredDisplayModeId следует использовать setFrameRate() . Функция 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 игнорируется для поверхностей, использующих 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 если вы ожидаете, что воспроизведение видео продлится несколько минут или меньше.

Пример интеграции для приложений воспроизведения видео.

Для интеграции переключателей частоты обновления в приложения для воспроизведения видео мы рекомендуем следующие шаги:

  1. Определите стратегию changeFrameRateStrategy :
    1. При воспроизведении длинного видео, например, фильма, используйте MATCH_CONTENT_FRAMERATE_ALWAYS
    2. При воспроизведении короткого видеоролика, например, трейлера к фильму, используйте CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
  2. Если параметр changeFrameRateStrategy имеет значение CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS , перейдите к шагу 4.
  3. Чтобы определить, произойдет ли неплавное переключение частоты обновления, убедитесь, что оба этих факта верны:
    1. Плавное переключение режимов с текущей частоты обновления (назовем ее C) на частоту кадров видео (назовем ее V) невозможно. Это произойдет, если C и V разные, и Display.getMode().getAlternativeRefreshRates не содержит значения, кратного V.
    2. Пользователь дал согласие на изменение частоты обновления экрана с нарушением плавности. Это можно определить, проверив, возвращает ли DisplayManager.getMatchContentFrameRateUserPreference MATCH_CONTENT_FRAMERATE_ALWAYS
  4. Чтобы переход прошел без сбоев, выполните следующие действия:
    1. Вызовите setFrameRate и передайте ей значения fps , FRAME_RATE_COMPATIBILITY_FIXED_SOURCE и changeFrameRateStrategy , где fps — частота кадров видео.
    2. Начать воспроизведение видео
  5. Если планируется переключение в неплавный режим, выполните следующие действия:
    1. Покажите пользователю всплывающее окно, чтобы уведомить его. Обратите внимание, что мы рекомендуем предусмотреть возможность закрытия этого всплывающего окна и пропуска дополнительной задержки на шаге 5.d. Это связано с тем, что рекомендуемая нами задержка больше, чем необходимо на экранах с более быстрым переключением.
    2. Вызовите setFrameRate и передайте ей значения fps , FRAME_RATE_COMPATIBILITY_FIXED_SOURCE и CHANGE_FRAME_RATE_ALWAYS , где fps — частота кадров видео.
    3. Дождитесь вызова функции обратного вызова onDisplayChanged .
    4. Подождите 2 секунды, пока завершится переключение режимов.
    5. Начать воспроизведение видео

Псевдокод, поддерживающий только бесшовное переключение, выглядит следующим образом:

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();
}