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

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

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

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

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

ليس على التطبيق مراعاة معدّلات تحديث الشاشة المتوافقة الفعلية، التي يمكن الحصول عليها من خلال استدعاء 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()، وذلك لتجنُّب حدوث أي انقطاعات مرئية.

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

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

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

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

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

أسطح متعدّدة

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

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

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

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

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

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

في بعض الحالات، قد تنتقل المنصة إلى مضاعف لعدد اللقطات في الثانية الذي حدّده التطبيق في setFrameRate(). على سبيل المثال، قد يطلب تطبيق عرض المحتوى بمعدل setFrameRate() هرتز، وقد يغيّر الجهاز معدل عرض الشاشة إلى 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 vs preferredDisplayModeId

إذا كانت التطبيقات تريد تغيير معدّل التحديث المفضّل فقط، يُفضّل استخدام preferredRefreshRate بدلاً من preferredDisplayModeId.

تجنُّب استدعاء setFrameRate() بشكل متكرّر

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

الاستخدام في الألعاب أو التطبيقات الأخرى غير المتعلقة بالفيديو

على الرغم من أنّ الفيديو هو حالة الاستخدام الأساسية لواجهة برمجة التطبيقات setFrameRate()، يمكن استخدامها مع تطبيقات أخرى. على سبيل المثال، يمكن للعبة التي لا تريد أن تعمل بمعدل أعلى من 60 هرتز (لتقليل استهلاك الطاقة وإتاحة جلسات لعب أطول) استدعاء Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT). بهذه الطريقة، سيتم تشغيل الجهاز بمعدل 60 هرتز بدلاً من 90 هرتز أثناء تشغيل اللعبة، ما سيؤدي إلى تجنُّب التقطُّع الذي كان سيحدث إذا تم تشغيل اللعبة بمعدل 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). سيحدث ذلك إذا كان 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();
}