다중 카메라 API

참고: 이 페이지에서는 Camera2 패키지를 다룹니다. 앱에 Camera2의 특정 하위 수준 기능이 필요하지 않다면 CameraX를 사용하는 것이 좋습니다. CameraX와 Camera2는 모두 Android 5.0(API 수준 21) 이상을 지원합니다.

다중 카메라는 Android 9 (API 수준 28)부터 도입되었습니다. 출시 이후 API를 지원하는 기기가 출시되고 있습니다. 다양한 다중 카메라 사용 사례 특정 하드웨어 구성과 밀접하게 결합되어 있습니다. 다시 말해 모든 사용 사례가 모든 기기와 호환되므로 다중 카메라가 Play 기능에 적합한 추천 후보입니다. 전송.

몇 가지 일반적인 사용 사례는 다음과 같습니다.

  • 확대/축소: 자르기 영역 또는 원하는 초점에 따라 카메라 간 전환 지정할 수 있습니다.
  • 심도: 여러 카메라를 사용하여 깊이 지도를 만듭니다.
  • 빛망울 효과: 추론된 심도 정보를 사용하여 DSLR처럼 좁은 화면을 시뮬레이션합니다. 초점을 맞출 것입니다.

논리 카메라와 물리적 카메라의 차이점

다중 카메라 API를 이해하려면 논리 카메라와 물리적 카메라입니다. 참고로, 3개의 영역이 있는 기기를 생각해 보세요. 후방 카메라와 유사합니다. 이 예에서 3개의 후면 카메라는 각각 물리적 카메라로 간주됩니다. 논리 카메라는 두 개 이상의 빛을 발합니다. 논리 연산자의 출력은 카메라는 기본 물리적 카메라 중 하나에서 들어오는 스트림일 수 있고, 2개 이상의 기본 물리적 카메라에서 나오는 합성 스트림이 동시에 사용할 수 있습니다. 어느 쪽이든 스트림은 카메라 하드웨어에 의해 처리됩니다. 추상화 계층 (HAL).

많은 휴대전화 제조업체에서 일반적으로 퍼스트 파티 카메라 애플리케이션을 개발합니다. 기기에 사전 설치되어 있습니다. 하드웨어의 모든 기능을 사용하려면 비공개 또는 숨겨진 API를 사용하거나 API로부터 특별 대우를 받을 수 다른 애플리케이션이 액세스할 수 없는 드라이버 구현을 제공합니다. 다소 유용함 장치는 장치의 융합 스트림을 제공하여 논리 카메라의 개념을 특정 권한이 있는 특정 카메라 프레임으로만 애플리케이션을 실행할 수 있습니다 보통 물리적 카메라 중 하나만 프레임워크입니다 Android 9 이전의 서드 파티 개발자는 다음과 같은 상황이 발생합니다. 다음 다이어그램에 나와 있습니다.

그림 1. 카메라 기능은 일반적으로 애플리케이션도

Android 9부터는 비공개 API가 Android 앱에서 더 이상 허용되지 않습니다. 프레임워크에 다중 카메라 지원이 포함되면서 Android는 휴대폰 제조업체가 논리 카메라를 노출시키고 동일한 방향을 향하는 모든 물리적 카메라의 경우 다음은 타사 개발자는 Android 9 및 높음:

그림 2. 모든 카메라 기기에 대한 전체 개발자 액세스 권한입니다. Android 9부터

논리 카메라가 제공하는 것은 전적으로 OEM 구현에 따라 다릅니다. 하위 클래스입니다. 예를 들어 Pixel 3와 같은 기기는 카메라를 사용하여 인코더-디코더 아키텍처를 기반으로 물리적 카메라 중 하나를 요청한 초점 거리와 자르기 영역입니다.

다중 카메라 API

새 API는 다음과 같은 새로운 상수, 클래스, 메서드를 추가합니다.

Android 호환성 정의 문서 (CDD)의 변경사항으로 인해 다중 카메라 API에는 개발자의 기대치도 있습니다. 기기 Android 9 이전에는 듀얼 카메라가 있었지만 두 개 이상의 카메라를 열었습니다. 시행착오를 동시에 수반합니다 Android 9 이상에서는 다중 카메라 한 쌍의 물리적 연결을 열 수 있는 시점을 지정하는 규칙 세트를 동일한 논리 카메라의 일부인 카메라를 사용합니다.

대부분의 경우 Android 9 이상을 실행하는 기기는 (적외선과 같이 일반적이지 않은 센서 유형은 제외) 더 쉽게 사용할 수 있는 논리 카메라를 개발하는 것입니다. 현재 사용 중인 스트림의 모든 조합에 논리 카메라에 속하는 하나의 스트림은 두 개의 스트림이 있습니다.

동시에 여러 스트림

여러 카메라 스트림 동시 사용 에서는 하나의 카메라에서 여러 스트림을 동시에 사용하는 규칙을 다룹니다. 한 가지 주목할 만한 사항이 추가되며, 여러 대의 카메라에 동일한 규칙이 적용됩니다. CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA 드림 논리적 YUV_420_888 또는 원시 스트림을 2개의 물리적 스트림인데요. 즉, YUV 또는 RAW 유형의 각 스트림은 두 개의 스트림이 있습니다. 카메라 스트림으로 시작할 수 있습니다. 단일 카메라 기기를 위한 다음과 같은 보장된 구성이 있습니다.

  • 스트림 1: YUV 유형, 논리 카메라의 MAXIMUM 크기 id = 0

그런 다음 다중 카메라가 지원되는 기기에서 세션을 만들 수 있습니다. 논리적 YUV 스트림을 두 개의 물리적 스트림으로 교체합니다.

  • 스트림 1: YUV 유형, 실제 카메라의 MAXIMUM 크기 id = 1
  • 스트림 2: YUV 유형, 실제 카메라의 MAXIMUM 크기 id = 2

YUV 또는 RAW 스트림을 동등한 두 스트림으로 대체할 수 있습니다. 이 두 카메라는 논리 카메라 그룹의 일부이며, CameraCharacteristics.getPhysicalCameraIds()

프레임워크가 제공하는 보장은 데이터 레이크를 제공하는 데 필요한 동시에 두 개 이상의 물리적 카메라에서 프레임을 가져옵니다. 추가 스트림 대부분의 기기에서 지원되며 때로는 여러 개의 물리적인 카메라 기기를 분리할 수 있습니다. Cloud Shell에서 확실히 보장하지 않으므로 이 작업을 하려면 기기당 테스트와 튜닝을 수행해야 합니다. 많은 시행착오를 겪을 수 있습니다.

여러 물리적 카메라가 있는 세션 만들기

다중 카메라 지원 기기에서 물리적 카메라를 사용할 때는 단일 카메라 CameraDevice (논리 카메라)를 호출하고 단일 내에서 이 카메라와 상호작용합니다. 세션입니다. API를 사용하여 단일 세션 만들기 CameraDevice.createCaptureSession(SessionConfiguration config), 이전 가격 API 수준 28에 추가되었습니다. 세션 구성에는 각 구성에는 일련의 출력 타겟과 원하는 실제 카메라 ID를 찾습니다.

그림 3. SessionConfiguration 및 OutputConfiguration 모델

캡처 요청에는 연결된 출력 대상이 있습니다. 프레임워크 요청이 전송되는 물리적 (또는 논리적) 카메라를 결정하는 기준 출력 대상을 지정합니다 출력 대상이 인코더-디코더 출력과 함께 출력 구성으로 전송된 카메라 ID가 되면 물리적 카메라가 요청을 수신하고 처리합니다.

한 쌍의 물리적 카메라 사용

다중 카메라용 카메라 API에 추가된 또 다른 기능은 논리 카메라 뒤에 있는 물리적 카메라를 찾습니다. 사용자는 이 함수는 사용할 수 있는 물리적 카메라의 잠재적인 쌍을 식별하는 데 도움이 됩니다. 를 사용하여 논리 카메라 스트림 중 하나를 교체합니다.

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
    }

자바

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

물리적 카메라의 상태 처리는 논리 카메라에 의해 제어됩니다. 받는사람 '듀얼 카메라'를 열고 물리적 카메라에 해당하는 논리 카메라를 열고 카메라:

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

자바

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

열려는 카메라를 선택하는 것 외에도 방법은 이전 Android 버전의 카메라에 대해 살펴보았습니다. 새 세션 구성 API는 특정 대상을 특정 물리적 카메라 ID:

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

자바

/**
 * 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)); }

자세한 내용은 createCaptureSession 드림 를 참조하세요. 스트림 결합 단일 논리 카메라의 여러 스트림을 위한 것입니다. 호환성은 두 스트림 중 하나를 2개의 스트림으로 교체합니다. 동일한 논리 카메라의 일부인 두 개의 물리적 카메라에서 생성됩니다.

카메라 세션 준비가 되면 원하는 포드를 캡처 요청을 받을 수 있습니다. 각 캡처 요청의 대상이 연결된 물리적인 카메라로 돌아가거나 논리 카메라로 돌아갑니다.

Zoom 사용 사례

물리적 카메라를 단일 스트림으로 병합하여 사용자가 여러 물리적 카메라 사이를 전환하여 실제 사용 환경을 경험할 수 있습니다. 다른 '확대/축소 수준'을 효과적으로 포착할 수 있습니다.

<ph type="x-smartling-placeholder">
</ph>
그림 4. 확대/축소 수준 사용 사례를 위한 카메라 교체 예시 (Pixel 3 광고)

사용자가 전환할 수 있도록 물리적 카메라 한 쌍을 선택하여 시작합니다. 있습니다. 최대의 효과를 얻기 위해 최소 및 최대 초점 거리.

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
}

자바

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

이를 위해 합리적인 아키텍처는 SurfaceViews: 스트림당 하나씩 이러한 SurfaceViews는 사용자 상호작용에 따라 교체되므로 하나만 사용할 수 있습니다. 표시할 수 있습니다.

다음 코드는 논리 카메라를 열고 카메라를 구성하는 방법을 보여줍니다. 카메라 세션을 만들고 두 개의 미리보기 스트림을 시작합니다.

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

자바

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

이제 두 모드 간에 전환할 수 있는 UI를 제공하기만 하면 됩니다. 표면(예: 버튼 또는 SurfaceView 두 번 탭) 다음을 수행할 수도 있습니다. 장면 분석을 수행하고 두 스트림 간에 전환합니다. 자동으로 확장 및 축소할 수 있습니다

렌즈 왜곡

모든 렌즈는 일정량의 왜곡을 일으킵니다. Android에서는 왜냐하면 렌즈에서 생성된 왜곡은 CameraCharacteristics.LENS_DISTORTION님, 이는 현재 지원 중단된 CameraCharacteristics.LENS_RADIAL_DISTORTION 논리 카메라의 경우 왜곡이 최소화되며 애플리케이션은 카메라에서 들어오는 프레임의 정도를 조정할 수 있습니다. 피지컬 카메라의 경우 렌즈 구성이 매우 다를 수 있으며, 특히 제공합니다.

일부 기기는 CaptureRequest.DISTORTION_CORRECTION_MODE 왜곡 수정은 대부분의 기기에서 기본적으로 사용 설정됩니다.

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(), ...)

자바

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

이 모드에서 캡처 요청을 설정하면 프레임 속도에 영향을 촬영할 수 있습니다. 왜곡 수정을 설정할 수 있습니다. 있습니다.