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

يمكنك تهيئة كل حالة من حالات استخدام 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 سوى واجهات برمجة التطبيقات هذه من سلاسل التعليمات في الخلفية، بحيث لا يتم حظر سلسلة التعليمات الرئيسية وتظل واجهة المستخدم سلسة. يدير CameraX داخليًا هذه السلاسل بحيث يبدو هذا السلوك شفافًا. ومع ذلك، تتطلب بعض التطبيقات التحكم الصارم في سلاسل التعليمات. يتيح CameraXConfig للتطبيق ضبط سلاسل محادثات الخلفية التي يتم استخدامها من خلال CameraXConfig.Builder.setCameraExecutor() وCameraXConfig.Builder.setSchedulerHandler().

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

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

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

يختار تطبيق 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)

اختيار عدة كاميرات بشكل متزامن

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

عند استخدام ميزة "الكاميرا المتزامنة"، يمكن للجهاز تشغيل كاميرتين بعدسات مواجهتَين مختلفتَين في الوقت نفسه، أو تشغيل كاميرتَين خلفيتَين في الوقت نفسه. تعرض مجموعة الرموز التالية كيفية ضبط كاميرتَين عند الاتصال بـ 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 الداخلي. يعرض الجدول التالي درجات الدقة:

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

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

ومع ذلك، إذا لم تكن هناك درجة دقة تساوي درجة الدقة المستهدفة أو أكبر منها، سيتم اختيار أقرب درجة دقة متاحة أصغر من درجة الدقة المستهدفة. تُعطى درجات الدقة التي لها نسبة العرض إلى الارتفاع نفسها في 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() يؤدي إلى تفعيل التركيز التلقائي ومقياس التعرّض للضوء من خلال ضبط مناطق قياس انبعاثات AF/AE/AWB استنادًا إلى إجراء FocusMeteringAction المحدّد. وغالبًا ما يستخدم هذا لتطبيق ميزة "النقر للتركيز" في العديد من تطبيقات الكاميرا.

أداة MeteringPoint

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

ويتراوح حجم 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 لمناطق قياس AdSense للنطاقات المستضافة/AE/AWB، ومقياس MeteringPoint آخر في AdSense وAE فقط.

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

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

تعويض التعرض

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

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