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

يمكنك تهيئة كل حالة استخدام للكاميرا للتحكم في جوانب مختلفة من عمليات حالة الاستخدام.

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

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 خصائص الكاميرات المتاحة على الجهاز وتطلب البحث عنها. وبما أنّ كاميرا X تحتاج إلى الاتصال بأجزاء مكونات الجهاز، قد تستغرق كل كاميرا وقتًا غير تافه، خاصةً على الأجهزة ذات المواصفات المنخفضة. إذا كان تطبيقك يستخدم كاميرات معيّنة فقط على الجهاز، مثل الكاميرا الأمامية التلقائية، يمكنك ضبط 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 ستراجع درجة الدقة ونِسب العرض إلى الارتفاع استنادًا إلى إمكانات الجهاز. يمكن أن يحدث الاختراق للأسباب التالية:

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

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

تختار 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 درجات دقة سطح الكاميرا2 الداخلية تلقائيًا. ويوضح الجدول التالي درجات الدقة:

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

نقطة القياس

للبدء، أنشئ 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 واحدة لمناطق قياس AF/AE/AWB وواحدة MeteringPoint أخرى لكلٍّ من AF وAE فقط.

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

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

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

ويكون تعويض التعرض للضوء مفيدًا عندما تحتاج التطبيقات إلى ضبط قيم التعرض للضوء (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