عدسات الكاميرا وإمكاناتها

ملاحظة: تشير هذه الصفحة إلى حزمة camera2. ننصحك باستخدام cameraX ما لم يكن تطبيقك يتطلب ميزات محدّدة ومنخفضة المستوى من Camera2. يتوافق كل من CameraX و Camera2 مع الإصدار Android 5.0 (المستوى 21 من واجهة برمجة التطبيقات) والإصدارات الأحدث.

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

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

Kotlin

try {
    val cameraIdList = cameraManager.cameraIdList // may be empty

    // iterate over available camera devices
    for (cameraId in cameraIdList) {
        val characteristics = cameraManager.getCameraCharacteristics(cameraId)
        val cameraLensFacing = characteristics.get(CameraCharacteristics.LENS_FACING)
        val cameraCapabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)

        // check if the selected camera device supports basic features
        // ensures backward compatibility with the original Camera API
        val isBackwardCompatible = cameraCapabilities?.contains(
            CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) ?: false
        ...
    }
} catch (e: CameraAccessException) {
    e.message?.let { Log.e(TAG, it) }
    ...
}

Java

try {
    String[] cameraIdList = cameraManager.getCameraIdList(); // may be empty

    // iterate over available camera devices
    for (String cameraId : cameraIdList) {
        CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
        int cameraLensFacing = characteristics.get(CameraCharacteristics.LENS_FACING);
        int[] cameraCapabilities =
            characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);

        // check if the selected camera device supports basic features
        // ensures backward compatibility with the original Camera API
        boolean isBackwardCompatible = false;
        for (int capability : cameraCapabilities) {
            if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) {
                isBackwardCompatible = true;
                break;
            }
        }
        ...
    }
} catch (CameraAccessException e) {
    Log.e(TAG, e.getMessage());
    ...
}

يصف المتغيّر cameraLensFacing الاتجاه الذي تواجهه الكاميرا بالنسبة إلى شاشة الجهاز، ويحتوي على إحدى القيم التالية:

لمزيد من المعلومات عن إعدادات واجهة العدسة، اطّلِع على القسم CameraCharacteristics.LENS_FACING.

يحتوي المتغير cameraCapabilities من عينة الرمز السابقة على معلومات حول الإمكانات المتنوعة، بما في ذلك ما إذا كانت الكاميرا قادرة على إنتاج إطارات قياسية كمخرجات (على سبيل المثال، بيانات أداة استشعار العمق فقط). يمكنك التحقّق ممّا إذا كانت ميزة CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE هي أحد الإمكانات المدرَجة في الكاميرا، ويتم تخزينها كعلم في isBackwardCompatible.

اختيار الإعدادات التلقائية المعقولة

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

Kotlin

fun getFirstCameraIdFacing(cameraManager: CameraManager,
                           facing: Int = CameraMetadata.LENS_FACING_BACK): String? {
    try {
        // Get list of all compatible cameras
        val cameraIds = cameraManager.cameraIdList.filter {
            val characteristics = cameraManager.getCameraCharacteristics(it)
            val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
            capabilities?.contains(
                    CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) ?: false
        }

        // Iterate over the list of cameras and return the first one matching desired
        // lens-facing configuration
        cameraIds.forEach {
            val characteristics = cameraManager.getCameraCharacteristics(it)
            if (characteristics.get(CameraCharacteristics.LENS_FACING) == facing) {
                return it
            }
        }

        // If no camera matched desired orientation, return the first one from the list
        return cameraIds.firstOrNull()
    } catch (e: CameraAccessException) {
        e.message?.let { Log.e(TAG, it) }
    }
}

Java

public String getFirstCameraIdFacing(CameraManager cameraManager, @Nullable Integer facing) {
    if (facing == null) facing = CameraMetadata.LENS_FACING_BACK;
    String cameraId = null;

    try {
        // Get a list of all compatible cameras
        String[] cameraIdList = cameraManager.getCameraIdList();

        // Iterate over the list of cameras and return the first one matching desired
        // lens-facing configuration and backward compatibility
        for (String id : cameraIdList) {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
            int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
            for (int capability : capabilities) {
                if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE
                        && characteristics.get(CameraCharacteristics.LENS_FACING).equals(facing)) {
                    cameraId = id;
                    break;
                }
            }
        }

        // If no camera matches the desired orientation, return the first one from the list
        cameraId = cameraIdList[0];
    } catch (CameraAccessException e) {
        Log.e(TAG, "getFirstCameraIdFacing: " + e.getMessage());
    }

    return cameraId;
}

تفعيل تبديل الكاميرات

توفّر العديد من تطبيقات الكاميرات للمستخدمين خيار التبديل بين الكاميرات:

الشكل 1. زر تبديل الكاميرا في تطبيق "كاميرا Google"

يحتوي العديد من الأجهزة على عدة كاميرات في الاتجاه نفسه. تحتوي بعضها أيضًا على كاميرات USB خارجية. ولتزويد المستخدمين بواجهة مستخدم تتيح لهم التبديل بين الكاميرات المختلفة، اختَر أول كاميرا متاحة لكل إعداد محتمل للعدسات.

على الرغم من عدم وجود منطق عام لاختيار الكاميرا التالية، تعمل التعليمة البرمجية التالية مع معظم حالات الاستخدام:

Kotlin

fun filterCompatibleCameras(cameraIds: Array<String>,
                            cameraManager: CameraManager): List<String> {
    return cameraIds.filter {
        val characteristics = cameraManager.getCameraCharacteristics(it)
        characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)?.contains(
                CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) ?: false
    }
}

fun filterCameraIdsFacing(cameraIds: List<String>, cameraManager: CameraManager,
                          facing: Int): List<String> {
    return cameraIds.filter {
        val characteristics = cameraManager.getCameraCharacteristics(it)
        characteristics.get(CameraCharacteristics.LENS_FACING) == facing
    }
}

fun getNextCameraId(cameraManager: CameraManager, currCameraId: String? = null): String? {
    // Get all front, back and external cameras in 3 separate lists
    val cameraIds = filterCompatibleCameras(cameraManager.cameraIdList, cameraManager)
    val backCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_BACK)
    val frontCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_FRONT)
    val externalCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_EXTERNAL)

    // The recommended order of iteration is: all external, first back, first front
    val allCameras = (externalCameras + listOf(
            backCameras.firstOrNull(), frontCameras.firstOrNull())).filterNotNull()

    // Get the index of the currently selected camera in the list
    val cameraIndex = allCameras.indexOf(currCameraId)

    // The selected camera may not be in the list, for example it could be an
    // external camera that has been removed by the user
    return if (cameraIndex == -1) {
        // Return the first camera from the list
        allCameras.getOrNull(0)
    } else {
        // Return the next camera from the list, wrap around if necessary
        allCameras.getOrNull((cameraIndex + 1) % allCameras.size)
    }
}

Java

public List<String> filterCompatibleCameras(CameraManager cameraManager, String[] cameraIds) {
    final List<String> compatibleCameras = new ArrayList<>();

    try {
        for (String id : cameraIds) {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
            int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
            for (int capability : capabilities) {
                if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) {
                    compatibleCameras.add(id);
                }
            }
        }
    } catch (CameraAccessException e) {
        Log.e(TAG, "filterCompatibleCameras: " + e.getMessage());
    }

    return compatibleCameras;
}

public List<String> filterCameraIdsFacing(CameraManager cameraManager, List<String> cameraIds, int lensFacing) {
    final List<String> compatibleCameras = new ArrayList<>();

    try {
        for (String id : cameraIds) {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
            if (characteristics.get(CameraCharacteristics.LENS_FACING) == lensFacing) {
                compatibleCameras.add(id);
            }
        }
    } catch (CameraAccessException e) {
        Log.e(TAG, "filterCameraIdsFacing: " + e.getMessage());
    }

    return compatibleCameras;
}

public String getNextCameraId(CameraManager cameraManager, @Nullable String currentCameraId) {
    String nextCameraId = null;

    try {
        // Get all front, back, and external cameras in 3 separate lists
        List<String> compatibleCameraIds = filterCompatibleCameras(cameraManager, cameraManager.getCameraIdList());
        List<String> backCameras = filterCameraIdsFacing(cameraManager, compatibleCameraIds, CameraMetadata.LENS_FACING_BACK);
        List<String> frontCameras = filterCameraIdsFacing(cameraManager, compatibleCameraIds, CameraMetadata.LENS_FACING_FRONT);
        List<String>externalCameras = filterCameraIdsFacing(cameraManager, compatibleCameraIds, CameraMetadata.LENS_FACING_EXTERNAL);

        // The recommended order of iteration is: all external, first back, first front
        List<String> allCameras = new ArrayList<>(externalCameras);
        if (!backCameras.isEmpty()) allCameras.add(backCameras.get(0));
        if (!frontCameras.isEmpty()) allCameras.add(frontCameras.get(0));

        // Get the index of the currently selected camera in the list
        int cameraIndex = allCameras.indexOf(currentCameraId);

        // The selected camera may not be in the list, for example it could be an
        // external camera that has been removed by the user
        if (cameraIndex == -1) {
            // Return the first camera from the list
            nextCameraId = !allCameras.isEmpty() ? allCameras.get(0) : null;
        else {
            if (!allCameras.isEmpty()) {
                // Return the next camera from the list, wrap around if necessary
                nextCameraId = allCameras.get((cameraIndex + 1) % allCameras.size());
            }
        }
    } catch (CameraAccessException e) {
        Log.e(TAG, "getNextCameraId: " + e.getMessage());
    }

    return nextCameraId;
}

ويتوافق هذا الرمز مع مجموعة كبيرة من الأجهزة ذات عمليات الضبط المختلفة. لمزيد من المعلومات حول احتساب الحالات الحدّية، اطّلِع على CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA.

إنشاء تطبيقات متوافقة

بالنسبة إلى التطبيقات التي لا تزال تستخدم واجهة برمجة تطبيقات الكاميرا التي تم إيقافها نهائيًا، يعتمد عدد الكاميرات التي تعرضها Camera.getNumberOfCameras() على مدى تنفيذ المصنّع الأصلي للجهاز. إذا كانت هناك كاميرات متعددة منطقية في النظام، للحفاظ على التوافق مع الأنظمة القديمة للتطبيق، ستعرض هذه الطريقة كاميرا واحدة فقط لكل مجموعة كاميرات منطقية ومجموعة كاميرات فعلية أساسية. يمكنك استخدام Camera2 API للاطّلاع على جميع الكاميرات.

لمزيد من المعلومات الأساسية حول اتجاهات الكاميرا، يُرجى الاطّلاع على Camera.CameraInfo.orientation.

وبشكل عام، استخدِم واجهة برمجة التطبيقات Camera.getCameraInfo() لإجراء طلبات بحث في جميع الكاميرات orientation، وعرض كاميرا واحدة فقط لكل اتجاه متاح للمستخدمين الذين يبدِّلون بين الكاميرات.

استيعاب جميع أنواع الأجهزة

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