لنزهای دوربین و قابلیت ها

توجه: این صفحه به پکیج Camera2 اشاره دارد. مگر اینکه برنامه شما به ویژگی‌های خاص و سطح پایینی از Camera2 نیاز داشته باشد، توصیه می‌کنیم از CameraX استفاده کنید. هر دو CameraX و Camera2 از اندروید ۵.۰ (سطح API ۲۱) و بالاتر پشتیبانی می‌کنند.

بسیاری از دستگاه‌های مدرن اندروید دارای دو یا چند دوربین در جلو، عقب یا هر دو طرف دستگاه هستند. هر لنز می‌تواند قابلیت‌های منحصر به فردی مانند عکاسی پیاپی، کنترل دستی یا ردیابی حرکت داشته باشد. یک برنامه برای واریز چک ممکن است فقط از اولین دوربین عقب استفاده کند، در حالی که یک برنامه رسانه اجتماعی ممکن است به طور پیش‌فرض از دوربین جلو استفاده کند، اما به کاربران این امکان را می‌دهد که بین تمام لنزهای موجود جابجا شوند. همچنین می‌تواند انتخاب‌های آنها را به خاطر بسپارد.

این صفحه نحوه فهرست کردن لنزهای دوربین و قابلیت‌های آنها را پوشش می‌دهد تا بتوانید در برنامه خود در مورد اینکه از کدام لنز در یک موقعیت خاص استفاده کنید، تصمیم‌گیری کنید. قطعه کد زیر لیستی از تمام دوربین‌ها را بازیابی کرده و روی آنها تکرار می‌کند:

کاتلین

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) }
    ...
}

جاوا

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 ذخیره می‌شود یا خیر.

پیش‌فرض‌های معقولی را انتخاب کنید

در برنامه‌تان، احتمالاً می‌خواهید به طور پیش‌فرض یک دوربین خاص را باز کنید (اگر موجود باشد). برای مثال، یک برنامه سلفی احتمالاً دوربین جلو را باز می‌کند، در حالی که یک برنامه واقعیت افزوده ممکن است با دوربین عقب شروع به کار کند. تابع زیر اولین دوربینی را که رو به جهت مشخص شده باشد، برمی‌گرداند:

کاتلین

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) }
    }
}

جاوا

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;
}

فعال کردن تعویض دوربین‌ها

بسیاری از برنامه‌های دوربین به کاربران این امکان را می‌دهند که بین دوربین‌ها جابجا شوند:

شکل ۱. دکمه تغییر دوربین در برنامه دوربین گوگل

بسیاری از دستگاه‌ها چندین دوربین دارند که رو به یک جهت هستند. برخی حتی دوربین‌های USB خارجی نیز دارند. برای اینکه رابط کاربری به کاربران امکان جابجایی بین دوربین‌های رو به جلوی مختلف را بدهد، اولین دوربین موجود را برای هر پیکربندی ممکن رو به جلوی لنز انتخاب کنید.

اگرچه هیچ منطق جهانی برای انتخاب دوربین بعدی وجود ندارد، کد زیر برای اکثر موارد استفاده کار می‌کند:

کاتلین

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)
    }
}

جاوا

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 مراجعه کنید.

ایجاد برنامه‌های سازگار

برای برنامه‌هایی که هنوز از API دوربین منسوخ‌شده استفاده می‌کنند، تعداد دوربین‌هایی که Camera.getNumberOfCameras() برمی‌گرداند، به پیاده‌سازی OEM بستگی دارد. اگر یک دوربین چندگانه منطقی در سیستم وجود داشته باشد، برای حفظ سازگاری برنامه با نسخه‌های قبلی، این روش فقط یک دوربین را برای هر دوربین منطقی و گروه دوربین‌های فیزیکی زیرین نمایش می‌دهد. از API Camera2 برای دیدن همه دوربین‌ها استفاده کنید.

برای اطلاعات پیش‌زمینه بیشتر در مورد جهت‌گیری دوربین، به Camera.CameraInfo.orientation مراجعه کنید.

به طور کلی، از API مربوط به Camera.getCameraInfo() برای پرس و جو در مورد تمام orientation دوربین استفاده کنید و فقط یک دوربین را برای هر جهت‌گیری موجود در اختیار کاربرانی قرار دهید که بین دوربین‌ها جابجا می‌شوند.

انواع دستگاه را در خود جای دهید

فرض نکنید که برنامه شما همیشه روی یک دستگاه دستی با یک یا دو دوربین اجرا می‌شود. در عوض، مناسب‌ترین دوربین‌ها را برای برنامه انتخاب کنید. اگر به دوربین خاصی نیاز ندارید، اولین دوربینی را که رو به جهت مورد نظر است انتخاب کنید. اگر دوربین رو به جهت خاصی در دسترس نیست، در نظر بگیرید که آیا کاربر می‌تواند سفر خود را با دوربین دیگری تکمیل کند یا خیر. دسترسی به برنامه خود را بر اساس سخت‌افزار دوربین در دستگاه‌های خاص محدود نکنید. اگر یک دوربین خارجی متصل باشد، کاربر احتمالاً آن را به عنوان پیش‌فرض ترجیح می‌دهد.