سرعت کادر

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

هدف اصلی این API این است که برنامه‌ها بتوانند از همه نرخ‌های بازآوری نمایشگر پشتیبانی‌شده بهتر استفاده کنند. برای مثال، برنامه‌ای که ویدیو ۲۴ هرتز پخش می‌کند و setFrameRate() را فراخوانی می‌کند ممکن است باعث شود دستگاه نرخ بازآوری نمایشگر را از ۶۰ هرتز به ۱۲۰ هرتز تغییر دهد. این نرخ بازآوری جدید امکان بازپخش روان و بدون لرزش ویدیو ۲۴ هرتز را فراهم می‌کند، بدون نیاز به تبدیل ۳:۲ که برای پخش همان ویدیو در نمایشگر ۶۰ هرتز لازم است. این کار منجر به تجربه کاربری بهتری می‌شود.

استفاده پایه

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

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

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

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

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

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

تغییر نرخ فریم غیریکپارچه

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

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

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

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

توصیه‌های بیشتر

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

چند سطح

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

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

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

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

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

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

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

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

setFrameRate()‎ درمقابل preferredDisplayModeId

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

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

بااین‌حال، سناریوهایی وجود دارد که در آن‌ها باید از 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 است، اما می‌تواند برای برنامه‌های دیگر نیز استفاده شود. برای مثال، بازی‌ای که قصد دارد با فرکانس بالاتر از ۶۰ هرتز اجرا نشود (برای کاهش مصرف برق و دستیابی به جلسات بازی طولانی‌تر) می‌تواند 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. نمایش تجربه کاربری برای مطلع کردن کاربر. توجه داشته باشید که توصیه می‌کنیم روشی را برای کاربر پیاده‌سازی کنید تا بتواند این UX را رد کند و از تأخیر اضافی در مرحله ۵.د صرف‌نظر کند. دلیل این امر این است که تأخیر توصیه‌شده ما بیشتر از حد لازم در نمایشگرهایی است که زمان‌های تعویض سریع‌تری را نشان می‌دهند.
    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();
}