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

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

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

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

کاتلین

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

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

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

شکل ۱. دکمه تعویض دوربین در برنامه «دوربین Google»

بسیاری از دستگاه‌ها چندین دوربین دارند که در یک جهت قرار گرفته‌اند. برخی‌از آن‌ها حتی دوربین 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 مراجعه کنید.

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

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

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

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

سازگاری با همه انواع دستگاه‌ها

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