Liczba klatek

Interfejs API częstotliwości klatek pozwala aplikacjom informować platformę Androida o chcianej częstotliwości klatek. Jest dostępny w aplikacjach kierowanych na Androida 11 (poziom interfejsu API 30) lub nowszego. Tradycyjnie większość urządzeń obsługiwała tylko jedną częstotliwość odświeżania ekranu, zazwyczaj 60 Hz, ale sytuacja się zmienia. Wiele urządzeń obsługuje teraz dodatkowe częstotliwości odświeżania, takie jak 90 Hz lub 120 Hz. Niektóre urządzenia obsługują płynną zmianę częstotliwości odświeżania, podczas gdy inne na krótko wyświetlają czarny ekran (zwykle trwa to około sekundy).

Głównym celem interfejsu API jest umożliwienie aplikacjom lepszego korzystania ze wszystkich obsługiwanych częstotliwości odświeżania wyświetlacza. Na przykład aplikacja odtwarzająca film w standardzie 24 Hz, która wywołuje funkcję setFrameRate(), może spowodować, że urządzenie zmieni częstotliwość odświeżania wyświetlacza z 60 Hz na 120 Hz. Ta nowa częstotliwość odświeżania umożliwia płynne odtwarzanie filmów w standardzie 24 Hz bez efektu trzęsienia obrazu. Nie trzeba już stosować funkcji pulldown 3:2, która jest wymagana do odtwarzania tego samego filmu na wyświetlaczu 60 Hz. W efekcie użytkownicy będą mieli lepsze wrażenia.

Podstawowe użycie

Android udostępnia kilka sposobów na uzyskiwanie dostępu do interfejsów i ich kontrolowanie, dlatego istnieje kilka wersji interfejsu setFrameRate() API. Każda wersja interfejsu API przyjmuje te same parametry i działa w ten sam sposób:

Aplikacja nie musi uwzględniać rzeczywistych obsługiwanych częstotliwości odświeżania wyświetlacza, których można użyć, wywołując funkcję Display.getSupportedModes(), aby bezpiecznie wywołać funkcję setFrameRate(). Na przykład nawet wtedy, gdy urządzenie obsługuje tylko 60 Hz, wywołanie setFrameRate() może mieć liczbę klatek na sekundę preferowaną przez Twoją aplikację. Urządzenia, które nie mają lepszego dopasowania do częstotliwości wyświetlania aplikacji, pozostaną przy bieżącej częstotliwości odświeżania wyświetlacza.

Aby sprawdzić, czy wywołanie funkcji setFrameRate() powoduje zmianę częstotliwości odświeżania wyświetlacza, zarejestruj się, aby otrzymywać powiadomienia o zmianach wyświetlacza. Aby to zrobić, wywołaj funkcję DisplayManager.registerDisplayListener() lub AChoreographer_registerRefreshRateCallback().

Gdy wywołujesz funkcję setFrameRate(), najlepiej przekazać dokładną liczbę klatek na sekundę zamiast zaokrąglenia do liczby całkowitej. Na przykład podczas renderowania filmu nagranego z częstotliwością 29,97 Hz podaj wartość 29,97, a nie zaokrągloną do 30.

W przypadku aplikacji wideo parametr compatibility przekazywany do setFrameRate() powinien mieć wartość Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, aby dać platformie Android dodatkową wskazówkę, że aplikacja będzie używać funkcji pulldown, aby dostosować się do niepasującej częstotliwości odświeżania wyświetlacza (co spowoduje drżenie obrazu).

W niektórych przypadkach powierzchnia wideo przestanie przesyłać klatki, ale przez jakiś czas będzie widoczna na ekranie. Typowe scenariusze to zakończenie odtwarzania filmu lub wstrzymanie odtwarzania przez użytkownika. W takich przypadkach wywołaj funkcję setFrameRate(), ustawiając parametr częstotliwości klatek na 0, aby przywrócić ustawienie częstotliwości klatek na powierzchni do wartości domyślnej. Wyczyszczenie ustawienia częstotliwości klatek nie jest konieczne, gdy usuwasz powierzchnię lub gdy jest ona ukryta, ponieważ użytkownik przełączył się na inną aplikację. Wyczyść ustawienie częstotliwości klatek tylko wtedy, gdy powierzchnia pozostaje widoczna bez użycia.

Przełączanie liczby klatek bez płynnego przejścia

Na niektórych urządzeniach przełączanie częstotliwości odświeżania może powodować przerwy w wyświetlaniu obrazu, np. przez sekundę może być czarny ekran. Zwykle dzieje się tak w przypadku dekoderów, telewizorów i podobnych urządzeń. Domyślnie framework Androida nie przełącza trybów podczas wywołania interfejsu Surface.setFrameRate()API, aby uniknąć takich wizualnych przerw.

Niektórzy użytkownicy wolą wizualne przerwy na początku i na końcu dłuższych filmów. Dzięki temu częstotliwość odświeżania wyświetlacza będzie odpowiadać częstotliwości odświeżania filmu, co pozwoli uniknąć artefaktów związanych z konwersją częstotliwości odświeżania, takich jak zjawisko juddera w przypadku obrazu 3:2 podczas odtwarzania filmu.

Z tego powodu przełączanie częstotliwości odświeżania bez płynnego przejścia może być włączone, jeśli użytkownik i aplikacje wyrażą na to zgodę:

Zalecamy, aby w przypadku długich filmów, takich jak filmy fabularne, zawsze używać tagu CHANGE_FRAME_RATE_ALWAYS. Dzieje się tak, ponieważ korzyści z dopasowania liczby klatek na sekundę filmu przeważają przerwę, która występuje podczas zmiany częstotliwości odświeżania.

Dodatkowe zalecenia

W przypadku typowych scenariuszy postępuj zgodnie z tymi zaleceniami.

Wiele powierzchni

Platforma Android została zaprojektowana tak, aby prawidłowo obsługiwać scenariusze, w których występuje wiele powierzchni z różnymi ustawieniami liczby klatek na sekundę. Jeśli aplikacja ma wiele powierzchni z różnymi częstotliwościami klatek, wywołaj funkcję setFrameRate() z prawidłową częstotliwością klatek dla każdej z nich. Nawet jeśli na urządzeniu działa kilka aplikacji jednocześnie, korzystając z podziału ekranu lub trybu obrazu w obrazie, każda z nich może bezpiecznie wywoływać setFrameRate() dla swoich powierzchni.

Platforma nie zmienia liczby klatek aplikacji

Nawet jeśli urządzenie obsługuje częstotliwość klatek określoną przez aplikację w wywołaniu setFrameRate(), w niektórych przypadkach urządzenie nie przełączy wyświetlacza na tę częstotliwość odświeżania. Na przykład powierzchnia o wyższym priorytecie może mieć inne ustawienie częstotliwości klatek lub urządzenie może być w trybie oszczędzania baterii (co powoduje ograniczenie częstotliwości odświeżania wyświetlacza w celu oszczędzania baterii). Aplikacja musi działać prawidłowo, gdy urządzenie nie przełączy częstotliwości odświeżania ekranu na ustawienie częstotliwości klatek aplikacji, nawet jeśli urządzenie przełączy się w normalnych okolicznościach.

Gdy częstotliwość odświeżania wyświetlacza nie pasuje do częstotliwości klatek aplikacji, to aplikacja decyduje, jak zareagować. W przypadku filmów liczba klatek jest stała i odpowiada liczbie klatek filmu źródłowego. Aby wyświetlić treści wideo, trzeba będzie przewinąć film. Zamiast tego gra może próbować działać z częstotliwością odświeżania wyświetlacza zamiast z preferowaną liczbą klatek. Aplikacja nie powinna zmieniać wartości przekazywanej do setFrameRate() w zależności od tego, co robi platforma. Należy ustawić go na preferowaną przez aplikację liczbę klatek na sekundę, niezależnie od tego, jak aplikacja radzi sobie w przypadkach, gdy platforma nie dostosowuje się do jej żądania. Dzięki temu, jeśli warunki na urządzeniu ulegną zmianie i umożliwią użycie dodatkowych częstotliwości odświeżania wyświetlacza, platforma będzie mieć prawidłowe informacje, aby przełączyć się na preferowaną częstotliwość klatek aplikacji.

Jeśli aplikacja nie będzie działać z częstotliwością odświeżania ekranu lub nie będzie mogła działać z taką częstotliwością, powinna określić sygnatury czasowe wyświetlania dla każdego obrazu za pomocą jednego z tych mechanizmów platformy do ustawiania sygnatur czasowych wyświetlania:

Dzięki tym sygnaturom czasowym platforma nie wyświetla ramki aplikacji zbyt wcześnie, co mogłoby spowodować niepotrzebne zacinanie. Prawidłowe używanie sygnatur czasowych wyświetlania klatek jest nieco kłopotliwe. W przypadku gier zapoznaj się z przewodnikiem po ustawianiu odstępu między klatkami, aby dowiedzieć się więcej o unikaniu zacięć. Możesz też skorzystać z biblioteki Android Frame Pacing.

W niektórych przypadkach platforma może przełączyć się na wielokrotność częstotliwości klatek aplikacji określonej w setFrameRate(). Aplikacja może na przykład wywołać setFrameRate() z częstotliwością 60 Hz, a urządzenie może przełączyć wyświetlacz na częstotliwość 120 Hz. Może się tak zdarzyć, jeśli inna aplikacja ma powierzchnię z ustawieniem częstotliwości klatek 24 Hz. W takim przypadku wyświetlanie z częstotliwością 120 Hz pozwoli na wyświetlanie powierzchni 60 Hz i 24 Hz bez konieczności przewijania.

Gdy wyświetlacz działa z wielokrotnością częstotliwości wyświetlania klatek aplikacji, aplikacja powinna określać sygnatury czasowe wyświetlania dla każdej klatki, aby uniknąć niepotrzebnego zacinania. W przypadku gier biblioteka Android Frame Pacing pomaga prawidłowo ustawiać sygnatury czasowe wyświetlania klatek.

setFrameRate() a preferredDisplayModeId

WindowManager.LayoutParams.preferredDisplayModeId to kolejny sposób, w jaki aplikacje mogą wskazać platformie swoją częstotliwość klatek. Niektóre aplikacje chcą zmienić tylko częstotliwość odświeżania ekranu, a nie inne ustawienia trybu wyświetlania, takie jak rozdzielczość. Zwykle zamiast preferredDisplayModeId używaj setFrameRate(). Funkcja setFrameRate() jest łatwiejsza w użyciu, ponieważ aplikacja nie musi przeszukiwać listy trybów wyświetlania w celu znalezienia trybu o określonej liczbie klatek na sekundę.

setFrameRate() daje platformie więcej możliwości wyboru zgodnej częstotliwości klatek w sytuacjach, gdy występuje wiele powierzchni z różnymi częstotliwościami klatek. Rozważmy na przykład scenariusz, w którym 2 aplikacje działają w trybie podzielonego ekranu na Pixelu 4. Jedna z nich odtwarza film w standardzie 24 Hz, a druga wyświetla użytkownikowi listę, którą można przewijać. Pixel 4 obsługuje 2 częstotliwości odświeżania: 60 Hz i 90 Hz. Za pomocą interfejsu API preferredDisplayModeId platforma wideo jest zmuszona do wybrania częstotliwości 60 Hz lub 90 Hz. Wywołanie setFrameRate() z częstotliwością 24 Hz powoduje, że platforma otrzymuje więcej informacji o częstotliwości wyświetlania obrazu w źródłowym filmie, co pozwala jej wybrać częstotliwość odświeżania wyświetlacza 90 Hz, która jest lepsza niż 60 Hz w tym scenariuszu.

Są jednak sytuacje, w których należy użyć preferredDisplayModeId zamiast setFrameRate(), na przykład:

  • Jeśli aplikacja chce zmienić rozdzielczość lub inne ustawienia trybu wyświetlania, użyj preferredDisplayModeId.
  • Platforma przełączy tryby wyświetlania tylko w odpowiedzi na wywołanie funkcji setFrameRate(), jeśli przełączenie trybu jest lekkie i nie powinno być zauważalne dla użytkownika. Jeśli aplikacja preferuje zmianę częstotliwości odświeżania wyświetlacza, nawet jeśli wymaga to przełączenia do trybu ciężkiego (np. na urządzeniu z Androidem TV), użyj preferredDisplayModeId.
  • Aplikacje, które nie obsługują wyświetlacza działającego z wielokrotnością częstotliwości odświeżania, co wymaga ustawiania sygnatur czasowych dla każdego klatki, powinny używać preferredDisplayModeId.

setFrameRate() a preferredRefreshRate

WindowManager.LayoutParams#preferredRefreshRate ustawia preferowaną liczbę klatek na sekundę w oknie aplikacji. Ta liczba jest stosowana do wszystkich powierzchni w oknie. Aplikacja powinna określić preferowaną częstotliwość klatek niezależnie od obsługiwanych częstotliwości odświeżania urządzenia (podobnie jak w przypadku setFrameRate()), aby podać planującemu lepszy wskaźnik docelowej częstotliwości klatek.

Wartość preferredRefreshRate jest ignorowana w przypadku powierzchni, które korzystają z setFrameRate(). W ogóle, jeśli to możliwe, używaj setFrameRate().

preferredRefreshRate vs preferredDisplayModeId

Jeśli aplikacje chcą zmienić tylko preferowaną częstotliwość odświeżania, zaleca się użycie wartości preferredRefreshRate zamiast preferredDisplayModeId.

Unikaj zbyt częstego wywoływania metody setFrameRate().

Mimo że wywołanie setFrameRate() nie jest bardzo kosztowne pod względem wydajności, aplikacje powinny unikać wywoływania setFrameRate() w każdej klatce lub wielokrotnie na sekundę. Wywołania funkcji setFrameRate() prawdopodobnie spowodują zmianę częstotliwości odświeżania wyświetlacza, co może spowodować utratę klatek podczas przejścia. Należy wcześniej określić prawidłową liczbę klatek na sekundę i raz wywołać funkcję setFrameRate().

Używanie w przypadku gier lub innych aplikacji nieobejmujących filmów

Chociaż filmy są głównym zastosowaniem interfejsu API setFrameRate(), można go używać w innych aplikacjach. Na przykład gra, która nie ma działać z częstotliwością większą niż 60 Hz (aby zmniejszyć zużycie energii i wydłużyć czas rozgrywki), może wywołać funkcję 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. Pozwoli to uniknąć zacięć, które wystąpiłyby, gdyby gra działała z częstotliwością 60 Hz, a wyświetlacz z częstotliwością 90 Hz.

Użycie parametru FRAME_RATE_COMPATIBILITY_FIXED_SOURCE

FRAME_RATE_COMPATIBILITY_FIXED_SOURCE jest przeznaczona tylko do aplikacji wideo. W przypadku innych zastosowań niż wideo użyj FRAME_RATE_COMPATIBILITY_DEFAULT.

Wybór strategii zmiany liczby klatek

  • Zalecamy, aby aplikacje wyświetlające długie filmy, takie jak filmy fabularne, wywoływały funkcję setFrameRate(fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS), gdzie fps to liczba klatek na sekundę filmu.
  • Zdecydowanie odradzamy wywoływanie przez aplikacje funkcji setFrameRate() z parametrami CHANGE_FRAME_RATE_ALWAYS, jeśli odtwarzanie filmu ma trwać kilka minut lub krócej.

Przykładowa integracja z aplikacjami do odtwarzania filmów

Aby zintegrować przełącznik częstotliwości odświeżania w aplikacjach do odtwarzania filmów, wykonaj te czynności:

  1. Zdecyduj, co changeFrameRateStrategy:
    1. Jeśli odtwarzasz długi film, np. film fabularny, użyj MATCH_CONTENT_FRAMERATE_ALWAYS.
    2. Jeśli odtwarzasz krótki film, np. zwiastun filmu, użyj CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS.
  2. Jeśli changeFrameRateStrategy to CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, przejdź do kroku 4.
  3. Aby wykryć, czy nastąpi zmiana częstotliwości odświeżania, która nie będzie płynna, sprawdź, czy są spełnione oba te warunki:
    1. Nie można płynnie przełączyć się z bieżącej częstotliwości odświeżania (nazwijmy ją C) na częstotliwość klatek filmu (nazwijmy ją V). Tak się stanie, jeśli C i V są różne, a Display.getMode().getAlternativeRefreshRates nie zawiera wielokrotności V.
    2. Użytkownik zgodził się na zmiany częstotliwości odświeżania, które nie są płynne. Możesz to sprawdzić, sprawdzając, czy DisplayManager.getMatchContentFrameRateUserPreference zwraca MATCH_CONTENT_FRAMERATE_ALWAYS
  4. Aby przejście było płynne, wykonaj te czynności:
    1. wywołać funkcję setFrameRate, przekazując jej argument fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE i changeFrameRateStrategy, gdzie fps to liczba klatek na sekundę w filmie.
    2. Rozpocznij odtwarzanie filmu
  5. Jeśli nastąpi zmiana trybu bez płynnego przejścia, wykonaj te czynności:
    1. Wyświetl interfejs użytkownika, aby go powiadomić. Pamiętaj, że na etapie 5d zalecamy wdrożenie sposobu, który pozwoli użytkownikowi zamknąć ten interfejs i pominąć dodatkowy czas oczekiwania. Dzieje się tak, ponieważ zalecana wartość opóźnienia jest większa niż wymagana w przypadku wyświetlaczy o krótszym czasie przełączania.
    2. wywołać funkcję setFrameRate, a potem przekazać jej argumenty fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE i CHANGE_FRAME_RATE_ALWAYS, gdzie fps to liczba klatek na sekundę w filmie.
    3. Poczekaj na oddzwonienie z numeru onDisplayChanged.
    4. Zaczekaj 2 sekundy, aż przełączenie trybu zostanie zakończone.
    5. Rozpocznij odtwarzanie filmu

Pseudokod, który tylko obsługuje płynne przełączanie, 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 obsługujący płynne i niepłynne przełączanie, jak opisano powyżej:

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