畫面更新率 API 可讓應用程式告知 Android 平台預期的影格速率,且適用於目標為 Android 11 (API 級別 30) 以上版本的應用程式。傳統上,大多數裝置只支援單一螢幕刷新率,通常為 60 Hz,但這項情況已開始改變。許多裝置現在支援額外的刷新率,例如 90Hz 或 120Hz。部分裝置支援無縫切換刷新率,其他裝置則會短暫顯示黑色畫面,通常持續一秒。
API 的主要用途是讓應用程式能更有效地利用所有支援的螢幕更新率。舉例來說,如果應用程式播放的 24Hz 影片會呼叫 setFrameRate()
,裝置可能會將顯示器的螢幕刷新率從 60Hz 變更為 120Hz。這項新刷新率可讓 24 Hz 影片流暢播放,且不會出現抖動,也不需要 3:2 的下拉式縮放,因為在 60 Hz 螢幕上播放相同影片時,需要使用這項技術。這樣即可提供更優質的使用者體驗。
基本用法
Android 提供多種方式存取及控制介面,因此 setFrameRate()
API 有幾個版本。每個 API 版本都會使用相同的參數,並以相同方式運作:
Surface.setFrameRate()
SurfaceControl.Transaction.setFrameRate()
ANativeWindow_setFrameRate()
ASurfaceTransaction_setFrameRate()
應用程式不需要考量實際支援的螢幕更新率,只要呼叫 Display.getSupportedModes()
即可取得,以便安全地呼叫 setFrameRate()
。舉例來說,即使裝置僅支援 60Hz,也可以使用應用程式偏好的畫面更新率呼叫 setFrameRate()
。如果裝置與應用程式的影格速率不相符,則會維持目前的顯示器刷新率。
如要查看呼叫 setFrameRate()
是否會導致顯示器更新率變更,請呼叫 DisplayManager.registerDisplayListener()
或 AChoreographer_registerRefreshRateCallback()
,註冊顯示器變更通知。
呼叫 setFrameRate()
時,建議您傳遞完整的影格速率,而不要四捨五入為整數。舉例來說,在以 29.97Hz 錄製的影片中,請傳入 29.97,而非四捨五入為 30。
針對影片應用程式,傳遞至 setFrameRate()
的相容性參數應設為 Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
,以便向 Android 平台提供額外提示,指出應用程式會使用下拉功能,以便適應不相符的螢幕更新率 (這會導致畫面抖動)。
在某些情況下,影片介面會停止提交影格,但在螢幕上仍會持續顯示一段時間。常見的情況包括影片播放結束時或使用者暫停播放。在這種情況下,請將影格速率參數設為 0,並呼叫 setFrameRate()
,藉此清除表面的幀率設定,並將其還原為預設值。在銷毀途徑時,或當使用者切換至其他應用程式而導致途徑隱藏時,就不需要清除這類影格速率設定。只有在途徑仍可見但未使用時,才需要清除影格速率設定。
非流暢的畫格速率切換
在某些裝置上,切換刷新率可能會造成視覺中斷,例如畫面會黑一兩秒。這種情況通常發生在機上盒、電視面板和類似裝置上。根據預設,Android 架構不會在呼叫 Surface.setFrameRate()
API 時切換模式,以避免發生這類視覺中斷情形。
部分使用者偏好在較長影片的開頭和結尾加入視覺中斷畫面。這樣一來,螢幕的刷新率就能與影片影格速率相符,並避免影格速率轉換產生的瑕疵,例如在播放電影時出現 3:2 的下拉鋸齒。
因此,如果使用者和應用程式都選擇加入,即可啟用非無縫的螢幕更新率切換功能:
- 使用者:如要啟用這項功能,使用者可以啟用「Match content frame rate」使用者設定。
- 應用程式:如要選擇加入,應用程式可以將
CHANGE_FRAME_RATE_ALWAYS
傳遞至setFrameRate()
。
如果是電影等長期執行的影片,建議您一律使用 CHANGE_FRAME_RATE_ALWAYS
。這是因為比起變更更新率時造成的中斷,比對影片影格速率的好處更為重要。
其他建議
請參考以下常見情境建議。
多個表面
Android 平台可正確處理有多個介面且設定的幀率不同的情境。如果應用程式有多個具有不同影格速率的介面,請為每個介面以正確的影格速率呼叫 setFrameRate()
。即使裝置同時執行多個應用程式,使用分割畫面或子母畫面模式,每個應用程式都能安全地為自己的途徑呼叫 setFrameRate()
。
平台未變更為應用程式的影格速率
即使裝置支援應用程式在呼叫 setFrameRate()
時指定的影格速率,裝置仍可能不會將螢幕切換至該刷新率。舉例來說,優先順序較高的途徑可能會有不同的影格速率設定,或是裝置可能處於省電模式 (針對顯示器的螢幕更新率設定限制,以便節省電力)。即使裝置在正常情況下會切換顯示器更新率,應用程式仍必須在裝置未將顯示器更新率切換為應用程式的影格速率設定時正常運作。
應用程式螢幕刷新率與應用程式畫面更新率不符時,應決定如何回應。影片的畫面更新率固定為來源影片的影格速率,也必須使用下拉式選單來顯示影片內容。遊戲可能會選擇以螢幕更新率執行,而不是維持偏好的影格速率。應用程式不應根據平台的運作方式,變更傳遞至 setFrameRate()
的值。無論應用程式如何處理平台未調整以符合應用程式要求的情況,都應將其設為應用程式偏好的影格速率。這樣一來,如果裝置條件有所變更,允許使用其他螢幕刷新率,平台就能取得正確的資訊,切換到應用程式的偏好畫面更新率。
如果應用程式不會或無法以顯示器更新率執行,則應使用平台的其中一個機制來設定呈現時間戳記,為每個影格指定呈現時間戳記:
使用這些時間戳記可避免平台過早顯示應用程式影格,進而導致不必要的鋸齒現象。正確使用影格呈現時間戳記有點棘手。如要進一步瞭解如何避免卡頓,請參閱我們的影格間隔指南,並考慮使用 Android Frame Pacing 程式庫。
在某些情況下,平台可能會切換到應用程式在 setFrameRate()
中指定的影格速率的倍數。舉例來說,應用程式可能會以 60Hz 呼叫 setFrameRate()
,而裝置可能會將螢幕切換為 120Hz。出現這種情況的其中一個可能原因是,如果其他應用程式的途徑採用 24 Hz 的畫面更新率,在這種情況下,以 120Hz 執行螢幕時,60Hz 途徑和 24Hz 途徑都能執行,且不需要下拉。
如果螢幕以應用程式影格速率的倍數執行,應用程式應為每個影格指定呈現時間戳記,以免產生不必要的鋸齒。對於遊戲而言,Android Frame Pacing 程式庫可協助正確設定影格顯示時間戳記。
setFrameRate() 與 preferredDisplayModeId
WindowManager.LayoutParams.preferredDisplayModeId
是應用程式向平台指出影格速率的另一種方式。有些應用程式只想變更螢幕更新率,而非變更其他顯示模式設定,例如螢幕解析度。一般來說,請使用 setFrameRate()
而非 preferredDisplayModeId
。setFrameRate()
函式更容易使用,因為應用程式不必搜尋顯示模式清單,即可找到具有特定影格速率的模式。
如果有多個介面以不同的影格速率執行,setFrameRate()
可讓平台有更多機會選擇相容的影格速率。舉例來說,假設有兩個應用程式在 Pixel 4 上以分割畫面模式執行,其中一個應用程式正在播放 24Hz 影片,另一個應用程式向使用者顯示可捲動的清單。Pixel 4 支援兩種螢幕刷新率:60 Hz 和 90 Hz。使用 preferredDisplayModeId
API 時,影片介面會強制選擇 60Hz 或 90Hz。透過以 24 Hz 呼叫 setFrameRate()
,影片介面會向平台提供更多來源影片影格速率的資訊,讓平台選擇 90 Hz 做為顯示刷新率,這在這種情況下比 60 Hz 更為理想。
不過,在某些情況下,應使用 preferredDisplayModeId
而非 setFrameRate()
,例如:
- 如果應用程式想要變更解析度或其他顯示模式設定,請使用
preferredDisplayModeId
。 - 如果模式切換功能輕量且不太可能讓使用者察覺,平台只會在回應對
setFrameRate()
的呼叫時切換顯示模式。如果應用程式偏好切換螢幕刷新率 (即使需要進行重型模式切換 (例如在 Android TV 裝置) 上),請使用preferredDisplayModeId
。 - 如果應用程式無法處理以應用程式影格速率的倍數執行的畫面 (這需要為每個影格設定顯示時間戳記),則應使用
preferredDisplayModeId
。
setFrameRate() 與 preferredRefreshRate
WindowManager.LayoutParams#preferredRefreshRate
會在應用程式視窗設定偏好的畫面更新率,且該速率會套用至視窗內的所有介面。無論裝置支援的刷新率為何 (與 setFrameRate()
類似),應用程式都應指定偏好的影格速率,讓排程器更能提示應用程式的預期影格速率。
對於使用 setFrameRate()
的介面,系統會忽略 preferredRefreshRate
。一般來說,請盡可能使用 setFrameRate()
。
preferredRefreshRate 與 preferredDisplayModeId
如果應用程式只想變更偏好的刷新率,建議使用 preferredRefreshRate
,而不是 preferredDisplayModeId
。
避免過於頻繁地呼叫 setFrameRate()
雖然 setFrameRate()
呼叫在效能方面並不會造成太大負擔,但應用程式應避免在每個影格或每秒多次呼叫 setFrameRate()
。呼叫 setFrameRate()
可能會導致顯示器更新率發生變化,進而導致轉換期間影格遺失。您應事先找出正確的畫面更新率,並呼叫 setFrameRate()
一次。
用於遊戲或其他非影片應用程式
雖然影片是 setFrameRate()
API 的主要用途,但也可以用於其他應用程式。舉例來說,如果遊戲意圖不要執行超過 60 Hz (為了減少耗電量並延長遊戲工作階段時間),可以呼叫 Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT)
。如此一來,預設以 90Hz 運作的裝置會在遊戲運作時以 60Hz 運作,避免遊戲以 60Hz 運作,而螢幕以 90Hz 運作時,會發生的畫面抖動現象。
使用 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)
,其中 fps 是影片的畫面更新率。 - 如果您預期影片播放時間為數分鐘或更短,強烈建議應用程式不要使用
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();
}