API nhiều máy ảnh

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.

Tính năng nhiều máy ảnh được ra mắt cùng với Android 9 (API cấp 28). Kể từ khi phát hành, các thiết bị hỗ trợ API đã được tung ra thị trường. Nhiều trường hợp sử dụng nhiều máy ảnh được kết hợp chặt chẽ với một cấu hình phần cứng cụ thể. Nói cách khác, không phải trường hợp sử dụng nào cũng tương thích với mọi thiết bị. Do đó,  các tính năng nhiều máy ảnh sẽ là một lựa chọn phù hợp cho Play Feature Delivery.

Sau đây là một số trường hợp sử dụng thường gặp:

  • Zoom (Thu phóng): chuyển đổi giữa các máy ảnh tuỳ thuộc vào vùng cắt hoặc tiêu cự mong muốn.
  • Độ sâu: sử dụng nhiều máy ảnh để tạo bản đồ độ sâu.
  • Bokeh: sử dụng thông tin chiều sâu dự đoán để mô phỏng phạm vi lấy nét hẹp giống như DSLR.

Sự khác biệt giữa máy ảnh logic và máy ảnh vật lý

Để hiểu được API nhiều máy ảnh, bạn cần hiểu sự khác biệt giữa máy ảnh logic và máy ảnh vật lý. Để tham khảo, hãy cân nhắc một thiết bị có 3 camera mặt sau. Trong ví dụ này, mỗi camera trong số 3 camera sau được coi là một camera thực. Khi đó, máy ảnh logic là một nhóm gồm hai hoặc nhiều máy ảnh vật lý đó. Đầu ra của máy ảnh logic có thể là một luồng đến từ một trong các máy ảnh thực cơ bản cơ bản, hoặc một luồng kết hợp đến từ nhiều máy ảnh thực cơ bản cơ bản đồng thời. Dù bằng cách nào, luồng cũng được lớp trừu tượng phần cứng (Hardware Abstraction Layer – HAL) xử lý.

Nhiều nhà sản xuất điện thoại phát triển các ứng dụng máy ảnh của bên thứ nhất. Những ứng dụng này thường được cài đặt sẵn trên thiết bị của họ. Để sử dụng tất cả tính năng của phần cứng, chúng có thể dùng API riêng tư hoặc API ẩn, hoặc được xử lý đặc biệt từ quá trình triển khai trình điều khiển mà các ứng dụng khác không có quyền truy cập. Một số thiết bị triển khai khái niệm máy ảnh logic bằng cách cung cấp luồng khung hình kết hợp từ nhiều máy ảnh thực, nhưng chỉ cho một số ứng dụng có đặc quyền. Thông thường, khung này chỉ hiển thị một trong các máy ảnh thực. Tình huống đối với các nhà phát triển bên thứ ba trước Android 9 được minh hoạ trong biểu đồ sau:

Hình 1. Các chức năng của máy ảnh thường chỉ dùng được cho các ứng dụng đặc quyền

Kể từ Android 9, các API riêng tư sẽ không còn được cho phép trong các ứng dụng Android nữa. Khi thêm tính năng hỗ trợ nhiều máy ảnh vào khung này, các phương pháp hay nhất dành cho Android đặc biệt khuyên nhà sản xuất điện thoại hiển thị máy ảnh logic cho tất cả các máy ảnh thực hướng về cùng một hướng. Dưới đây là những điều mà nhà phát triển bên thứ ba sẽ thấy trên các thiết bị chạy Android 9 trở lên:

Hình 2. Nhà phát triển có toàn quyền truy cập vào tất cả các thiết bị máy ảnh kể từ Android 9

Những gì mà máy ảnh logic cung cấp hoàn toàn phụ thuộc vào cách OEM triển khai HAL (Lớp trừu tượng phần cứng) cho máy ảnh. Ví dụ: một thiết bị như Pixel 3 triển khai máy ảnh logic theo cách chọn một trong các máy ảnh thực dựa trên vùng tiêu cự và vùng cắt được yêu cầu.

API nhiều máy ảnh

API mới bổ sung các hằng số, lớp và phương thức mới sau đây:

Do những thay đổi trong Tài liệu định nghĩa về khả năng tương thích (CDD) cho Android, API nhiều camera cũng đáp ứng một số kỳ vọng của các nhà phát triển. Các thiết bị có máy ảnh kép tồn tại trước Android 9, nhưng việc mở nhiều máy ảnh đồng thời liên quan đến quá trình dùng thử và lỗi. Trên Android 9 trở lên, chế độ nhiều máy ảnh cung cấp một bộ quy tắc để chỉ định thời điểm bạn có thể mở một cặp máy ảnh thực trong cùng một máy ảnh logic.

Trong hầu hết các trường hợp, các thiết bị chạy Android 9 trở lên sẽ hiển thị tất cả máy ảnh thực (ngoại trừ các loại cảm biến ít phổ biến hơn như hồng ngoại) cùng với một máy ảnh logic dễ sử dụng hơn. Đối với mọi tổ hợp luồng được đảm bảo hoạt động, một luồng thuộc về camera logic có thể được thay thế bằng hai luồng từ các camera thực cơ bản.

Nhiều luồng cùng lúc

Việc sử dụng đồng thời nhiều luồng máy ảnh đáp ứng các quy tắc sử dụng đồng thời nhiều luồng máy ảnh trong một máy ảnh. Chỉ có một lần thêm đáng chú ý, các quy tắc tương tự sẽ áp dụng cho nhiều máy ảnh. CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA giải thích cách thay thế một luồng logic YUV_420_888 hoặc luồng thô bằng hai luồng thực. Điều đó nghĩa là mỗi luồng loại YUV hoặc RAW có thể được thay thế bằng hai luồng cùng loại và kích thước. Bạn có thể bắt đầu bằng một luồng camera có cấu hình được đảm bảo sau đây cho các thiết bị có một camera:

  • Luồng 1: Loại YUV, kích thước MAXIMUM từ máy ảnh logic id = 0

Sau đó, một thiết bị có hỗ trợ nhiều máy ảnh cho phép bạn tạo một phiên thay thế luồng YUV logic đó bằng hai luồng thực:

  • Luồng 1: Loại YUV, kích thước MAXIMUM từ máy ảnh thực id = 1
  • Luồng 2: Loại YUV, kích thước MAXIMUM từ máy ảnh thực id = 2

Bạn có thể thay thế một luồng YUV hoặc RAW bằng hai luồng tương đương khi và chỉ khi hai máy ảnh đó thuộc một nhóm máy ảnh logic,  được liệt kê trong CameraCharacteristics.getPhysicalCameraIds().

Những đảm bảo mà khung này đưa ra chỉ là điều kiện tối thiểu cần thiết để nhận cùng lúc các khung hình từ nhiều máy ảnh thực. Hầu hết các thiết bị đều hỗ trợ các luồng bổ sung, đôi khi cho phép mở nhiều thiết bị máy ảnh thực một cách độc lập. Vì đây không phải là sự đảm bảo chắc chắn từ khung này, nên để làm được việc này, bạn phải kiểm thử và điều chỉnh trên mỗi thiết bị bằng cách dùng phương pháp thử và lỗi.

Tạo phiên có nhiều camera thực

Khi sử dụng máy ảnh thực trên thiết bị hỗ trợ nhiều máy ảnh, hãy mở một CameraDevice (máy ảnh logic) và tương tác với máy ảnh đó trong một phiên duy nhất. Tạo một phiên duy nhất bằng API CameraDevice.createCaptureSession(SessionConfiguration config) đã được thêm vào API cấp 28. Cấu hình phiên có một số cấu hình đầu ra, mỗi cấu hình trong số đó có một tập hợp các mục tiêu đầu ra và có thể có một mã máy ảnh thực mong muốn (không bắt buộc).

Hình 3. Mô hình SessionConfiguration và OutputConfiguration

Các yêu cầu thu thập có liên kết với một mục tiêu đầu ra. Khung này xác định máy ảnh vật lý (hoặc logic) mà các yêu cầu được gửi đến dựa trên mục tiêu đầu ra nào được đính kèm. Nếu mục tiêu đầu ra tương ứng với một trong các mục tiêu đầu ra đã được gửi dưới dạng cấu hình đầu ra cùng với mã máy ảnh thực, thì máy ảnh thực tế đó sẽ nhận được và xử lý yêu cầu.

Sử dụng một cặp máy ảnh vật lý

Một tính năng bổ sung khác cho các API máy ảnh cho nhiều máy ảnh là khả năng xác định các máy ảnh logic và tìm các máy ảnh vật lý phía sau. Bạn có thể xác định một hàm để giúp xác định các cặp camera thực tiềm năng mà bạn có thể sử dụng để thay thế một trong các luồng camera logic:

Kotlin

/**
     * Helper class used to encapsulate a logical camera and two underlying
     * physical cameras
     */
    data class DualCamera(val logicalId: String, val physicalId1: String, val physicalId2: String)

    fun findDualCameras(manager: CameraManager, facing: Int? = null): List {
        val dualCameras = MutableList()

        // Iterate over all the available camera characteristics
        manager.cameraIdList.map {
            Pair(manager.getCameraCharacteristics(it), it)
        }.filter {
            // Filter by cameras facing the requested direction
            facing == null || it.first.get(CameraCharacteristics.LENS_FACING) == facing
        }.filter {
            // Filter by logical cameras
            // CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA requires API >= 28
            it.first.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)!!.contains(
                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)
        }.forEach {
            // All possible pairs from the list of physical cameras are valid results
            // NOTE: There could be N physical cameras as part of a logical camera grouping
            // getPhysicalCameraIds() requires API >= 28
            val physicalCameras = it.first.physicalCameraIds.toTypedArray()
            for (idx1 in 0 until physicalCameras.size) {
                for (idx2 in (idx1 + 1) until physicalCameras.size) {
                    dualCameras.add(DualCamera(
                        it.second, physicalCameras[idx1], physicalCameras[idx2]))
                }
            }
        }

        return dualCameras
    }

Java

/**
     * Helper class used to encapsulate a logical camera and two underlying
     * physical cameras
     */
    final class DualCamera {
        final String logicalId;
        final String physicalId1;
        final String physicalId2;

        DualCamera(String logicalId, String physicalId1, String physicalId2) {
            this.logicalId = logicalId;
            this.physicalId1 = physicalId1;
            this.physicalId2 = physicalId2;
        }
    }
    List findDualCameras(CameraManager manager, Integer facing) {
        List dualCameras = new ArrayList<>();

        List cameraIdList;
        try {
            cameraIdList = Arrays.asList(manager.getCameraIdList());
        } catch (CameraAccessException e) {
            e.printStackTrace();
            cameraIdList = new ArrayList<>();
        }

        // Iterate over all the available camera characteristics
        cameraIdList.stream()
                .map(id -> {
                    try {
                        CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
                        return new Pair<>(characteristics, id);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                        return null;
                    }
                })
                .filter(pair -> {
                    // Filter by cameras facing the requested direction
                    return (pair != null) &&
                            (facing == null || pair.first.get(CameraCharacteristics.LENS_FACING).equals(facing));
                })
                .filter(pair -> {
                    // Filter by logical cameras
                    // CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA requires API >= 28
                    IntPredicate logicalMultiCameraPred =
                            arg -> arg == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA;
                    return Arrays.stream(pair.first.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES))
                            .anyMatch(logicalMultiCameraPred);
                })
                .forEach(pair -> {
                    // All possible pairs from the list of physical cameras are valid results
                    // NOTE: There could be N physical cameras as part of a logical camera grouping
                    // getPhysicalCameraIds() requires API >= 28
                    String[] physicalCameras = pair.first.getPhysicalCameraIds().toArray(new String[0]);
                    for (int idx1 = 0; idx1 < physicalCameras.length; idx1++) {
                        for (int idx2 = idx1 + 1; idx2 < physicalCameras.length; idx2++) {
                            dualCameras.add(
                                    new DualCamera(pair.second, physicalCameras[idx1], physicalCameras[idx2]));
                        }
                    }
                });
return dualCameras;
}

Việc xử lý trạng thái của camera vật lý do camera logic kiểm soát. Để mở "máy ảnh kép", hãy mở máy ảnh logic tương ứng với các máy ảnh thực:

Kotlin

fun openDualCamera(cameraManager: CameraManager,
                       dualCamera: DualCamera,
        // AsyncTask is deprecated beginning API 30
                       executor: Executor = AsyncTask.SERIAL_EXECUTOR,
                       callback: (CameraDevice) -> Unit) {

        // openCamera() requires API >= 28
        cameraManager.openCamera(
            dualCamera.logicalId, executor, object : CameraDevice.StateCallback() {
                override fun onOpened(device: CameraDevice) = callback(device)
                // Omitting for brevity...
                override fun onError(device: CameraDevice, error: Int) = onDisconnected(device)
                override fun onDisconnected(device: CameraDevice) = device.close()
            })
    }

Java

void openDualCamera(CameraManager cameraManager,
                        DualCamera dualCamera,
                        Executor executor,
                        CameraDeviceCallback cameraDeviceCallback
    ) {

        // openCamera() requires API >= 28
        cameraManager.openCamera(dualCamera.logicalId, executor, new CameraDevice.StateCallback() {
            @Override
            public void onOpened(@NonNull CameraDevice cameraDevice) {
               cameraDeviceCallback.callback(cameraDevice);
            }

            @Override
            public void onDisconnected(@NonNull CameraDevice cameraDevice) {
                cameraDevice.close();
            }

            @Override
            public void onError(@NonNull CameraDevice cameraDevice, int i) {
                onDisconnected(cameraDevice);
            }
        });
    }

Bên cạnh việc chọn máy ảnh cần mở, quy trình này cũng giống như mở máy ảnh trong các phiên bản Android trước đây. Việc tạo phiên chụp bằng API cấu hình phiên mới sẽ yêu cầu khung liên kết một số mục tiêu với mã nhận dạng máy ảnh thực cụ thể:

Kotlin

/**
 * Helper type definition that encapsulates 3 sets of output targets:
 *
 *   1. Logical camera
 *   2. First physical camera
 *   3. Second physical camera
 */
typealias DualCameraOutputs =
        Triple?, MutableList?, MutableList?>

fun createDualCameraSession(cameraManager: CameraManager,
                            dualCamera: DualCamera,
                            targets: DualCameraOutputs,
                            // AsyncTask is deprecated beginning API 30
                            executor: Executor = AsyncTask.SERIAL_EXECUTOR,
                            callback: (CameraCaptureSession) -> Unit) {

    // Create 3 sets of output configurations: one for the logical camera, and
    // one for each of the physical cameras.
    val outputConfigsLogical = targets.first?.map { OutputConfiguration(it) }
    val outputConfigsPhysical1 = targets.second?.map {
        OutputConfiguration(it).apply { setPhysicalCameraId(dualCamera.physicalId1) } }
    val outputConfigsPhysical2 = targets.third?.map {
        OutputConfiguration(it).apply { setPhysicalCameraId(dualCamera.physicalId2) } }

    // Put all the output configurations into a single flat array
    val outputConfigsAll = arrayOf(
        outputConfigsLogical, outputConfigsPhysical1, outputConfigsPhysical2)
        .filterNotNull().flatMap { it }

    // Instantiate a session configuration that can be used to create a session
    val sessionConfiguration = SessionConfiguration(
        SessionConfiguration.SESSION_REGULAR,
        outputConfigsAll, executor, object : CameraCaptureSession.StateCallback() {
            override fun onConfigured(session: CameraCaptureSession) = callback(session)
            // Omitting for brevity...
            override fun onConfigureFailed(session: CameraCaptureSession) = session.device.close()
        })

    // Open the logical camera using the previously defined function
    openDualCamera(cameraManager, dualCamera, executor = executor) {

        // Finally create the session and return via callback
        it.createCaptureSession(sessionConfiguration)
    }
}

Java

/**
 * Helper class definition that encapsulates 3 sets of output targets:
 * 

* 1. Logical camera * 2. First physical camera * 3. Second physical camera */ final class DualCameraOutputs { private final List logicalCamera; private final List firstPhysicalCamera; private final List secondPhysicalCamera; public DualCameraOutputs(List logicalCamera, List firstPhysicalCamera, List third) { this.logicalCamera = logicalCamera; this.firstPhysicalCamera = firstPhysicalCamera; this.secondPhysicalCamera = third; } public List getLogicalCamera() { return logicalCamera; } public List getFirstPhysicalCamera() { return firstPhysicalCamera; } public List getSecondPhysicalCamera() { return secondPhysicalCamera; } } interface CameraCaptureSessionCallback { void callback(CameraCaptureSession cameraCaptureSession); } void createDualCameraSession(CameraManager cameraManager, DualCamera dualCamera, DualCameraOutputs targets, Executor executor, CameraCaptureSessionCallback cameraCaptureSessionCallback) { // Create 3 sets of output configurations: one for the logical camera, and // one for each of the physical cameras. List outputConfigsLogical = targets.getLogicalCamera().stream() .map(OutputConfiguration::new) .collect(Collectors.toList()); List outputConfigsPhysical1 = targets.getFirstPhysicalCamera().stream() .map(s -> { OutputConfiguration outputConfiguration = new OutputConfiguration(s); outputConfiguration.setPhysicalCameraId(dualCamera.physicalId1); return outputConfiguration; }) .collect(Collectors.toList()); List outputConfigsPhysical2 = targets.getSecondPhysicalCamera().stream() .map(s -> { OutputConfiguration outputConfiguration = new OutputConfiguration(s); outputConfiguration.setPhysicalCameraId(dualCamera.physicalId2); return outputConfiguration; }) .collect(Collectors.toList()); // Put all the output configurations into a single flat array List outputConfigsAll = Stream.of( outputConfigsLogical, outputConfigsPhysical1, outputConfigsPhysical2 ) .filter(Objects::nonNull) .flatMap(Collection::stream) .collect(Collectors.toList()); // Instantiate a session configuration that can be used to create a session SessionConfiguration sessionConfiguration = new SessionConfiguration( SessionConfiguration.SESSION_REGULAR, outputConfigsAll, executor, new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { cameraCaptureSessionCallback.callback(cameraCaptureSession); } // Omitting for brevity... @Override public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { cameraCaptureSession.getDevice().close(); } }); // Open the logical camera using the previously defined function openDualCamera(cameraManager, dualCamera, executor, (CameraDevice c) -> // Finally create the session and return via callback c.createCaptureSession(sessionConfiguration)); }

Vui lòng xem createCaptureSession để biết thông tin về những kiểu kết hợp luồng được hỗ trợ. Việc kết hợp các luồng là cho nhiều luồng trên một máy ảnh logic duy nhất. Khả năng tương thích mở rộng sang việc sử dụng cùng một cấu hình và thay thế một trong các luồng đó bằng hai luồng từ hai camera vật lý thuộc cùng một máy ảnh logic.

Khi phiên camera đã sẵn sàng, hãy gửi các yêu cầu chụp mong muốn. Mỗi mục tiêu của yêu cầu chụp sẽ nhận dữ liệu từ máy ảnh thực được liên kết (nếu có máy ảnh đang được sử dụng) hoặc quay lại máy ảnh logic.

Ví dụ về trường hợp sử dụng Zoom

Bạn có thể sử dụng việc hợp nhất các máy ảnh vật lý thành một luồng duy nhất để người dùng có thể chuyển đổi giữa các máy ảnh vật lý khác nhau nhằm trải nghiệm một trường nhìn khác, chụp lại hiệu quả "mức thu phóng" khác.

Hình 4. Ví dụ về việc hoán đổi máy ảnh cho trường hợp sử dụng mức thu phóng (trong Quảng cáo trên Pixel 3)

Hãy bắt đầu bằng cách chọn một cặp camera thực để cho phép người dùng chuyển đổi giữa các camera. Để có hiệu ứng tối đa, bạn có thể chọn cặp camera cung cấp tiêu cự tối thiểu và tối đa hiện có.

Kotlin

fun findShortLongCameraPair(manager: CameraManager, facing: Int? = null): DualCamera? {

    return findDualCameras(manager, facing).map {
        val characteristics1 = manager.getCameraCharacteristics(it.physicalId1)
        val characteristics2 = manager.getCameraCharacteristics(it.physicalId2)

        // Query the focal lengths advertised by each physical camera
        val focalLengths1 = characteristics1.get(
            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(0F)
        val focalLengths2 = characteristics2.get(
            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(0F)

        // Compute the largest difference between min and max focal lengths between cameras
        val focalLengthsDiff1 = focalLengths2.maxOrNull()!! - focalLengths1.minOrNull()!!
        val focalLengthsDiff2 = focalLengths1.maxOrNull()!! - focalLengths2.minOrNull()!!

        // Return the pair of camera IDs and the difference between min and max focal lengths
        if (focalLengthsDiff1 < focalLengthsDiff2) {
            Pair(DualCamera(it.logicalId, it.physicalId1, it.physicalId2), focalLengthsDiff1)
        } else {
            Pair(DualCamera(it.logicalId, it.physicalId2, it.physicalId1), focalLengthsDiff2)
        }

        // Return only the pair with the largest difference, or null if no pairs are found
    }.maxByOrNull { it.second }?.first
}

Java

// Utility functions to find min/max value in float[]
    float findMax(float[] array) {
        float max = Float.NEGATIVE_INFINITY;
        for(float cur: array)
            max = Math.max(max, cur);
        return max;
    }
    float findMin(float[] array) {
        float min = Float.NEGATIVE_INFINITY;
        for(float cur: array)
            min = Math.min(min, cur);
        return min;
    }

DualCamera findShortLongCameraPair(CameraManager manager, Integer facing) {
        return findDualCameras(manager, facing).stream()
                .map(c -> {
                    CameraCharacteristics characteristics1;
                    CameraCharacteristics characteristics2;
                    try {
                        characteristics1 = manager.getCameraCharacteristics(c.physicalId1);
                        characteristics2 = manager.getCameraCharacteristics(c.physicalId2);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                        return null;
                    }

                    // Query the focal lengths advertised by each physical camera
                    float[] focalLengths1 = characteristics1.get(
                            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
                    float[] focalLengths2 = characteristics2.get(
                            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);

                    // Compute the largest difference between min and max focal lengths between cameras
                    Float focalLengthsDiff1 = findMax(focalLengths2) - findMin(focalLengths1);
                    Float focalLengthsDiff2 = findMax(focalLengths1) - findMin(focalLengths2);

                    // Return the pair of camera IDs and the difference between min and max focal lengths
                    if (focalLengthsDiff1 < focalLengthsDiff2) {
                        return new Pair<>(new DualCamera(c.logicalId, c.physicalId1, c.physicalId2), focalLengthsDiff1);
                    } else {
                        return new Pair<>(new DualCamera(c.logicalId, c.physicalId2, c.physicalId1), focalLengthsDiff2);
                    }

                }) // Return only the pair with the largest difference, or null if no pairs are found
                .max(Comparator.comparing(pair -> pair.second)).get().first;
    }

Bạn nên sử dụng hai SurfaceViews cho mỗi trình phát trực tiếp. Các SurfaceViews này được hoán đổi dựa trên tương tác của người dùng để chỉ hiển thị một quảng cáo tại một thời điểm bất kỳ.

Mã sau đây cho biết cách mở máy ảnh logic, định cấu hình đầu ra của máy ảnh, tạo một phiên máy ảnh và bắt đầu hai luồng xem trước:

Kotlin

val cameraManager: CameraManager = ...

// Get the two output targets from the activity / fragment
val surface1 = ...  // from SurfaceView
val surface2 = ...  // from SurfaceView

val dualCamera = findShortLongCameraPair(manager)!!
val outputTargets = DualCameraOutputs(
    null, mutableListOf(surface1), mutableListOf(surface2))

// Here you open the logical camera, configure the outputs and create a session
createDualCameraSession(manager, dualCamera, targets = outputTargets) { session ->

  // Create a single request which has one target for each physical camera
  // NOTE: Each target receive frames from only its associated physical camera
  val requestTemplate = CameraDevice.TEMPLATE_PREVIEW
  val captureRequest = session.device.createCaptureRequest(requestTemplate).apply {
    arrayOf(surface1, surface2).forEach { addTarget(it) }
  }.build()

  // Set the sticky request for the session and you are done
  session.setRepeatingRequest(captureRequest, null, null)
}

Java

CameraManager manager = ...;

        // Get the two output targets from the activity / fragment
        Surface surface1 = ...;  // from SurfaceView
        Surface surface2 = ...;  // from SurfaceView

        DualCamera dualCamera = findShortLongCameraPair(manager, null);
                DualCameraOutputs outputTargets = new DualCameraOutputs(
                null, Collections.singletonList(surface1), Collections.singletonList(surface2));

        // Here you open the logical camera, configure the outputs and create a session
        createDualCameraSession(manager, dualCamera, outputTargets, null, (session) -> {
            // Create a single request which has one target for each physical camera
            // NOTE: Each target receive frames from only its associated physical camera
            CaptureRequest.Builder captureRequestBuilder;
            try {
                captureRequestBuilder = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                Arrays.asList(surface1, surface2).forEach(captureRequestBuilder::addTarget);

                // Set the sticky request for the session and you are done
                session.setRepeatingRequest(captureRequestBuilder.build(), null, null);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        });

Bạn chỉ cần cung cấp một giao diện người dùng để người dùng chuyển đổi giữa hai nền tảng, chẳng hạn như một nút hoặc nhấn đúp vào SurfaceView. Bạn thậm chí có thể thực hiện một số hình thức phân tích cảnh và tự động chuyển đổi giữa hai luồng.

Ống kính bị méo

Tất cả các ống kính đều tạo ra một mức méo nhất định. Trong Android, bạn có thể truy vấn độ méo do ống kính tạo ra bằng cách sử dụng CameraCharacteristics.LENS_DISTORTION, thay thế CameraCharacteristics.LENS_RADIAL_DISTORTION hiện không còn được dùng nữa. Đối với máy ảnh logic, độ méo hình ở mức tối thiểu và ứng dụng có thể sử dụng khung hình nhiều hơn hoặc ít hơn khi lấy từ máy ảnh. Đối với máy ảnh vật lý, có thể có nhiều cấu hình ống kính rất khác nhau, đặc biệt là trên ống kính rộng.

Một số thiết bị có thể triển khai tính năng tự động sửa méo hình thông qua CaptureRequest.DISTORTION_CORRECTION_MODE. Theo mặc định, tính năng này được bật cho hầu hết thiết bị.

Kotlin

val cameraSession: CameraCaptureSession = ...

        // Use still capture template to build the capture request
        val captureRequest = cameraSession.device.createCaptureRequest(
            CameraDevice.TEMPLATE_STILL_CAPTURE
        )

        // Determine if this device supports distortion correction
        val characteristics: CameraCharacteristics = ...
        val supportsDistortionCorrection = characteristics.get(
            CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES
        )?.contains(
            CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
        ) ?: false

        if (supportsDistortionCorrection) {
            captureRequest.set(
                CaptureRequest.DISTORTION_CORRECTION_MODE,
                CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
            )
        }

        // Add output target, set other capture request parameters...

        // Dispatch the capture request
        cameraSession.capture(captureRequest.build(), ...)

Java

CameraCaptureSession cameraSession = ...;

        // Use still capture template to build the capture request
        CaptureRequest.Builder captureRequestBuilder = null;
        try {
            captureRequestBuilder = cameraSession.getDevice().createCaptureRequest(
                    CameraDevice.TEMPLATE_STILL_CAPTURE
            );
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        // Determine if this device supports distortion correction
        CameraCharacteristics characteristics = ...;
        boolean supportsDistortionCorrection = Arrays.stream(
                        characteristics.get(
                                CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES
                        ))
                .anyMatch(i -> i == CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY);
        if (supportsDistortionCorrection) {
            captureRequestBuilder.set(
                    CaptureRequest.DISTORTION_CORRECTION_MODE,
                    CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
            );
        }

        // Add output target, set other capture request parameters...

        // Dispatch the capture request
        cameraSession.capture(captureRequestBuilder.build(), ...);

Việc đặt yêu cầu chụp ở chế độ này có thể ảnh hưởng đến tốc độ khung hình do máy ảnh tạo ra. Bạn có thể chọn chỉ đặt tính năng chỉnh méo hình trên các ảnh tĩnh.