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

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 дополнительный намек на то, что приложение будет использовать раскрывающийся список для адаптации к несоответствующей частоте обновления дисплея (что приведет к дрожанию). ).

В некоторых сценариях поверхность видео перестанет отправлять кадры, но останется видимой на экране в течение некоторого времени. Общие сценарии включают ситуацию, когда воспроизведение достигает конца видео или когда пользователь приостанавливает воспроизведение. В этих случаях вызовите 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() против предпочтительногоDisplayModeId

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() против предпочтительногоRefreshRate

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

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

привилегированныйRefreshRate и привилегированныйDisplayModeId

Если приложения хотят изменить только предпочтительную частоту обновления, предпочтительно использовать 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. Покажите UX, чтобы уведомить пользователя. Обратите внимание, что мы рекомендуем вам реализовать возможность отклонения пользователем этого пользовательского интерфейса и пропуска дополнительной задержки на шаге 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();
}