نرخ فریم

API نرخ فریم به برنامه‌ها اجازه می‌دهد تا نرخ فریم مورد نظر خود را به پلتفرم اندروید اطلاع دهند و در برنامه‌هایی که اندروید ۱۱ (سطح API 30) یا بالاتر را هدف قرار می‌دهند، در دسترس است. به طور سنتی، اکثر دستگاه‌ها فقط از یک نرخ تازه‌سازی صفحه نمایش، معمولاً ۶۰ هرتز، پشتیبانی می‌کردند، اما این روند در حال تغییر است. اکنون بسیاری از دستگاه‌ها از نرخ‌های تازه‌سازی اضافی مانند ۹۰ هرتز یا ۱۲۰ هرتز پشتیبانی می‌کنند. برخی از دستگاه‌ها از سوئیچ‌های نرخ تازه‌سازی یکپارچه پشتیبانی می‌کنند، در حالی که برخی دیگر به طور خلاصه یک صفحه سیاه نشان می‌دهند که معمولاً یک ثانیه طول می‌کشد.

هدف اصلی این API این است که برنامه‌ها بتوانند از تمام نرخ‌های تازه‌سازی پشتیبانی‌شده‌ی نمایشگر بهتر استفاده کنند. برای مثال، برنامه‌ای که در حال پخش یک ویدیوی 24 هرتز است و setFrameRate() را فراخوانی می‌کند، ممکن است باعث شود دستگاه نرخ تازه‌سازی نمایشگر را از 60 هرتز به 120 هرتز تغییر دهد. این نرخ تازه‌سازی جدید، پخش روان و بدون لرزش ویدیوی 24 هرتز را بدون نیاز به پایین کشیدن تصویر 3:2 که برای پخش همان ویدیو در نمایشگر 60 هرتز لازم است، امکان‌پذیر می‌کند. این امر منجر به تجربه‌ی کاربری بهتری می‌شود.

کاربرد اولیه

اندروید روش‌های مختلفی برای دسترسی و کنترل سطوح ارائه می‌دهد، بنابراین نسخه‌های مختلفی از API setFrameRate() وجود دارد. هر نسخه از API پارامترهای یکسانی را دریافت می‌کند و مانند نسخه‌های دیگر عمل می‌کند:

برنامه نیازی به در نظر گرفتن نرخ‌های نوسازی واقعی پشتیبانی‌شده‌ی نمایشگر ندارد، که می‌توان با فراخوانی Display.getSupportedModes() به دست آورد تا بتوان setFrameRate() با خیال راحت فراخوانی کرد. به عنوان مثال، حتی اگر دستگاه فقط از 60 هرتز پشتیبانی کند، setFrameRate() با نرخ فریمی که برنامه شما ترجیح می‌دهد فراخوانی کنید. دستگاه‌هایی که تطابق بهتری با نرخ فریم برنامه ندارند، با نرخ نوسازی فعلی نمایشگر باقی می‌مانند.

برای مشاهده‌ی اینکه آیا فراخوانی تابع setFrameRate() منجر به تغییر نرخ نوسازی نمایشگر می‌شود یا خیر، با فراخوانی تابع DisplayManager.registerDisplayListener() یا AChoreographer_registerRefreshRateCallback() اعلان‌های تغییر نمایشگر را ثبت کنید.

هنگام فراخوانی تابع setFrameRate() ، بهتر است نرخ فریم دقیق را به جای گرد کردن به یک عدد صحیح، ارسال کنید. برای مثال، هنگام رندر کردن یک ویدیو ضبط شده با فرکانس ۲۹.۹۷ هرتز، به جای گرد کردن به ۳۰، عدد ۲۹.۹۷ را ارسال کنید.

برای برنامه‌های ویدیویی، پارامتر سازگاری که به setFrameRate() ارسال می‌شود، باید روی Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE تنظیم شود تا به پلتفرم اندروید نشان دهد که برنامه از منوی کشویی برای تطبیق با نرخ نوسازی صفحه نمایش غیر منطبق (که منجر به لرزش می‌شود) استفاده خواهد کرد.

در برخی سناریوها، سطح ویدیو ارسال فریم‌ها را متوقف می‌کند اما برای مدتی روی صفحه قابل مشاهده باقی می‌ماند. سناریوهای رایج شامل زمانی است که پخش به انتهای ویدیو می‌رسد یا زمانی که کاربر پخش را متوقف می‌کند. در این موارد، تابع setFrameRate() با پارامتر نرخ فریم تنظیم شده روی ۰ فراخوانی کنید تا تنظیمات نرخ فریم سطح به مقدار پیش‌فرض بازگردد. پاک کردن تنظیمات نرخ فریم مانند این هنگام از بین بردن سطح یا زمانی که سطح به دلیل تغییر برنامه کاربر پنهان شده است، ضروری نیست. تنظیمات نرخ فریم را فقط زمانی پاک کنید که سطح بدون استفاده قابل مشاهده باقی بماند.

سوئیچ نرخ فریم غیریکپارچه

در برخی دستگاه‌ها، تغییر نرخ تازه‌سازی ممکن است با وقفه‌های بصری مانند سیاه شدن صفحه نمایش برای یک یا دو ثانیه همراه باشد. این اتفاق معمولاً در گیرنده‌های دیجیتال، پنل‌های تلویزیون و دستگاه‌های مشابه رخ می‌دهد. به طور پیش‌فرض، چارچوب اندروید هنگام فراخوانی API Surface.setFrameRate() حالت‌ها را تغییر نمی‌دهد تا از چنین وقفه‌های بصری جلوگیری شود.

برخی از کاربران ترجیح می‌دهند در ابتدا و انتهای ویدیوهای طولانی‌تر، وقفه بصری وجود داشته باشد. این کار باعث می‌شود نرخ تازه‌سازی نمایشگر با نرخ فریم ویدیو مطابقت داشته باشد و از مصنوعات تبدیل نرخ فریم مانند لرزش ۳:۲ هنگام پخش فیلم جلوگیری شود.

به همین دلیل، در صورت تمایل کاربر و برنامه‌ها، می‌توان سوئیچ‌های نرخ نوسازی غیریکپارچه را فعال کرد:

توصیه می‌کنیم همیشه برای ویدیوهای طولانی مدت مانند فیلم‌ها از CHANGE_FRAME_RATE_ALWAYS استفاده کنید. دلیل این امر این است که مزیت تطبیق نرخ فریم ویدیو، از وقفه‌ای که هنگام تغییر نرخ تازه‌سازی رخ می‌دهد، بیشتر است.

توصیه‌های اضافی

برای سناریوهای رایج، این توصیه‌ها را دنبال کنید.

سطوح چندگانه

پلتفرم اندروید طوری طراحی شده است که سناریوهایی را که در آن‌ها چندین سطح با تنظیمات نرخ فریم متفاوت وجود دارد، به درستی مدیریت کند. وقتی برنامه شما چندین سطح با نرخ فریم متفاوت دارد، تابع setFrameRate() را با نرخ فریم صحیح برای هر سطح فراخوانی کنید. حتی اگر دستگاه چندین برنامه را به طور همزمان اجرا می‌کند، با استفاده از حالت تقسیم صفحه یا تصویر در تصویر، هر برنامه می‌تواند با خیال راحت setFrameRate() برای سطوح خود فراخوانی کند.

پلتفرم با نرخ فریم برنامه تغییر نمی‌کند

حتی اگر دستگاه از نرخ فریمی که برنامه در فراخوانی setFrameRate() مشخص می‌کند پشتیبانی کند، مواردی وجود دارد که دستگاه صفحه نمایش را به آن نرخ تازه‌سازی تغییر نمی‌دهد. به عنوان مثال، یک سطح با اولویت بالاتر ممکن است تنظیم نرخ فریم متفاوتی داشته باشد، یا ممکن است دستگاه در حالت صرفه‌جویی در مصرف باتری باشد (تنظیم محدودیتی روی نرخ تازه‌سازی صفحه نمایش برای حفظ باتری). حتی اگر دستگاه در شرایط عادی این تغییر را انجام دهد، برنامه باید همچنان به درستی کار کند، حتی اگر دستگاه نرخ تازه‌سازی صفحه نمایش را به تنظیمات نرخ فریم برنامه تغییر ندهد.

این به برنامه بستگی دارد که وقتی نرخ تازه‌سازی صفحه نمایش با نرخ فریم برنامه مطابقت ندارد، چگونه واکنش نشان دهد. برای ویدیو، نرخ فریم ثابت و برابر با ویدیوی منبع است و برای نمایش محتوای ویدیو، باید به پایین بکشید. یک بازی ممکن است به جای اینکه نرخ فریم دلخواه خود را حفظ کند، سعی کند با نرخ تازه‌سازی صفحه نمایش اجرا شود. برنامه نباید مقداری را که به setFrameRate() می‌دهد، بر اساس عملکرد پلتفرم تغییر دهد. باید روی نرخ فریم دلخواه برنامه تنظیم شود، صرف نظر از اینکه برنامه چگونه مواردی را که پلتفرم برای مطابقت با درخواست برنامه تنظیم نمی‌شود، مدیریت می‌کند. به این ترتیب، اگر شرایط دستگاه تغییر کند تا امکان استفاده از نرخ‌های تازه‌سازی صفحه نمایش اضافی فراهم شود، پلتفرم اطلاعات صحیحی برای تغییر به نرخ فریم دلخواه برنامه دارد.

در مواردی که برنامه نمی‌تواند یا نمی‌تواند با نرخ تازه‌سازی صفحه نمایش اجرا شود، برنامه باید با استفاده از یکی از سازوکارهای پلتفرم برای تنظیم مهرهای زمانی ارائه، مهرهای زمانی ارائه را برای هر فریم مشخص کند:

استفاده از این مهرهای زمانی، مانع از ارائه زودهنگام فریم برنامه توسط پلتفرم می‌شود که منجر به لرزش غیرضروری می‌شود. استفاده صحیح از مهرهای زمانی ارائه فریم کمی پیچیده است. برای بازی‌ها، برای اطلاعات بیشتر در مورد جلوگیری از لرزش، به راهنمای تنظیم سرعت فریم ما مراجعه کنید و استفاده از کتابخانه تنظیم سرعت فریم اندروید را در نظر بگیرید.

در برخی موارد، پلتفرم ممکن است به مضربی از نرخ فریم مشخص شده توسط برنامه در setFrameRate() تغییر کند. برای مثال، یک برنامه ممکن است setFrameRate() با 60 هرتز فراخوانی کند و دستگاه ممکن است نمایشگر را به 120 هرتز تغییر دهد. یکی از دلایلی که ممکن است این اتفاق بیفتد این است که اگر برنامه دیگری دارای سطحی با تنظیم نرخ فریم 24 هرتز باشد. در این صورت، اجرای نمایشگر با سرعت 120 هرتز به هر دو سطح 60 هرتز و 24 هرتز اجازه می‌دهد بدون نیاز به پایین کشیدن صفحه، اجرا شوند.

وقتی نمایشگر با ضریبی از نرخ فریم برنامه اجرا می‌شود، برنامه باید برای هر فریم، زمان نمایش را مشخص کند تا از لرزش‌های غیرضروری جلوگیری شود. برای بازی‌ها، کتابخانه Android Frame Pacing برای تنظیم صحیح زمان نمایش فریم مفید است.

تابع ()setFrameRate در مقابل تابع ()preferredDisplayModeId

WindowManager.LayoutParams.preferredDisplayModeId روش دیگری است که برنامه‌ها می‌توانند نرخ فریم خود را به پلتفرم نشان دهند. برخی از برنامه‌ها فقط می‌خواهند نرخ تازه‌سازی صفحه نمایش را تغییر دهند تا اینکه سایر تنظیمات حالت نمایش، مانند وضوح صفحه نمایش را تغییر دهند. به طور کلی، به جای preferredDisplayModeId از setFrameRate() استفاده کنید. استفاده از تابع setFrameRate() آسان‌تر است زیرا برنامه نیازی به جستجو در لیست حالت‌های نمایش برای یافتن حالتی با نرخ فریم خاص ندارد.

setFrameRate() به پلتفرم فرصت‌های بیشتری برای انتخاب نرخ فریم سازگار در سناریوهایی می‌دهد که در آن‌ها چندین صفحه نمایش با نرخ فریم‌های مختلف اجرا می‌شوند. برای مثال، سناریویی را در نظر بگیرید که در آن دو برنامه در حالت تقسیم صفحه روی Pixel 4 اجرا می‌شوند، که در آن یکی از برنامه‌ها یک ویدیوی 24 هرتز را پخش می‌کند و دیگری یک لیست قابل پیمایش را به کاربر نشان می‌دهد. Pixel 4 از دو نرخ تازه‌سازی نمایشگر پشتیبانی می‌کند: 60 هرتز و 90 هرتز. با استفاده از API preferredDisplayModeId ، صفحه نمایش مجبور می‌شود 60 هرتز یا 90 هرتز را انتخاب کند. با فراخوانی تابع setFrameRate() با 24 هرتز، صفحه نمایش اطلاعات بیشتری در مورد نرخ فریم ویدیوی منبع به پلتفرم می‌دهد و پلتفرم را قادر می‌سازد تا 90 هرتز را برای نرخ تازه‌سازی نمایشگر انتخاب کند، که در این سناریو بهتر از 60 هرتز است.

با این حال، سناریوهایی وجود دارد که در آنها باید از preferredDisplayModeId به جای setFrameRate() استفاده شود، مانند موارد زیر:

  • اگر برنامه می‌خواهد وضوح تصویر یا سایر تنظیمات حالت نمایش را تغییر دهد، از preferredDisplayModeId استفاده کنید.
  • این پلتفرم فقط در پاسخ به فراخوانی تابع setFrameRate() حالت‌های نمایش را تغییر می‌دهد، اگر تغییر حالت سبک باشد و بعید است که برای کاربر قابل توجه باشد. اگر برنامه ترجیح می‌دهد نرخ تازه‌سازی صفحه نمایش را تغییر دهد، حتی اگر به تغییر حالت سنگین نیاز داشته باشد (مثلاً در یک دستگاه تلویزیون اندروید)، از preferredDisplayModeId استفاده کنید.
  • برنامه‌هایی که نمی‌توانند نمایشگر را با نرخ فریمی چند برابر نرخ فریم برنامه اجرا کنند، که نیاز به تنظیم مهر زمانی ارائه در هر فریم دارد، باید از preferredDisplayModeId استفاده کنند.

تابع setFrameRate() در مقابل تابع preferredRefreshRate

WindowManager.LayoutParams#preferredRefreshRate نرخ فریم دلخواه را روی پنجره برنامه تنظیم می‌کند و این نرخ برای تمام سطوح درون پنجره قابل اجرا است. برنامه باید نرخ فریم دلخواه خود را صرف نظر از نرخ‌های به‌روزرسانی پشتیبانی‌شده دستگاه، مشابه setFrameRate() مشخص کند تا به زمان‌بند، اشاره بهتری از نرخ فریم مورد نظر برنامه ارائه دهد.

برای سطوحی که از setFrameRate() استفاده می‌کنند، preferredRefreshRate نادیده گرفته می‌شود. در حالت کلی، در صورت امکان setFrameRate() استفاده کنید.

preferredRefreshRate در مقابل preferredDisplayModeId

اگر برنامه‌ها فقط می‌خواهند نرخ نوسازی ترجیحی را تغییر دهند، بهتر است از preferredRefreshRate به جای preferredDisplayModeId استفاده شود.

جلوگیری از فراخوانی بیش از حد setFrameRate()

اگرچه فراخوانی setFrameRate() از نظر عملکرد خیلی پرهزینه نیست، اما برنامه‌ها باید از فراخوانی setFrameRate() در هر فریم یا چندین بار در ثانیه خودداری کنند. فراخوانی‌های setFrameRate() احتمالاً منجر به تغییر در نرخ تازه‌سازی صفحه نمایش می‌شوند که ممکن است در طول انتقال منجر به افت فریم شود. شما باید نرخ فریم صحیح را از قبل تعیین کنید و setFrameRate() یک بار فراخوانی کنید.

استفاده برای بازی‌ها یا سایر برنامه‌های غیر ویدیویی

اگرچه ویدیو کاربرد اصلی API مربوط به setFrameRate() است، اما می‌توان از آن برای برنامه‌های دیگر نیز استفاده کرد. برای مثال، یک بازی که قصد دارد با فرکانس بالاتر از ۶۰ هرتز اجرا نشود (برای کاهش مصرف برق و افزایش زمان بازی)، می‌تواند Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT) را فراخوانی کند. به این ترتیب، دستگاهی که به طور پیش‌فرض با فرکانس ۹۰ هرتز اجرا می‌شود، در حین فعال بودن بازی با فرکانس ۶۰ هرتز اجرا خواهد شد که از لرزش تصویر که در صورت اجرای بازی با فرکانس ۶۰ هرتز و نمایشگر با فرکانس ۹۰ هرتز رخ می‌داد، جلوگیری می‌کند.

استفاده از 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 باشد، به مرحله ۴ بروید.
  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. نمایش تجربه کاربری برای اطلاع‌رسانی به کاربر. توجه داشته باشید که توصیه می‌کنیم روشی را پیاده‌سازی کنید که کاربر بتواند این تجربه کاربری را نادیده بگیرد و از تأخیر اضافی در مرحله ۵.د صرف‌نظر کند. دلیل این امر این است که تأخیر توصیه‌شده ما در نمایشگرهایی که زمان تعویض سریع‌تری را نشان می‌دهند، بیشتر از حد لازم است.
    2. تابع setFrameRate فراخوانی کنید و fps ، FRAME_RATE_COMPATIBILITY_FIXED_SOURCE و CHANGE_FRAME_RATE_ALWAYS به آن ارسال کنید که در آن fps نرخ فریم ویدیو است.
    3. منتظر فراخوانی onDisplayChanged باشید.
    4. ۲ ثانیه صبر کنید تا تغییر حالت کامل شود.
    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();
}