عدد اللقطات في الثانية

تتيح واجهة برمجة التطبيقات frame rate API للتطبيقات إبلاغ نظام Android الأساسي بعدد اللقطات في الثانية المطلوب، وهي متاحة للتطبيقات التي تستهدف الإصدار Android 11 (مستوى واجهة برمجة التطبيقات 30) أو الإصدارات الأحدث. في السابق، كانت معظم الأجهزة تتيح معدّل تحديث واحدًا فقط للشاشة، وهو عادةً 60 هرتز، ولكن هذا الوضع يتغيّر. تتيح العديد من الأجهزة الآن معدلات تحديث إضافية مثل 90 هرتز أو 120 هرتز. تتيح بعض الأجهزة التبديل السلس لمعدّل التحديث، بينما يعرض البعض الآخر شاشة سوداء لفترة وجيزة، تدوم عادةً لثانية واحدة.

الغرض الأساسي من واجهة برمجة التطبيقات هو تمكين التطبيقات من الاستفادة بشكل أفضل من جميع معدلات تحديث الشاشة المتاحة. على سبيل المثال، قد يؤدي تشغيل تطبيق لفيديو بمعدل 24 هرتز يستدعي setFrameRate() إلى تغيير معدّل تحديث الشاشة في الجهاز من 60 هرتز إلى 120 هرتز. يتيح معدّل التحديث الجديد هذا تشغيل فيديو بمعدّل 24 هرتز بسلاسة وبدون اهتزاز، وبدون الحاجة إلى استخدام تقنية 3:2 pulldown كما هو مطلوب لتشغيل الفيديو نفسه على شاشة بمعدّل 60 هرتز. يؤدي هذا إلى تجربة مستخدم أفضل.

الاستخدام الأساسي

يتيح Android عدة طرق للوصول إلى الشاشات والتحكّم فيها، لذا تتوفّر عدة إصدارات من واجهة برمجة التطبيقات setFrameRate(). Each version of the API takes the same parameters and works the same as the others:

لا يحتاج التطبيق إلى مراعاة معدّلات إعادة تحميل الشاشة الفعلية المتوافقة، والتي يمكن الحصول عليها من خلال طلب Display.getSupportedModes()، لطلب setFrameRate() بأمان. على سبيل المثال، حتى إذا كان الجهاز يتوافق مع معدّل 60 هرتز فقط، يمكنك استدعاء setFrameRate() باستخدام عدد اللقطات في الثانية الذي يفضّله تطبيقك. أما الأجهزة التي لا يتوفّر فيها معدّل تحديث أفضل يتوافق مع عدد اللقطات في الثانية للتطبيق، فستستمر في استخدام معدّل تحديث الشاشة الحالي.

لمعرفة ما إذا كان استدعاء setFrameRate() يؤدي إلى تغيير في معدّل إعادة تحميل الشاشة، يمكنك التسجيل لتلقّي إشعارات تغيير الشاشة من خلال استدعاء DisplayManager.registerDisplayListener() أو AChoreographer_registerRefreshRateCallback().

عند استدعاء setFrameRate()، من الأفضل ضبط عدد اللقطات في الثانية بدقة بدلاً من تقريبه إلى عدد صحيح. على سبيل المثال، عند عرض فيديو تم تسجيله بمعدل 29.97 هرتز، يجب إدخال 29.97 بدلاً من تقريب الرقم إلى 30.

بالنسبة إلى تطبيقات الفيديو، يجب ضبط مَعلمة التوافق التي تم تمريرها إلى setFrameRate() على Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE لإعطاء تلميح إضافي لنظام Android الأساسي بأنّ التطبيق سيستخدم القائمة المنسدلة للتكيّف مع معدّل تحديث الشاشة غير المتطابق (ما سيؤدي إلى حدوث اهتزاز).

في بعض الحالات، سيتوقف سطح الفيديو عن إرسال اللقطات، ولكنه سيظل مرئيًا على الشاشة لبعض الوقت. وتشمل السيناريوهات الشائعة وصول التشغيل إلى نهاية الفيديو أو إيقاف المستخدم للتشغيل مؤقتًا. في هذه الحالات، يمكنك استدعاء setFrameRate() مع ضبط مَعلمة عدد اللقطات في الثانية على 0 لإعادة ضبط إعدادات عدد اللقطات في الثانية على السطح على القيمة التلقائية. إنّ محو إعداد عدد اللقطات في الثانية بهذه الطريقة ليس ضروريًا عند إتلاف السطح أو عندما يكون السطح مخفيًا بسبب تبديل المستخدم إلى تطبيق مختلف. يجب محو إعداد عدد اللقطات في الثانية فقط عندما يظل السطح مرئيًا بدون استخدامه.

التبديل غير السلس لعدد اللقطات في الثانية

على بعض الأجهزة، قد يؤدي تبديل معدّل التحديث إلى حدوث انقطاعات مرئية، مثل ظهور شاشة سوداء لثانية أو ثانيتين. يحدث هذا عادةً على أجهزة الاستقبال الرقمية ولوحات التلفزيون والأجهزة المماثلة. لا يبدّل إطار عمل Android الأوضاع تلقائيًا عند طلب واجهة برمجة التطبيقات Surface.setFrameRate() لتجنُّب حدوث مثل هذه الانقطاعات المرئية.

يفضل بعض المستخدمين المقاطعة المرئية في بداية ونهاية مقاطع الفيديو الأطول. يتيح ذلك مطابقة معدّل تحديث الشاشة مع عدد اللقطات في الثانية للفيديو، وتجنُّب عيوب تحويل عدد اللقطات في الثانية، مثل اهتزاز الصورة الناتج عن تحويل معدّل اللقطات من 24 إلى 30 لقطة في الثانية أثناء تشغيل الأفلام.

لهذا السبب، يمكن تفعيل عمليات تبديل معدّل التحديث غير السلسة إذا وافق كل من المستخدم والتطبيقات على ذلك:

ننصحك باستخدام CHANGE_FRAME_RATE_ALWAYS دائمًا للفيديوهات الطويلة، مثل الأفلام. ويعود السبب في ذلك إلى أنّ فائدة مطابقة عدد اللقطات في الثانية للفيديو تفوق فائدة المقاطعة التي تحدث عند تغيير معدّل إعادة التحميل.

اقتراحات إضافية

اتّبِع هذه الاقتراحات للسيناريوهات الشائعة.

مساحات عرض متعددة

تم تصميم نظام Android الأساسي للتعامل بشكل صحيح مع السيناريوهات التي تتضمّن مساحات عرض متعددة بإعدادات مختلفة لعدد اللقطات في الثانية. عندما يتضمّن تطبيقك عدة مساحات عرض ذات عدد لقطات في الثانية مختلف، يمكنك استدعاء setFrameRate() مع تحديد عدد اللقطات في الثانية الصحيح لكل مساحة عرض. حتى إذا كان الجهاز يشغّل تطبيقات متعددة في الوقت نفسه، باستخدام وضع "تقسيم الشاشة" أو "نافذة ضمن النافذة"، يمكن لكل تطبيق استدعاء setFrameRate() بأمان لمساحات العرض الخاصة به.

لا تتغيّر المنصة إلى عدد اللقطات في الثانية للتطبيق

حتى إذا كان الجهاز يتيح عدد اللقطات في الثانية الذي يحدّده التطبيق في طلب إلى setFrameRate()، هناك حالات لا يبدّل فيها الجهاز الشاشة إلى معدّل التحديث هذا. على سبيل المثال، قد يكون لسطح ذي أولوية أعلى إعداد مختلف لعدد اللقطات في الثانية، أو قد يكون الجهاز في وضع "توفير شحن البطارية" (يتم ضبط قيد على معدّل تحديث الشاشة للحفاظ على شحن البطارية). يجب أن يستمر التطبيق في العمل بشكل صحيح عندما لا يغيّر الجهاز معدّل تحديث الشاشة إلى إعداد عدد اللقطات في الثانية للتطبيق، حتى إذا كان الجهاز يغيّر المعدّل في الظروف العادية.

يعود الأمر إلى التطبيق لتحديد كيفية الاستجابة عندما لا يتطابق معدّل تحديث الشاشة مع عدد اللقطات في الثانية في التطبيق. بالنسبة إلى الفيديو، يتم تثبيت عدد اللقطات في الثانية على عدد اللقطات في الثانية للفيديو المصدر، وسيكون من الضروري استخدام تقنية pulldown لعرض محتوى الفيديو. قد تختار اللعبة بدلاً من ذلك محاولة التشغيل بمعدّل تحديث الشاشة بدلاً من البقاء على عدد اللقطات في الثانية المفضّل لديها. يجب ألا يغيّر التطبيق القيمة التي يمررها إلى setFrameRate() استنادًا إلى ما يفعله النظام الأساسي. ويجب أن يظل مضبوطًا على عدد اللقطات في الثانية المفضّل للتطبيق، بغض النظر عن كيفية تعامل التطبيق مع الحالات التي لا يتم فيها ضبط النظام الأساسي ليتوافق مع طلب التطبيق. بهذه الطريقة، إذا تغيّرت ظروف الجهاز للسماح باستخدام معدّلات تحديث إضافية للشاشة، ستحصل المنصة على المعلومات الصحيحة للتبديل إلى عدد اللقطات في الثانية المفضّل للتطبيق.

في الحالات التي لا يتم فيها تشغيل التطبيق بمعدّل تحديث الشاشة أو لا يمكن تشغيله، يجب أن يحدّد التطبيق الطوابع الزمنية للعرض لكل لقطة، وذلك باستخدام إحدى آليات النظام الأساسي لضبط الطوابع الزمنية للعرض:

يؤدي استخدام هذه الطوابع الزمنية إلى منع المنصة من عرض إطار التطبيق في وقت مبكر جدًا، ما قد يؤدي إلى حدوث اهتزاز غير ضروري. إنّ الاستخدام الصحيح للطوابع الزمنية لعرض الإطارات أمر صعب بعض الشيء. بالنسبة إلى الألعاب، يُرجى الاطّلاع على دليل معدّل عرض اللقطات لمزيد من المعلومات حول تجنُّب الاهتزاز، وننصحك باستخدام مكتبة معدّل عرض اللقطات في Android.

في بعض الحالات، قد تنتقل المنصة إلى مضاعف لعدد اللقطات في الثانية الذي حدّده التطبيق في setFrameRate(). على سبيل المثال، قد يطلب أحد التطبيقات setFrameRate() بتردد 60 هرتز وقد يبدّل الجهاز الشاشة إلى 120 هرتز. قد يحدث ذلك إذا كان هناك تطبيق آخر يحتوي على سطح تم ضبط عدد اللقطات في الثانية فيه على 24 هرتز. في هذه الحالة، سيؤدي تشغيل الشاشة بمعدّل 120 هرتز إلى السماح بتشغيل كل من سطح 60 هرتز وسطح 24 هرتز بدون الحاجة إلى سحب لأسفل.

عندما تكون الشاشة تعمل بمعدّل لقطات في الثانية مضاعف لمعدّل اللقطات في الثانية للتطبيق، يجب أن يحدّد التطبيق الطوابع الزمنية للعرض لكل لقطة لتجنُّب الاهتزاز غير الضروري. بالنسبة إلى الألعاب، تكون مكتبة Android Frame Pacing مفيدة في ضبط الطوابع الزمنية لعرض اللقطات بشكل صحيح.

‫setFrameRate()‎ مقابل preferredDisplayModeId

WindowManager.LayoutParams.preferredDisplayModeId هي طريقة أخرى يمكن للتطبيقات من خلالها إبلاغ النظام الأساسي بعدد اللقطات في الثانية. تريد بعض التطبيقات تغيير معدّل تحديث الشاشة فقط بدلاً من تغيير إعدادات وضع العرض الأخرى، مثل درجة دقة العرض. بشكل عام، استخدِم setFrameRate() بدلاً من preferredDisplayModeId. تُعدّ الدالة setFrameRate() أسهل في الاستخدام لأنّ التطبيق لا يحتاج إلى البحث في قائمة أوضاع العرض للعثور على وضع يتضمّن عدد اللقطات في الثانية معيّن.

يمنح setFrameRate() المنصة المزيد من الفرص لاختيار عدد لقطات في الثانية متوافق في السيناريوهات التي تتضمّن مساحات عرض متعددة تعمل بأعداد لقطات في الثانية مختلفة. على سبيل المثال، لنفترض أنّ هناك تطبيقَين قيد التشغيل في وضع تقسيم الشاشة على هاتف Pixel 4، حيث يشغّل أحد التطبيقَين فيديو بمعدّل 24 هرتز ويعرض التطبيق الآخر للمستخدم قائمة قابلة للتمرير. يتوافق هاتف Pixel 4 مع معدّلَي تحديث للشاشة: 60 هرتز و90 هرتز. باستخدام واجهة برمجة التطبيقات preferredDisplayModeId، يتم فرض اختيار معدّل تحديث 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()، يمكن استخدامها في تطبيقات أخرى. على سبيل المثال، يمكن للعبة التي لا تهدف إلى العمل بمعدّل أعلى من 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) عند عرض فيديوهات طويلة، مثل الأفلام، حيث يشير 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 مختلفين و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. انتظِر لمدة ثانيتين حتى يكتمل تبديل الوضع.
    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();
}