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

Lưu ý: Trang này đề cập đến gói Camera2. Bạn nên sử dụng CameraX, trừ phi ứng dụng yêu cầu các tính năng cụ thể ở cấp thấp trong Camera2. 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ó 2 camera trở lên ở trước, sau hoặc cả hai bên của thiết bị. Mỗi ống kính có thể có các tính năng riêng biệt, 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 chỉ có thể sử dụng máy ảnh mặt sau đầu tiên, trong khi ứng dụng mạng xã hội có thể mặc định sử dụng máy ảnh mặt trước, nhưng vẫn cho phép người dùng chuyển đổi giữa tất cả các ống kính có sẵn. Trợ lý cũng có thể ghi nhớ các lựa chọn của trẻ.

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

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 camera so với màn hình thiết bị, đồng thời có một trong các giá trị sau:

Để biết thêm thông tin về cấu hình mặt ố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ả thông tin về việc máy ảnh có thể tạo khung tiêu chuẩn ở dạng đầu ra hay không (ví dụ: chỉ có dữ liệu cảm biến độ sâu). Bạn có thể tìm hiểu xem CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE có phải là một trong các chức năng có trong danh sách của máy ảnh (được lưu trữ dưới dạng cờ trong isBackwardCompatible) hay không.

Chọn chế độ mặc định hợp lý

Trong ứng dụng của mình, bạn nên mở một máy ảnh cụ thể theo mặc định (nếu có). Ví dụ: ứng dụng tự chụp ảnh chân dung có thể mở máy ảnh mặt trước, trong khi ứng dụng thực tế tăng cường có thể bắt đầu bằng máy ảnh sau. Hàm sau trả về máy ảnh đầu tiên đối diện với một hướng nhất định:

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 máy ảnh

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 máy ảnh trong ứng dụng Google Máy ảnh

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

Mặc dù không có logic chung để chọn máy ảnh tiếp theo, nhưng đoạn mã sau đây phù hợp với 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 dùng được cho một nhóm lớn các thiết bị có nhiều cấu hình khác nhau. Để biết thêm thông tin về cách tính các trường hợp đặc biệt, hãy 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 dùng Camera API (API Máy ảnh) không dùng nữa, số lượng máy ảnh mà Camera.getNumberOfCameras() trả về sẽ tuỳ thuộc vào phương thức triển khai của OEM. Nếu có nhiều máy ảnh logic trong hệ thống, thì để 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 máy ảnh cho mọi máy ảnh logic và nhóm máy ảnh thực cơ bản. 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.

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

Dùng được mọi loại thiết bị

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