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ề khung hình dự kiến và có sẵn trên những ứng dụng nhắm đến Android 11 (API cấp 30) trở lên. Thông 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, thường là 60 Hz, nhưng điều này đã thay đổi. Nhiều thiết bị hiện hỗ trợ thêm tốc độ làm mới, chẳng hạn như 90 Hz hoặc 120 Hz. Một số thiết bị hỗ trợ tốc độ làm mới liền mạch công tắc, trong khi một số khác hiện màn hình đen một cách nhanh chóng, thường kéo dài một giây.

Mục đích chính của API là cho phé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 việc gọi setFrameRate() có thể khiến thiết bị thay đổi màn hình tốc độ làm mới từ 60Hz đến 120Hz. Tốc độ làm mới mới này mang lại trải nghiệm mượt mà phát lại video 24Hz mà không bị giật, không cần kéo xuống 3:2 như có thể cần thiết để phát cùng một video trên màn hình 60 Hz. Nhờ đó, người dùng có trải nghiệm tốt hơn của bạn.

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

Android đưa ra một số cách để truy cập và điều khiển 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 của API sẽ lấy cùng tham số và hoạt động giống như các tham số 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ể được lấy 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 ưu tiên. Các thiết bị không có tốc độ khung hình phù hợp hơn với ứ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 làm mới màn hình hay không gọi, đăng ký nhận thông báo về việc thay đổi hiển thị 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ì so với làm tròn đến một số nguyên. Ví dụ: khi kết xuất một video được quay ở 29,97Hz, 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 phải đặt thông số tương thích được truyền đến setFrameRate() cho Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE để cung cấp thêm gợi ý cho nền tảng Android mà ứng dụng sẽ sử dụng tốc độ làm mới hiển thị (dẫn đến độ 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ẽ giữ nguyê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 phát lại đến cuối video hoặc khi người dùng tạm dừng phát. Trong những trường hợp này, gọi setFrameRate() với thông số tốc độ khung hình được đặt thành 0 để xoá dữ liệu của nền tảng đặt tốc độ khung hình về giá trị mặc định. Đang xoá chế độ cài đặt tốc độ khung hình như thế là không cần thiết khi phá 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. Xoá tốc độ khung hình chỉ được cài đặt khi bề mặt 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ể gây gián đoạn hình ảnh, chẳng hạn như hình ảnh bị đen trên màn hình trong một hoặc hai 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 đ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 giữa các chế độ khi Surface.setFrameRate() API được gọi để tránh tình trạng gián đoạn hình ảnh như vậy.

Một số người dùng muốn hiển thị gián đoạn ở phần đầu và phần cuối của những video dài hơn. Đ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 cấu phần phần mềm chuyển đổi tốc độ khung hình như 3:2 kéo xuống để phát lại phim.

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

Bạn nên luôn sử dụng CHANGE_FRAME_RATE_ALWAYS cho các video dài hạn như phim. Điều này là do lợi ích của việc so khớp tốc độ khung hình video lớn hơn 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 trong 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 có 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 có các tốc độ khung hình khác nhau, hãy gọi setFrameRate() với tốc độ khung hình cho từng nền tảng. Ngay cả khi thiết bị đang chạy nhiều ứng dụng tại bằ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() cho nền tảng của riêng họ.

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() thân mến, có những trường hợp mà thiết bị không chuyển màn hình sang tốc độ làm mới đó. Ví dụ: một giao diện có mức độ ưu tiên cao hơn có thể có cài đặt tốc độ khung hình hoặc thiết bị có thể ở chế độ tiết kiệm pin (đặt 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 cài đặt tốc độ khung hình của ứng dụng, ngay cả khi thiết bị chuyển đổi ở chế độ bình thường trong trường hợp cụ thể.

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

Khi bạn sử dụng những dấu thời gian này, nền tảng cũng sẽ ngừng hiển thị khung ứng dụng sớm, điều này sẽ dẫn đến những bước nhảy không cần thiết. Cách sử dụng khung hình chính xác dấu thời gian trình bày hơi phức tạp. Đối với trò chơi, hãy xem hướng dẫn về tốc độ khung hình để biết thêm thông tin về cách tránh rung lắc và cân nhắc việ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 được chỉ định trong setFrameRate(). Ví dụ: một ứng dụng có thể gọi setFrameRate() 60Hz và thiết bị có thể chuyển màn hình sang 120Hz. Một lý do khiến điều này có thể xảy ra là khi một ứng dụng khác sử dụng nền tảng có cài đặt tốc độ khung hình là 24Hz. Trong trong trường hợp đó, chạy màn hình ở 120Hz sẽ cho phép cả bề mặt 60Hz và Bề mặt 24Hz để chạy mà không cần kéo xuống.

Khi màn hình đang chạy ở mức bội số của 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 để tránh trường hợp không cần thiết ngốc nghếch. Đối với trò chơi, thư viện Android Frame Pacing (Tốc độ khung hình Android) rất hữu ích khi đặt dấu thời gian trình chiếu khung.

setFrameRate() so với PreferredDisplayModeId

WindowManager.LayoutParams.preferredDisplayModeId là một cách khác giúp ứng dụng cho nền tảng biết tốc độ khung hình. Hơi nhiều các ứng dụng chỉ muốn thay đổi tốc độ làm mới màn hình hơn là thay đổi cài đặt 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. setFrameRate() giúp sử dụng dễ 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 chế độ có tốc độ khung hình cụ thể.

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

Tuy nhiên, có những trường hợp mà bạn nên 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 khác về chế độ hiển thị, sử 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 chú ý với người dùng. Nếu ứng dụng muốn chuyển đổi chế độ làm mới màn hình chuyển đổi ngay cả khi cần chuyển sang chế độ cao (ví dụ: trên Android TV thiết bị), sử dụng preferredDisplayModeId.
  • Ứng dụng không xử lý được màn hình chạy tại nhiều khung hình của ứng dụng tốc độ, vốn yêu cầu phải đặt dấu thời gian trình chiếu trên mỗi khung hình, 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 độ đó có thể áp dụng vào mọi bề mặt trong cửa sổ. Ứng dụng phải chỉ định lựa chọn ưu tiên tốc độ khung hình bất kể tốc độ làm mới được hỗ trợ của thiết bị là gì, tương tự như setFrameRate(), để cung cấp cho trình lập lịch biểu rõ hơn về ý định của ứng dụng tốc độ khung hình.

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

PreferredRefreshRate so với PreferredDisplayModeId

Nếu ứng dụng chỉ muốn thay đổi tốc độ làm mới ưu tiên, ứng dụng sẽ được ưu tiê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 chi phí về hiệu suất, 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 đối với tốc độ làm mới màn hình, có thể dẫn đến hiện tượng rớt khung hình trong quá trình chuyển đổi. Bạn nên tìm ra tốc độ khung hình chính xác trước và gọi setFrameRate() một lần.

Việc sử dụng 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 có thể dùng 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 sử dụng pin và đạt được các phiên phát lâu hơn) có thể gọi Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT). Trong phần này nói cách khác, một thiết bị chạy ở tốc độ 90 Hz theo mặc định sẽ chạy ở 60 Hz trong khi trò chơi đang hoạt động, điều này sẽ giúp tránh được sự rung chuyển thường xảy ra nếu trò chơi chạy ở 60 Hz trong khi màn hình chạy ở 90 Hz.

Sử dụng FRAME_RATE_COMPATIBILITY_FIXED_SOURCE

FRAME_RATE_COMPATIBILITY_FIXED_SOURCE chỉ dành cho các ứng dụng video. Cho không phải video, vui lòng 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 hạn như phim, 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 dùng các ứng dụng gọi setFrameRate() bằng CHANGE_FRAME_RATE_ALWAYS khi bạn muốn video phát lại kéo dài vài phút trở xuống.

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

Bạn nên thực hiện các bước sau đây để tích hợp công tắc tốc độ làm mới trong các ứ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 sử dụng MATCH_CONTENT_FRAMERATE_ALWAYS
    2. Nếu phát một video ngắn như đoạn giới thiệu chuyển động, hãy sử 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 quá trình chuyển đổi tốc độ làm mới không liền mạch có sắp xảy ra hay không bằng cách kiểm tra cả hai điều trên đều đúng:
    1. 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) thành tốc độ khung hình của video (gọi là V). Thao tác này sẽ là trường hợp 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 thay đổi 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 trả về MATCH_CONTENT_FRAMERATE_ALWAYS
  4. Nếu quá trình chuyển đổi sẽ diễn ra liền mạch, hãy làm như sau:
    1. Gọi setFrameRate rồi truyền fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, và changeFrameRateStrategy, trong đó fps là tốc độ khung hình của video.
    2. Bắt đầu phát video
  5. Nếu chế độ thay đổi không liền mạch sắp xảy ra, hãy làm như sau:
    1. Hãy hiện trải nghiệm người dùng để 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 cho người dùng bỏ qua trải nghiệm người dùng này và bỏ qua độ trễ bổ sung trong bước 5.d. Đây là vì chúng tôi đề xuất độ trễ lớn hơn mức cần thiết trên các màn hình cho thấy thời gian chuyển đổi nhanh hơn.
    2. Gọi setFrameRate rồi truyền fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, và CHANGE_FRAME_RATE_ALWAYS, trong đó fps là tốc độ khung hình của video.
    3. Đợ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

Mã giả để chỉ hỗ trợ chuyển đổi liền mạch như sau:

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