ה-API של קצב הפריימים מאפשר לאפליקציות ליידע את פלטפורמת Android לגבי קצב הפריימים המיועד שלהן, והוא זמין באפליקציות שמטרגטות ל-Android 11 (רמת API 30) ומעלה. בדרך כלל, רוב המכשירים תומכים רק בקצב רענון מסך אחד, בדרך כלל 60Hz, אבל המצב הזה משתנה. הרבה מכשירים תומכים עכשיו בקצבי רענון נוספים, כמו 90Hz או 120Hz. חלק מהמכשירים תומכים במעברים חלקים בין קצבי רענון, ואילו במכשירים אחרים מוצג מסך שחור לזמן קצר, בדרך כלל למשך שנייה.
המטרה העיקרית של ה-API היא לאפשר לאפליקציות לנצל בצורה טובה יותר את כל קצבי רענון התצוגה הנתמכים. לדוגמה, אם אפליקציה מפעילה סרטון בקצב פריימים של 24 Hz וקוראת ל-setFrameRate()
, יכול להיות שהמכשיר ישנה את קצב הרענון של המסך מ-60 Hz ל-120 Hz. קצב הרענון החדש הזה מאפשר הפעלה חלקה של סרטון בקצב פריימים של 24 Hz, ללא גמגום, ולא צריך להשתמש בשיטת 3:2 pulldown כמו שנדרש כדי להפעיל את אותו סרטון בתצוגה בקצב פריימים של 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.97 Hz, צריך להעביר את הערך 29.97 ולא לעגל אותו ל-30.
באפליקציות של סרטונים, פרמטר התאימות שמועבר אל setFrameRate()
צריך להיות מוגדר כ-Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
כדי לספק רמז נוסף לפלטפורמת Android שהאפליקציה תשתמש בשיטת pulldown כדי להתאים את עצמה לקצב רענון של התצוגה שלא תואם (מה שיגרום לטלטול).
בתרחישים מסוימים, משטח הסרטון יפסיק לשלוח פריימים אבל יישאר גלוי על המסך למשך זמן מסוים. תרחישים נפוצים כוללים מצבים שבהם ההפעלה מגיעה לסוף הסרטון או כשהמשתמש משהה את ההפעלה. במקרים האלה, צריך להפעיל את setFrameRate()
עם פרמטר קצב הפריימים שמוגדר ל-0 כדי לנקות את הגדרת קצב הפריימים של השטח ולהחזיר אותה לערך ברירת המחדל. אין צורך לנקות את הגדרת קצב הפריימים כמו שמתואר כאן כשמבטלים את המשטח או כשהמשטח מוסתר כי המשתמש עובר לאפליקציה אחרת. צריך לנקות את הגדרת קצב הפריימים רק כשהמשטח נשאר גלוי בלי שימוש.
מעבר לא חלק בין קצבי פריימים
במכשירים מסוימים, יכול להיות שמעבר בין קצבי רענון יגרום להפרעות ויזואליות, כמו מסך שחור למשך שנייה או שתיים. הבעיה הזו מתרחשת בדרך כלל בממירים, במסכי טלוויזיה ובמכשירים דומים. כברירת מחדל, מסגרת Android לא מחליפה מצבים כשקוראים ל-API Surface.setFrameRate()
, כדי למנוע הפרעות ויזואליות כאלה.
יש משתמשים שמעדיפים הפרעה ויזואלית בתחילת סרטונים ארוכים ובסופם. כך אפשר להתאים את קצב הרענון של המסך לקצב הפריימים של הסרטון, ולהימנע מאפקטים של המרת קצב פריימים, כמו תנודות של 3:2 בעת הפעלת סרט.
לכן, אפשר להפעיל מעברים לא חלקים בין קצבי רענון אם גם המשתמשים וגם האפליקציות מסכימים לכך:
- משתמשים: כדי להפעיל את ההגדרה, המשתמשים יכולים להפעיל את הגדרת המשתמש התאמת קצב הפריימים של התוכן.
- אפליקציות: כדי להפעיל את האפשרות הזו, האפליקציות יכולות להעביר את הערך
CHANGE_FRAME_RATE_ALWAYS
אלsetFrameRate()
.
מומלץ להשתמש תמיד בCHANGE_FRAME_RATE_ALWAYS
לסרטונים ארוכים כמו סרטים. הסיבה לכך היא שהיתרון של התאמת קצב הפריימים של הסרטון עולה על ההפרעה שמתרחשת כשמשנים את קצב הרענון.
המלצות נוספות
הנה המלצות לתרחישים נפוצים.
מספר משטחים
פלטפורמת Android מיועדת לטפל נכון בתרחישים שבהם יש כמה משטחים עם הגדרות שונות של קצב פריימים. אם לאפליקציה יש כמה סביבות עם קצב פריימים שונה, צריך להפעיל את setFrameRate()
עם קצב הפריימים הנכון לכל סביבה. גם אם במכשיר פועלות כמה אפליקציות בו-זמנית, באמצעות מסך מפוצל או מצב 'תמונה בתוך תמונה', כל אפליקציה יכולה לקרוא בבטחה ל-setFrameRate()
עבור המשטחים שלה.
קצב הפריימים של הפלטפורמה לא משתנה לקצב הפריימים של האפליקציה
גם אם המכשיר תומך בקצב הפריימים שהאפליקציה מציינת בקריאה ל-setFrameRate()
, יש מקרים שבהם המכשיר לא יעביר את התצוגה לקצב הרענון הזה. לדוגמה, יכול להיות שמשטח עם עדיפות גבוהה יותר יוגדר עם קצב פריימים שונה, או שהמכשיר יהיה במצב חיסכון בסוללה (הגדרה של הגבלה על קצב רענון התצוגה כדי לחסוך בסוללה). האפליקציה צריכה להמשיך לפעול בצורה תקינה גם אם המכשיר לא משנה את קצב הרענון של התצוגה להגדרה של קצב הפריימים של האפליקציה, גם אם בדרך כלל המכשיר מבצע את השינוי.
האפליקציה מחליטה איך להגיב כשקצב הרענון של המסך לא תואם לקצב הפריימים של האפליקציה. בסרטונים, קצב הפריימים קבוע וזהה לקצב הפריימים של סרטון המקור, וצריך להשתמש בשיטת Pulldown כדי להציג את תוכן הסרטון. במקום זאת, יכול להיות שהמשחק ינסה לפעול בקצב הרענון של המסך ולא בקצב הפריימים המועדף שלו. האפליקציה לא צריכה לשנות את הערך שהיא מעבירה אל setFrameRate()
על סמך הפעולות של הפלטפורמה. הערך צריך להישאר מוגדר לקצב הפריימים המועדף של האפליקציה, בלי קשר לאופן שבו האפליקציה מטפלת במקרים שבהם הפלטפורמה לא מותאמת לבקשה של האפליקציה. כך, אם התנאים במכשיר ישתנו ויאפשרו שימוש בקצבי רענון נוספים של התצוגה, לפלטפורמה יהיה המידע הנכון כדי לעבור לקצב הפריימים המועדף של האפליקציה.
במקרים שבהם האפליקציה לא תפעל או לא יכולה לפעול בקצב רענון התצוגה, האפליקציה צריכה לציין חותמות זמן של הצגה לכל פריים, באמצעות אחד מהמנגנונים של הפלטפורמה להגדרת חותמות זמן של הצגה:
השימוש בחותמות הזמן האלה מונע מהפלטפורמה להציג מסגרת של אפליקציה מוקדם מדי, מה שיוביל לריצוד מיותר. השימוש הנכון בחותמות זמן של הצגת פריים הוא קצת מסובך. לגבי משחקים, כדאי לעיין במדריך שלנו בנושא קצב פריימים כדי לקבל מידע נוסף על מניעת תנודות, ולשקול להשתמש בספריית Android Frame Pacing.
במקרים מסוימים, יכול להיות שהפלטפורמה תעבור לכפולה של קצב הפריימים שהאפליקציה ציינה ב-setFrameRate()
. לדוגמה, אפליקציה יכולה להתקשר אל setFrameRate()
עם 60Hz והמכשיר יכול להעביר את התצוגה ל-120Hz. סיבה אפשרית לכך היא שאפליקציה אחרת כוללת משטח עם הגדרת קצב פריימים של 24Hz. במקרה כזה, הפעלת התצוגה ב-120Hz תאפשר גם למשטח 60Hz וגם למשטח 24Hz לפעול ללא צורך בהסרה של פריים כפול.
כשהתצוגה פועלת בקצב פריימים שהוא כפולה של קצב הפריימים של האפליקציה, האפליקציה צריכה לציין חותמות זמן של הצגה לכל פריים כדי למנוע תנודות מיותרות. במשחקים, הספרייה Android Frame Pacing עוזרת להגדיר נכון את חותמות הזמן של הצגת הפריימים.
setFrameRate() לעומת preferredDisplayModeId
WindowManager.LayoutParams.preferredDisplayModeId
היא דרך נוספת שבה אפליקציות יכולות לציין את קצב הפריימים שלהן בפלטפורמה. יש אפליקציות שרוצות לשנות רק את קצב הרענון של המסך, ולא לשנות הגדרות אחרות של מצב התצוגה, כמו רזולוציית המסך. באופן כללי, צריך להשתמש ב-setFrameRate()
במקום ב-preferredDisplayModeId
. setFrameRate()
קל יותר להשתמש בפונקציה כי האפליקציה לא צריכה לחפש ברשימת מצבי התצוגה מצב עם קצב פריימים ספציפי.
setFrameRate()
נותן לפלטפורמה יותר הזדמנויות לבחור קצב פריימים תואם בתרחישים שבהם יש כמה פלטפורמות שפועלות בקצב פריימים שונה. לדוגמה, נניח שתי אפליקציות פועלות במצב מסך מפוצל ב-Pixel 4. באחת מהן מוצג סרטון בקצב של 24Hz ובשנייה מוצגת למשתמש רשימה שאפשר לגלול בה. טלפון Pixel 4 תומך בשני קצבי רענון של המסך: 60Hz ו-90Hz. באמצעות preferredDisplayModeId
API, המערכת בוחרת אוטומטית את קצב הרענון של הווידאו: 60Hz או 90Hz. כשקוראים ל-setFrameRate()
עם 24 Hz, פלטפורמת הווידאו מקבלת יותר מידע על קצב הפריימים של סרטון המקור, וכך היא יכולה לבחור 90 Hz לקצב הרענון של המסך, שהוא טוב יותר מ-60 Hz בתרחיש הזה.
עם זאת, יש תרחישים שבהם צריך להשתמש ב-preferredDisplayModeId
במקום ב-setFrameRate()
, למשל:
- אם האפליקציה רוצה לשנות את הרזולוציה או הגדרות אחרות של מצב התצוגה, צריך להשתמש ב-
preferredDisplayModeId
. - הפלטפורמה תעבור בין מצבי תצוגה רק בתגובה לקריאה אל
setFrameRate()
אם המעבר בין המצבים קל ולא צפוי להיות מורגש למשתמש. אם האפליקציה מעדיפה להחליף את קצב הרענון של המסך גם אם זה דורש מעבר למצב כבד (לדוגמה, במכשיר Android TV), צריך להשתמש ב-preferredDisplayModeId
. - באפליקציות שלא יכולות לטפל בהצגה שפועלת בקצב פריימים שהוא כפולה של קצב הפריימים של האפליקציה, שדורש הגדרת חותמות זמן של הצגה בכל פרים, צריך להשתמש ב-
preferredDisplayModeId
.
setFrameRate() לעומת preferredRefreshRate
WindowManager.LayoutParams#preferredRefreshRate
מגדיר קצב פריימים מועדף בחלון של האפליקציה, והקצב חל על כל המשטחים בתוך החלון. האפליקציה צריכה לציין את קצב הפריימים המועדף שלה, ללא קשר לקצב הרענון הנתמך במכשיר, בדומה ל-setFrameRate()
, כדי לתת למתזמן רמז טוב יותר לגבי קצב הפריימים המיועד של האפליקציה.
המערכת מתעלמת מ-preferredRefreshRate
בפלטפורמות שמשתמשות ב-setFrameRate()
. בדרך כלל, מומלץ להשתמש ב-setFrameRate()
אם אפשר.
preferredRefreshRate לעומת preferredDisplayModeId
אם האפליקציות רוצות לשנות רק את קצב הרענון המועדף, עדיף להשתמש ב-preferredRefreshRate
ולא ב-preferredDisplayModeId
.
הימנעות מהפעלת setFrameRate() בתדירות גבוהה מדי
למרות שהקריאה setFrameRate()
לא יקרה מדי מבחינת ביצועים, מומלץ שאפליקציות לא יקראו ל-setFrameRate()
בכל פריים או כמה פעמים בשנייה. שיחות אל setFrameRate()
צפויות להוביל לשינוי בקצב רענון התצוגה, מה שעשוי לגרום להשמטת פריימים במהלך המעבר.
צריך להבין מראש מהו קצב הפריימים הנכון ולהתקשר אל setFrameRate()
פעם אחת.
שימוש במשחקים או באפליקציות אחרות שאינן סרטונים
למרות שהשימוש העיקרי ב-setFrameRate()
API הוא לווידאו, אפשר להשתמש בו גם באפליקציות אחרות. לדוגמה, משחק שלא אמור לפעול בקצב רענון גבוה מ-60Hz (כדי לצמצם את צריכת החשמל ולהאריך את משך הסשנים) יכול לקרוא ל-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 הוא קצב הפריימים של הסרטון. - אנחנו לא ממליצים שאפליקציות יתקשרו אל
setFrameRate()
עםCHANGE_FRAME_RATE_ALWAYS
כשאתם מצפים שהפעלת הווידאו תימשך כמה דקות או פחות.
דוגמה לשילוב באפליקציות להפעלת סרטונים
אנחנו ממליצים לבצע את השלבים הבאים כדי לשלב החלפות של קצב רענון באפליקציות להפעלת סרטונים:
- מחליטים על
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.ד. הסיבה לכך היא שההשהיה המומלצת שלנו גדולה מהנדרש במסכים שזמני המעבר שלהם מהירים יותר.
- תתקשר אל
setFrameRate
ותעביר לו אתfps
, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
, וCHANGE_FRAME_RATE_ALWAYS
, כאשרfps
הוא קצב הפריימים של הסרטון. - מחכים לקריאה החוזרת (callback) של
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();
}