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 của chúng và có sẵn trên các ứng dụng nhắm đến Android 11 (cấp độ API 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, thường là 60 Hz, nhưng điều này đang thay đổi. Hiện tại, nhiều thiết bị hỗ trợ thêm các tốc độ làm mới như 90 Hz hoặc 120 Hz. Một số thiết bị hỗ trợ chuyển đổi tốc độ làm mới liền mạch, trong khi các thiết bị khác 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 là cho phép các ứng dụng tận dụng tốt hơn tất cả cá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 cho phép phát video 24 Hz mượt mà, không bị giật mà không cần kéo xuống 3:2 như khi phát cùng một video trên màn hình 60 Hz. 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 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 đều có cùng các tham số và hoạt động giống như các phiên bản khác:
Surface.setFrameRate()SurfaceControl.Transaction.setFrameRate()ANativeWindow_setFrameRate()ASurfaceTransaction_setFrameRate()
Ứ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 ưu tiên.
Các thiết bị không có tốc độ khung hình phù hợp hơn cho ứng dụng sẽ giữ nguyên tốc độ làm mới màn hình hiện tại.
Để xem liệu việc gọi setFrameRate() có dẫn đến thay đổi 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(), tốt nhất là bạn nên truyền 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 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ố khả năng tương thích được truyền đến setFrameRate() phải được đặt thành Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE để đưa ra gợi ý bổ sung cho nền tảng Android rằng ứng dụng sẽ 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).
Trong một số trường hợp, bề mặt 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 quá trình phát đạt đế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, 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ề 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ỷ nền tảng hoặc khi nền tảng 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 nền tảng 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 hiện tượng gián đoạn hình ảnh như màn hình đen trong một hoặc hai giây. Điều này thường xảy ra trên các hộp giải mã tín hiệu truyền hình, 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 hiện tượng gián đoạn hình ảnh như vậy.
Một số người dùng thích hiện tượng 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 khớp với tốc độ khung hình của video và tránh các hiện tượng giả tạo do chuyển đổi tốc độ khung hình như hiện tượng giật khi kéo xuống 3:2 để phát phim.
Vì lý do này, bạn có thể bật tính năng 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 tham gia:
- Người dùng: Để chọn tham gia, người dùng có thể bật chế độ cài đặt người dùng Khớp tốc độ khung hình nội dung.
- Ứng dụng: Để chọn tham gia, ứng dụng có thể truyền
CHANGE_FRAME_RATE_ALWAYSvàosetFrameRate().
Bạn nên luôn sử dụng CHANGE_FRAME_RATE_ALWAYS
cho các video chạy dài như phim. Lý do là vì lợi ích của việc khớp tốc độ khung hình của video lớn hơn hiện tượng 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 này 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 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 nền tảng với các 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 (sử dụng 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 mình.
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 lệnh gọi đến setFrameRate(), vẫn có những trường hợp thiết bị sẽ không chuyển đổi 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ế 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 đổi tốc độ làm mới màn hình thành 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 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 độ khung hình của video nguồn và bạn phải kéo xuống để hiển thị nội dung video. Thay vào đó, một trò chơi có thể chọn chạy ở tốc độ làm mới màn hình thay vì giữ nguyên 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 những gì nền tảng thực hiện. Ứng dụng phải giữ nguyên 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 ưu tiên của ứng dụng.
Trong trường hợp ứng dụng không chạy 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 hiển thị cho mỗi khung hình bằng một trong các cơ chế của nền tảng để đặt dấu thời gian hiển thị:
Việc sử dụng các dấu thời gian này sẽ ngăn nền tảng hiển thị khung hình ứng dụng quá sớm, dẫn đến hiện tượng giật không cần thiết. Việc sử dụng chính xác dấu thời gian hiển thị khung hình 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 hiện tượng giật và cân nhắc sử dụng thư viện Tốc độ khung hình Android.
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 60 Hz và thiết bị có thể chuyển đổi màn hình sang 120 Hz. Một lý do khiến điều này có thể xảy ra là nếu một ứng dụng khác có 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ốc độ 120 Hz sẽ cho phép cả nền tảng 60 Hz và nền tảng 24 Hz chạy mà không cần kéo xuống.
Khi màn hình đang chạy ở 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 hiển thị cho mỗi 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 Tốc độ khung hình Android rất hữu ích để đặt chính xác dấu thời gian hiển thị khung hình.
setFrameRate() so với preferredDisplayModeId
WindowManager.LayoutParams.preferredDisplayModeId
là một cách khác để ứng dụng có thể cho nền tảng biết tốc độ khung hình của chú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. 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 chế độ có tốc độ khung hình cụ thể.
setFrameRate() mang đến cho nền tảng nhiều cơ hội hơn để chọn tốc độ khung hình tương thích trong các trường hợp có nhiều bề mặt đ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 hiển thị cho người dùng 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 60 Hz hoặc 90 Hz. Bằng cách gọi setFrameRate() với 24 Hz, nền tảng video cung cấp cho nền tảng thêm thông tin 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ư sau:
- 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 sử 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 việc chuyển đổi chế độ này 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 yêu cầu chuyển đổi chế độ nặng (ví dụ: trên thiết bị Android TV), hãy sử dụngpreferredDisplayModeId. - Các ứng dụng không xử lý được màn hình chạy ở bội số của tốc độ khung hình của ứng dụng (yêu cầu đặt dấu thời gian hiển thị 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 nền tảng trong cửa sổ. Ứng dụng phải chỉ định tốc độ khung hình ưu tiên của mình bất kể tốc độ làm mới được thiết bị hỗ trợ, 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 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 ứ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 kém nhiều về 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 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 rớt 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.
Cách 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ể sử 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 sử dụng pin 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 độ 90 Hz theo mặc định sẽ chạy ở tốc độ 60 Hz trong khi trò chơi đang hoạt động, điều này sẽ tránh được hiện tượng giật nếu trò chơi chạy ở tốc độ 60 Hz trong khi màn hình chạy ở tốc độ 90 Hz.
Cách 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 việc sử dụng 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
- Bạn nên yêu cầu các ứng dụng, khi hiển thị các video chạy dài 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 yêu cầu các ứng dụng gọi
setFrameRate()bằngCHANGE_FRAME_RATE_ALWAYSkhi bạn dự kiến quá trình phát video sẽ kéo dài vài phút trở xuống.
Ví dụ về tích hợp cho các ứng dụng phát video
Bạn nên thực hiện các bước sau để tích hợp tính năng chuyển đổi tốc độ làm mới trong các ứng dụng phát video:
- Quyết định
changeFrameRateStrategy:- Nếu phát video chạy dài như phim, hãy sử dụng
MATCH_CONTENT_FRAMERATE_ALWAYS - Nếu phát video ngắn như đoạn giới thiệu phim, hãy sử dụng
CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
- Nếu phát video chạy dài như phim, hãy sử dụng
- Nếu
changeFrameRateStrategylàCHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, hãy chuyển đến bước 4. - Phát hiện xem có sắp xảy ra hiện tượng 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 sự kiện này có đúng hay không:
- 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). Trường hợp này sẽ
xảy ra nếu C và V khác nhau và
Display.getMode().getAlternativeRefreshRateskhông chứa bội số của V. - Người dùng đã chọn tham gia 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.getMatchContentFrameRateUserPreferencecó trả vềMATCH_CONTENT_FRAMERATE_ALWAYShay không
- 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). Trường hợp này sẽ
xảy ra nếu C và V khác nhau và
- Nếu quá trình chuyển đổi sẽ liền mạch, hãy làm như sau:
- Gọi
setFrameRatevà truyềnfps,FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, vàchangeFrameRateStrategy, trong đófpslà tốc độ khung hình của video. - Bắt đầu phát video
- Gọi
- Nếu sắp xảy ra hiện tượng thay đổi chế độ không liền mạch, hãy làm như sau:
- Hiển thị UX để thông báo cho người dùng. Xin lưu ý rằng bạn nên triển khai cách để người dùng đóng UX này và bỏ qua độ trễ bổ sung trong bước 5.d. Lý do là vì độ trễ mà chúng tôi đề xuất 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.
- Gọi
setFrameRatevà truyềnfps,FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, vàCHANGE_FRAME_RATE_ALWAYS, trong đófpslà tốc độ khung hình của video. - Chờ lệnh gọi lại
onDisplayChanged. - Chờ 2 giây để hoàn tất quá trình chuyển đổi chế độ.
- 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();
}