API частоты кадров позволяет приложениям сообщать платформе Android о желаемой частоте кадров и доступен в приложениях, предназначенных для Android 11 (уровень API 30) или более поздней версии. Традиционно большинство устройств поддерживали только одну частоту обновления экрана, обычно 60 Гц, но сейчас ситуация меняется. Многие устройства теперь поддерживают дополнительные частоты обновления, такие как 90 Гц или 120 Гц. Некоторые устройства поддерживают плавное переключение частоты обновления, а на других на секунду появляется черный экран.
Основная цель API – помочь приложениям эффективнее использовать все поддерживаемые частоты обновления экрана. Например, если приложение воспроизводит видео с частотой 24 Гц и вызывает setFrameRate(), устройство может изменить частоту обновления экрана с 60 Гц на 120 Гц. Новая частота обновления позволяет плавно воспроизводить видео с частотой 24 Гц без дрожания и преобразования 3:2, которое требуется для воспроизведения того же видео на экране с частотой 60 Гц. Это повышает удобство для пользователей.
Основные функции
В Android есть несколько способов доступа к экранам и управления ими, поэтому существует несколько версий setFrameRate() API. Каждая версия API принимает одни и те же параметры и работает так же, как и другие:
Surface.setFrameRate()SurfaceControl.Transaction.setFrameRate()ANativeWindow_setFrameRate()ASurfaceTransaction_setFrameRate()
Приложению не нужно учитывать поддерживаемые частоты обновления экрана, которые можно получить, вызвав Display.getSupportedModes(), чтобы безопасно вызвать setFrameRate(). Например, даже если устройство поддерживает только 60 Гц, вызовите setFrameRate() с частотой кадров, которую предпочитает ваше приложение.
Устройства, для которых нет более подходящего варианта частоты кадров приложения, будут использовать текущую частоту обновления экрана.
Чтобы узнать, приводит ли вызов setFrameRate() к изменению частоты обновления экрана, зарегистрируйтесь для получения уведомлений об изменении экрана, вызвав DisplayManager.registerDisplayListener() или AChoreographer_registerRefreshRateCallback().
При вызове setFrameRate() лучше передавать точную частоту кадров, а не округлять ее до целого числа. Например, если вы обрабатываете видео, записанное с частотой 29,97 Гц, укажите именно это значение, а не округляйте его до 30.
Для видеоприложений параметр совместимости, передаваемый в setFrameRate(), должен иметь значение Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE. Это дополнительно указывает платформе Android, что приложение будет использовать преобразование частоты кадров для адаптации к несовпадающей частоте обновления экрана (что приведет к дрожанию).
В некоторых случаях видео перестает передавать кадры, но остается видимым на экране в течение некоторого времени. Обычно это происходит, когда воспроизведение доходит до конца видео или когда пользователь приостанавливает воспроизведение. В таких случаях вызовите setFrameRate() с параметром частоты кадров, равным 0, чтобы сбросить частоту кадров поверхности до значения по умолчанию. Очищать настройку частоты кадров таким образом не нужно, если поверхность уничтожается или скрывается, когда пользователь переключается на другое приложение. Очищайте настройку частоты кадров, только когда поверхность остается видимой, но не используется.
Неплавное переключение частоты кадров
На некоторых устройствах при переключении частоты обновления могут возникать визуальные помехи, например черный экран на секунду или две. Обычно это происходит на телеприставках, телевизионных панелях и подобных устройствах. По умолчанию фреймворк Android не переключает режимы при вызове API Surface.setFrameRate(), чтобы избежать таких визуальных прерываний.
Некоторым пользователям нравится, когда в начале и конце длинных видео есть визуальные перебивки. Это позволяет сопоставить частоту обновления экрана с частотой кадров видео и избежать артефактов преобразования частоты кадров, таких как дрожание при воспроизведении фильмов с преобразованием 3:2.
Поэтому переключение частоты обновления с задержкой можно включить, если и пользователь, и приложения согласны на это:
- Пользователи могут включить параметр Соответствие частоты кадров контента.
- Приложения. Чтобы включить функцию, приложения могут передавать
CHANGE_FRAME_RATE_ALWAYSвsetFrameRate().
Мы рекомендуем всегда использовать CHANGE_FRAME_RATE_ALWAYS для длинных видео, например фильмов. Это связано с тем, что преимущества соответствия частоты кадров видео перевешивают прерывание, которое происходит при изменении частоты обновления.
Рекомендации
Ниже приведены рекомендации для распространенных сценариев.
Несколько платформ
Платформа Android разработана для правильной обработки сценариев, в которых есть несколько поверхностей с разными настройками частоты кадров. Если в приложении несколько поверхностей с разной частотой кадров, вызовите setFrameRate() с правильной частотой кадров для каждой поверхности. Даже если на устройстве одновременно запущено несколько приложений, например в режиме разделенного экрана или "картинка в картинке", каждое из них может безопасно вызывать setFrameRate() для своих областей.
Платформа не переключается на частоту кадров приложения.
Даже если устройство поддерживает частоту кадров, указанную приложением в вызове setFrameRate(), в некоторых случаях устройство не переключит дисплей на эту частоту обновления. Например, у поверхности с более высоким приоритетом может быть другая частота кадров или устройство может находиться в режиме энергосбережения (ограничение частоты обновления экрана для экономии заряда батареи). Приложение должно работать корректно, даже если устройство не переключает частоту обновления экрана на частоту кадров приложения, даже если устройство делает это в обычных условиях.
Приложение само решает, как реагировать, если частота обновления экрана не совпадает с частотой кадров приложения. Частота кадров видео фиксируется на уровне исходного видео, и для показа видеоконтента потребуется преобразование частоты кадров. Вместо этого игра может попытаться работать с частотой обновления экрана, а не с предпочтительной частотой кадров. Приложение не должно менять значение, которое оно передает в setFrameRate(), в зависимости от действий платформы. Оно должно оставаться равным предпочтительной частоте кадров приложения, независимо от того, как приложение обрабатывает случаи, когда платформа не подстраивается под запрос приложения. Таким образом, если условия устройства изменятся и позволят использовать дополнительные частоты обновления экрана, у платформы будет правильная информация для переключения на предпочтительную частоту кадров приложения.
Если приложение не может работать с частотой обновления экрана, оно должно указывать временные метки для каждого кадра, используя один из механизмов платформы для установки временных меток:
Это позволяет избежать слишком раннего показа кадра и, как следствие, дрожания изображения. Правильное использование временных меток презентации кадров немного сложное. Если вы разрабатываете игры, ознакомьтесь с руководством по синхронизации кадров, чтобы узнать, как избежать дрожания изображения. Также рекомендуем использовать библиотеку Android Frame Pacing.
В некоторых случаях платформа может переключиться на частоту кадров, кратную той, которую приложение указало в setFrameRate(). Например, приложение может вызвать setFrameRate() с частотой 60 Гц, а устройство – переключить дисплей на 120 Гц. Это может произойти, если у другого приложения есть область с частотой кадров 24 Гц. В этом случае при частоте обновления 120 Гц оба видео будут воспроизводиться без преобразования частоты кадров.
Если частота обновления экрана кратна частоте кадров приложения, то для каждого кадра следует указать временную метку, чтобы избежать дрожания. Для игр рекомендуется использовать библиотеку 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 игнорируется для поверхностей, использующих 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– частота кадров видео. - Начать воспроизведение видео
- Вызовите
- Если должен произойти переход в режим, не поддерживающий плавное переключение, выполните следующие действия:
- Покажите пользователю уведомление. Рекомендуем добавить возможность закрыть это окно и пропустить дополнительную задержку на шаге 5.г. Это связано с тем, что рекомендуемая задержка больше, чем необходимо на дисплеях с более быстрым временем переключения.
- Вызовите
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();
}