Liczba klatek

Interfejs API liczby klatek umożliwia aplikacjom informowanie platformy Androida o zamierzonej liczbie klatek. Ten interfejs jest dostępny w aplikacjach kierowanych na Androida 11 (poziom interfejsu API 30) lub nowszego. Zazwyczaj większość urządzeń obsługiwała tylko jedną częstotliwość odświeżania wyświetlacza, zwykle 60 Hz, ale to się zmienia. Wiele urządzeń obsługuje teraz dodatkowe częstotliwości odświeżania, np. 90 Hz lub 120 Hz. Niektóre urządzenia obsługują płynne przełączanie częstotliwości odświeżania, a inne tylko przez chwilę pojawiają się czarny ekran, zwykle przez sekundę.

Głównym celem interfejsu API jest umożliwienie aplikacjom lepszego korzystania ze wszystkich obsługiwanych częstotliwości odświeżania ekranu. Na przykład, gdy aplikacja odtwarza film 24 Hz i wywołuje funkcję setFrameRate(), może to spowodować zmianę częstotliwości odświeżania ekranu z 60 Hz na 120 Hz. Ta nowa częstotliwość odświeżania umożliwia płynne odtwarzanie filmów w częstotliwości 24 Hz bez zbędnych płynności i bez zwijania się obrazu 3:2, które byłoby wymagane do odtwarzania tego samego filmu na wyświetlaczu o częstotliwości 60 Hz. Zwiększa to wygodę użytkowników.

Podstawowe wykorzystanie

Android udostępnia kilka sposobów dostępu do platform i sterowania nimi, dlatego istnieje kilka wersji interfejsu API setFrameRate(). Każda wersja interfejsu API ma te same parametry i działa tak samo jak pozostałe:

Aplikacja nie musi uwzględniać rzeczywistej obsługiwanych częstotliwości odświeżania wyświetlacza, które można uzyskać, wywołując metodę Display.getSupportedModes(), aby bezpiecznie wywołać funkcję setFrameRate(). Na przykład nawet jeśli urządzenie obsługuje tylko częstotliwość 60 Hz, wywołaj funkcję setFrameRate() z preferowaną przez aplikację liczbą klatek. Urządzenia, które nie mają lepszego dopasowania do liczby klatek w aplikacji, zachowają bieżącą częstotliwość odświeżania ekranu.

Aby sprawdzić, czy wywołanie metody setFrameRate() spowoduje zmianę częstotliwości odświeżania wyświetlacza, zarejestruj się w celu otrzymywania powiadomień o zmianie sposobu wyświetlania, wywołując funkcję DisplayManager.registerDisplayListener() lub AChoreographer_registerRefreshRateCallback().

Podczas wywoływania funkcji setFrameRate() najlepiej jest podać dokładną liczbę klatek, zamiast zaokrąglać je do liczby całkowitej. Na przykład podczas renderowania filmu nagranego przy 29, 97 Hz przekaż wartość 29, 97 zamiast 30.

W przypadku aplikacji wideo parametr zgodności przekazany do funkcji setFrameRate() powinien mieć wartość Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE. Będzie to dla platformy Androida dodatkową wskazówkę, że aplikacja będzie używać menu rozwijanego, aby dostosować się do niezgodnej częstotliwości odświeżania wyświetlacza (co spowoduje dyskomfort).

W niektórych przypadkach panel wideo przestanie przesyłać klatki, ale pozostanie widoczny przez jakiś czas. Najczęściej dzieje się tak, gdy odtwarzanie dochodzi do końca filmu lub gdy użytkownik wstrzymuje odtwarzanie. W takich przypadkach wywołaj funkcję setFrameRate() z parametrem liczby klatek ustawionym na 0, aby przywrócić domyślną wartość liczby klatek na powierzchni. Wyczyszczenie ustawienia liczby klatek nie jest konieczne w przypadku niszczenia powierzchni lub gdy jest ona ukryta, ponieważ użytkownik przełączył się na inną aplikację. Ustawienia liczby klatek można usuwać tylko wtedy, gdy powierzchnia pozostaje widoczna i nie jest używana.

Niepłynny przełącznik liczby klatek

Na niektórych urządzeniach przełączanie częstotliwości odświeżania może powodować zakłócenia wizualne, np. czarny ekran na sekundę lub dwie. Zwykle dzieje się tak na dekoderach, panelach telewizyjnych i podobnych urządzeniach. Domyślnie platforma Androida nie przełącza trybów po wywołaniu interfejsu API Surface.setFrameRate(), aby uniknąć takich przerw w wyświetlaniu.

Niektórzy użytkownicy wolą, aby na początku i na końcu dłuższych filmów wizualna przerwa w oglądaniu. Dzięki temu częstotliwość odświeżania wyświetlacza jest zgodna z liczbą klatek filmu i unikniesz zakłóceń związanych z konwertowaniem liczby klatek, takich jak przeskakiwanie w formacie 3:2 podczas odtwarzania filmu.

Dlatego niepłynne przełączniki częstotliwości odświeżania można włączyć, jeśli wyrażą na to zgodę zarówno użytkownik, jak i aplikacje:

W przypadku długotrwałych filmów, takich jak filmy, zalecamy zawsze używać CHANGE_FRAME_RATE_ALWAYS. Wynika to z faktu, że korzyści wynikające z dopasowania liczby klatek w filmie są ważniejsze niż przerwa w działaniu, która pojawia się przy zmianie częstotliwości odświeżania.

Dodatkowe zalecenia

Postępuj zgodnie z tymi zaleceniami w typowych scenariuszach.

Wiele platform

Platforma Android została zaprojektowana tak, aby poprawnie obsługiwać sytuacje, gdy istnieje wiele platform z różnymi ustawieniami liczby klatek. Jeśli aplikacja ma kilka powierzchni o różnych liczbach klatek na sekundę, wywołaj funkcję setFrameRate() z właściwą liczbą klatek dla każdej z nich. Nawet jeśli na urządzeniu działa wiele aplikacji jednocześnie, korzystając z podzielonego ekranu lub trybu obrazu w obrazie, każda z nich może bezpiecznie wywołać setFrameRate() we własnych platformach.

Platforma nie zmienia liczby klatek w aplikacji.

Nawet jeśli urządzenie obsługuje liczbę klatek określoną przez aplikację w wywołaniu setFrameRate(), w niektórych przypadkach urządzenie nie przełączy się na taką częstotliwość. Na przykład ekran o wyższym priorytecie może mieć inne ustawienie liczby klatek lub urządzenie może być w trybie oszczędzania baterii (przez ustawienie ograniczenia częstotliwości odświeżania wyświetlacza, by oszczędzać baterię). Aplikacja musi działać prawidłowo, nawet jeśli częstotliwość odświeżania wyświetlacza nie zostanie przełączona na ustawienie liczby klatek w aplikacji, nawet jeśli w normalnych sytuacjach będzie się zmieniać.

To aplikacja decyduje, jak zareagować, gdy częstotliwość odświeżania wyświetlacza nie jest zgodna z liczbą klatek w aplikacji. W przypadku wideo liczba klatek jest stała, więc do wyświetlenia treści wideo wymagane jest menu rozwijane. Gra może być uruchamiana z częstotliwością odświeżania wyświetlacza, a nie z preferowaną liczbą klatek. Aplikacja nie powinna zmieniać wartości przekazywanej do setFrameRate() w zależności od tego, jak działa platforma. Powinno ono pozostawać ustawione na preferowaną liczbę klatek przez aplikację niezależnie od tego, jak aplikacja obsługuje przypadki, gdy platforma nie dostosowuje się do żądania aplikacji. Dzięki temu, gdy warunki urządzenia zmienią się i umożliwi korzystanie z dodatkowych częstotliwości odświeżania ekranu, platforma uzyska odpowiednie informacje, aby przełączyć się na preferowaną liczbę klatek w aplikacji.

W sytuacjach, gdy aplikacja nie działa lub nie może działać z częstotliwością odświeżania ekranu, powinna określić sygnatury czasowe każdej klatki prezentacji, korzystając z jednego z mechanizmów platformy do ustawienia sygnatur czasowych prezentacji:

Użycie tych sygnatur czasowych powstrzymuje platformę przed zbyt wczesnemu wyświetlaniu ramki aplikacji, co mogłoby powodować niepotrzebne przestraszenie. Prawidłowe użycie sygnatur czasowych prezentacji klatek może być trudne. W przypadku gier zapoznaj się z naszym przewodnikiem po tempie klatek, aby dowiedzieć się, jak unikać zbędnych zmian. Możesz też skorzystać z biblioteki Android Frame Pacing.

W niektórych przypadkach platforma może przełączyć się na wielokrotność liczby klatek określonej przez aplikację w polu setFrameRate(). Na przykład aplikacja może wywołać metodę setFrameRate() z częstotliwością 60 Hz, a urządzenie może przełączyć wyświetlacz na 120 Hz. Może się tak zdarzyć, jeśli inna aplikacja ma powierzchnię z częstotliwością klatek 24 Hz. W takim przypadku uruchomienie wyświetlacza z częstotliwością 120 Hz pozwoli na działanie zarówno powierzchni 60 Hz, jak i 24 Hz bez konieczności opuszczania ekranu.

Gdy na ekranie jest kilka klatek z więcej niż 1 częstotliwości aplikacji, aplikacja powinna określić sygnatury czasowe prezentacji dla każdej klatki, by uniknąć zbędnych przestojów. W przypadku gier biblioteka Android Frame Pacing może pomóc w prawidłowym ustawianiu sygnatur czasowych prezentacji klatek.

setFrameRate() a PreferredDisplayModeId

WindowManager.LayoutParams.preferredDisplayModeId to kolejny sposób, w jaki aplikacje mogą określać liczbę klatek na platformie. Niektóre aplikacje chcą tylko zmienić częstotliwość odświeżania, a nie inne ustawienia trybu wyświetlania, takie jak rozdzielczość. Ogólnie używaj setFrameRate() zamiast preferredDisplayModeId. Funkcja setFrameRate() jest łatwiejsza w użyciu, ponieważ aplikacja nie musi przeszukiwać listy trybów wyświetlania, aby znaleźć tryb z określoną liczbą klatek.

setFrameRate() daje platformie większe możliwości wyboru zgodnej liczby klatek w sytuacjach, gdy istnieje wiele platform działających z różnymi liczbami klatek. Przyjrzyjmy się sytuacji, w której na Pixelu 4 są 2 aplikacje w trybie podzielonego ekranu, w których jedna odtwarza film w częstotliwości 24 Hz, a druga wyświetla użytkownikowi przewijaną listę. Pixel 4 obsługuje 2 częstotliwości odświeżania wyświetlacza: 60 Hz i 90 Hz. W przypadku interfejsu API preferredDisplayModeId interfejs wideo jest zmuszany do wyboru częstotliwości 60 Hz lub 90 Hz. Wywołując funkcję setFrameRate() z częstotliwością 24 Hz, powierzchnia wideo dostarcza platformie więcej informacji o liczbie klatek filmu źródłowego. Dzięki temu platforma może wybrać częstotliwość odświeżania obrazu 90 Hz, która w tym scenariuszu jest lepsza niż 60 Hz.

W niektórych sytuacjach zamiast setFrameRate() należy jednak używać właściwości preferredDisplayModeId. Na przykład:

  • Jeśli aplikacja chce zmienić rozdzielczość lub inne ustawienia trybu wyświetlania, użyj funkcji preferredDisplayModeId.
  • Platforma przełącza tryby wyświetlania w odpowiedzi na wywołanie funkcji setFrameRate() tylko wtedy, gdy przełącznik trybu jest niewielki i mało prawdopodobne, że będzie widoczny dla użytkownika. Jeśli aplikacja woli zmieniać częstotliwość odświeżania ekranu nawet wtedy, gdy wymaga dużego przełącznika trybu (np. na urządzeniu z Androidem TV), użyj funkcji preferredDisplayModeId.
  • Aplikacje, które nie potrafią obsłużyć wyświetlacza działającego z wieloma szybkością klatek, co wymaga ustawienia sygnatur czasowych prezentacji w każdej klatce, powinny używać właściwości preferredDisplayModeId.

setFrameRate() a PreferredRefreshRate

WindowManager.LayoutParams#preferredRefreshRate ustawia preferowaną liczbę klatek w oknie aplikacji, która ma zastosowanie do wszystkich platform w oknie. Aplikacja powinna określić preferowaną liczbę klatek niezależnie od obsługiwanych częstotliwości odświeżania (tak jak w przypadku funkcji setFrameRate()), aby dać algorytmowi szeregowania lepszą wskazówkę dotyczącą docelowej liczby klatek w aplikacji.

Pole preferredRefreshRate jest ignorowane w przypadku platform, które używają setFrameRate(). W miarę możliwości należy używać właściwości setFrameRate().

preferowany częstotliwość odświeżania a PreferredDisplayModeId

Jeśli aplikacje chcą zmienić tylko preferowaną częstotliwość odświeżania, lepiej jest użyć preferredRefreshRate, a nie preferredDisplayModeId.

unikanie zbyt częstego wywoływania funkcji setFrameRate();

Wywołanie setFrameRate() nie jest bardzo kosztowne pod względem wydajności, ale aplikacje nie powinny wywoływać funkcji setFrameRate() co klatkę ani wiele razy na sekundę. Wywołania setFrameRate() prawdopodobnie spowodują zmianę częstotliwości odświeżania wyświetlacza, co może spowodować spadek liczby klatek podczas przejścia. Musisz z wyprzedzeniem określić prawidłową liczbę klatek i wywołać funkcję setFrameRate() raz.

Do użytku w grach i aplikacjach innych niż wideo.

Interfejs API setFrameRate() służy przede wszystkim do oglądania filmów, ale można go też używać w innych aplikacjach. Na przykład gra, której nie zamierzasz używać więcej niż 60 Hz (w celu zmniejszenia zużycia energii i zwiększenia dłuższych sesji gry), może wywołać Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT). Dzięki temu urządzenie, które domyślnie działa z częstotliwością 90 Hz, będzie działać z częstotliwością 60 Hz, gdy gra jest aktywna. Zapobiegnie to występowaniu szarpania, jeśli gra działa z częstotliwością 60 Hz, a wyświetlacz z częstotliwością 90 Hz.

Wykorzystanie FRAME_RATE_COMPATIBILITY_FIXED_SOURCE

Aplikacja FRAME_RATE_COMPATIBILITY_FIXED_SOURCE jest przeznaczona tylko dla aplikacji wideo. W przypadku innych niż wideo używaj FRAME_RATE_COMPATIBILITY_DEFAULT.

Wybieranie strategii zmiany liczby klatek

  • Zdecydowanie zalecamy, aby aplikacje do wyświetlania długotrwałych filmów, takich jak filmy, wywoływały setFrameRate( kl./s, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS), gdzie kl./s oznacza liczbę klatek filmu.
  • Zdecydowanie odradzamy używanie aplikacji wywołujących setFrameRate() z użyciem CHANGE_FRAME_RATE_ALWAYS, jeśli spodziewasz się, że odtwarzanie filmu może potrwać kilka minut.

Przykładowa integracja z aplikacjami do odtwarzania filmów

Aby zintegrować przełączniki częstotliwości odświeżania z aplikacjami do odtwarzania filmów, zalecamy wykonanie tych czynności:

  1. Wybierz changeFrameRateStrategy:
    1. W przypadku odtwarzania długotrwałego filmu, na przykład filmu, użyj funkcji MATCH_CONTENT_FRAMERATE_ALWAYS.
    2. Jeśli odtwarzasz krótki film, np. zwiastun z przenoszeniem, użyj aplikacji CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
  2. Jeśli changeFrameRateStrategy to CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, przejdź do kroku 4.
  3. Aby sprawdzić, czy nastąpi niepłynna zmiana częstotliwości odświeżania, sprawdź, czy spełnione są oba te warunki:
    1. Trybu płynnego nie da się zmienić z bieżącej częstotliwości odświeżania (nazwijmy ją C) do liczby klatek filmu (nazwijmy ją V). Stanie się tak, jeśli wartości C i V są różne, a Display.getMode().getAlternativeRefreshRates nie zawiera wielokrotności liczby V.
    2. Użytkownik wyraził zgodę na niepłynne zmiany częstotliwości odświeżania. Możesz to wykryć, sprawdzając, czy DisplayManager.getMatchContentFrameRateUserPreference zwraca MATCH_CONTENT_FRAMERATE_ALWAYS
  4. Jeśli przejście będzie działać bezproblemowo, wykonaj te czynności:
    1. Wywołaj metodę setFrameRate i przekaż ją fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE oraz changeFrameRateStrategy, gdzie fps to liczba klatek filmu.
    2. Rozpocznij odtwarzanie filmu
  5. Jeśli nastąpi zmiana trybu niepłynnego, wykonaj te czynności:
    1. Pokaż UX, aby powiadomić użytkownika. Zalecamy zaimplementowanie sposobu, który umożliwi użytkownikowi zamknięcie tego interfejsu i pominięcie dodatkowego opóźnienia opisanego w kroku 5.d. Dzieje się tak, ponieważ zalecane opóźnienie jest większe niż to konieczne w przypadku wyświetlaczy, które wykazują krótsze czasy przełączania.
    2. Wywołaj setFrameRate i przekaż mu fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE oraz CHANGE_FRAME_RATE_ALWAYS, gdzie fps to liczba klatek filmu.
    3. Poczekaj na wywołanie zwrotne onDisplayChanged.
    4. Poczekaj 2 sekundy na zakończenie przełączania trybu.
    5. Rozpocznij odtwarzanie filmu

Pseudokod służący do obsługi płynnego przełączania tylko wygląda tak:

SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
transaction.setFrameRate(surfaceControl,
    contentFrameRate,
    FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
    CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
transaction.apply();
beginPlayback();

Pseudokod do obsługi płynnego i niepłynnego przełączania zgodnie z opisem powyżej jest następujący:

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