خيارات الإعداد

يمكنك ضبط كل حالة استخدام من حالات استخدام CameraX للتحكّم في جوانب مختلفة من عمليات حالة الاستخدام.

على سبيل المثال، في حالة استخدام "التقاط الصور"، يمكنك ضبط نسبة عرض إلى ارتفاع مستهدفة ووضع فلاش. يعرض الرمز البرمجي التالي مثالاً واحدًا:

Kotlin

val imageCapture = ImageCapture.Builder()
    .setFlashMode(...)
    .setTargetAspectRatio(...)
    .build()

Java

ImageCapture imageCapture =
    new ImageCapture.Builder()
        .setFlashMode(...)
        .setTargetAspectRatio(...)
        .build();

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

CameraXConfig

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

باستخدام CameraXConfig، يمكن للتطبيق تنفيذ ما يلي:

نموذج الاستخدام

توضّح الخطوات التالية كيفية استخدام CameraXConfig:

  1. أنشئ عنصر CameraXConfig باستخدام الإعدادات المخصّصة.
  2. نفِّذ واجهة CameraXConfig.Provider في Application، وأرِد عنصر CameraXConfig في getCameraXConfig().
  3. أضِف فئة Application إلى ملف AndroidManifest.xml كما هو описанهنا.

على سبيل المثال، يحصر نموذج الرمز البرمجي التالي تسجيل CameraX في رسائل الخطأ فقط:

Kotlin

class CameraApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
           .setMinimumLoggingLevel(Log.ERROR).build()
   }
}

احتفظ بنسخة محلية من عنصر CameraXConfig إذا كان تطبيقك يحتاج إلى معرفة إعدادات CameraX بعد ضبطها.

أداة وضع الحدّ الأقصى المسموح به في الكاميرا

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

إذا تم استبعاد كاميرا من خلال CameraSelector المرسَلة إلى CameraXConfig.Builder.setAvailableCamerasLimiter() ، سيتصرف CameraX كما لو أنّ هذه الكاميرا غير متوفّرة. على سبيل المثال، يحدّ الرمز البرمجي التالي من استخدام التطبيق لكاميرا الالتقاط الخلفي التلقائية فقط في الجهاز:

Kotlin

class MainApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
              .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA)
              .build()
   }
}

Threads

تتطلّب العديد من واجهات برمجة التطبيقات الخاصة بالنظام الأساسي التي تم إنشاء CameraX استنادًا إليها حظر التواصل بين العمليات (IPC) مع الأجهزة التي يمكن أن تستغرق أحيانًا مئات المللي ثانية للردّ. لهذا السبب، لا تستدعي CameraX واجهات برمجة التطبيقات هذه إلا من سلاسل التعليمات التي تعمل في الخلفية، وذلك حتى لا يتم حظر السلسلة الرئيسية وتبقى واجهة المستخدم płynna. تدير CameraX هذه سلاسل المهام في الخلفية داخليًا لكي يبدو هذا السلوك شفافيًا. ومع ذلك، تتطلّب بعض التطبيقات التحكّم بشكل صارم في سلاسل المحادثات. يتيح CameraXConfig للتطبيق ضبط سلاسل المهام التي تعمل في الخلفية التي يتم استخدامها من خلال CameraXConfig.Builder.setCameraExecutor() و CameraXConfig.Builder.setSchedulerHandler().

منفّذ الكاميرا

يتم استخدام أداة تنفيذ الكاميرا لجميع طلبات البيانات الداخلية من Camera platform API، بالإضافة إلى طلبات البيانات المرسَلة من هذه واجهات برمجة التطبيقات. تخصص CameraX Executor داخليًا وتُديره لتنفيذ هذه المهام. ومع ذلك، إذا كان تطبيقك يتطلّب التحكّم بشكل أكثر صرامة في سلاسل المحادثات، استخدِم CameraXConfig.Builder.setCameraExecutor().

معالج المخطِّط

يُستخدَم معالِج المخطِّط لتحديد موعد المهام الداخلية على فترات ثابتة، مثل إعادة محاولة فتح الكاميرا عندما لا تكون متاحة. لا يؤدي معالِج الأحداث هذا إلى تنفيذ المهام، بل يرسلها فقط إلى مشغّل الكاميرا. ويُستخدَم أيضًا في بعض الأحيان على منصات واجهات برمجة التطبيقات القديمة التي تتطلّب استخدام Handler لطلبات إعادة الاتصال. في هذه الحالات، لا يتم إرسال callbacks إلا إلى مشغّل الكاميرا مباشرةً. تحدِّد CameraX HandlerThread داخليًا وتُديره لتنفيذ هذه المهام، ولكن يمكنك إلغاء ذلك باستخدام CameraXConfig.Builder.setSchedulerHandler().

التسجيل

تتيح ميزة تسجيل CameraX للتطبيقات فلترة رسائل logcat، لأنّه من الجيد تجنُّب الرسائل المفصّلة في رمز الإصدار العلني. تتيح CameraX أربعة مستويات تسجيل، بدءًا من المستوى الأكثر تفصيلاً إلى المستوى الأكثر صرامة:

  • Log.DEBUG (تلقائي)
  • Log.INFO
  • Log.WARN
  • Log.ERROR

يُرجى الرجوع إلى مستندات سجلّ Android للحصول على أوصاف تفصيلية لمستويات السجلّ هذه. استخدِم CameraXConfig.Builder.setMinimumLoggingLevel(int) لضبط مستوى التسجيل المناسب لتطبيقك.

الاختيار التلقائي

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

هدف CameraX هو إعداد جلسة الكاميرا بنجاح. وهذا يعني أنّه يُجري CameraX تسوية في درجة الدقة ونسَب العرض إلى الارتفاع استنادًا إلى إمكانات الجهاز. يمكن أن يحدث اختراق الحساب للأسباب التالية:

  • لا يتيح الجهاز درجة الدقة المطلوبة.
  • الجهاز يواجه مشاكل في التوافق، مثل الأجهزة القديمة التي تتطلّب درجات دقة معيّنة للعمل بشكل صحيح.
  • على بعض الأجهزة، لا تتوفّر بعض التنسيقات إلا بنسبة قياس معيّنة.
  • يفضّل الجهاز استخدام أسلوب "أقرب عدد صحيح mod16" لترميز JPEG أو الفيديو. لمزيد من المعلومات، يُرجى الاطّلاع على SCALER_STREAM_CONFIGURATION_MAP.

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

الدوران

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

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

قد يتم تخزين بيانات الصور التي تم التقاطها بدون معلومات حول دورانها. تحتوي بيانات Exif على معلومات حول التدوير حتى تتمكّن تطبيقات معرض الصور من عرض الصورة في الاتجاه الصحيح بعد حفظها.

لعرض بيانات المعاينة بالاتجاه الصحيح، يمكنك استخدام المخرجات الخاصة بالبيانات الوصفية من Preview.PreviewOutput() لإنشاء عمليات التحويل.

يوضّح نموذج الرمز البرمجي التالي كيفية ضبط عملية التدوير في حدث الاتجاه:

Kotlin

override fun onCreate() {
    val imageCapture = ImageCapture.Builder().build()

    val orientationEventListener = object : OrientationEventListener(this as Context) {
        override fun onOrientationChanged(orientation : Int) {
            // Monitors orientation values to determine the target rotation value
            val rotation : Int = when (orientation) {
                in 45..134 -> Surface.ROTATION_270
                in 135..224 -> Surface.ROTATION_180
                in 225..314 -> Surface.ROTATION_90
                else -> Surface.ROTATION_0
            }

            imageCapture.targetRotation = rotation
        }
    }
    orientationEventListener.enable()
}

Java

@Override
public void onCreate() {
    ImageCapture imageCapture = new ImageCapture.Builder().build();

    OrientationEventListener orientationEventListener = new OrientationEventListener((Context)this) {
       @Override
       public void onOrientationChanged(int orientation) {
           int rotation;

           // Monitors orientation values to determine the target rotation value
           if (orientation >= 45 && orientation < 135) {
               rotation = Surface.ROTATION_270;
           } else if (orientation >= 135 && orientation < 225) {
               rotation = Surface.ROTATION_180;
           } else if (orientation >= 225 && orientation < 315) {
               rotation = Surface.ROTATION_90;
           } else {
               rotation = Surface.ROTATION_0;
           }

           imageCapture.setTargetRotation(rotation);
       }
    };

    orientationEventListener.enable();
}

استنادًا إلى عملية التدوير المحدّدة، يُحوّل كل حالة استخدام بيانات الصورة بشكل مباشر أو يقدّم بيانات وصفية للتدوير لمستهلكي بيانات الصورة غير المُحوَّلة.

  • المعاينة: يتم توفير البيانات الوصفية لتحديد دوران دقة الهدف باستخدام Preview.getTargetRotation().
  • تحليل الصور: يتم توفير مخرجات البيانات الوصفية لكي تكون إحداثيات ملف التخزين المؤقت للصورة معلومة بالنسبة إلى إحداثيات الشاشة.
  • ImageCapture: يتم تغيير البيانات الوصفية Exif للصورة أو المخزن المؤقت أو كليهما لتسجيل إعدادات التدوير. تعتمد القيمة التي تم تغييرها على طريقة تنفيذ HAL.

مستطيل الاقتصاص

بشكلٍ افتراضي، يكون مستطيل الاقتصاص هو مستطيل المخزن المؤقت الكامل. ويمكنك تخصيصها باستخدام ViewPort و UseCaseGroup. من خلال تجميع حالات الاستخدام وضبط مساحة العرض، تضمن CameraX أنّ مستطيلات الاقتصاص لجميع حالات الاستخدام في المجموعة تشير إلى المنطقة نفسها في حساس الكاميرا.

يوضّح المقتطف التالي من الرمز كيفية استخدام هاتين الفئتَين:

Kotlin

val viewPort =  ViewPort.Builder(Rational(width, height), display.rotation).build()
val useCaseGroup = UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup)

Java

ViewPort viewPort = new ViewPort.Builder(
         new Rational(width, height),
         getDisplay().getRotation()).build();
UseCaseGroup useCaseGroup = new UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build();
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup);

تحدِّد ViewPort مستطيل التخزين المؤقت المرئي للمستخدمين النهائيين. بعد ذلك، تحتسب CameraX أكبر مستطيل اقتصاص ممكن استنادًا إلى خصائص مساحة العرض و حالات الاستخدام المرفقة. عادةً، لتحقيق تأثير WYSIWYG، يمكنك ضبط إطار العرض استنادًا إلى حالة استخدام المعاينة. يمكنك الحصول على إطار العرض باستخدام PreviewView.

توضِّح المقتطفات التالية من الرموز البرمجية كيفية الحصول على عنصر ViewPort:

Kotlin

val viewport = findViewById<PreviewView>(R.id.preview_view).viewPort

Java

ViewPort viewPort = ((PreviewView)findViewById(R.id.preview_view)).getViewPort();

في المثال السابق، يتطابق ما يحصل عليه التطبيق من ImageAnalysis و ImageCapture مع ما يراه المستخدم النهائي في PreviewView، بافتراض أنّه تم ضبط نوع مقياس PreviewView على الإعداد التلقائي، FILL_CENTER. بعد تطبيق مستطيل الاقتصاص والدوران على ذاكرة التخزين المؤقت للإخراج، تكون الصورة من جميع حالات الاستخدام نفسها، إلا أنّه من المحتمل أن تكون بدرجات دقة مختلفة. لمزيد من المعلومات عن كيفية تطبيق معلومات التحويل، اطّلِع على transform output.

اختيار الكاميرا

يختار CameraX تلقائيًا أفضل جهاز كاميرا لمتطلبات تطبيقك وحالات استخدامه. إذا أردت استخدام جهاز مختلف عن الجهاز الذي تم اختياره لك، إليك بعض الخيارات:

يوضّح نموذج الرمز البرمجي التالي كيفية إنشاء CameraSelector لتأثيره في اختيار الجهاز:

Kotlin

fun selectExternalOrBestCamera(provider: ProcessCameraProvider):CameraSelector? {
   val cam2Infos = provider.availableCameraInfos.map {
       Camera2CameraInfo.from(it)
   }.sortedByDescending {
       // HARDWARE_LEVEL is Int type, with the order of:
       // LEGACY < LIMITED < FULL < LEVEL_3 < EXTERNAL
       it.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
   }

   return when {
       cam2Infos.isNotEmpty() -> {
           CameraSelector.Builder()
               .addCameraFilter {
                   it.filter { camInfo ->
                       // cam2Infos[0] is either EXTERNAL or best built-in camera
                       val thisCamId = Camera2CameraInfo.from(camInfo).cameraId
                       thisCamId == cam2Infos[0].cameraId
                   }
               }.build()
       }
       else -> null
    }
}

// create a CameraSelector for the USB camera (or highest level internal camera)
val selector = selectExternalOrBestCamera(processCameraProvider)
processCameraProvider.bindToLifecycle(this, selector, preview, analysis)

اختيار كاميرات متعددة في الوقت نفسه

بدءًا من الإصدار 1.3 من CameraX، يمكنك أيضًا اختيار كاميرات متعددة في الوقت نفسه. على سبيل المثال، يمكنك ربط الكاميرا الأمامية والخلفية لالتقاط صور أو تسجيل فيديوهات من كلا الجانبَين في الوقت نفسه.

عند استخدام ميزة "الكاميرا المتزامنة"، يمكن للجهاز تشغيل كاميرتَين بعدسات ذات اتجاهَين مختلفَين في الوقت نفسه، أو تشغيل كاميرتَين خلفيتَين في الوقت نفسه. توضِّح مجموعة الرموز البرمجية التالية كيفية ضبط كاميرتَين عند استدعاء bindToLifecycle، وكيفية الحصول على كلا عنصرَي Camera من عنصر ConcurrentCamera الذي تم إرجاعه.

Kotlin

// Build ConcurrentCameraConfig
val primary = ConcurrentCamera.SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val secondary = ConcurrentCamera.SingleCameraConfig(
    secondaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val concurrentCamera = cameraProvider.bindToLifecycle(
    listOf(primary, secondary)
)

val primaryCamera = concurrentCamera.cameras[0]
val secondaryCamera = concurrentCamera.cameras[1]

Java

// Build ConcurrentCameraConfig
SingleCameraConfig primary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

SingleCameraConfig secondary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

ConcurrentCamera concurrentCamera =  
    mCameraProvider.bindToLifecycle(Arrays.asList(primary, secondary));

Camera primaryCamera = concurrentCamera.getCameras().get(0);
Camera secondaryCamera = concurrentCamera.getCameras().get(1);

درجة دقة الكاميرا

يمكنك اختيار السماح لـ CameraX بضبط درجة دقة الصورة استنادًا إلى مجموعة من ميزات الجهاز ومستوى الأجهزة المتوافقة وحالة الاستخدام ونسبة العرض إلى الارتفاع المقدَّمة. بدلاً من ذلك، يمكنك ضبط دقة تهدف إليها أو نسبة عرض إلى ارتفاع محدّدة في حالات الاستخدام التي تتيح هذا الإعداد.

الدقة التلقائية

يمكن لـ CameraX تحديد أفضل إعدادات الدقة تلقائيًا استنادًا إلى حالات الاستخدام المحدّدة في cameraProcessProvider.bindToLifecycle(). كلما كان ذلك ممكنًا، حدِّد جميع حالات الاستخدام المطلوبة لتشغيلها بشكل متزامن في جلسة واحدة في مكالمة bindToLifecycle() واحدة. تحدِّد CameraX درجات الدقة استنادًا إلى مجموعة حالات الاستخدام المرتبطة بالمستوى المتوافق للأجهزة وبما يراعي التباين الخاص بالجهاز (حيث يتجاوز الجهاز إعدادات البث المتوفّرة أو لا يستوفيها). والهدف من ذلك هو السماح بتشغيل التطبيق على مجموعة متنوعة من الأجهزة مع تقليل مسارات الرموز البرمجية الخاصة بالأجهزة.

نسبة العرض إلى الارتفاع التلقائية لحالات استخدام التقاط الصور وتحليلها هي 4:3.

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

على سبيل المثال، يمكن للتطبيق تنفيذ أي مما يلي:

  • تحديد درجة دقة مستهدَفة بنسبة عرض إلى ارتفاع 4:3 أو 16:9 لحالة استخدام
  • تحديد دقة مخصّصة يحاول CameraX العثور على أقرب مطابقة لها
  • تحديد نسبة عرض إلى ارتفاع للاقتصاص في ImageCapture

يختار CameraX دقة عرض Camera2 الداخلية تلقائيًا. يعرض الجدول التالي درجات الدقة:

حالة الاستخدام درجة دقة السطح الداخلي دقة بيانات الإخراج
معاينة نسبة العرض إلى الارتفاع: هي درجة الدقة التي تناسب الهدف بشكلٍ أفضل في الإعداد. درجة دقة السطح الداخلي يتم توفير البيانات الوصفية للسماح بقطع "طريقة العرض" وتغيير حجمها وتدويرها لتناسب نسبة العرض إلى الارتفاع المستهدَفة.
درجة الدقة التلقائية: هي أعلى درجة دقة للمعاينة أو أعلى درجة دقّة يفضّلها الجهاز وتتطابق مع نسبة العرض إلى الارتفاع للمعاينة.
الحد الأقصى للدقة: حجم المعاينة، ويشير إلى أفضل حجم يتوافق مع درجة دقة شاشة الجهاز أو 1080p (1920x1080)، أيهما أصغر.
تحليل الصور نسبة العرض إلى الارتفاع: هي درجة الدقة التي تناسب الهدف بشكلٍ أفضل في الإعداد. درجة دقة السطح الداخلي
درجة الدقة التلقائية: الإعداد التلقائي لدرجة الدقة المستهدَفة هو 640×480. يؤدي تعديل كلّ من درجة الدقة المستهدَفة ونسبة العرض إلى الارتفاع المقابلة إلى الحصول على أفضل درجة دقة متوافقة.
الحد الأقصى للدقة: الحد الأقصى لدقة الإخراج لجهاز الكاميرا بتنسيق YUV_420_888 الذي يتم استرجاعه من StreamConfigurationMap.getOutputSizes(). يتم ضبط درجة الدقة المستهدَفة على 640×480 تلقائيًا، لذا إذا أردت درجة دقة أكبر من 640×480، عليك استخدام setTargetResolution() و setTargetAspectRatio() للحصول على أقرب درجة دقة من درجات الدقة المتوافقة.
التقاط الصور نسبة العرض إلى الارتفاع: نسبة العرض إلى الارتفاع التي تناسب الإعداد على أفضل وجه درجة دقة السطح الداخلي
الدقة التلقائية: هي أعلى دقة متاحة أو أعلى درجة دقة يفضّلها الجهاز وتتطابق مع نسبة العرض إلى الارتفاع في ImageCapture.
الحد الأقصى لدرجة الدقة: الحد الأقصى لدرجة دقة الإخراج لجهاز الكاميرا بتنسيق JPEG استخدِم StreamConfigurationMap.getOutputSizes() لاسترداد هذا الرمز.

تحديد درجة دقة

يمكنك ضبط درجات دقة معيّنة عند إنشاء حالات الاستخدام باستخدام الأسلوب setTargetResolution(Size resolution)، كما هو موضّح في المثال التالي على الرمز العميق:

Kotlin

val imageAnalysis = ImageAnalysis.Builder()
    .setTargetResolution(Size(1280, 720))
    .build()

Java

ImageAnalysis imageAnalysis =
  new ImageAnalysis.Builder()
    .setTargetResolution(new Size(1280, 720))
    .build();

لا يمكنك ضبط نسبة العرض إلى الارتفاع المستهدَفة ودرجة الدقة المستهدَفة في حالة الاستخدام نفسها. يؤدي ذلك إلى ظهور IllegalArgumentException عند إنشاء ملف تعريف العميل.

عبِّر عن درجة الدقة Size في إطار الإحداثيات بعد تدوير الأحجام المتوافقة حسب درجة التدوير المستهدَفة. على سبيل المثال، يمكن لجهاز بالاتجاه العمودي الطبيعي في الاتجاه الطبيعي المستهدَف الذي يطلب صورة عمودية تحديد 480×640، ويمكن للجهاز نفسه الذي تم تدويره 90 درجة والذي يستهدف الاتجاه الأفقي تحديد 640×480.

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

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

تطبِّق CameraX أفضل درجة دقة مناسبة استنادًا إلى الطلبات. إذا كانت الحاجة الأساسية هي استيفاء نسبة العرض إلى الارتفاع، حدِّد setTargetAspectRatio فقط، وحدِّد CameraX درجة دقة معيّنة مناسبة استنادًا إلى الجهاز. إذا كانت الحاجة الأساسية للتطبيق هي تحديد درجة دقة لجعل معالجة الصور أكثر فعالية (على سبيل المثال، صورة صغيرة أو متوسطة الحجم استنادًا إلى قدرة الجهاز على المعالجة)، استخدِم setTargetResolution(Size resolution).

إذا كان تطبيقك يتطلّب درجة دقة معيّنة، اطّلِع على الجدول ضمن createCaptureSession() لتحديد الحد الأقصى لدرجات الدقة المتوافقة مع كل مستوى من مستويات الأجهزة. للاطّلاع على درجات الدقة المحدّدة التي يتوافق معها الجهاز الحالي، يُرجى الاطّلاع على StreamConfigurationMap.getOutputSizes(int).

إذا كان تطبيقك يعمل بنظام التشغيل Android 10 أو إصدار أحدث، يمكنك استخدام isSessionConfigurationSupported() لإثبات ملكية SessionConfiguration معيّن.

التحكّم في إخراج الكاميرا

بالإضافة إلى السماح لك بضبط إخراج الكاميرا حسب الحاجة لكل حالة استخدام فردیة، تطبِّق CameraX أيضًا الواجهات التالية لتفعيل عمليات الكاميرا الشائعة لجميع حالات الاستخدام المرتبطة:

  • يتيح لك تطبيق CameraControl ضبط ميزات الكاميرا الشائعة.
  • يتيح لك CameraInfo طلب حالات ميزات الكاميرا الشائعة هذه.

في ما يلي ميزات الكاميرا المتوافقة مع CameraControl:

  • Zoom
  • ضوء
  • التركيز والقياس (النقر للتركيز)
  • تعويض درجة الإضاءة

الحصول على نُسخ من CameraControl وCameraInfo

استرِدّ نُسخًا من CameraControl وCameraInfo باستخدام عنصر Camera الذي يعرضه ProcessCameraProvider.bindToLifecycle(). يوضّح الرمز البرمجي التالي مثالاً:

Kotlin

val camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
val cameraControl = camera.cameraControl
// For querying information and states.
val cameraInfo = camera.cameraInfo

Java

Camera camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
CameraControl cameraControl = camera.getCameraControl()
// For querying information and states.
CameraInfo cameraInfo = camera.getCameraInfo()

على سبيل المثال، يمكنك إرسال عمليات التكبير/التصغير وغيرها من عمليات CameraControl بعد استدعاء bindToLifecycle(). بعد إيقاف النشاط المستخدَم لربط مثيل الكاميرا أو إزالته، لن يعود بإمكان CameraControl تنفيذ العمليات ويُعرِضListenableFuture تعذُّرًا.

Zoom

يوفّر CameraControl طريقتَين لتغيير مستوى التكبير/التصغير:

  • setZoomRatio() يضبط التكبير أو التصغير حسب نسبة التكبير أو التصغير.

    يجب أن تكون النسبة ضمن النطاق الذي يتراوح بين CameraInfo.getZoomState().getValue().getMinZoomRatio() و CameraInfo.getZoomState().getValue().getMaxZoomRatio(). بخلاف ذلك، تعرِض الدالةListenableFuture تعذّر.

  • setLinearZoom() يضبط مستوى التكبير/التصغير الحالي باستخدام قيمة تكبير/تصغير خطية تتراوح بين 0 و1.0.

    تتمثل ميزة التكبير/التصغير الخطي في أنّه يجعل مجال الرؤية (FOV) يتزايد مع التغييرات في التكبير/التصغير. ما يجعله مثاليًا للاستخدام مع عرض Slider.

CameraInfo.getZoomState() يعرض LiveData لحالة التصغير/التكبير الحالية. تتغيّر القيمة عند بدء استخدام الكاميرا أو في حال ضبط مستوى التكبير/التصغير باستخدام setZoomRatio() أو setLinearZoom(). يؤدي استدعاء أي من الطريقتَين إلى ضبط القيم التي تدعم ZoomState.getZoomRatio() و ZoomState.getLinearZoom(). يكون هذا مفيدًا إذا كنت تريد عرض نص نسبة التكبير/التصغير بجانب شريط تمرير. ما عليك سوى مراقبة ZoomState LiveData لتعديل كليهما بدون الحاجة إلى إجراء تحويل.

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

ضوء

CameraControl.enableTorch(boolean) تفعيل ضوء الفلاش أو إيقافه

يمكن استخدام CameraInfo.getTorchState() للاستعلام عن حالة مصباح يدوي الحالي. يمكنك التحقّق من القيمة المعروضة بواسطة CameraInfo.hasFlashUnit() لتحديد ما إذا كان مصباح يدوي متوفّرًا. وإذا لم يكن الأمر كذلك، يؤدي استدعاء CameraControl.enableTorch(boolean) إلى إكمالListenableFuture المعروض على الشاشة على الفور بنتيجة تشير إلى تعذُّر إكمال العملية وضبط حالة مصباح يدوي على TorchState.OFF.

عند تفعيل ضوء الفلاش، يظل مُفعَّلاً أثناء التقاط الصور والفيديوهات بغض النظر عن إعداد flashMode. لا يعمل الرمز flashMode في ImageCapture إلا عندما يكون مصباح يدوي غير مفعَّل.

التركيز وقياس الإضاءة

CameraControl.startFocusAndMetering() يؤدي إلى بدء التركيز التلقائي وقياس التعريض من خلال ضبط مناطق قياس التركيز التلقائي/التعريض/التوازن التلقائي للأبيض والأسود استنادًا إلى FocusMeteringAction المحدَّد. ويُستخدَم هذا الإجراء غالبًا لتنفيذ ميزة "النقر للتركيز" في العديد من تطبيقات الكاميرا.

MeteringPoint

للبدء، أنشئ MeteringPoint باستخدام MeteringPointFactory.createPoint(float x, float y, float size). يمثّل الرمز MeteringPoint نقطة واحدة على الكاميرا Surface. ويتم تخزينه في تنسيق موحّد حتى يمكن تحويله بسهولة إلى إحداثيات أداة الاستشعار لتحديد مناطق ضبط التركيز التلقائي/ضبط الإضاءة التلقائية/ضبط التوازن الأبيض التلقائي.

يتراوح حجم MeteringPoint بين 0 و1، مع حجم تلقائي هو 0.15f. عند استدعاء MeteringPointFactory.createPoint(float x, float y, float size)، تنشئ CameraX منطقة مستطيلة تتمحور حول (x, y) للملف الشخصي الذي تم تقديمه size.

يوضّح الرمز البرمجي التالي كيفية إنشاء MeteringPoint:

Kotlin

// Use PreviewView.getMeteringPointFactory if PreviewView is used for preview.
previewView.setOnTouchListener((view, motionEvent) ->  {
val meteringPoint = previewView.meteringPointFactory
    .createPoint(motionEvent.x, motionEvent.y)

}

// Use DisplayOrientedMeteringPointFactory if SurfaceView / TextureView is used for
// preview. Please note that if the preview is scaled or cropped in the View,
// it’s the application's responsibility to transform the coordinates properly
// so that the width and height of this factory represents the full Preview FOV.
// And the (x,y) passed to create MeteringPoint might need to be adjusted with
// the offsets.
val meteringPointFactory = DisplayOrientedMeteringPointFactory(
     surfaceView.display,
     camera.cameraInfo,
     surfaceView.width,
     surfaceView.height
)

// Use SurfaceOrientedMeteringPointFactory if the point is specified in
// ImageAnalysis ImageProxy.
val meteringPointFactory = SurfaceOrientedMeteringPointFactory(
     imageWidth,
     imageHeight,
     imageAnalysis)

startFocusAndMetering وFocusMeteringAction

لتشغيل startFocusAndMetering()، يجب أن تنشئ التطبيقات ملفًا برمجيًا بعنوان FocusMeteringAction، يتألف من MeteringPoints واحد أو أكثر مع مجموعات وضع القياس الاختياري من FLAG_AF، FLAG_AE، FLAG_AWB. يوضّح الرمز البرمجي التالي كيفية استخدام هذه الطريقة:

Kotlin

val meteringPoint1 = meteringPointFactory.createPoint(x1, x1)
val meteringPoint2 = meteringPointFactory.createPoint(x2, y2)
val action = FocusMeteringAction.Builder(meteringPoint1) // default AF|AE|AWB
      // Optionally add meteringPoint2 for AF/AE.
      .addPoint(meteringPoint2, FLAG_AF | FLAG_AE)
      // The action is canceled in 3 seconds (if not set, default is 5s).
      .setAutoCancelDuration(3, TimeUnit.SECONDS)
      .build()

val result = cameraControl.startFocusAndMetering(action)
// Adds listener to the ListenableFuture if you need to know the focusMetering result.
result.addListener({
   // result.get().isFocusSuccessful returns if the auto focus is successful or not.
}, ContextCompat.getMainExecutor(this)

كما هو موضّح في الرمز البرمجي السابق، يأخذ العنصر startFocusAndMetering() FocusMeteringAction يتألّف من MeteringPoint واحد لمناطق قياس الإضاءة لميزة "ضبط التركيز التلقائي/ضبط الإضاءة التلقائي/ضبط توازن اللون الأبيض تلقائيًا" ونقطة قياس أخرى لميزة "ضبط التركيز التلقائي" وميزة "ضبط الإضاءة التلقائي" فقط.

وداخليًا، تحوّل CameraX هذا الطلب إلى Camera2 MeteringRectangles وتضبط المَعلمات المقابلة CONTROL_AF_REGIONS / CONTROL_AE_REGIONS / CONTROL_AWB_REGIONS في طلب الالتقاط.

بما أنّ بعض الأجهزة لا تتيح استخدام ميزة ضبط التركيز التلقائي/التوازن التلقائي للأبيض والأسود/ضبط التوازن التلقائي للألوان ومناطق متعددة، تنفِّذ CameraXFocusMeteringAction بأفضل الجهود. يستخدم CameraX الحد الأقصى لعدد نقاط القياس المتاحة، وذلك بالترتيب الذي تمت فيه إضافة النقاط. يتم تجاهل كل MeteringPoints التي تمت إضافتها بعد الحد الأقصى لعدد النقاط. على سبيل المثال، إذا تم تزويد FocusMeteringAction بـ 3 نقاط قياس على منصة تتيح استخدام نقطتَين فقط، سيتم استخدام أول نقطتَي قياس فقط. يتم تجاهل MeteringPoint النهائي من خلال CameraX.

تعويض درجة الإضاءة

تكون ميزة تعويض التعريض مفيدة عندما تحتاج التطبيقات إلى ضبط قيم التعريض (EV) بشكل أدق من نتيجة التعريض التلقائي (AE). يتم دمج قيم تعويض قياس الإضاءة بالطريقة التالية لتحديد قياس الإضاءة اللازم لحال الصورة الحالية:

Exposure = ExposureCompensationIndex * ExposureCompensationStep

توفّر CameraX الدالة Camera.CameraControl.setExposureCompensationIndex() لضبط تعويض التعريض كقيمة فهرس.

تزيد قيم الفهرس الموجبة من سطوع الصورة، بينما تؤدي القيم السالبة إلى تعتيم الصورة. يمكن للتطبيقات طلب المعلومات من النطاق المتوافق باستخدام CameraInfo.ExposureState.exposureCompensationRange() описанном в следующем разделе. إذا كانت القيمة متوافقة، اكتمال القيمة المعروضة ListenableFuture عند تفعيل القيمة بنجاح في طلب الالتقاط. إذا كان الفهرس المحدّد خارج النطاق المسموح به، يؤديsetExposureCompensationIndex() إلى اكتمال القيمة المعروضةListenableFuture على الفور بنتيجة تشير إلى تعذّر إكمال العملية.

لا تحتفظ CameraX إلا بآخر setExposureCompensationIndex() طلب غير مكتمل، ويؤدي استدعاء الدالة عدة مرات قبل تنفيذ الطلب السابق إلى إلغائه.

يحدِّد المقتطف التالي مؤشر تعويض الإضاءة ويُسجِّل دالّة callback عند تنفيذ طلب تغيير الإضاءة:

Kotlin

camera.cameraControl.setExposureCompensationIndex(exposureCompensationIndex)
   .addListener({
      // Get the current exposure compensation index, it might be
      // different from the asked value in case this request was
      // canceled by a newer setting request.
      val currentExposureIndex = camera.cameraInfo.exposureState.exposureCompensationIndex
      
   }, mainExecutor)
  • تُستخدَم دالة Camera.CameraInfo.getExposureState() لاسترداد القيمة الحالية لسمة ExposureState ، بما في ذلك:

    • مدى توفّر عنصر التحكّم في تعويض درجة الإضاءة
    • مؤشر تعويض درجة الإضاءة الحالي
    • نطاق مؤشر تعويض درجة الإضاءة
    • خطوة تعويض درجة الإضاءة المستخدَمة في احتساب قيمة تعويض درجة الإضاءة

على سبيل المثال، يُنشئ الرمز التالي إعدادات التعرّض SeekBar باستخدام قيم ExposureState الحالية:

Kotlin

val exposureState = camera.cameraInfo.exposureState
binding.seekBar.apply {
   isEnabled = exposureState.isExposureCompensationSupported
   max = exposureState.exposureCompensationRange.upper
   min = exposureState.exposureCompensationRange.lower
   progress = exposureState.exposureCompensationIndex
}

مصادر إضافية

للاطّلاع على مزيد من المعلومات عن CameraX، يمكنك الرجوع إلى المراجع الإضافية التالية.

الدرس التطبيقي حول الترميز

  • البدء في استخدام CameraX
  • نموذج رمز

  • نماذج تطبيقات CameraX
  • منتدى المطوّرين

    مجموعة مناقشة Android CameraX