구성 옵션

각 CameraX 사용 사례를 구성하여 사용 사례 작업의 여러 측면을 제어할 수 있습니다.

예를 들어 이미지 캡처 사용 사례에서는 타겟 가로세로 비율과 플래시 모드를 설정할 수 있습니다. 다음 코드가 한 가지 예입니다.

Kotlin

val imageCapture = ImageCapture.Builder()
    .setFlashMode(...)
    .setTargetAspectRatio(...)
    .build()

Java

ImageCapture imageCapture =
    new ImageCapture.Builder()
        .setFlashMode(...)
        .setTargetAspectRatio(...)
        .build();

어떤 사용 사례에서는 구성 옵션 외에도 사용 사례 작성 후 설정을 동적으로 변경하기 위해 API를 노출합니다. 개별 사용 사례에 적용되는 구성에 관한 정보는 미리보기 구현, 이미지 분석, 이미지 캡처를 참고하세요.

CameraXConfig

편의를 위해 CameraX에는 대부분의 사용 시나리오에 적합한 내부 실행자 및 핸들러와 같은 기본 구성이 있습니다. 하지만 애플리케이션에 특별한 요구사항이 있거나 기본 구성을 맞춤설정하는 것이 좋은 경우에는 CameraXConfig가 인터페이스로 적합합니다.

CameraXConfig를 사용하면 애플리케이션에서 다음 작업을 할 수 있습니다.

사용 모델

다음 절차에서는 CameraXConfig를 사용하는 방법을 설명합니다.

  1. 맞춤설정된 구성으로 CameraXConfig 객체를 만듭니다.
  2. Application에서 CameraXConfig.Provider 인터페이스를 구현하고 getCameraXConfig()에서 CameraXConfig 객체를 반환합니다.
  3. 여기에 설명된 대로 Application 클래스를 AndroidManifest.xml 파일에 추가합니다.

예를 들어 다음 코드 샘플은 CameraX 로깅을 오류 메시지로만 제한합니다.

Kotlin

class CameraApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
           .setMinimumLoggingLevel(Log.ERROR).build()
   }
}

CameraX 구성을 설정한 후 애플리케이션에서 설정된 구성을 알아야 하는 경우 CameraXConfig 객체의 로컬 사본을 보관하세요.

카메라 리미터

ProcessCameraProvider.getInstance()를 처음 호출하는 동안 CameraX는 기기에서 사용할 수 있는 카메라의 특성을 열거하고 쿼리합니다. CameraX가 하드웨어 구성요소와 통신해야 하므로 카메라별로(특히 중저가형 기기인 경우) 이 프로세스에 적지 않은 시간이 소요될 수 있습니다. 애플리케이션이 기기의 특정 카메라(예: 기본 전면 카메라)만 사용하는 경우 다른 카메라를 무시하도록 CameraX를 설정할 수 있습니다. 그러면 애플리케이션이 사용하는 카메라의 시작 지연 시간을 줄일 수 있습니다.

CameraXConfig.Builder.setAvailableCamerasLimiter()에 전달된 CameraSelector가 카메라를 필터링하면 CameraX는 필터링된 카메라가 존재하지 않는 것처럼 동작합니다. 예를 들어 다음 코드는 애플리케이션이 기기의 기본 후면 카메라만 사용하도록 제한합니다.

Kotlin

class MainApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
              .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA)
              .build()
   }
}

스레드

CameraX 빌드에 사용되는 대부분의 플랫폼 API는 하드웨어와의 프로세스 간 통신(IPC)을 차단할 것을 요구합니다. 응답하는 데 수백 밀리초가 걸릴 수 있기 때문입니다. 이러한 이유로 CameraX는 백그라운드 스레드에서만 이러한 API를 호출하므로 기본 스레드가 차단되지 않고 UI가 유동적으로 유지됩니다. CameraX는 이 동작이 투명하게 표시되도록 이러한 백그라운드 스레드를 내부적으로 관리합니다. 하지만 일부 애플리케이션은 스레드를 엄격하게 제어해야 합니다. CameraXConfig를 사용하면 애플리케이션이 CameraXConfig.Builder.setCameraExecutor()CameraXConfig.Builder.setSchedulerHandler()를 통해 사용되는 백그라운드 스레드를 설정할 수 있습니다.

카메라 실행자

카메라 실행자는 모든 내부 Camera 플랫폼 API 호출은 물론, 이러한 API의 콜백에도 사용됩니다. CameraX는 이러한 작업을 실행하도록 내부 Executor를 할당하고 관리합니다. 그러나 스레드를 더욱 엄격하게 제어해야 하는 애플리케이션이라면 CameraXConfig.Builder.setCameraExecutor()를 사용합니다.

스케줄러 핸들러

스케줄러 핸들러는 카메라를 사용할 수 없을 때 카메라 열기를 다시 시도하는 등 내부 작업을 고정된 간격으로 예약하는 데 사용됩니다. 이 핸들러는 작업을 실행하지 않고 카메라 실행자에 전달하기만 합니다. 또한 콜백에 Handler가 필요한 기존 API 플랫폼에서 사용되기도 합니다. 이러한 경우에도 콜백을 카메라 실행자에 직접 전달만 합니다. CameraX는 내부 HandlerThread를 할당하고 관리하여 이러한 작업을 실행하지만 개발자가 CameraXConfig.Builder.setSchedulerHandler()를 사용하여 재정의할 수 있습니다.

로깅

CameraX 로깅을 사용하면 애플리케이션이 logcat 메시지를 필터링할 수 있습니다. 이는 프로덕션 코드에서 상세 메시지를 피하는 것이 좋을 수 있기 때문입니다. CameraX는 가장 상세한 수준부터 가장 간소한 수준까지 4가지 로깅 수준을 지원합니다.

  • Log.DEBUG(기본값)
  • Log.INFO
  • Log.WARN
  • Log.ERROR

로그 수준에 관한 자세한 설명은 Android 로그 문서를 참고하세요. CameraXConfig.Builder.setMinimumLoggingLevel(int)을 사용하여 애플리케이션에 적합한 로깅 수준을 설정하시기 바랍니다.

자동 선택

CameraX는 앱이 실행되는 기기에 적합한 기능을 자동으로 제공합니다. 예를 들어 CameraX는 해상도를 지정하지 않거나 지정한 해상도가 지원되지 않는 경우 사용할 최적의 해상도를 자동으로 결정합니다. 이 모든 작업은 라이브러리에서 처리되므로 기기별 코드를 작성할 필요가 없습니다.

CameraX의 목표는 카메라 세션을 성공적으로 초기화하는 것입니다. 즉, CameraX는 기기 기능에 따라 해상도 및 가로세로 비율을 절충합니다. 이러한 절충은 다음과 같은 이유로 발생할 수 있습니다.

  • 요청된 해상도를 기기에서 지원하지 않습니다.
  • 올바르게 작동하려면 특정 해상도가 필요한 레거시 기기와 같이 기기에 호환성 문제가 있습니다.
  • 일부 기기에서는 특정 형식을 특정 가로세로 비율에서만 사용할 수 있습니다.
  • 기기에서 JPEG 또는 동영상 인코딩의 경우 'nearest mod16'을 선호합니다. 자세한 내용은 SCALER_STREAM_CONFIGURATION_MAP를 참고하세요.

CameraX는 세션을 만들고 관리하지만 항상 코드의 사용 사례 출력에서 반환된 이미지 크기를 확인하고 그에 따라 조정하세요.

회전

기본적으로 카메라 회전은 사용 사례가 생성되는 동안 기본 디스플레이의 회전과 일치하도록 설정됩니다. 이 기본 사례에서 CameraX는 앱이 미리보기에서 볼 것으로 예상되는 이미지와 일치시킬 수 있도록 출력을 생성합니다. 사용 사례 객체를 구성할 때 현재 디스플레이 방향에 전달하여 다중 디스플레이 기기를 지원하도록 회전을 맞춤 값으로 변경하거나 사용 사례 객체가 생성된 후 동적으로 변경되도록 할 수 있습니다.

앱이 구성 설정을 사용하여 타겟 회전을 설정할 수 있습니다. 그러면 수명 주기가 실행 중인 경우에도 앱은 사용 사례 API의 메서드(예: ImageAnalysis.setTargetRotation())를 사용하여 회전 설정을 업데이트할 수 있습니다. 앱이 세로 모드로 잠겨 있어 회전 시 재구성이 발생하지 않을 때 이 메서드를 사용할 수 있지만, 사진 또는 분석 사용 사례에서 기기의 현재 회전을 알고 있어야 합니다. 예를 들어 얼굴 인식에서 얼굴의 방향을 올바르게 감지하거나 사진이 가로 또는 세로 모드로 설정되도록 하기 위해 회전 인식이 필요할 수 있습니다.

캡처된 이미지의 데이터는 회전 정보 없이 저장될 수 있습니다. Exif 데이터에는 회전 정보가 포함되므로 이미지를 저장한 후 갤러리 애플리케이션에서 올바른 방향으로 이미지를 표시할 수 있습니다.

미리보기 데이터를 올바른 방향으로 표시하기 위해 Preview.PreviewOutput()의 메타데이터 출력을 사용하여 변환을 생성할 수 있습니다.

다음 코드 샘플은 방향 이벤트에 회전을 설정하는 방법을 보여줍니다.

Kotlin

override fun onCreate() {
    val imageCapture = ImageCapture.Builder().build()

    val orientationEventListener = object : OrientationEventListener(this as Context) {
        override fun onOrientationChanged(orientation : Int) {
            // Monitors orientation values to determine the target rotation value
            val rotation : Int = when (orientation) {
                in 45..134 -> Surface.ROTATION_270
                in 135..224 -> Surface.ROTATION_180
                in 225..314 -> Surface.ROTATION_90
                else -> Surface.ROTATION_0
            }

            imageCapture.targetRotation = rotation
        }
    }
    orientationEventListener.enable()
}

Java

@Override
public void onCreate() {
    ImageCapture imageCapture = new ImageCapture.Builder().build();

    OrientationEventListener orientationEventListener = new OrientationEventListener((Context)this) {
       @Override
       public void onOrientationChanged(int orientation) {
           int rotation;

           // Monitors orientation values to determine the target rotation value
           if (orientation >= 45 && orientation < 135) {
               rotation = Surface.ROTATION_270;
           } else if (orientation >= 135 && orientation < 225) {
               rotation = Surface.ROTATION_180;
           } else if (orientation >= 225 && orientation < 315) {
               rotation = Surface.ROTATION_90;
           } else {
               rotation = Surface.ROTATION_0;
           }

           imageCapture.setTargetRotation(rotation);
       }
    };

    orientationEventListener.enable();
}

설정된 회전에 따라 각 사용 사례에서는 이미지 데이터를 직접 회전하거나 회전하지 않은 이미지 데이터의 소비자에게 회전 메타데이터를 제공합니다.

  • Preview: Preview.getTargetRotation()을 사용하여 타겟 해상도의 회전이 인식되도록 메타데이터 출력이 제공됩니다.
  • ImageAnalysis: 디스플레이 좌표를 기준으로 이미지 버퍼 좌표를 알 수 있도록 메타데이터 출력이 제공됩니다.
  • ImageCapture: 회전 설정을 기록하도록 이미지 Exif 메타데이터, 버퍼 또는 이 두 가지 모두가 변경됩니다. 변경되는 값은 HAL 구현에 따라 다릅니다.

자르기 rect

기본적으로 자르기 rect는 전체 버퍼 rect입니다. ViewPortUseCaseGroup을 사용하여 이를 맞춤설정할 수 있습니다. CameraX는 사용 사례를 그룹화하고 표시 영역을 설정하여 그룹 내 모든 사용 사례의 자르기 rect가 카메라 센서의 동일한 영역을 가리키게 합니다.

다음 코드 스니펫은 이 두 가지 클래스를 사용하는 방법을 보여줍니다.

Kotlin

val viewPort =  ViewPort.Builder(Rational(width, height), display.rotation).build()
val useCaseGroup = UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup)

Java

ViewPort viewPort = new ViewPort.Builder(
         new Rational(width, height),
         getDisplay().getRotation()).build();
UseCaseGroup useCaseGroup = new UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build();
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup);

ViewPort는 최종 사용자에게 표시되는 버퍼 rect를 정의합니다. 그런 다음 CameraX는 표시 영역의 속성과 연결된 사용 사례를 기반으로 최대한 큰 자르기 rect를 계산합니다. 일반적으로 WYSIWYG 효과를 얻으려면 미리보기 사용 사례에 따라 표시 영역을 구성하면 됩니다. 표시 영역을 구성하는 간단한 방법은 PreviewView를 사용하는 것입니다.

다음 코드 스니펫은 ViewPort 객체를 가져오는 방법을 보여줍니다.

Kotlin

val viewport = findViewById<PreviewView>(R.id.preview_view).viewPort

Java

ViewPort viewPort = ((PreviewView)findViewById(R.id.preview_view)).getViewPort();

앞의 예에서 PreviewView의 조정 유형이 기본값(FILL_CENTER)으로 설정되었다고 가정하면 앱이 ImageAnalysisImageCapture에서 가져오는 것이 PreviewView에 최종 사용자에게 표시되는 것과 일치합니다. 자르기 rect 및 회전을 출력 버퍼에 적용하면 모든 사용 사례의 이미지는 동일하지만 해상도가 다를 수 있습니다. 변환 정보를 적용하는 방법에 관한 자세한 내용은 출력 변환을 참고하세요.

카메라 선택

CameraX는 애플리케이션의 요구사항과 사용 사례에 가장 적합한 카메라 기기를 자동으로 선택합니다. 자동으로 선택된 기기가 아닌 다른 기기를 사용하려는 경우를 위한 몇 가지 옵션도 있습니다.

다음 코드 샘플은 기기 선택에 영향을 주는 CameraSelector를 만드는 방법을 보여줍니다.

Kotlin

fun selectExternalOrBestCamera(provider: ProcessCameraProvider):CameraSelector? {
   val cam2Infos = provider.availableCameraInfos.map {
       Camera2CameraInfo.from(it)
   }.sortedByDescending {
       // HARDWARE_LEVEL is Int type, with the order of:
       // LEGACY < LIMITED < FULL < LEVEL_3 < EXTERNAL
       it.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
   }

   return when {
       cam2Infos.isNotEmpty() -> {
           CameraSelector.Builder()
               .addCameraFilter {
                   it.filter { camInfo ->
                       // cam2Infos[0] is either EXTERNAL or best built-in camera
                       val thisCamId = Camera2CameraInfo.from(camInfo).cameraId
                       thisCamId == cam2Infos[0].cameraId
                   }
               }.build()
       }
       else -> null
    }
}

// create a CameraSelector for the USB camera (or highest level internal camera)
val selector = selectExternalOrBestCamera(processCameraProvider)
processCameraProvider.bindToLifecycle(this, selector, preview, analysis)

여러 대의 카메라를 동시에 선택

CameraX 1.3부터 동시에 여러 카메라를 선택할 수도 있습니다. 예를 들어 전면 및 후면 카메라에 바인딩하여 두 시점 모두에서 동시에 사진을 찍거나 동영상을 녹화할 수 있습니다.

동시 카메라 기능을 사용하면 기기는 렌즈의 방향이 다른 두 카메라를 동시에 작동하거나 동시에 두 개의 후면 카메라를 작동할 수 있습니다. 다음 코드 블록은 bindToLifecycle을 호출할 때 두 카메라를 설정하는 방법과 반환된 ConcurrentCamera 객체에서 두 카메라 객체를 모두 가져오는 방법을 보여줍니다.

Kotlin

// Build ConcurrentCameraConfig
val primary = ConcurrentCamera.SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val secondary = ConcurrentCamera.SingleCameraConfig(
    secondaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val concurrentCamera = cameraProvider.bindToLifecycle(
    listOf(primary, secondary)
)

val primaryCamera = concurrentCamera.cameras[0]
val secondaryCamera = concurrentCamera.cameras[1]

Java

// Build ConcurrentCameraConfig
SingleCameraConfig primary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

SingleCameraConfig secondary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

ConcurrentCamera concurrentCamera =  
    mCameraProvider.bindToLifecycle(Arrays.asList(primary, secondary));

Camera primaryCamera = concurrentCamera.getCameras().get(0);
Camera secondaryCamera = concurrentCamera.getCameras().get(1);

카메라 해상도

기기 기능, 기기의 지원되는 하드웨어 수준, 사용 사례, 제공된 가로세로 비율의 조합을 바탕으로 CameraX가 이미지 해상도를 설정하도록 선택할 수 있습니다. 또는 해당 구성을 지원하는 사용 사례에 특정 타겟 해상도 또는 특정 가로세로 비율을 설정할 수 있습니다.

자동 해상도

CameraX는 cameraProcessProvider.bindToLifecycle()에 지정된 사용 사례를 바탕으로 최적의 해상도 설정을 자동으로 결정할 수 있습니다. 가능한 한 단일 bindToLifecycle() 호출로 단일 세션에서 동시에 실행하는 데 필요한 모든 사용 사례를 지정하세요. CameraX는 기기의 지원되는 하드웨어 수준을 고려하고 기기별 변동(기기가 사용 가능한 스트림 구성을 충족하거나 충족하지 않는 경우)을 고려하여 결합된 사용 사례 집합을 바탕으로 해상도를 결정합니다. 애플리케이션이 다양한 기기에서 실행되도록 하면서 기기별 코드 경로를 최소화하는 것이 목적입니다.

이미지 캡처 및 이미지 분석 사용 사례의 기본 가로세로 비율은 4:3입니다.

사용 사례에는 애플리케이션이 UI 디자인에 따라 원하는 가로세로 비율을 지정할 수 있게 하는 구성 가능한 가로세로 비율이 있습니다. 기기에서 지원하는 비율에 최대한 가깝게 요청된 가로세로 비율과 일치하도록 CameraX 출력이 생성됩니다. 정확히 일치하는 지원 해상도가 없는 경우 가장 많은 조건을 충족하는 해상도가 선택됩니다. 따라서 애플리케이션이 앱에서 카메라가 표시되는 방식을 지정하고, CameraX는 여러 기기에서 이 조건을 충족하기 위한 최적의 카메라 해상도 설정을 선택합니다.

예를 들어 앱에서 할 수 있는 작업은 다음과 같습니다.

  • 사용 사례의 타겟 해상도를 4:3 또는 16:9로 지정
  • 맞춤 해상도 지정. CameraX가 그에 가장 근접한 값을 찾으려고 시도함
  • ImageCapture의 자르기 가로세로 비율 지정

CameraX는 내부 Camera2 노출 영역 해상도를 자동으로 선택합니다. 다음 표에서는 해상도를 보여줍니다.

사용 사례 내부 표면 해상도 출력 데이터 해상도
프리뷰 가로세로 비율: 설정에 가장 잘 맞는 해상도 내부 노출 영역 해상도 View에서 타겟 가로세로 비율에 맞게 자르고 조정하고 회전할 수 있도록 메타데이터가 제공됩니다.
기본 해상도: 가장 높은 미리보기 해상도 또는 미리보기의 가로세로 비율과 일치하는 가장 높은 기기 최적 해상도
최대 해상도: 미리보기 크기로, 기기의 화면 해상도에 맞는 최적의 크기 또는 1080p (1920x1080) 중 더 작은 크기
이미지 분석 가로세로 비율: 설정에 가장 잘 맞는 해상도 내부 표면 해상도
기본 해상도: 기본 타겟 해상도 설정은 640x480입니다. 타겟 해상도와 이에 상응하는 가로세로 비율을 모두 조정하면 가장 잘 지원되는 해상도로 설정됩니다.
최대 해상도: StreamConfigurationMap.getOutputSizes()에서 가져온 카메라 기기의 YUV_420_888 형식 최대 출력 해상도. 타겟 해상도는 기본적으로 640x480으로 설정되므로 640x480보다 큰 해상도를 원하는 경우 setTargetResolution()setTargetAspectRatio()를 사용하여 지원되는 해상도 중 가장 가까운 해상도를 가져와야 합니다.
이미지 캡처 가로세로 비율: 설정에 가장 잘 맞는 가로세로 비율 내부 노출 영역 해상도
기본 해상도: 사용 가능한 가장 높은 해상도 또는 ImageCapture의 가로세로 비율과 일치하는 가장 높은 기기 최적 해상도
최대 해상도: 카메라 기기의 최대 출력 해상도(JPEG 형식). StreamConfigurationMap.getOutputSizes()를 사용하여 가져올 수 있습니다.

해상도 지정

다음 코드 샘플에서와 같이 setTargetResolution(Size resolution) 메서드를 사용하여 사용 사례를 빌드할 때 특정 해상도를 설정할 수 있습니다.

Kotlin

val imageAnalysis = ImageAnalysis.Builder()
    .setTargetResolution(Size(1280, 720))
    .build()

Java

ImageAnalysis imageAnalysis =
  new ImageAnalysis.Builder()
    .setTargetResolution(new Size(1280, 720))
    .build();

동일한 사용 사례에 타겟 가로세로 비율과 타겟 해상도를 모두 설정할 수는 없습니다. 이렇게 하면 구성 객체를 빌드할 때 IllegalArgumentException이 발생합니다.

지원되는 크기를 타겟 회전으로 회전한 후 좌표계에서 해상도 Size를 지정합니다. 예를 들어 자연스러운 세로 방향을 갖는 기기가 자연스러운 타겟 회전에서 세로 모드 이미지를 요청하는 경우 480x640을 지정할 수 있습니다. 동일한 기기가 90도 회전하고 가로 방향을 타겟팅하는 경우에는 640x480을 지정할 수 있습니다.

타겟 해상도는 이미지 해상도에 최소 범위를 설정하려고 합니다. 실제 이미지 해상도는 카메라 구현에서 결정된 타겟 해상도보다 작지 않은 크기에서 사용 가능한 가장 근접한 해상도입니다.

하지만 타겟 해상도와 같거나 그보다 큰 해상도가 없으면 타겟 해상도보다 작은 사용 가능한 해상도 가운데 가장 근접한 해상도가 선택됩니다. 제공된 Size의 가로세로 비율이 동일한 해상도가 가로세로 비율이 다른 해상도보다 우선순위가 높습니다.

CameraX는 요청에 따라 가장 적합한 해상도를 적용합니다. 최우선적으로 필요한 것이 가로세로 비율을 충족하는 것이라면 setTargetAspectRatio만 지정하면 CameraX가 기기에 따라 적합한 특정 해상도를 결정합니다. 앱에 최우선적으로 필요한 것이 이미지 처리 효율을 높이도록 해상도를 지정하는 것이라면(예: 기기 처리 능력에 따라 작거나 중간 크기의 이미지) setTargetResolution(Size resolution)을 사용하세요.

앱에 정확한 해상도가 필요한 경우 createCaptureSession() 내의 표를 참고하여 하드웨어 수준별로 지원되는 최대 해상도를 확인하세요. 현재 기기에서 지원되는 특정 해상도를 확인하려면 StreamConfigurationMap.getOutputSizes(int)를 참고하세요.

앱이 Android 10 이상에서 실행 중인 경우 isSessionConfigurationSupported()를 사용하여 특정 SessionConfiguration을 확인할 수 있습니다.

카메라 출력 제어

CameraX를 사용하면 개별 사용 사례의 필요에 따라 카메라 출력을 구성할 수 있을 뿐만 아니라 결합된 모든 사용 사례에 공통으로 적용되는 카메라 작업을 지원하는 다음 인터페이스도 구현됩니다.

  • CameraControl을 사용하면 일반적인 카메라 기능을 구성할 수 있습니다.
  • CameraInfo를 사용하면 이러한 일반적인 카메라 기능의 상태를 쿼리할 수 있습니다.

CameraControl에서 지원되는 카메라 기능은 다음과 같습니다.

  • 확대/축소
  • 토치
  • 초점 및 측정(탭하여 초점 맞추기)
  • 노출 보정

CameraControl 및 CameraInfo 인스턴스 가져오기

ProcessCameraProvider.bindToLifecycle()에서 반환된 Camera 객체를 사용하여 CameraControlCameraInfo의 인스턴스를 검색합니다. 다음 코드의 예제를 참고하세요.

Kotlin

val camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
val cameraControl = camera.cameraControl
// For querying information and states.
val cameraInfo = camera.cameraInfo

Java

Camera camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
CameraControl cameraControl = camera.getCameraControl()
// For querying information and states.
CameraInfo cameraInfo = camera.getCameraInfo()

예를 들어 bindToLifecycle()을 호출한 후 확대/축소 및 기타 CameraControl 작업을 제출할 수 있습니다. 카메라 인스턴스를 결합하는 데 사용되는 활동을 중지하거나 폐기한 후에는 CameraControl에서 더 이상 작업 실행이 불가능하며 ListenableFuture 실패가 반환됩니다.

Zoom

CameraControl은 확대/축소 수준을 변경하는 두 가지 메서드를 제공합니다.

  • setZoomRatio()는 확대/축소 비율로 확대/축소를 설정합니다.

    이 비율은 CameraInfo.getZoomState().getValue().getMinZoomRatio()에서 CameraInfo.getZoomState().getValue().getMaxZoomRatio() 사이의 범위 내에 있어야 합니다. 그렇지 않으면 함수는 ListenableFuture 실패를 반환합니다.

  • setLinearZoom()은 현재 확대/축소를 0에서 1.0 사이의 선형 확대/축소 값으로 설정합니다.

    선형 확대/축소의 이점은 확대/축소 변경에 따라 시야(FOV)가 조정된다는 점입니다. 따라서 Slider 뷰와 함께 사용하기에 적합합니다.

CameraInfo.getZoomState()는 현재 확대/축소 상태의 LiveData를 반환합니다. 이 값은 카메라가 초기화되거나 확대/축소 수준이 setZoomRatio() 또는 setLinearZoom()을 사용해 설정되면 변경됩니다. 두 메서드 중 하나를 호출하면 ZoomState.getZoomRatio()ZoomState.getLinearZoom()을 지원하는 값이 설정됩니다. 슬라이더와 함께 확대/축소 비율 텍스트를 표시하려는 경우 유용합니다. 변환을 처리하지 않아도 ZoomState LiveData를 관찰하는 것만으로 두 메서드를 업데이트할 수 있습니다.

두 API에서 반환되는 ListenableFuture는 지정된 확대/축소 값이 있는 반복 요청이 완료되면 애플리케이션에 알림을 보내는 옵션을 제공합니다. 또한 이전 작업이 실행 중인 동안 새 확대/축소 값을 설정하면 즉시 이전 확대/축소 작업의 ListenableFuture가 실패합니다.

토치

CameraControl.enableTorch(boolean)는 토치(손전등이라고도 함)를 사용 설정하거나 중지합니다.

CameraInfo.getTorchState()를 사용하여 현재 토치 상태를 쿼리할 수 있습니다. CameraInfo.hasFlashUnit()에서 반환된 값을 확인하면 토치의 사용 가능 여부를 확인할 수 있습니다. 사용 불가능한 경우 CameraControl.enableTorch(boolean)를 호출하면 반환된 ListenableFuture가 즉시 완료되고 실패 결과가 반환되며 토치 상태가 TorchState.OFF로 설정됩니다.

토치가 사용 설정된 경우에는 flashMode 설정과 관계없이 사진 및 동영상 캡처 중에 사용 상태로 유지됩니다. ImageCaptureflashMode는 토치가 사용 중지된 경우에만 작동합니다.

초점 및 측정

CameraControl.startFocusAndMetering()은 지정된 FocusMeteringAction을 기반으로 AF/AE/AWB 측정 영역을 설정하여 자동 초점 및 노출 측정을 트리거합니다. 다수의 카메라 애플리케이션에서 '탭하여 초점 맞추기' 기능을 구현하는 데 자주 사용됩니다.

MeteringPoint

시작하려면 MeteringPointFactory.createPoint(float x, float y, float size)를 사용하여 MeteringPoint를 만듭니다. MeteringPoint는 카메라 Surface의 단일 지점을 나타냅니다. 정규화된 형식으로 저장되어 AF/AE/AWB 영역을 지정하기 위한 센서 좌표로 쉽게 변환될 수 있습니다.

MeteringPoint의 크기는 0에서 1 사이이며 기본 크기는 0.15f입니다. MeteringPointFactory.createPoint(float x, float y, float size)를 호출하면 CameraX는 제공된 size에 맞춰 (x, y)를 중심으로 한 직사각형 영역을 만듭니다.

다음 코드는 MeteringPoint를 만드는 방법을 보여줍니다.

Kotlin

// Use PreviewView.getMeteringPointFactory if PreviewView is used for preview.
previewView.setOnTouchListener((view, motionEvent) ->  {
val meteringPoint = previewView.meteringPointFactory
    .createPoint(motionEvent.x, motionEvent.y)
…
}

// Use DisplayOrientedMeteringPointFactory if SurfaceView / TextureView is used for
// preview. Please note that if the preview is scaled or cropped in the View,
// it’s the application's responsibility to transform the coordinates properly
// so that the width and height of this factory represents the full Preview FOV.
// And the (x,y) passed to create MeteringPoint might need to be adjusted with
// the offsets.
val meteringPointFactory = DisplayOrientedMeteringPointFactory(
     surfaceView.display,
     camera.cameraInfo,
     surfaceView.width,
     surfaceView.height
)

// Use SurfaceOrientedMeteringPointFactory if the point is specified in
// ImageAnalysis ImageProxy.
val meteringPointFactory = SurfaceOrientedMeteringPointFactory(
     imageWidth,
     imageHeight,
     imageAnalysis)

startFocusAndMetering 및 FocusMeteringAction

startFocusAndMetering()을 호출하려면 애플리케이션은 하나 이상의 MeteringPoints 및 선택사항인 FLAG_AF, FLAG_AE, FLAG_AWB 측정 모드의 조합으로 구성된 FocusMeteringAction을 빌드해야 합니다. 다음 코드는 이 사용법을 보여줍니다.

Kotlin

val meteringPoint1 = meteringPointFactory.createPoint(x1, x1)
val meteringPoint2 = meteringPointFactory.createPoint(x2, y2)
val action = FocusMeteringAction.Builder(meteringPoint1) // default AF|AE|AWB
      // Optionally add meteringPoint2 for AF/AE.
      .addPoint(meteringPoint2, FLAG_AF | FLAG_AE)
      // The action is canceled in 3 seconds (if not set, default is 5s).
      .setAutoCancelDuration(3, TimeUnit.SECONDS)
      .build()

val result = cameraControl.startFocusAndMetering(action)
// Adds listener to the ListenableFuture if you need to know the focusMetering result.
result.addListener({
   // result.get().isFocusSuccessful returns if the auto focus is successful or not.
}, ContextCompat.getMainExecutor(this)

위 코드와 같이 startFocusAndMetering()은 AF/AE/AWB 측정 영역에 관한 하나의 MeteringPoint와 AF 및 AE 전용의 또 다른 MeteringPoint로 구성되는 FocusMeteringAction을 사용합니다.

내부적으로 CameraX는 이 작업을 Camera2 MeteringRectangles로 변환하고 상응하는 CONTROL_AF_REGIONS/CONTROL_AE_REGIONS/CONTROL_AWB_REGIONS 매개변수를 캡처 요청에 설정합니다.

모든 기기가 AF/AE/AWB와 여러 영역을 지원하지는 않으므로 CameraX는 최선의 노력으로 FocusMeteringAction을 실행합니다. CameraX는 지원되는 최대 개수의 MeteringPoint를 지점이 추가된 순서대로 사용합니다. 최대 개수에 도달한 후에 추가된 MeteringPoint는 모두 무시됩니다. 예를 들어 2개만 지원하는 플랫폼에서 MeteringPoint 3개가 포함된 FocusMeteringAction이 제공되는 경우 처음 2개의 MeteringPoint만 사용됩니다. CameraX는 마지막 MeteringPoint를 무시합니다.

노출 보정

노출 보정은 애플리케이션이 자동 노출(AE) 출력 결과보다 높게 노출 값(EV)을 미세 조정해야 할 때 유용합니다. 노출 보정 값은 다음과 같은 방식으로 결합되어 현재 이미지 조건에 필요한 노출을 결정합니다.

Exposure = ExposureCompensationIndex * ExposureCompensationStep

CameraX는 노출 보정을 색인 값으로 설정하는 Camera.CameraControl.setExposureCompensationIndex() 함수를 제공합니다.

양수 색인 값은 이미지를 더 밝게 만들고 음수 값은 이미지를 어둡게 합니다. 애플리케이션은 다음 섹션에서 설명하는 CameraInfo.ExposureState.exposureCompensationRange()가 지원하는 범위를 쿼리할 수 있습니다. 이 값이 지원되는 경우 캡처 요청에서 값이 사용 설정되었을 때 ListenableFuture가 완료됩니다. 지정된 색인이 지원 범위를 벗어난 경우 setExposureCompensationIndex()를 실행하면 반환된 ListenableFuture가 즉시 완료되고 실패 결과가 반환됩니다.

CameraX는 대기 중인 최신 setExposureCompensationIndex() 요청만 유지하며, 이전 요청이 실행되기 전에 함수를 여러 번 호출하면 취소됩니다.

다음 스니펫은 노출 보정 색인을 설정하고 노출 변경 요청이 실행된 경우 콜백을 등록합니다.

Kotlin

camera.cameraControl.setExposureCompensationIndex(exposureCompensationIndex)
   .addListener({
      // Get the current exposure compensation index, it might be
      // different from the asked value in case this request was
      // canceled by a newer setting request.
      val currentExposureIndex = camera.cameraInfo.exposureState.exposureCompensationIndex
      …
   }, mainExecutor)
  • Camera.CameraInfo.getExposureState()는 다음을 포함한 현재 ExposureState를 검색합니다.

    • 노출 보정 제어의 지원 여부
    • 현재 노출 보정 색인
    • 노출 보정 색인 범위
    • 노출 보정 값 계산에 사용되는 노출 보정 단계

예를 들어 다음 코드는 현재 ExposureState 값으로 노출 SeekBar의 설정을 초기화합니다.

Kotlin

val exposureState = camera.cameraInfo.exposureState
binding.seekBar.apply {
   isEnabled = exposureState.isExposureCompensationSupported
   max = exposureState.exposureCompensationRange.upper
   min = exposureState.exposureCompensationRange.lower
   progress = exposureState.exposureCompensationIndex
}

추가 리소스

CameraX에 관해 자세히 알아보려면 다음 추가 리소스를 참고하세요.

Codelab

  • CameraX 시작하기
  • 코드 샘플

  • CameraX 샘플 앱
  • 개발자 커뮤니티

    Android CameraX 토론방