קצב מסגרות

ממשק ה-API של קצב הפריימים מאפשר לאפליקציות לעדכן את פלטפורמת Android לגבי הפריים הרצוי וזמין באפליקציות שמטרגטות את Android 11 (רמת API 30) ואילך. בעבר, רוב המכשירים תמכו רק בקצב רענון מסך אחד, בדרך כלל 60Hz, אבל המצב הזה השתנה. במכשירים רבים יש עכשיו תמיכה בתכונות נוספות קצבי רענון כמו 90Hz או 120Hz. חלק מהמכשירים תומכים בקצב רענון חלק בזמן שאחרים מציגים לרגע מסך שחור, שלרוב נמשך שנייה.

המטרה העיקרית של ה-API היא לאפשר לאפליקציות לנצל טוב יותר את כל היתרונות של כל את קצב הרענון הנתמכים של המסך. לדוגמה, אפליקציה שמפעילה וידאו של 24Hz שמפעילה את setFrameRate() עשויה לגרום למכשיר לשנות את קצב הרענון של המסך מ-60Hz ל-120Hz. קצב הרענון החדש מאפשר הפעלה חלקה של סרטונים באיכות 24Hz ללא רעידות, ללא צורך ב-3:2 pulldown, כפי שנדרש להפעלת אותו סרטון במסך 60Hz. כך המשתמש יהיה טוב יותר חוויה אישית.

שימוש בסיסי

ב-Android יש כמה דרכים לגשת למשטחים ולשלוט בהם, ולכן יש כמה גרסאות של ה-API‏ setFrameRate(). כל גרסה של ה-API לוקחת את אותם פרמטרים ופועלת בדיוק כמו האחרים:

האפליקציה לא צריכה לקחת בחשבון את קצב הרענון הנתמך בפועל של התצוגה, שאפשר לקבל באמצעות קריאה Display.getSupportedModes() כדי להתקשר אל setFrameRate() בבטחה. לדוגמה, גם אם המכשיר בלבד יש תמיכה ב-60Hz, או לבצע קריאה אל setFrameRate() עם קצב הפריימים המועדף לאפליקציה. במכשירים שלא מתאים להם קצב רענון טוב יותר של המסך לקצב הפריימים של האפליקציה, קצב הרענון של המסך לא ישתנה.

כדי לבדוק אם קריאה ל-setFrameRate() גורמת לשינוי בקצב הרענון של המסך, צריך להירשם לקבלת התראות על שינויים במסך באמצעות קריאה למספר DisplayManager.registerDisplayListener() או AChoreographer_registerRefreshRateCallback().

כשקוראים ל-setFrameRate(), עדיף להעביר את קצב הפריימים המדויק במקום לעגל למספר שלם. לדוגמה, בעת רינדור סרטון שהוקלט 29.97Hz, מעבירים ב-29.97 במקום לעגל ל-30.

באפליקציות וידאו, יש להגדיר את פרמטר התאימות שמועבר אל setFrameRate() אל Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE כדי לתת רמז נוסף פלטפורמת Android שהאפליקציה תשתמש בה בתפריט הנפתח כדי להתאים את עצמה לפלטפורמת Android לא תואמת את קצב הרענון של המסך (שיגרמו לרעידות).

בתרחישים מסוימים, תכונת הווידאו תפסיק לשלוח פריימים, אבל היא תישאר גלוי במסך לזמן מה. התרחישים הנפוצים: בזמן ההפעלה מגיע לסוף הסרטון או כשהמשתמש משהה את ההפעלה. במקרים כאלה, צריך להפעיל את setFrameRate() עם הפרמטר של קצב הפריימים מוגדר ל-0 כדי למחוק את ההגדרה של קצב הפריימים של פני השטח ולהחזיר אותה לערך ברירת המחדל. ניקוי ההגדרה של קצב הפריימים גם אין צורך בכך כשהרוסים את המשטח, או כשהפני השטח מוסתרת כי המשתמש עובר לאפליקציה אחרת. ניקוי של קצב הפריימים רק כאשר המשטח נשאר גלוי וללא שימוש.

מתג לקצב פריימים בצורה לא חלקה

במכשירים מסוימים, המעבר בין שיעורי הרענון עשוי לגרום להפרעות חזותיות, כמו מסך שחור למשך שנייה או שתיים. פעולה זו מתרחשת בדרך כלל בממירים, בלוחות טלוויזיה, ומכשירים דומים. כברירת מחדל, ה-framework של Android לא מחליף מצבים כאשר Surface.setFrameRate() מתבצעת קריאה ל-API, כדי למנוע הפרעות חזותיות כאלה.

חלק מהמשתמשים מעדיפים הפרעה חזותית בתחילת הסרטון ובסופו, במיוחד בסרטונים ארוכים. כך קצב הרענון של המסך יכול להתאים את קצב הפריימים של הסרטון, ונמנעים מיצירת ארטיפקטים של המרה לקצב פריימים כמו 3:2 למשוך למטה להפעלת סרט.

מהסיבה הזו, ניתן להפעיל מתגים לא יעילים של קצב רענון אם הבעת הסכמה מצד משתמשים ואפליקציות:

מומלץ תמיד להשתמש ב-CHANGE_FRAME_RATE_ALWAYS בסרטונים ארוכים כמו סרטים. הסיבה לכך היא שהיתרון של התאמת קצב הפריימים של הסרטון עולה על החיסרון של ההפרעה שמתרחשת כשמשנים את קצב הרענון.

המלצות נוספות

מומלץ לפעול לפי ההמלצות הבאות בתרחישים נפוצים.

מספר משטחים

פלטפורמת Android תוכננה לטפל בצורה נכונה בתרחישים שבהם יש כמה משטחים עם הגדרות שונות של קצב פריימים. אם באפליקציה יש כמה משטחים עם שיעורי פריימים שונים, צריך לבצע קריאה ל-setFrameRate() עם שיעור הפריימים הנכון לכל משטח. גם אם פועלות במכשיר מספר אפליקציות ב- פעם אחת, באמצעות מסך מפוצל או מצב 'תמונה בתוך תמונה', כל אפליקציה יכולה להתקשר בבטחה setFrameRate() לפלטפורמות השונות.

הפלטפורמה לא משתנה לקצב הפריימים של האפליקציה

גם אם המכשיר תומך בקצב הפריימים שהאפליקציה מציינת בקריאה ל-setFrameRate(), יש מקרים שבהם המכשיר לא יעביר את המסך לקצב הרענון הזה. לדוגמה, יכול להיות שלפלטפורמה בעדיפות גבוהה יותר הגדרת קצב הפריימים, או שהמכשיר נמצא במצב חיסכון בסוללה (הגדרה של הגבלה על קצב הרענון של המסך כדי לחסוך בצריכת הסוללה). האפליקציה צריכה להמשיך פועלות בצורה תקינה כשהמכשיר לא משנה את קצב הרענון של המסך הגדרת קצב הפריימים של האפליקציה, גם אם המכשיר משתנה במצב רגיל בנסיבות השונות.

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

preferencesרענןRate לעומת גורם מועדף ל-DisplayModeId

אם האפליקציות רוצות לשנות רק את קצב הרענון המועדף, עדיף להשתמש בהן 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 כאשר אתם מצפים שמשך הפעלת הסרטון יימשך כמה דקות או פחות.

שילוב לדוגמה של אפליקציות להפעלת סרטונים

מומלץ לפעול לפי השלבים הבאים לשילוב מתגי קצב רענון באפליקציות להפעלת סרטונים:

  1. קובעים את changeFrameRateStrategy:
    1. אם מפעילים סרטון ארוך, כמו סרט, משתמשים ב-MATCH_CONTENT_FRAMERATE_ALWAYS
    2. אם מפעילים סרטון קצר, כמו טריילר, צריך להשתמש בסמל CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
  2. אם הערך של changeFrameRateStrategy הוא CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS , מעבר לשלב 4.
  3. כדי לזהות אם עומדת להתרחש החלפה לא חלקה של קצב הרענון, צריך לוודא ששני העובדות הבאות מתקיימות:
    1. אי אפשר לעבור בצורה חלקה ממצב של קצב רענון נוכחי (נניח C) לקצב הפריימים של הסרטון (נניח V). המצב הזה יקרה אם C ו-V שונים ו-Display.getMode().getAlternativeRefreshRates לא מכיל מכפלה של V.
    2. המשתמש הביע הסכמה לשינויים לא פשוטים בקצב הרענון. תוכלו לזהות כדי לבדוק אם DisplayManager.getMatchContentFrameRateUserPreference החזרות MATCH_CONTENT_FRAMERATE_ALWAYS
  4. אם המעבר יתבצע בצורה חלקה, מבצעים את הפעולות הבאות:
    1. חיוג אל setFrameRate ולהעביר אותה fps, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, ו-changeFrameRateStrategy, כאשר fps הוא קצב הפריימים של הסרטון.
    2. התחלת הפעלת הסרטון
  5. אם עומד להתבצע שינוי מצב לא חלק, צריך לבצע את הפעולות הבאות:
    1. הצגת חוויית משתמש כדי להודיע למשתמש. חשוב לזכור: מומלץ להטמיע דרך שבה המשתמש יוכל לסגור את ממשק המשתמש הזה ולדלג על העיכוב הנוסף בשלב 5.ד. הסיבה לכך היא שההשהיה המומלצת שלנו גדולה מהנדרש במסכים עם זמני מעבר מהירים יותר.
    2. קוראים ל-setFrameRate ומעבירים אליו את הערכים fps,‏ FRAME_RATE_COMPATIBILITY_FIXED_SOURCE ו-CHANGE_FRAME_RATE_ALWAYS, כאשר fps הוא קצב הפריימים של הסרטון.
    3. יש להמתין ל-onDisplayChanged קריאה חוזרת.
    4. ממתינים 2 שניות עד ששינוי המצב יסתיים.
    5. הפעלת הסרטון

הקוד המדומה בלבד שתומך במעבר חלק הוא:

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();
}