Tốc độ khung hình

API tốc độ khung hình cho phép các ứng dụng thông báo cho nền tảng Android về tốc độ khung hình dự kiến. API này có sẵn trên các ứng dụng nhắm đến Android 11 (API cấp 30) trở lên. Trước đây, hầu hết các thiết bị chỉ hỗ trợ một tốc độ làm mới màn hình, thường là 60 Hz, nhưng điều này đã và đang thay đổi. Nhiều thiết bị hiện hỗ trợ thêm tốc độ làm mới như 90Hz hoặc 120Hz. Một số thiết bị hỗ trợ chuyển đổi tốc độ làm mới liền mạch, trong khi một số thiết bị khác hiện một màn hình đen trong thời gian ngắn, thường kéo dài một giây.

Mục đích chính của API này là cho phép các ứng dụng tận dụng tốt hơn tất cả tốc độ làm mới màn hình được hỗ trợ. Ví dụ: Một ứng dụng phát video 24 Hz gọi setFrameRate() có thể dẫn đến việc thiết bị thay đổi tốc độ làm mới màn hình từ 60 Hz thành 120 Hz. Tốc độ làm mới mới này cho phép phát video 24 Hz mượt mà, không bị rung hình mà không cần kéo xuống theo tỷ lệ 3:2 vì để phát cùng một video trên màn hình 60 Hz. Điều này mang lại trải nghiệm người dùng tốt hơn.

Cách sử dụng cơ bản

Android cho thấy một số cách để truy cập và kiểm soát các nền tảng, vì vậy, có một số phiên bản của API setFrameRate(). Mỗi phiên bản API sẽ lấy cùng các tham số và hoạt động giống như các phiên bản khác:

Ứng dụng không cần xem xét tốc độ làm mới màn hình được hỗ trợ trên thực tế. Bạn có thể lấy tốc độ này bằng cách gọi Display.getSupportedModes() để có thể gọi setFrameRate() một cách an toàn. Ví dụ: ngay cả khi thiết bị chỉ hỗ trợ 60 Hz, hãy gọi setFrameRate() theo tốc độ khung hình mà ứng dụng của bạn ưu tiên. Các thiết bị không phù hợp hơn với tốc độ khung hình của ứng dụng sẽ vẫn sử dụng tốc độ làm mới màn hình hiện tại.

Để xem lệnh gọi đến setFrameRate() có dẫn đến thay đổi đối với tốc độ làm mới màn hình hay không, hãy đăng ký thông báo về sự thay đổi màn hình bằng cách gọi DisplayManager.registerDisplayListener() hoặc AChoreographer_registerRefreshRateCallback().

Khi gọi setFrameRate(), tốt nhất bạn nên truyền tốc độ khung hình chính xác thay vì làm tròn đến một số nguyên. Ví dụ: khi kết xuất một video được quay ở tốc độ 29,97 Hz, hãy truyền 29,97 thay vì làm tròn thành 30.

Đối với các ứng dụng video, tham số tương thích được truyền đến setFrameRate() phải được đặt thành Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE để cung cấp thêm gợi ý cho nền tảng Android rằng ứng dụng sẽ sử dụng tính năng kéo xuống để điều chỉnh với tốc độ làm mới màn hình không phù hợp (dẫn đến hiện tượng giật).

Trong một số trường hợp, nền tảng video sẽ ngừng gửi khung hình nhưng vẫn sẽ hiển thị trên màn hình trong một thời gian. Có một số trường hợp phổ biến là khi video đến cuối video hoặc khi người dùng tạm dừng phát. Trong những trường hợp như vậy, hãy gọi setFrameRate() với tham số tốc độ khung hình được đặt thành 0 để xoá chế độ cài đặt tốc độ khung hình của giao diện về giá trị mặc định. Bạn không cần xoá chế độ cài đặt tốc độ khung hình như thế này khi huỷ bỏ bề mặt hoặc khi giao diện bị ẩn do người dùng chuyển sang một ứng dụng khác. Chỉ xoá chế độ cài đặt tốc độ khung hình khi giao diện vẫn hiển thị mà không cần sử dụng.

Nút chuyển tốc độ khung hình liền mạch

Trên một số thiết bị, việc chuyển đổi tốc độ làm mới có thể khiến hình ảnh bị gián đoạn, chẳng hạn như màn hình đen trong 1 hoặc 2 giây. Điều này thường xảy ra trên hộp giải mã tín hiệu số, bảng TV và các thiết bị tương tự. Theo mặc định, khung Android không chuyển đổi chế độ khi API Surface.setFrameRate() được gọi, để tránh những gián đoạn hình ảnh như vậy.

Một số người dùng muốn gián đoạn hình ảnh ở đầu và cuối video dài hơn. Điều này cho phép tốc độ làm mới của màn hình phù hợp với tốc độ khung hình của video và tránh các cấu phần phần mềm chuyển đổi tốc độ khung hình, chẳng hạn như ứng dụng kéo xuống theo tỷ lệ 3:2 khi phát phim.

Vì lý do này, các nút chuyển tốc độ làm mới liền mạch có thể được bật nếu cả người dùng và ứng dụng chọn sử dụng:

Bạn nên luôn sử dụng CHANGE_FRAME_RATE_ALWAYS cho các video dài, chẳng hạn như phim. Lý do là lợi ích của việc khớp tốc độ khung hình của video lớn hơn thời gian gián đoạn xảy ra khi thay đổi tốc độ làm mới.

Đề xuất khác

Hãy làm theo các đề xuất sau cho các trường hợp phổ biến.

Nhiều nền tảng

Nền tảng Android được thiết kế để xử lý chính xác các tình huống có nhiều nền tảng với các chế độ cài đặt tốc độ khung hình khác nhau. Khi ứng dụng của bạn có nhiều nền tảng với tốc độ khung hình khác nhau, hãy gọi setFrameRate() với tốc độ khung hình chính xác cho từng nền tảng. Ngay cả khi thiết bị đang chạy nhiều ứng dụng cùng một lúc, ở chế độ chia đôi màn hình hoặc hình trong hình, mỗi ứng dụng vẫn có thể gọi setFrameRate() một cách an toàn cho các nền tảng của riêng chúng.

Nền tảng này không thay đổi tốc độ khung hình của ứng dụng

Ngay cả khi thiết bị hỗ trợ tốc độ khung hình mà ứng dụng chỉ định trong lệnh gọi đến setFrameRate(), vẫn có một số trường hợp thiết bị sẽ không chuyển màn hình sang tốc độ làm mới đó. Ví dụ: nền tảng có mức độ ưu tiên cao hơn có thể có chế độ cài đặt tốc độ khung hình khác hoặc thiết bị có thể đang ở chế độ tiết kiệm pin (đặt hạn chế về tốc độ làm mới màn hình để tiết kiệm pin). Ứng dụng vẫn phải hoạt động chính xác khi thiết bị không chuyển tốc độ làm mới màn hình sang chế độ cài đặt tốc độ khung hình của ứng dụng, ngay cả khi thiết bị chuyển đổi trong các trường hợp bình thường.

Ứng dụng có thể quyết định cách phản hồi khi tốc độ làm mới màn hình không khớp với tốc độ khung hình của ứng dụng. Đối với video, tốc độ khung hình được cố định với tốc độ của video nguồn và phải kéo xuống để hiển thị nội dung video. Thay vào đó, trò chơi có thể chọn chạy ở tốc độ làm mới màn hình thay vì duy trì tốc độ khung hình ưu tiên. Ứng dụng không nên thay đổi giá trị nó chuyển thành setFrameRate() dựa trên hoạt động của nền tảng. Tốc độ này phải luôn được đặt ở tốc độ khung hình ưu tiên của ứng dụng, bất kể ứng dụng xử lý các trường hợp mà nền tảng không điều chỉnh cho phù hợp với yêu cầu của ứng dụng như thế nào. Bằng cách đó, nếu các điều kiện của thiết bị thay đổi để cho phép sử dụng tốc độ làm mới màn hình bổ sung, thì nền tảng sẽ có thông tin chính xác để chuyển sang tốc độ khung hình ưu tiên của ứng dụng.

Trong trường hợp ứng dụng không hoặc không thể chạy ở tốc độ làm mới màn hình, ứng dụng phải chỉ định dấu thời gian trình bày cho từng khung hình, sử dụng một trong các cơ chế của nền tảng để đặt dấu thời gian trình bày:

Việc sử dụng những dấu thời gian này sẽ ngăn nền tảng trình chiếu khung ứng dụng quá sớm, dẫn đến tình trạng giật không cần thiết. Việc sử dụng đúng dấu thời gian trình bày khung là một chút khó khăn. Đối với trò chơi, hãy xem hướng dẫn về tốc độ khung hình của chúng tôi để biết thêm thông tin về cách tránh hiện tượng rung hình và cân nhắc sử dụng thư viện Android Frame Pacing.

Trong một số trường hợp, nền tảng có thể chuyển sang nhiều tốc độ khung hình mà ứng dụng chỉ định trong setFrameRate(). Ví dụ: một ứng dụng có thể gọi setFrameRate() với tần số 60 Hz và thiết bị có thể chuyển màn hình sang 120 Hz. Một lý do khiến điều này có thể xảy ra là khi một ứng dụng khác có nền tảng có chế độ cài đặt tốc độ khung hình là 24Hz. Trong trường hợp đó, việc chạy màn hình ở 120 Hz sẽ cho phép chạy cả bề mặt 60 Hz và 24 Hz mà không cần kéo xuống.

Khi màn hình đang chạy ở nhiều tốc độ khung hình của ứng dụng, ứng dụng phải chỉ định dấu thời gian trình bày cho từng khung hình để tránh hiện tượng giật không cần thiết. Đối với trò chơi, thư viện Android Frame Pacing rất hữu ích trong việc thiết lập chính xác dấu thời gian trình bày khung hình.

setFrameRate() so với PreferredDisplayModeId

WindowManager.LayoutParams.preferredDisplayModeId là một cách khác mà các ứng dụng có thể cho biết tốc độ khung hình của chúng cho nền tảng. Một số ứng dụng chỉ muốn thay đổi tốc độ làm mới màn hình thay vì thay đổi các chế độ cài đặt khác của chế độ hiển thị, chẳng hạn như độ phân giải màn hình. Nói chung, hãy sử dụng setFrameRate() thay vì preferredDisplayModeId. Hàm setFrameRate() dễ sử dụng hơn vì ứng dụng không cần tìm kiếm trong danh sách các chế độ hiển thị để tìm một chế độ có tốc độ khung hình cụ thể.

setFrameRate() mang lại cho nền tảng nhiều cơ hội hơn để chọn tốc độ khung hình tương thích trong trường hợp có nhiều nền tảng được chạy ở các tốc độ khung hình khác nhau. Ví dụ: hãy xem xét một tình huống trong đó 2 ứng dụng đang chạy ở chế độ chia đôi màn hình trên Pixel 4, trong đó một ứng dụng đang phát video 24Hz và ứng dụng còn lại đang cho người dùng xem danh sách có thể cuộn. Pixel 4 hỗ trợ 2 tốc độ làm mới màn hình: 60 Hz và 90 Hz. Khi sử dụng API preferredDisplayModeId, nền tảng video buộc phải chọn 60Hz hoặc 90Hz. Bằng cách gọi setFrameRate() với 24Hz, nền tảng video sẽ cung cấp cho nền tảng thêm thông tin về tốc độ khung hình của video nguồn. Nhờ vậy, nền tảng có thể chọn tốc độ làm mới màn hình là 90Hz trong trường hợp này.

Tuy nhiên, có những trường hợp bạn nên sử dụng preferredDisplayModeId thay vì setFrameRate(), chẳng hạn như sau:

  • Nếu ứng dụng muốn thay đổi độ phân giải hoặc các chế độ cài đặt khác về chế độ hiển thị, hãy dùng preferredDisplayModeId.
  • Nền tảng sẽ chỉ chuyển đổi các chế độ hiển thị để phản hồi lệnh gọi đến setFrameRate() nếu nút chuyển chế độ nhẹ và ít có khả năng người dùng nhận thấy. Nếu ứng dụng muốn chuyển đổi tốc độ làm mới màn hình ngay cả khi cần chuyển đổi ở chế độ nặng (ví dụ: trên thiết bị Android TV), hãy sử dụng preferredDisplayModeId.
  • Những ứng dụng không thể xử lý màn hình đang chạy ở nhiều tốc độ khung hình của ứng dụng (yêu cầu đặt dấu thời gian trình bày trên mỗi khung hình) nên sử dụng preferredDisplayModeId.

setFrameRate() so với PreferredRefreshRate

WindowManager.LayoutParams#preferredRefreshRate đặt tốc độ khung hình ưu tiên trên cửa sổ ứng dụng và tốc độ này áp dụng cho mọi nền tảng trong cửa sổ. Ứng dụng nên chỉ định tốc độ khung hình ưu tiên bất kể tốc độ làm mới được hỗ trợ của thiết bị, tương tự như setFrameRate(), để cung cấp cho trình lập lịch biểu gợi ý tốt hơn về tốc độ khung hình dự kiến của ứng dụng.

preferredRefreshRate sẽ bị bỏ qua đối với các Nền tảng sử dụng setFrameRate(). Nói chung, hãy sử dụng setFrameRate() nếu có thể.

PreferredRefreshRate so với PreferredDisplayModeId

Nếu chỉ muốn thay đổi tốc độ làm mới ưu tiên, bạn nên sử dụng preferredRefreshRate thay vì preferredDisplayModeId.

Tránh gọi setFrameRate() quá thường xuyên

Mặc dù lệnh gọi setFrameRate() không tốn nhiều hiệu suất về mặt hiệu suất, nhưng ứng dụng nên tránh gọi setFrameRate() ở mọi khung hình hoặc nhiều lần trong mỗi giây. Các lệnh gọi đến setFrameRate() có thể làm thay đổi tốc độ làm mới màn hình, điều này có thể làm giảm khung hình trong quá trình chuyển đổi. Trước đó, bạn nên tìm ra tốc độ khung hình chính xác và gọi setFrameRate() một lần.

Mức sử dụng cho trò chơi hoặc các ứng dụng khác không phải video

Mặc dù video là trường hợp sử dụng chính của API setFrameRate(), nhưng bạn có thể sử dụng video cho các ứng dụng khác. Ví dụ: một trò chơi có ý định không chạy cao hơn 60 Hz (để giảm mức sử dụng pin và có được phiên chơi lâu hơn) có thể gọi Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT). Bằng cách này, một thiết bị chạy ở tốc độ 90 Hz theo mặc định sẽ chạy ở tốc độ 60 Hz khi trò chơi đang hoạt động. Điều này sẽ giúp tránh hiện tượng rung hình thường xảy ra nếu trò chơi chạy ở tốc độ 60 Hz trong khi màn hình chạy ở tốc độ 90 Hz.

Sử dụng FRAME_RATE_COMPATIBILITY_FIXED_SOURCE

FRAME_RATE_COMPATIBILITY_FIXED_SOURCE chỉ dành cho ứng dụng video. Để sử dụng dữ liệu không phải video, hãy sử dụng FRAME_RATE_COMPATIBILITY_DEFAULT.

Chọn chiến lược thay đổi tốc độ khung hình

  • Khi hiển thị các video dài, chẳng hạn như phim, các ứng dụng nên gọi setFrameRate( khung hình/giây, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS), trong đó khung hình/giây là tốc độ khung hình của video.
  • Bạn không nên gọi các ứng dụng gọi setFrameRate() bằng CHANGE_FRAME_RATE_ALWAYS nếu muốn thời gian phát video chỉ kéo dài vài phút trở xuống.

Ví dụ về hoạt động tích hợp cho ứng dụng phát video

Bạn nên thực hiện các bước sau để tích hợp nút chuyển tốc độ làm mới vào ứng dụng phát video:

  1. Quyết định changeFrameRateStrategy:
    1. Nếu phát một video dài, chẳng hạn như một bộ phim, hãy sử dụng biểu tượng MATCH_CONTENT_FRAMERATE_ALWAYS
    2. Nếu phát một video ngắn như đoạn giới thiệu di chuyển, hãy sử dụng biểu tượng CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
  2. Nếu changeFrameRateStrategyCHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, hãy chuyển đến bước 4.
  3. Phát hiện xem quá trình chuyển đổi tốc độ làm mới liền mạch có sắp xảy ra hay không bằng cách kiểm tra để đảm bảo cả hai thông tin sau đây đều đúng:
    1. Bạn không thể chuyển đổi chế độ liền mạch từ tốc độ làm mới hiện tại (hãy gọi là C) sang tốc độ khung hình của video (hãy gọi là V). Trường hợp này sẽ xảy ra nếu C và V khác nhau và Display.getMode().getAlternativeRefreshRates không chứa bội số của V.
    2. Người dùng đã chọn thực hiện các thay đổi liên tục đối với tốc độ làm mới. Bạn có thể phát hiện điều này bằng cách kiểm tra xem DisplayManager.getMatchContentFrameRateUserPreference có trả về MATCH_CONTENT_FRAMERATE_ALWAYS hay không
  4. Nếu quá trình chuyển đổi này diễn ra liền mạch, hãy làm như sau:
    1. Gọi setFrameRate rồi truyền hàm này fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCEchangeFrameRateStrategy, trong đó fps là tốc độ khung hình của video.
    2. Bắt đầu phát video
  5. Nếu sắp xảy ra thay đổi về chế độ liền mạch, hãy làm như sau:
    1. Hiển thị trải nghiệm người dùng để thông báo cho người dùng. Lưu ý rằng bạn nên triển khai cách để người dùng đóng trải nghiệm người dùng này và bỏ qua độ trễ bổ sung ở bước 5.d. Lý do là độ trễ được đề xuất của chúng tôi lớn hơn mức cần thiết trên các màn hình có thời gian chuyển đổi nhanh hơn.
    2. Gọi setFrameRate rồi truyền hàm này fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, và CHANGE_FRAME_RATE_ALWAYS, trong đó fps là tốc độ khung hình của video.
    3. Chờ lệnh gọi lại onDisplayChanged.
    4. Đợi 2 giây để quá trình chuyển đổi chế độ hoàn tất.
    5. Bắt đầu phát video

Sau đây là mã giả để chỉ hỗ trợ quá trình chuyển đổi liền mạch:

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

Mã giả hỗ trợ việc chuyển đổi liền mạch và không liền mạch theo mô tả ở trên:

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