影格速率 API 可讓應用程式向 Android 平台通知預期的影格速率,並顯示在目標版本為 Android 11 (API 級別 30) 以上版本的應用程式中。傳統上,大多數裝置僅支援單一螢幕刷新率 (通常為 60 Hz),但我們已能改變這一點。許多裝置現在都支援額外的刷新率,例如 90Hz 或 120Hz。部分裝置支援流暢切換刷新率,其他裝置則會短暫顯示黑色畫面,通常會持續一秒。
API 的主要用途,是讓應用程式能充分利用所有支援的螢幕刷新率。舉例來說,如果應用程式播放的 24Hz 影片呼叫 setFrameRate()
,裝置可能會導致螢幕刷新率從 60Hz 變更為 120Hz。新的刷新率可讓您流暢地播放 24Hz 影片,無須使用 3:2 下拉式選單,就像在 60Hz 螢幕上播放同一部影片時需要此畫面。這樣可以提供更優質的使用者體驗。
基本用法
Android 提供幾種存取及控制途徑的方式,因此 setFrameRate()
API 有幾種版本。每個 API 版本採用的參數相同,運作方式與其他版本相同:
Surface.setFrameRate()
SurfaceControl.Transaction.setFrameRate()
ANativeWindow_setFrameRate()
ASurfaceTransaction_setFrameRate()
應用程式不需要考量實際支援的螢幕刷新率,只要呼叫 Display.getSupportedModes()
即可取得,以便安全地呼叫 setFrameRate()
。舉例來說,即使裝置僅支援 60 Hz,請使用應用程式偏好的畫面更新率呼叫 setFrameRate()
。如果裝置不符合應用程式影格速率,就會維持在目前的螢幕刷新率。
如要查看呼叫 setFrameRate()
是否導致螢幕刷新率發生變化,請呼叫 DisplayManager.registerDisplayListener()
或 AChoreographer_registerRefreshRateCallback()
來註冊顯示變更通知。
呼叫 setFrameRate()
時,建議您傳入確切的畫面更新率,而非四捨五入為整數。舉例來說,算繪以 29.97Hz 錄製的影片時,請傳入 29.97,而不要四捨五入至 30。
如果是影片應用程式,傳遞至 setFrameRate()
的相容性參數應設為 Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
,為 Android 平台提供額外提示,應用程式會使用下拉功能,以配合不符合的螢幕刷新率 (這會導致看似不匹配)。
在某些情況下,影片途徑會停止提交影格,但會在螢幕上持續顯示一段時間。常見情況包括播放到影片結尾或使用者暫停播放時。在這種情況下,您可以呼叫 setFrameRate()
,並將影格速率參數設為 0,將途徑的影格速率設定清除回預設值。刪除介面或因使用者切換至其他應用程式而隱藏時,不必清除這類畫面更新率設定。只有在途徑保持可見,且未使用的情況下,才能清除影格速率設定。
非流暢影格速率切換鈕
在某些裝置上,切換刷新率可能會導致視覺幹擾 (例如黑色畫面一到兩秒),這通常發生在機上盒、電視面板和類似裝置上。根據預設,Android 架構不會在呼叫 Surface.setFrameRate()
API 時切換模式,以免出現這類視覺中斷情形。
部分使用者偏好在長片的開頭和結尾處使用視覺幹擾元素。如此一來,螢幕的刷新率就能符合影片影格速率,並避免在播放電影時,避免產生畫面更新率的轉換失真,例如 3:2 的提取跳線。
因此,只要使用者和應用程式都選擇採用,即可啟用不流暢的重新整理頻率切換功能:
- 使用者:如要選擇加入,使用者可以啟用「採用內容影格速率」使用者設定。
- 應用程式:如要選擇加入,應用程式可以將
CHANGE_FRAME_RATE_ALWAYS
傳遞至setFrameRate()
。
如果是電影等長時間執行的影片,建議您一律使用 CHANGE_FRAME_RATE_ALWAYS
。這是因為比對影片影格速率的好處,高於變更刷新率時發生的中斷情形。
其他建議
請針對常見情境採用以下建議。
多個介面
如果多個途徑採用不同的影格速率設定,Android 平台就能妥善處理這類情境。如果應用程式有多個畫面更新率不同的介面,請呼叫 setFrameRate()
,並提供每個途徑的正確影格速率。即使裝置會同時執行多個應用程式,只要使用分割畫面或子母畫面模式,每個應用程式都能安全地為自身的介面呼叫 setFrameRate()
。
平台不會變更為應用程式的影格速率
即使裝置支援應用程式在呼叫 setFrameRate()
時指定的影格速率,但在某些情況下,裝置不會將顯示畫面切換為該刷新率。舉例來說,優先順序較高的介面可能會有不同的影格速率設定,或是裝置可能處於省電模式 (設定螢幕刷新率的限制來節省電力)。即使裝置未在正常情況下切換顯示刷新率,應用程式仍須正常運作。
螢幕刷新率與應用程式畫面更新率不符時,應用程式可自行判斷如何回應。以影片來說,畫面更新率固定為來源影片,您必須開啟下拉式選單才能顯示影片內容。遊戲可能會改為嘗試以螢幕刷新率執行,而非繼續使用偏好的畫面更新率。應用程式不應根據平台功能,變更傳送至 setFrameRate()
的值。無論應用程式如何處理平台未配合應用程式的要求進行調整時,系統應一律採用應用程式的偏好影格速率。這樣一來,如果裝置條件變更為允許使用額外的螢幕刷新率,平台便會取得正確的資訊,切換至應用程式的偏好影格速率。
如果應用程式無法或無法以螢幕刷新率執行,應用程式應使用平台的其中一種機制設定顯示時間戳記,為每個影格指定顯示時間戳記:
一旦使用這些時間戳記,平台就無法太早顯示應用程式影格,導致不必要的輪廓。正確使用影格顯示時間戳記並不容易。如果是遊戲,請參閱影格同步指南來進一步瞭解如何避免跳動,並考慮使用 Android Frame Pacing 程式庫。
在某些情況下,平台可能會切換至 setFrameRate()
中指定的應用程式影格速率的倍數。舉例來說,應用程式可以使用 60Hz 呼叫 setFrameRate()
,而裝置可能會將螢幕切換為 120Hz。這可能是因為另一個應用程式的介面,其畫面更新率設為 24Hz。在這種情況下,以 120 Hz 執行螢幕將同時執行 60Hz 表面和 24Hz 表面,而無需下拉。
如果螢幕以應用程式畫面更新率的倍數執行,應用程式應為每個影格指定顯示時間戳記,以避免不必要的判斷。如果是遊戲,Android Frame Pacing 程式庫可正確設定影格顯示時間戳記。
setFrameRate() 與 preferredDisplayModeId
WindowManager.LayoutParams.preferredDisplayModeId
是另一個應用程式向平台表示影格速率的方法。有些應用程式只想變更螢幕刷新率,而不變更其他顯示模式設定,例如螢幕解析度。一般來說,請使用 setFrameRate()
而非 preferredDisplayModeId
。setFrameRate()
函式更容易使用,因為應用程式不需要搜尋顯示模式清單,就能找出具有特定影格速率的模式。
如果有多個途徑以不同的影格速率執行,setFrameRate()
可讓平台有更多機會選擇相容的影格速率。舉例來說,假設在 Pixel 4 上,有兩個應用程式以分割畫面模式執行,其中有一個應用程式播放 24Hz 影片,另一個應用程式顯示了可捲動的清單。Pixel 4 支援兩種螢幕刷新率:60 Hz 和 90Hz。使用 preferredDisplayModeId
API 時,系統會強制選擇 60Hz 或 90Hz 影片介面。透過 24Hz 呼叫 setFrameRate()
可讓平台進一步瞭解來源影片的影格速率,使平台能夠為螢幕刷新率選擇 90Hz,在上述情境中會大於 60 Hz。
但在某些情況下,應該使用 preferredDisplayModeId
而不是 setFrameRate()
,例如:
- 如果應用程式要變更解析度或其他顯示模式設定,請使用
preferredDisplayModeId
。 - 只有在模式切換按鈕較輕重,且使用者不太可能注意到時,平台才會切換顯示模式來回應對
setFrameRate()
的呼叫。如果應用程式偏好切換螢幕刷新率,即使需要頻繁切換裝置 (例如在 Android TV 裝置) 的情況下使用,請使用preferredDisplayModeId
。 - 如果應用程式無法處理以應用程式畫面更新率的倍數執行螢幕,必須為每個影格設定顯示時間戳記,則應使用
preferredDisplayModeId
。
setFrameRate() 與 PreferredRefreshRate 的比較
WindowManager.LayoutParams#preferredRefreshRate
會在應用程式視窗上設定偏好的影格速率,且該速率適用於視窗內的所有介面。無論裝置支援的刷新率為何,應用程式都應指定偏好的影格速率 (與 setFrameRate()
類似),為排程器提供更多有關應用程式預期影格速率的提示。
使用 setFrameRate()
的 Surface 會略過 preferredRefreshRate
。請盡可能使用 setFrameRate()
。
PreferredRefreshRate 與 preferredDisplayModeId
如果應用程式只想變更偏好的重新整理頻率,建議使用 preferredRefreshRate
,而非 preferredDisplayModeId
。
避免太常呼叫 setFrameRate()
雖然 setFrameRate()
呼叫的效能的成本並不高,但應用程式應避免在每個影格呼叫 setFrameRate()
,或每秒呼叫多次。呼叫 setFrameRate()
可能會導致螢幕刷新率改變,而可能會導致畫面在轉換期間遺失。應提前找出正確的影格速率,並呼叫 setFrameRate()
一次。
遊戲或其他非影片應用程式的使用情形
雖然影片是 setFrameRate()
API 的主要用途,但可用於其他應用程式。舉例來說,如果遊戲意圖不高於 60 Hz (為了降低耗電量並達到較長的遊戲工作階段時間),可呼叫 Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT)
。如此一來,在遊戲處於啟用狀態時,預設以 90 Hz 執行的裝置將改為以 60 Hz 執行,避免在螢幕以 90 Hz 執行時,以 60Hz 執行時遇到的波動。
「FRAME_RATE_COMPATIBILITY_FIXED_SOURCE」的使用方式
FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
僅適用於影片應用程式。如果是非影片,請使用 FRAME_RATE_COMPATIBILITY_DEFAULT
。
選擇變更影格速率的策略
- 強烈建議您在應用程式顯示長時間執行的影片 (例如電影) 時呼叫
setFrameRate(
fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS)
,其中每秒影格數為影片畫面更新率。 - 如果希望影片播放持續數分鐘以內,強烈建議不要使用
CHANGE_FRAME_RATE_ALWAYS
呼叫setFrameRate()
的應用程式。
影片播放應用程式整合範例
建議您按照下列步驟,在影片播放應用程式中整合刷新率切換鈕:
- 決定
changeFrameRateStrategy
:- 如要播放長跑影片 (例如電影),請使用
MATCH_CONTENT_FRAMERATE_ALWAYS
。 - 如要播放短片 (例如移動預告片),請使用
CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
。
- 如要播放長跑影片 (例如電影),請使用
- 如果
changeFrameRateStrategy
為CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
,請前往步驟 4。 - 如要偵測是否即將發生無法流暢的刷新率切換情形,請檢查下列兩項資訊:
- 目前的刷新率 (我們稱之為 C) 無法切換影片的畫面更新率 (我們稱之為 V)。如果 C 和 V 不同,且
Display.getMode().getAlternativeRefreshRates
沒有包含多個 V 的倍數,就屬於這種情況。 - 使用者已選擇啟用不流暢的刷新率變更功能。您可以藉由檢查
DisplayManager.getMatchContentFrameRateUserPreference
是否傳回MATCH_CONTENT_FRAMERATE_ALWAYS
來偵測其狀態。
- 目前的刷新率 (我們稱之為 C) 無法切換影片的畫面更新率 (我們稱之為 V)。如果 C 和 V 不同,且
- 如果切換過程順暢,請執行下列操作:
- 呼叫
setFrameRate
並傳遞fps
、FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
和changeFrameRateStrategy
,其中fps
是影片的畫面更新率。 - 開始播放影片
- 呼叫
- 如果即將發生無法流暢模式的變更,請按照下列步驟操作:
- 顯示使用者體驗通知使用者。請注意,建議您實作一種方法,讓使用者關閉此使用者體驗,並略過步驟 5.d 的額外延遲時間。這是因為在螢幕切換時間較短的螢幕上,我們建議的延遲時間大於必要延遲時間。
- 呼叫
setFrameRate
並傳遞fps
、FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
和CHANGE_FRAME_RATE_ALWAYS
,其中fps
是影片的畫面更新率。 - 等待
onDisplayChanged
回呼。 - 等待 2 秒鐘,讓模式切換完成。
- 開始播放影片
虛擬程式碼「只」支援流暢切換,如下所示:
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
transaction.setFrameRate(surfaceControl,
contentFrameRate,
FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
transaction.apply();
beginPlayback();
以下虛擬程式碼支援無流暢切換的虛擬程式碼,如下所示:
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();
}