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

يمكنك ضبط كل حالة استخدام في 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()
   }
}

سلاسل المحادثات

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

Camera Executor

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

معالج أداة الجدولة

يُستخدَم معالج الجدولة لجدولة المهام الداخلية على فترات زمنية ثابتة، مثل إعادة محاولة فتح الكاميرا عندما لا تكون متاحة. لا ينفّذ هذا المعالج المهام، بل يرسلها فقط إلى منفّذ الكاميرا. ويتم استخدامها أيضًا في بعض الأحيان على منصات واجهة برمجة التطبيقات القديمة التي تتطلب Handler لعمليات الاستدعاء. في هذه الحالات، لا يتم إرسال عمليات الاستدعاء إلا إلى منفّذ الكاميرا مباشرةً. تخصّص واجهة 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().
  • ImageAnalysis: يتم توفير مخرجات البيانات الوصفية بحيث تكون إحداثيات مخزن الصور المؤقت معروفة بالنسبة إلى إحداثيات العرض.
  • ImageCapture: يتم تغيير بيانات Exif الوصفية للصورة أو المخزن المؤقت أو كليهما لتسجيل إعدادات التدوير. وتعتمد القيمة التي تم تغييرها على تنفيذ HAL.

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

By default, the crop rect is the full buffer rect. يمكنك تخصيصها باستخدام 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. بعد تطبيق مستطيل الاقتصاص والتدوير على المخزن المؤقت للإخراج، تكون الصورة من جميع حالات الاستخدام هي نفسها، ولكن ربما بدرجات دقة مختلفة. لمزيد من المعلومات حول كيفية تطبيق معلومات التحويل، يُرجى الاطّلاع على تحويل الناتج.

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

تختار واجهة 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، وكيفية الحصول على عنصرَي الكاميرا من عنصر 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 الداخلية تلقائيًا. يوضح الجدول التالي درجات الدقة:

حالة الاستخدام درجة دقة الشاشة الداخلية دقة بيانات الناتج
معاينة نسبة العرض إلى الارتفاع: هي درجة الدقة التي تناسب الهدف على أفضل نحو وفقًا للإعدادات. درجة دقة الشاشة الداخلية يتم توفير البيانات الوصفية للسماح لـ View بالاقتصاص وتغيير الحجم والتدوير لنسبة العرض إلى الارتفاع المستهدَفة.
درجة الدقة التلقائية: أعلى درجة دقة للمعاينة أو أعلى درجة دقة يفضّلها الجهاز وتتطابق مع نسبة العرض إلى الارتفاع في المعاينة.
الحد الأقصى للدقة: حجم المعاينة، والذي يشير إلى أفضل حجم يتطابق مع دقة شاشة الجهاز، أو إلى 1080p‏ (1920x1080)، أيهما أصغر.
تحليل الصور نسبة العرض إلى الارتفاع: الدقة التي تناسب الهدف بشكل أفضل مع الإعداد. درجة دقة الشاشة الداخلية
درجة الدقة التلقائية: الإعداد التلقائي لدرجة الدقة المستهدفة هو 640x480. يؤدي تعديل كل من درجة الدقة المستهدَفة ونسبة العرض إلى الارتفاع المقابلة إلى الحصول على أفضل درجة دقة متوافقة.
الحد الأقصى للدقة: الحد الأقصى لدقة الإخراج في جهاز الكاميرا بتنسيق YUV_420_888 الذي يتم استرداده من StreamConfigurationMap.getOutputSizes(). يتم ضبط الدقة المستهدَفة على 640x480 تلقائيًا، لذا إذا أردت دقة أكبر من 640x480، عليك استخدام 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 في إطار الإحداثيات بعد تدوير الأحجام المتوافقة حسب درجة التدوير المستهدَفة. على سبيل المثال، يمكن لجهاز ذي اتجاه طبيعي عمودي في تدوير مستهدف طبيعي يطلب صورة عمودية أن يحدّد ‎480x640، ويمكن للجهاز نفسه، الذي تم تدويره بمقدار 90 درجة ويستهدف الاتجاه الأفقي، أن يحدّد ‎640x480.

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

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

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

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

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

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

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

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

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

  • تكبير/تصغير
  • شعلة
  • التركيز والقياس (النقر للتركيز)
  • تعويض درجة الإضاءة

الحصول على نُسخ من 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".

تكبير/تصغير

توفّر 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 واحد لمناطق قياس التركيز التلقائي/التعرّض التلقائي/توازن اللون الأبيض التلقائي وMeterPoint آخر للتركيز التلقائي والتعرّض التلقائي فقط.

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

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

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

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

Exposure = ExposureCompensationIndex * ExposureCompensationStep

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

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

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

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

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