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

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 при воспроизведении фильмов.

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

Мы рекомендуем всегда использовать CHANGE_FRAME_RATE_ALWAYS для продолжительных видео, таких как фильмы. Это связано с тем, что преимущество соответствия видеочастоте кадров перевешивает прерывания, возникающие при изменении частоты обновления.

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

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

Несколько поверхностей

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

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

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

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

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

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

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

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

setFrameRate() против preferedDisplayModeId

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() против preferedRefreshRate

WindowManager.LayoutParams#preferredRefreshRate устанавливает предпочтительную частоту кадров в окне приложения, которая применяется ко всем поверхностям в пределах окна. Приложение должно указать предпочтительную частоту кадров независимо от поддерживаемой устройством частоты обновления, аналогично setFrameRate() , чтобы планировщик мог точнее определить предполагаемую частоту кадров приложения.

preferredRefreshRate игнорируется для Surfaces, использующих setFrameRate() . В общем случае, по возможности, используйте setFrameRate() .

preferedRefreshRate против preferedDisplayModeId

Если приложения хотят изменить только предпочтительную частоту обновления, предпочтительнее использовать preferredRefreshRate , а не preferredDisplayModeId .

Избегайте слишком частого вызова setFrameRate()

Хотя вызов 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();
}