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

ملاحظة: تشير هذه الصفحة إلى حزمة Camera2. ننصحك باستخدام الكاميراX ما لم يكن تطبيقك يتطلب ميزات محدَّدة منخفضة المستوى من تطبيق 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 API المتوقّفة نهائيًا الذي/التي Camera.getNumberOfCameras() والعائدات تعتمد على التنفيذ من قِبل المصنّع الأصلي للجهاز. فإذا كانت هناك كاميرات متعددة منطقية في للحفاظ على التوافق مع الأنظمة القديمة للتطبيق، ستعرض هذه الطريقة واحدة فقط لكل كاميرا منطقية ومجموعة كاميرات مادية أساسية. استخدِم واجهة برمجة التطبيقات للكاميرا 2 للاطّلاع على جميع الكاميرات.

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

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

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

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