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 và có sẵn trên các ứng dụng nhắm đến Android 11 (API cấp 30) trở lên. Theo truyền thống, hầu hết các thiết bị chỉ hỗ trợ một tốc độ làm mới màn hình duy nhất, thường là 60Hz, nhưng điều này đã thay đổi. Hiện tại, nhiều thiết bị hỗ trợ các tốc độ làm mới bổ sung 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 sẽ hiển thị màn hình đen trong thời gian ngắn, thường là một giây.

Mục đích chính của API này là giúp các ứng dụng tận dụng hiệu quả 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ể khiến 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 giúp phát video 24Hz mượt mà, không bị giật mà không cần sử dụng phương pháp kéo 3:2 như khi phát cùng một video trên màn hình 60Hz. Nhờ vậy, người dùng có trải nghiệm tốt hơn.

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

Android cung cấp một số cách để truy cập và kiểm soát các bề mặt, vì vậy có một số phiên bản của API setFrameRate(). Mỗi phiên bản của API đều có 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 thực tế được hỗ trợ, có thể lấy được bằng cách gọi Display.getSupportedModes(), để 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() với tốc độ khung hình mà ứng dụng của bạn muốn. Những thiết bị không có tốc độ làm mới phù hợp hơn với tốc độ khung hình của ứng dụng sẽ giữ nguyên tốc độ làm mới màn hình hiện tại.

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

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

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

Trong một số trường hợp, giao diện video sẽ ngừng gửi khung hình nhưng vẫn hiển thị trên màn hình trong một khoảng thời gian. Các trường hợp phổ biến bao gồm khi nội dung phát đạt đến cuối video hoặc khi người dùng tạm dừng nội dung phát. Trong những trường hợp nà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 nền tảng và chuyể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ề mặt hoặc khi bề mặt bị ẩn vì 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 bề mặt vẫn hiển thị mà không được sử dụng.

Chuyển đổi tốc độ khung hình không 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ể gây ra gián đoạn về hình ảnh, chẳng hạn như màn hình đen trong một hoặc hai giây. Vấn đề này thường xảy ra trên hộp giải mã tín hiệu số, bảng điều khiển 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 về hình ảnh như vậy.

Một số người dùng thích thấy một đoạn gián đoạn bằng hình ảnh ở đầu và cuối video dài. Điều này cho phép tốc độ làm mới của màn hình khớp với tốc độ khung hình của video và tránh các nhiễu do chuyển đổi tốc độ khung hình, chẳng hạn như hiện tượng giật do chuyển đổi 3:2 khi phát phim.

Vì lý do này, bạn có thể bật các chế độ chuyển đổi tốc độ làm mới không liền mạch nếu cả người dùng và ứng dụng đều 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à vì lợi ích của việc điều chỉnh tốc độ khung hình video lớn hơn sự 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 đây cho các trường hợp phổ biến.

Nhiều bề mặt

Nền tảng Android được thiết kế để xử lý chính xác các trường hợp 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 giao diện 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 giao diện. Ngay cả khi thiết bị đang chạy nhiều ứng dụng cùng lúc, sử dụng chế độ chia đôi màn hình hoặc chế độ hình trong hình, mỗi ứng dụng đều có thể gọi setFrameRate() một cách an toàn cho các thành phần riêng của ứng dụng.

Nền tảng không thay đổi thành 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 một lệnh gọi đến setFrameRate(), vẫn có những trường hợp thiết bị không chuyển màn hình sang tốc độ làm mới đó. Ví dụ: một 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ể ở chế độ tiết kiệm pin (đặt hạn chế đối với 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 bình thường 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 điều kiện bình thường.

Ứng dụng sẽ 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 theo tốc độ khung hình của video nguồn và bạn sẽ phải sử dụng phương pháp pulldown để hiển thị nội dung video. Thay vào đó, một trò chơi có thể chọn cố gắng 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 được thay đổi giá trị mà ứng dụng truyền đến setFrameRate() dựa trên hoạt động của nền tảng. Giá trị này phải được đặt thành tốc độ khung hình ưu tiên của ứng dụng, bất kể cách ứng dụng xử lý các trường hợp mà nền tảng không điều chỉnh để khớp với yêu cầu của ứng dụng. Bằng cách đó, nếu điều kiện thiết bị thay đổi để cho phép sử dụng thêm tốc độ làm mới màn hình, thì nền tảng sẽ có thông tin chính xác để chuyển sang tốc độ khung hình mà ứng dụng ưu tiên.

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

Việc sử dụng các dấu thời gian này sẽ ngăn nền tảng trình bày khung ứng dụng quá sớm, dẫn đến hiện tượng rung lắc không cần thiết. Việc sử dụng chính xác dấu thời gian trình bày khung hình là một việc khá 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 giật 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 bội số của 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ốc độ 60 Hz và thiết bị có thể chuyển màn hình sang tốc độ 120 Hz. Một lý do có thể gây ra tình trạng này là do một ứng dụng khác có một nền tảng với chế độ cài đặt tốc độ khung hình là 24 Hz. Trong trường hợp đó, việc chạy màn hình ở tần số 120Hz sẽ cho phép cả bề mặt 60Hz và bề mặt 24Hz chạy mà không cần giảm tốc độ.

Khi màn hình chạy ở tốc độ khung hình gấp nhiều lần tốc độ khung hình của ứng dụng, ứng dụng phải chỉ định dấu thời gian trình chiếu cho mỗi khung hình để tránh hiện tượng giật hình 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 để các ứng dụng cho biết tốc độ khung hình của mình với 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 chế độ hiển thị khác, chẳng hạn như độ phân giải màn hình. Nhìn 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 trong danh sách chế độ hiển thị để tìm một chế độ có tốc độ khung hình cụ thể.

setFrameRate() giúp nền tảng có thêm cơ hội chọn tốc độ khung hình tương thích trong trường hợp có nhiều nền tảng đang chạy ở các tốc độ khung hình khác nhau. Ví dụ: hãy xem xét trường hợp 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 24 Hz và ứng dụng còn lại đang cho người dùng thấy một danh sách có thể di chuyể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, bề mặt video buộc phải chọn 60Hz hoặc 90Hz. Bằng cách gọi setFrameRate() với 24 Hz, giao diện video sẽ cung cấp cho nền tảng nhiều thông tin hơn về tốc độ khung hình của video nguồn, cho phép nền tảng chọn 90 Hz cho tốc độ làm mới màn hình, tốt hơn 60 Hz 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ư:

  • Nếu ứng dụng muốn thay đổi độ phân giải hoặc các chế độ cài đặt chế độ hiển thị khác, hãy dùng preferredDisplayModeId.
  • Nền tảng sẽ chỉ chuyển đổi chế độ hiển thị để phản hồi lệnh gọi đến setFrameRate() nếu chế độ chuyển đổi có dung lượng nhẹ và người dùng khó 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 xử lý được màn hình chạy ở tốc độ khung hình gấp nhiều lần tốc độ khung hình của ứng dụng (cần đặt dấu thời gian trình chiếu 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ổ của ứng dụng và tốc độ này áp dụng cho tất cả các thành phần 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 mà thiết bị hỗ trợ, tương tự như setFrameRate(), để đưa ra cho trình lập lịch một 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 những 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 các ứng dụng chỉ muốn thay đổi tốc độ làm mới ưu tiên, thì 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 tài nguyên về hiệu suất, nhưng các ứng dụng nên tránh gọi setFrameRate() mỗi khung hình hoặc nhiều lần mỗi giây. Các lệnh gọi đến setFrameRate() có thể dẫn đến thay đổi tốc độ làm mới màn hình, điều này có thể dẫn đến hiện tượng giảm khung hình trong quá trình chuyển đổi. Bạn nên xác định tốc độ khung hình chính xác trước thời gian và gọi setFrameRate() một lần.

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

Mặc dù video là trường hợp sử dụng chính cho API setFrameRate(), nhưng bạn có thể dùng API này cho các ứng dụng khác. Ví dụ: một trò chơi không có ý định chạy cao hơn 60 Hz (để giảm mức tiêu thụ điện năng và đạt được thời gian 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 độ 90Hz theo mặc định sẽ chạy ở tốc độ 60Hz trong khi trò chơi đang hoạt động. Điều này sẽ tránh được hiện tượng giật hình nếu trò chơi chạy ở tốc độ 60Hz trong khi màn hình chạy ở tốc độ 90Hz.

Sử dụng FRAME_RATE_COMPATIBILITY_FIXED_SOURCE

FRAME_RATE_COMPATIBILITY_FIXED_SOURCE chỉ dành cho các ứng dụng video. Đối với mục đích sử dụng không phải video, hãy 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(fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS), trong đó fps là tốc độ khung hình của video.
  • Bạn không nên để các ứng dụng gọi setFrameRate() bằng CHANGE_FRAME_RATE_ALWAYS khi dự kiến thời gian phát video kéo dài vài phút hoặc ít hơn.

Ví dụ về việc tích hợp cho các ứng dụng phát video

Bạn nên làm theo các bước sau để tích hợp các chế độ chuyển đổi tốc độ làm mới trong ứ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ư phim, hãy dùng MATCH_CONTENT_FRAMERATE_ALWAYS
    2. Nếu phát một video ngắn, chẳng hạn như đoạn giới thiệu phim, hãy dùng CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
  2. Nếu changeFrameRateStrategyCHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, hãy chuyển sang bước 4.
  3. Phát hiện xem có sắp xảy ra trường hợp chuyển đổi tốc độ làm mới không liền mạch hay không bằng cách kiểm tra xem cả hai điều kiện sau có đúng hay khô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 (gọi là C) sang tốc độ khung hình của video (gọi là V). Điều 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 sử dụng các thay đổi về tốc độ làm mới không liền mạch. 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 bạn muốn chuyển đổi liền mạch, hãy làm như sau:
    1. Gọi setFrameRate và truyền fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCEchangeFrameRateStrategy vào, trong đó fps là tốc độ khung hình của video.
    2. Bắt đầu phát video
  5. Nếu sắp có một thay đổi chế độ không liền mạch, hãy làm như sau:
    1. Hiện UX để thông báo cho người dùng. Xin lưu ý rằng bạn nên triển khai một cách để người dùng loại bỏ UX này và bỏ qua độ trễ bổ sung ở bước 5.d. Điều này là do độ trễ mà chúng tôi đề xuất lớn hơn mức cần thiết trên những màn hình có thời gian chuyển đổi nhanh hơn.
    2. Gọi setFrameRate và truyền fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCECHANGE_FRAME_RATE_ALWAYS vào, trong đó fps là tốc độ khung hình của video.
    3. Đợi lệnh gọi lại onDisplayChanged.
    4. Chờ 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ợ 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ợ chuyển đổi liền mạch và không liền mạch như mô tả ở trên có dạng như sau:

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