نرخ فریم

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

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

استفاده اساسی

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

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

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

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

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

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

سوئیچ نرخ فریم بدون درز

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

برخی از کاربران یک وقفه بصری را در ابتدا و انتهای ویدیوهای طولانی تر ترجیح می دهند. این اجازه می دهد تا نرخ تازه سازی نمایشگر با نرخ فریم ویدیو مطابقت داشته باشد و از مصنوعات تبدیل نرخ فریم مانند لرزش 3:2 برای پخش فیلم جلوگیری شود.

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

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

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

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

سطوح متعدد

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

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

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

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

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

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

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

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

setFrameRate() در مقابل preferredDisplayModeId

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

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

با این حال، سناریوهایی وجود دارد که در آن 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 است، اما می توان از آن برای سایر برنامه ها استفاده کرد. برای مثال، بازی‌ای که قصد دارد بالاتر از 60 هرتز اجرا نشود (برای کاهش مصرف انرژی و دستیابی به جلسات بازی طولانی‌تر)، می‌تواند Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT) را فراخوانی کند. به این ترتیب، دستگاهی که به طور پیش‌فرض با فرکانس 90 هرتز کار می‌کند، در حالی که بازی فعال است، در عوض با فرکانس 60 هرتز کار می‌کند، که در غیر این صورت اگر بازی با فرکانس 60 هرتز اجرا می‌شد در حالی که نمایشگر با فرکانس 90 هرتز کار می‌کرد، از تکان خوردن جلوگیری می‌کند.

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