ה-API של קצב הפריימים מאפשר לאפליקציות להודיע לפלטפורמת Android על קצב הפריימים הרצוי, והוא זמין באפליקציות שמטרגטות ל-Android 11 (רמת API 30) ומעלה. בעבר, רוב המכשירים תמכו רק בקצב רענון מסך אחד, בדרך כלל 60Hz, אבל זה השתנה. הרבה מכשירים תומכים עכשיו בקצבי רענון נוספים, כמו 90Hz או 120Hz. יש מכשירים שתומכים במעברים חלקים בין קצבי רענון, ובמכשירים אחרים מוצג מסך שחור לזמן קצר, בדרך כלל למשך שנייה.
המטרה העיקרית של ה-API היא לאפשר לאפליקציות לנצל טוב יותר את כל קצבי הרענון הנתמכים של המסך. לדוגמה, אם אפליקציה מפעילה סרטון בקצב פריימים של 24Hz וקוראת ל-setFrameRate(), יכול להיות שהמכשיר ישנה את קצב הרענון של המסך מ-60Hz ל-120Hz. קצב הרענון החדש הזה מאפשר הפעלה חלקה של סרטון בקצב של 24Hz, ללא צורך ב-3:2 pulldown, כפי שנדרש כדי להפעיל את אותו סרטון במסך של 60Hz. כך חוויית המשתמש משתפרת.
שימוש בסיסי
ב-Android יש כמה דרכים לגשת למשטחים ולשלוט בהם, ולכן יש כמה גרסאות של ה-API setFrameRate(). כל גרסה של ה-API מקבלת את אותם פרמטרים ופועלת כמו האחרות:
Surface.setFrameRate()SurfaceControl.Transaction.setFrameRate()ANativeWindow_setFrameRate()ASurfaceTransaction_setFrameRate()
כדי להפעיל את setFrameRate() באופן בטוח, האפליקציה לא צריכה להביא בחשבון את קצבי הרענון של המסך שנתמכים בפועל, שאפשר לקבל על ידי הפעלת Display.getSupportedModes(). לדוגמה, גם אם המכשיר תומך רק ב-60Hz, צריך להפעיל את setFrameRate() עם קצב הפריימים המועדף באפליקציה.
במכשירים שאין בהם התאמה טובה יותר לקצב הפריימים של האפליקציה, קצב הרענון של המסך לא ישתנה.
כדי לבדוק אם קריאה ל-setFrameRate() גורמת לשינוי בקצב הרענון של המסך, צריך להירשם לקבלת התראות על שינויים במסך באמצעות קריאה ל-DisplayManager.registerDisplayListener() או ל-AChoreographer_registerRefreshRateCallback().
כשקוראים ל-setFrameRate(), מומלץ להעביר את קצב הפריימים המדויק במקום לעגל אותו למספר שלם. לדוגמה, כשמעבדים סרטון שהוקלט בקצב של 29.97Hz, צריך להעביר את הערך 29.97 ולא לעגל אותו ל-30.
באפליקציות וידאו, צריך להגדיר את פרמטר התאימות שמועבר אל setFrameRate() לערך Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE כדי לתת רמז נוסף לפלטפורמת Android שהאפליקציה תשתמש ב-pulldown כדי להתאים לקצב רענון תצוגה לא תואם (מה שיגרום לריצוד).
במקרים מסוימים, משטח הסרטון יפסיק לשלוח פריימים אבל יישאר גלוי במסך למשך זמן מה. תרחישים נפוצים כוללים מצבים שבהם ההפעלה מגיעה לסוף הסרטון או כשהמשתמש משהה את ההפעלה. במקרים כאלה, צריך להפעיל את setFrameRate() כשפרמטר קצב הפריימים מוגדר ל-0 כדי לאפס את הגדרת קצב הפריימים של המשטח לערך ברירת המחדל. לא צריך למחוק את הגדרת קצב הפריימים כשמבטלים את ה-surface, או כשה-surface מוסתר כי המשתמש עובר לאפליקציה אחרת. צריך למחוק את הגדרת קצב הפריימים רק כשה-surface נשאר גלוי בלי שמשתמשים בו.
מעבר לא חלק בין קצבי פריימים
במכשירים מסוימים, המעבר בין קצבי הרענון עלול לגרום להפרעות חזותיות, כמו מסך שחור למשך שנייה או שתיים. הבעיה הזו מתרחשת בדרך כלל בממירים, במסכי טלוויזיה ובמכשירים דומים. כברירת מחדל, מסגרת Android לא מחליפה מצבים כשמתבצעת קריאה ל-API Surface.setFrameRate(), כדי למנוע הפרעות חזותיות כאלה.
חלק מהמשתמשים מעדיפים הפסקה ויזואלית בתחילת הסרטון ובסופו, במיוחד בסרטונים ארוכים. כך קצב הרענון של המסך יכול להתאים לקצב הפריימים של הסרטון, ולמנוע פגמים בהמרת קצב הפריימים, כמו רעידות ב-3:2 pulldown בהפעלת סרט.
לכן, אפשר להפעיל מעברים לא חלקים בין קצבי רענון אם גם המשתמש וגם האפליקציות מביעים הסכמה:
- משתמשים: כדי להצטרף, המשתמשים יכולים להפעיל את הגדרת המשתמש התאמה לקצב הפריימים של התוכן.
- אפליקציות: כדי להביע הסכמה, אפליקציות יכולות להעביר את
CHANGE_FRAME_RATE_ALWAYSאלsetFrameRate().
אנחנו ממליצים להשתמש תמיד ב-CHANGE_FRAME_RATE_ALWAYS בסרטונים ארוכים, כמו סרטים. הסיבה לכך היא שהיתרון של התאמת קצב הפריימים של הסרטון עולה על ההפרעה שמתרחשת כשמשנים את קצב הרענון.
המלצות נוספות
מומלץ לפעול לפי ההמלצות הבאות בתרחישים נפוצים.
כמה פלטפורמות
פלטפורמת Android מיועדת לטפל בצורה נכונה בתרחישים שבהם יש כמה משטחים עם הגדרות שונות של קצב פריימים. אם לאפליקציה יש כמה משטחים עם קצבי פריימים שונים, צריך להפעיל את setFrameRate() עם קצב הפריימים הנכון לכל משטח. גם אם המכשיר מריץ כמה אפליקציות בו-זמנית, באמצעות מצב מסך מפוצל או תמונה בתוך תמונה, כל אפליקציה יכולה להפעיל את setFrameRate() בבטחה למשטחים שלה.
הפלטפורמה לא משתנה לקצב הפריימים של האפליקציה
גם אם המכשיר תומך בקצב הפריימים שהאפליקציה מציינת בקריאה ל-setFrameRate(), יש מקרים שבהם המכשיר לא יעביר את התצוגה לקצב הרענון הזה. לדוגמה, יכול להיות שמשטח בעדיפות גבוהה יותר יוגדר עם קצב פריימים שונה, או שהמכשיר יהיה במצב חיסכון בסוללה (שמגביל את קצב הרענון של המסך כדי לחסוך בסוללה). האפליקציה צריכה לפעול בצורה תקינה גם אם המכשיר לא מחליף את קצב הרענון של המסך להגדרת קצב הפריימים של האפליקציה, אפילו אם המכשיר מחליף את קצב הרענון בנסיבות רגילות.
האפליקציה קובעת איך להגיב כשקצב הרענון של המסך לא תואם לקצב הפריימים של האפליקציה. בסרטונים, קצב הפריימים קבוע בהתאם לסרטון המקור, וצריך להשתמש ב-pulldown כדי להציג את תוכן הסרטון. במקום זאת, יכול להיות שהמשחק ינסה לפעול בקצב הרענון של המסך ולא בקצב הפריימים המועדף. האפליקציה לא אמורה לשנות את הערך שהיא מעבירה אל setFrameRate() על סמך מה שהפלטפורמה עושה. הוא צריך להישאר מוגדר לקצב הפריימים המועדף של האפליקציה, בלי קשר לאופן שבו האפליקציה מטפלת במקרים שבהם הפלטפורמה לא מתאימה את עצמה לבקשת האפליקציה. כך, אם תנאי המכשיר משתנים ומאפשרים שימוש בקצבי רענון נוספים של המסך, לפלטפורמה יש את המידע הנכון כדי לעבור לקצב הפריימים המועדף של האפליקציה.
אם האפליקציה לא תפעל או לא תוכל לפעול בקצב הרענון של המסך, צריך לציין באפליקציה חותמות זמן של הצגה לכל פריים, באמצעות אחד מהמנגנונים של הפלטפורמה להגדרת חותמות זמן של הצגה:
השימוש בחותמות הזמן האלה מונע מהפלטפורמה להציג פריים של אפליקציה מוקדם מדי, מה שעלול לגרום לריצוד מיותר. השימוש הנכון בחותמות זמן של הצגת פריימים הוא קצת מסובך. במשחקים, כדאי לעיין במדריך שלנו בנושא קצב פריימים כדי לקבל מידע נוסף על מניעת רעידות, ולשקול להשתמש בספריית קצב הפריימים של Android.
במקרים מסוימים, הפלטפורמה עשויה לעבור לכפולה של קצב הפריימים שהאפליקציה ציינה ב-setFrameRate(). לדוגמה, אפליקציה יכולה להפעיל את setFrameRate() עם 60Hz והמכשיר יכול להחליף את התצוגה ל-120Hz. סיבה אפשרית אחת לכך היא שאפליקציה אחרת כוללת משטח עם הגדרת קצב פריימים של 24Hz. במקרה כזה, הפעלת התצוגה בתדר 120Hz תאפשר גם למשטח של 60Hz וגם למשטח של 24Hz לפעול בלי צורך ב-pulldown.
כשהמסך פועל בקצב פריימים שהוא כפולה של קצב הפריימים של האפליקציה, האפליקציה צריכה לציין חותמות זמן של הצגה לכל פריים כדי למנוע רעידות מיותרות. במשחקים, ספריית Android Frame Pacing עוזרת להגדיר בצורה נכונה את חותמות הזמן של הצגת הפריימים.
setFrameRate() לעומת preferredDisplayModeId
WindowManager.LayoutParams.preferredDisplayModeId
היא דרך נוספת שבה אפליקציות יכולות לציין את קצב הפריימים שלהן לפלטפורמה. באפליקציות מסוימות, המטרה היא רק לשנות את קצב הרענון של המסך ולא את ההגדרות האחרות של מצב התצוגה, כמו רזולוציית המסך. באופן כללי, צריך להשתמש ב-setFrameRate() במקום ב-preferredDisplayModeId. הפונקציה setFrameRate() קלה יותר לשימוש כי האפליקציה לא צריכה לחפש ברשימת מצבי התצוגה כדי למצוא מצב עם קצב פריימים ספציפי.
ההגדרה setFrameRate() נותנת לפלטפורמה יותר הזדמנויות לבחור קצב פריימים תואם בתרחישים שבהם יש כמה משטחים שפועלים בקצבי פריימים שונים. לדוגמה, נניח ששתי אפליקציות פועלות במצב מסך מפוצל ב-Pixel 4, כשאפליקציה אחת מפעילה סרטון בקצב של 24Hz והאפליקציה השנייה מציגה למשתמש רשימה שאפשר לגלול בה. ב-Pixel 4 יש שני קצבי רענון של המסך: 60Hz ו-90Hz. כשמשתמשים ב-API של preferredDisplayModeId,
משטח הווידאו נאלץ לבחור בין 60Hz ל-90Hz. כשמפעילים את setFrameRate() עם 24Hz, משטח הסרטון מספק לפלטפורמה מידע נוסף על קצב הפריימים של סרטון המקור. כך הפלטפורמה יכולה לבחור קצב רענון של 90Hz לתצוגה, שהוא טוב יותר מ-60Hz בתרחיש הזה.
עם זאת, יש תרחישים שבהם צריך להשתמש ב-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() פעם אחת.
שימוש במשחקים או באפליקציות אחרות שהן לא אפליקציות וידאו
למרות שווידאו הוא תרחיש השימוש העיקרי ב-API של setFrameRate(), אפשר להשתמש בו גם באפליקציות אחרות. לדוגמה, במשחק שלא אמור לפעול בקצב גבוה מ-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();
}