ה-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 pulldown בהפעלת סרטונים.
לכן, אפשר להפעיל מעברים לא חלקים בין קצבי רענון אם גם המשתמשים וגם האפליקציות מסכימים לכך:
- משתמשים: כדי להפעיל את ההגדרה, המשתמשים יכולים להפעיל את הגדרת המשתמש התאמת קצב הפריימים של התוכן.
- אפליקציות: כדי להפעיל את האפשרות הזו, האפליקציות יכולות להעביר את הערך
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();
}