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

يمكنك ضبط كل حالة استخدام في 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().
  • ImageAnalysis: يتم توفير ناتج البيانات الوصفية حتى تكون إحداثيات مخزن الصور المؤقت معروفة بالنسبة إلى إحداثيات العرض.
  • 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)

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

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

عند استخدام ميزة &quot;الكاميرا المتزامنة&quot;، يمكن للجهاز تشغيل كاميرتَين بعدسات متقابلة في الوقت نفسه، أو تشغيل كاميرتَين خلفيتَين في الوقت نفسه. يوضّح نموذج الرمز البرمجي التالي كيفية ضبط كاميرتَين عند استدعاء 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 تلقائيًا. يوضّح الجدول التالي درجات الدقة:

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

إذا كان تطبيقك يعمل على الإصدار 10 من نظام التشغيل Android أو إصدار أحدث، يمكنك استخدام 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 واحد لمناطق قياس AF/AE/AWB وMeteringPoint آخر لـ AF وAE فقط.

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

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

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

تكون ميزة &quot;تعويض التعرض للضوء&quot; مفيدة عندما تحتاج التطبيقات إلى ضبط قيم التعرض للضوء (EV) بدقة بما يتجاوز نتيجة الإخراج من ميزة &quot;التعرض التلقائي للضوء&quot; (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، يُرجى الرجوع إلى المراجع الإضافية التالية.

Codelab

  • بدء استخدام CameraX
  • عيّنة التعليمات البرمجية

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

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