Ống kính máy ảnh và khả năng

Lưu ý: Trang này đề cập đến gói Camera2. Trừ phi ứng dụng của bạn yêu cầu các tính năng cụ thể ở cấp độ thấp từ Camera2, bạn nên sử dụng CameraX. Cả CameraX và Camera2 đều hỗ trợ Android 5.0 (API cấp 21) trở lên.

Nhiều thiết bị Android hiện đại có từ 2 máy ảnh trở lên ở mặt trước, mặt sau hoặc cả hai bên của thiết bị. Mỗi ống kính có thể có các chức năng độc đáo, chẳng hạn như chụp liên tục, điều khiển thủ công hoặc theo dõi chuyển động. Ứng dụng để gửi séc có thể chỉ sử dụng máy ảnh mặt sau đầu tiên, trong khi một ứng dụng mạng xã hội có thể mặc định dùng máy ảnh mặt trước, nhưng cung cấp cho người dùng lựa chọn chuyển đổi giữa tất cả ống kính. Ứng dụng này cũng có thể ghi nhớ những lựa chọn của trẻ.

Trang này trình bày cách liệt kê các ống kính camera và chức năng của chúng để bạn có thể đưa ra quyết định trong ứng dụng về việc sử dụng ống kính nào trong một tình huống nhất định. Đoạn mã sau đây truy xuất danh sách tất cả các máy ảnh và lặp lại chúng:

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

Biến cameraLensFacing mô tả hướng của máy ảnh tương ứng với màn hình thiết bị và có một trong các giá trị sau:

Để biết thêm thông tin về cấu hình dành cho ống kính, hãy xem CameraCharacteristics.LENS_FACING.

Biến cameraCapabilities trong mã mẫu trước đó chứa thông tin về các tính năng khác, bao gồm cả việc camera có có thể tạo kết quả các khung hình tiêu chuẩn (ví dụ: chỉ dữ liệu cảm biến độ sâu). Bạn có thể tìm hiểu xem CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE là một trong những tính năng được liệt kê của camera và được lưu trữ dưới dạng cờ trong isBackwardCompatible.

Chọn các giá trị mặc định hợp lý

Trong ứng dụng của mình, có thể bạn sẽ muốn mở một máy ảnh cụ thể theo mặc định (nếu có). Ví dụ: một ứng dụng selfie có thể mở mặt trước còn ứng dụng thực tế tăng cường có thể khởi động bằng camera sau. Hàm sau đây trả về máy ảnh đầu tiên quay về một hướng cho trước:

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

Bật tính năng chuyển đổi camera

Nhiều ứng dụng máy ảnh cho phép người dùng chuyển đổi giữa các máy ảnh:

Hình 1. Nút chuyển đổi máy ảnh trong ứng dụng Google Máy ảnh

Nhiều thiết bị có nhiều máy ảnh hướng theo cùng một hướng. Một số người thậm chí có camera USB bên ngoài. Để cung cấp cho người dùng một giao diện người dùng cho phép họ chuyển đổi giữa các máy ảnh mặt khác nhau, hãy chọn máy ảnh có sẵn đầu tiên cho mỗi máy ảnh có thể máy ảnh mặt trước.

Mặc dù không có logic chung để chọn camera tiếp theo, mã sau hoạt động cho hầu hết các trường hợp sử dụng:

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

Mã này hoạt động cho một tập hợp lớn các thiết bị có nhiều phiên bản . Để biết thêm thông tin về cách tính đến các trường hợp hiếm gặp, vui lòng xem CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA.

Tạo ứng dụng tương thích

Đối với các ứng dụng vẫn sử dụng API Máy ảnh không dùng nữa, số lượng máy ảnh để Camera.getNumberOfCameras() lợi nhuận phụ thuộc vào cách triển khai của OEM. Nếu có một cụm camera logic trong hệ thống của mình, để duy trì khả năng tương thích ngược của ứng dụng, phương thức này sẽ chỉ hiển thị một camera cho mọi camera logic và nhóm camera thực bên dưới. Sử dụng API Camera2 để xem tất cả các máy ảnh.

Để biết thêm thông tin cơ bản về hướng máy ảnh, hãy xem Camera.CameraInfo.orientation.

Nói chung, hãy sử dụng Camera.getCameraInfo() API để truy vấn tất cả máy ảnh orientation giây, và chỉ phơi sáng một máy ảnh cho mỗi hướng có sẵn cho những người dùng chuyển đổi giữa các camera.

Hỗ trợ mọi loại thiết bị

Đừng cho rằng ứng dụng của bạn luôn chạy trên thiết bị cầm tay có một hoặc 2 camera. Thay vào đó, hãy chọn camera phù hợp nhất với ứng dụng. Nếu bạn không cần một máy ảnh cụ thể, hãy chọn máy ảnh đầu tiên hướng vào đối tượng mong muốn . Nếu máy ảnh bên ngoài được kết nối, bạn có thể cho rằng người dùng đặt nó làm mặc định.