CameraX 아키텍처

이 페이지에서는 CameraX의 구조, API의 작동 방식, 수명 주기 활용 방법, 사용 사례를 결합하는 방법을 포함하여 CameraX의 아키텍처를 다룹니다.

CameraX 구조

CameraX를 사용하여 사용 사례라고 하는 추상화를 통해 기기의 카메라와 연결할 수 있습니다. 다음 사용 사례를 이용할 수 있습니다.

  • 미리보기: PreviewView 같은 미리보기를 표시할 영역을 허용합니다.
  • 이미지 분석: 머신러닝 등의 분석을 위해 CPU에서 액세스할 수 있는 버퍼를 제공합니다.
  • 이미지 캡처: 사진을 캡처하고 저장합니다.
  • 동영상 캡처: VideoCapture로 동영상 및 오디오를 캡처합니다.

사용 사례를 결합하고 동시에 활성화할 수 있습니다. 예를 들어 앱이 미리보기 사용 사례를 사용하여 카메라에 표시되는 이미지를 사용자가 볼 수 있게 하고, 이미지 분석 사용 사례를 통해 사진 속의 인물이 웃고 있는지 확인하고, 웃고 있는 경우 사진을 찍도록 이미지 캡처 사용 사례를 포함할 수 있습니다.

API 모델

라이브러리에 작동하도록 하려면 다음을 지정하세요.

  • 원하는 사용 사례와 구성 옵션 지정
  • 리스너를 첨부하여 출력 데이터로 할 일 지정
  • 사용 사례를 Android 아키텍처 수명 주기에 결합하여 카메라 사용 시기 및 데이터 생성 시기와 같은 의도된 흐름 지정

CameraX 앱은 CameraController(CameraX를 사용하는 가장 간단한 방법을 원하는 경우 좋음) 또는 CameraProvider(더 많은 유연성이 필요한 경우 좋음)의 두 가지 방법으로 작성할 수 있습니다.

CameraController

CameraController는 단일 클래스로 대부분의 CameraX 핵심 기능을 제공합니다. 설정 코드가 거의 필요하지 않으며 카메라 초기화, 사용 사례 관리, 타겟 회전, 탭하여 초점 맞추기, 손가락을 모으거나 펼쳐 확대/축소 등을 자동으로 처리합니다. CameraController를 확장하는 구체적인 클래스는 LifecycleCameraController입니다.

Kotlin

val previewView: PreviewView = viewBinding.previewView
var cameraController = LifecycleCameraController(baseContext)
cameraController.bindToLifecycle(this)
cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
previewView.controller = cameraController

Java

PreviewView previewView = viewBinding.previewView;
LifecycleCameraController cameraController = new LifecycleCameraController(baseContext);
cameraController.bindToLifecycle(this);
cameraController.setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA);
previewView.setController(cameraController);

CameraController의 기본 UseCasePreview, ImageCapture, ImageAnalysis입니다. ImageCapture 또는 ImageAnalysis를 사용 중지하거나 VideoCapture를 사용 설정하려면 setEnabledUseCases() 메서드를 사용하세요.

CameraController의 더 많은 사용 방법은 QR 코드 스캐너 샘플 또는 CameraController 기본사항 동영상을 참고하세요.

CameraProvider

CameraProvider는 여전히 사용하기 쉽지만, 앱 개발자가 더 많은 설정을 처리하므로 구성을 더 많이 맞춤설정할 수 있습니다(예: 출력 이미지 회전 사용 설정 또는 ImageAnalysis에서 출력 이미지 형식 설정). 카메라 미리보기를 위해 맞춤 Surface를 사용하여 더 많은 유연성을 확보할 수 있지만 CameraController에서는 PreviewView를 사용해야 합니다. 기존 Surface 코드를 사용하는 것은 이미 앱의 다른 부분에 대한 입력인 경우 유용할 수 있습니다.

사용 사례는 set() 메서드를 사용하여 구성하고 build() 메서드를 사용하여 완료합니다. 각 사용 사례 객체는 일련의 사용 사례별 API를 제공합니다. 예를 들어 이미지 캡처 사용 사례에서는 takePicture() 메서드 호출을 제공합니다.

애플리케이션이 onResume()onPause()에 특정 시작 및 중지 메서드 호출을 배치하지 않고 cameraProvider.bindToLifecycle()을 사용하여 카메라와 연결할 수명 주기를 지정합니다. 그러면 수명 주기에서 CameraX에 카메라 캡처 세션을 구성할 시기를 알려 주고 수명 주기 전환에 맞춰 카메라 상태가 적절히 변경될 수 있도록 합니다.

각 사용 사례의 구현 단계는 미리보기 구현, 이미지 분석, 이미지 캡처, 동영상 캡처를 참고하세요.

미리보기 사용 사례는 표시할 Surface와 상호작용합니다. 애플리케이션은 다음 코드를 사용하여 사용 사례와 구성 옵션을 만듭니다.

Kotlin

val preview = Preview.Builder().build()
val viewFinder: PreviewView = findViewById(R.id.previewView)

// The use case is bound to an Android Lifecycle with the following code
val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// PreviewView creates a surface provider and is the recommended provider
preview.setSurfaceProvider(viewFinder.getSurfaceProvider())

Java

Preview preview = new Preview.Builder().build();
PreviewView viewFinder = findViewById(R.id.view_finder);

// The use case is bound to an Android Lifecycle with the following code
Camera camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview);

// PreviewView creates a surface provider, using a Surface from a different
// kind of view will require you to implement your own surface provider.
preview.previewSurfaceProvider = viewFinder.getSurfaceProvider();

더 많은 코드 예를 보려면 공식 CameraX 샘플 앱을 참고하세요.

CameraX 수명 주기

CameraX는 카메라를 여는 시점, 캡처 세션을 생성할 시점, 중지 및 종료할 시점을 결정하기 위해 수명 주기를 따릅니다. 사용 사례 API에서는 진행 상황을 모니터링할 메서드 호출과 콜백을 제공합니다.

사용 사례 결합에 설명된 것처럼 몇 가지 사용 사례 조합을 하나의 수명 주기로 결합할 수 있습니다. 앱에서 결합할 수 없는 사용 사례를 지원해야 하는 경우 다음 중 하나를 실행하세요.

  • 호환되는 사용 사례를 둘 이상의 프래그먼트로 그룹화하고 프래그먼트 간에 전환합니다.
  • 맞춤 수명 주기 구성요소를 만들고 이를 사용하여 카메라 수명 주기를 수동으로 관리합니다.

뷰와 카메라 사용 사례 수명 주기 소유자를 분리하는 경우(예: 맞춤 수명 주기 또는 유지 프래그먼트를 사용하는 경우) ProcessCameraProvider.unbindAll()을 사용하거나 각 사용 사례를 개별적으로 결합 해제하여 CameraX에서 모든 사용 사례의 결합이 해제되도록 해야 합니다. 또는 사용 사례를 수명 주기로 결합할 때 CameraX가 캡처 세션 열기 및 닫기와 사용 사례 결합 해제를 관리하도록 허용할 수 있습니다.

카메라의 모든 기능이 AppCompatActivity 또는 AppCompat 프래그먼트와 같은 단일 수명 주기 인식 구성요소의 수명 주기와 일치하는 경우, 원하는 모든 사용 사례 결합 시 이 구성요소의 수명 주기를 사용하면 수명 주기 인식 구성요소가 활성화되어 있을 때는 카메라 기능을 즉시 사용할 수 있고 활성화되어 있지 않을 때는 안전하게 해제되어 리소스를 소모하지 않게 됩니다.

맞춤 LifecycleOwners

고급 사용 사례의 경우 맞춤 LifecycleOwner를 만들어 앱이 표준 Android LifecycleOwner에 연결하지 않고 명시적으로 CameraX 세션 수명 주기를 관리하도록 할 수 있습니다.

다음 코드 샘플에서는 간단한 맞춤 LifecycleOwner를 만드는 방법을 보여줍니다.

Kotlin

class CustomLifecycle : LifecycleOwner {
    private val lifecycleRegistry: LifecycleRegistry

    init {
        lifecycleRegistry = LifecycleRegistry(this);
        lifecycleRegistry.markState(Lifecycle.State.CREATED)
    }
    ...
    fun doOnResume() {
        lifecycleRegistry.markState(State.RESUMED)
    }
    ...
    override fun getLifecycle(): Lifecycle {
        return lifecycleRegistry
    }
}

Java

public class CustomLifecycle implements LifecycleOwner {
    private LifecycleRegistry lifecycleRegistry;
    public CustomLifecycle() {
        lifecycleRegistry = new LifecycleRegistry(this);
        lifecycleRegistry.markState(Lifecycle.State.CREATED);
    }
   ...
   public void doOnResume() {
        lifecycleRegistry.markState(State.RESUMED);
    }
   ...
    public Lifecycle getLifecycle() {
        return lifecycleRegistry;
    }
}

LifecycleOwner를 사용하면 앱이 코드의 원하는 지점에 상태 전환을 배치할 수 있습니다. 앱에 이 기능을 구현하는 방법을 자세히 알아보려면 맞춤 LifecycleOwner 구현을 참고하세요.

동시 사용 사례

사용 사례를 동시에 실행할 수 있습니다. 사용 사례를 수명 주기에 순차적으로 결합할 수도 있지만, CameraProcessProvider.bindToLifecycle()을 한 번 호출하여 모든 사용 사례를 결합하는 것이 더 좋습니다. 구성 변경에 관한 권장사항을 자세히 알아보려면 구성 변경 처리를 참고하세요.

다음 코드 샘플에서는 동시에 생성되어 실행될 두 개의 사용 사례를 앱이 지정합니다. 또한 두 사용 사례가 수명 주기에 따라 시작되고 중지되도록 두 사용 사례를 위한 수명 주기도 지정합니다.

Kotlin

private lateinit var imageCapture: ImageCapture

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

    cameraProviderFuture.addListener(Runnable {
        // Camera provider is now guaranteed to be available
        val cameraProvider = cameraProviderFuture.get()

        // Set up the preview use case to display camera preview.
        val preview = Preview.Builder().build()

        // Set up the capture use case to allow users to take photos.
        imageCapture = ImageCapture.Builder()
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                .build()

        // Choose the camera by requiring a lens facing
        val cameraSelector = CameraSelector.Builder()
                .requireLensFacing(CameraSelector.LENS_FACING_FRONT)
                .build()

        // Attach use cases to the camera with the same lifecycle owner
        val camera = cameraProvider.bindToLifecycle(
                this as LifecycleOwner, cameraSelector, preview, imageCapture)

        // Connect the preview use case to the previewView
        preview.setSurfaceProvider(
                previewView.getSurfaceProvider())
    }, ContextCompat.getMainExecutor(this))
}

자바

private ImageCapture imageCapture;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    PreviewView previewView = findViewById(R.id.previewView);

    ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
            ProcessCameraProvider.getInstance(this);

    cameraProviderFuture.addListener(() -> {
        try {
            // Camera provider is now guaranteed to be available
            ProcessCameraProvider cameraProvider = cameraProviderFuture.get();

            // Set up the view finder use case to display camera preview
            Preview preview = new Preview.Builder().build();

            // Set up the capture use case to allow users to take photos
            imageCapture = new ImageCapture.Builder()
                    .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                    .build();

            // Choose the camera by requiring a lens facing
            CameraSelector cameraSelector = new CameraSelector.Builder()
                    .requireLensFacing(lensFacing)
                    .build();

            // Attach use cases to the camera with the same lifecycle owner
            Camera camera = cameraProvider.bindToLifecycle(
                    ((LifecycleOwner) this),
                    cameraSelector,
                    preview,
                    imageCapture);

            // Connect the preview use case to the previewView
            preview.setSurfaceProvider(
                    previewView.getSurfaceProvider());
        } catch (InterruptedException | ExecutionException e) {
            // Currently no exceptions thrown. cameraProviderFuture.get()
            // shouldn't block since the listener is being called, so no need to
            // handle InterruptedException.
        }
    }, ContextCompat.getMainExecutor(this));
}

CameraX를 사용하면 Preview, VideoCapture, ImageAnalysis, ImageCapture의 인스턴스를 각각 하나씩 동시에 사용할 수 있습니다. 또한 다음 사항에 유의하세요.

  • 모든 사용 사례가 단독으로 작동할 수 있습니다. 예를 들어, 앱은 미리보기를 사용하지 않고 동영상을 녹화할 수 있습니다.
  • 확장 프로그램을 사용하는 경우 ImageCapturePreview 조합만 작동이 보장됩니다. OEM 구현에 따라 ImageAnalysis를 추가하지 못할 수도 있습니다. VideoCapture 사용 사례에 확장 프로그램을 사용할 수 없습니다. 자세한 내용은 확장 프로그램 참조 문서를 확인하세요.
  • 카메라 기능에 따라 일부 카메라는 낮은 해상도 모드에서 조합을 지원할 수 있지만, 일부 높은 해상도에서는 동일한 조합을 지원할 수 없습니다.
  • 카메라 하드웨어 수준이 FULL 이하인 기기에서 Preview, VideoCaptureImageCapture 또는 ImageAnalysis를 결합하면 CameraX가 PreviewVideoCapture의 카메라의 PRIV 스트림을 강제로 복제할 수 있습니다. 스트림 공유라고 하는 이 중복을 사용하면 이러한 기능을 동시에 사용할 수 있지만 처리 요구사항이 증가합니다. 그 결과 지연 시간이 약간 길어지고 배터리 수명이 줄어들 수 있습니다.

지원되는 하드웨어 수준Camera2CameraInfo에서 가져올 수 있습니다. 다음은 기본 후면 카메라가 LEVEL_3 기기인지 확인하는 코드 예입니다.

Kotlin

@androidx.annotation.OptIn(ExperimentalCamera2Interop::class)
fun isBackCameraLevel3Device(cameraProvider: ProcessCameraProvider) : Boolean {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        return CameraSelector.DEFAULT_BACK_CAMERA
            .filter(cameraProvider.availableCameraInfos)
            .firstOrNull()
            ?.let { Camera2CameraInfo.from(it) }
            ?.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
    }
    return false
}

Java

@androidx.annotation.OptIn(markerClass = ExperimentalCamera2Interop.class)
Boolean isBackCameraLevel3Device(ProcessCameraProvider cameraProvider) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        List\<CameraInfo\> filteredCameraInfos = CameraSelector.DEFAULT_BACK_CAMERA
                .filter(cameraProvider.getAvailableCameraInfos());
        if (!filteredCameraInfos.isEmpty()) {
            return Objects.equals(
                Camera2CameraInfo.from(filteredCameraInfos.get(0)).getCameraCharacteristic(
                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL),
                CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3);
        }
    }
    return false;
}

권한

앱에 CAMERA 권한이 있어야 합니다. Android 10 이상을 실행하는 기기가 아닌 경우 파일에 이미지를 저장하려면 WRITE_EXTERNAL_STORAGE 권한도 있어야 합니다.

앱의 권한 구성에 관해 자세히 알아보려면 앱 권한 요청을 참고하세요.

요구사항

CameraX의 최소 버전 요구사항은 다음과 같습니다.

  • Android API 수준 21
  • Android 아키텍처 구성요소 1.1.1

수명 주기 인식 활동의 경우 FragmentActivity 또는 AppCompatActivity를 사용합니다.

종속 항목 선언

CameraX에 종속 항목을 추가하려면 프로젝트에 Google Maven 저장소를 추가해야 합니다.

프로젝트의 settings.gradle 파일을 열고 다음과 같이 google() 저장소를 추가합니다.

Groovy

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

Kotlin

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

Android 블록 끝에 다음을 추가합니다.

Groovy

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    // For Kotlin projects
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

Kotlin

android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    // For Kotlin projects
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

앱에서 각 모듈의 build.gradle 파일에 다음을 추가합니다.

Groovy

dependencies {
  // CameraX core library using the camera2 implementation
  def camerax_version = "1.5.0-alpha03"
  // The following line is optional, as the core library is included indirectly by camera-camera2
  implementation "androidx.camera:camera-core:${camerax_version}"
  implementation "androidx.camera:camera-camera2:${camerax_version}"
  // If you want to additionally use the CameraX Lifecycle library
  implementation "androidx.camera:camera-lifecycle:${camerax_version}"
  // If you want to additionally use the CameraX VideoCapture library
  implementation "androidx.camera:camera-video:${camerax_version}"
  // If you want to additionally use the CameraX View class
  implementation "androidx.camera:camera-view:${camerax_version}"
  // If you want to additionally add CameraX ML Kit Vision Integration
  implementation "androidx.camera:camera-mlkit-vision:${camerax_version}"
  // If you want to additionally use the CameraX Extensions library
  implementation "androidx.camera:camera-extensions:${camerax_version}"
}

Kotlin

dependencies {
    // CameraX core library using the camera2 implementation
    val camerax_version = "1.5.0-alpha03"
    // The following line is optional, as the core library is included indirectly by camera-camera2
    implementation("androidx.camera:camera-core:${camerax_version}")
    implementation("androidx.camera:camera-camera2:${camerax_version}")
    // If you want to additionally use the CameraX Lifecycle library
    implementation("androidx.camera:camera-lifecycle:${camerax_version}")
    // If you want to additionally use the CameraX VideoCapture library
    implementation("androidx.camera:camera-video:${camerax_version}")
    // If you want to additionally use the CameraX View class
    implementation("androidx.camera:camera-view:${camerax_version}")
    // If you want to additionally add CameraX ML Kit Vision Integration
    implementation("androidx.camera:camera-mlkit-vision:${camerax_version}")
    // If you want to additionally use the CameraX Extensions library
    implementation("androidx.camera:camera-extensions:${camerax_version}")
}

이러한 요구사항을 준수하도록 앱을 구성하는 방법을 자세히 알아보려면 종속 항목 선언을 참고하세요.

Camera2를 사용한 CameraX 상호 운용성

CameraX는 Camera2를 기반으로 하며 Camera2 구현에서 속성을 읽고 쓰는 방법을 보여줍니다. 자세한 내용은 상호 운용성 패키지를 참고하세요.

CameraX에서 Camera2 속성을 구성한 방법에 관한 자세한 내용을 확인하려면 Camera2CameraInfo를 사용하여 기본 CameraCharacteristics를 읽어보세요. 다음 두 경로 중 하나에서 기본 Camera2 속성을 작성하도록 선택할 수도 있습니다.

다음 코드 샘플은 스트림 사용 사례를 사용하여 영상 통화에 맞게 최적화합니다. Camera2CameraInfo를 사용하여 영상 통화 스트림 사용 사례를 사용할 수 있는지 여부를 가져옵니다. 그런 다음 Camera2Interop.Extender를 사용하여 기본 스트림 사용 사례를 설정합니다.

Kotlin

// Set underlying Camera2 stream use case to optimize for video calls.

val videoCallStreamId =
    CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong()

// Check available CameraInfos to find the first one that supports
// the video call stream use case.
val frontCameraInfo = cameraProvider.getAvailableCameraInfos()
    .first { cameraInfo ->
        val isVideoCallStreamingSupported = Camera2CameraInfo.from(cameraInfo)
            .getCameraCharacteristic(
                CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES
            )?.contains(videoCallStreamId)
        val isFrontFacing = (cameraInfo.getLensFacing() == 
                             CameraSelector.LENS_FACING_FRONT)
        (isVideoCallStreamingSupported == true) && isFrontFacing
    }

val cameraSelector = frontCameraInfo.cameraSelector

// Start with a Preview Builder.
val previewBuilder = Preview.Builder()
    .setTargetAspectRatio(screenAspectRatio)
    .setTargetRotation(rotation)

// Use Camera2Interop.Extender to set the video call stream use case.
Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId)

// Bind the Preview UseCase and the corresponding CameraSelector.
val preview = previewBuilder.build()
camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)

Java

// Set underlying Camera2 stream use case to optimize for video calls.

Long videoCallStreamId =
    CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong();

// Check available CameraInfos to find the first one that supports
// the video call stream use case.
List<CameraInfo> cameraInfos = cameraProvider.getAvailableCameraInfos();
CameraInfo frontCameraInfo = null;
for (cameraInfo in cameraInfos) {
    Long[] availableStreamUseCases = Camera2CameraInfo.from(cameraInfo)
        .getCameraCharacteristic(
            CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES
        );
    boolean isVideoCallStreamingSupported = Arrays.List(availableStreamUseCases)
                .contains(videoCallStreamId);
    boolean isFrontFacing = (cameraInfo.getLensFacing() ==
                             CameraSelector.LENS_FACING_FRONT);

    if (isVideoCallStreamingSupported && isFrontFacing) {
        frontCameraInfo = cameraInfo;
    }
}

if (frontCameraInfo == null) {
    // Handle case where video call streaming is not supported.
}

CameraSelector cameraSelector = frontCameraInfo.getCameraSelector();

// Start with a Preview Builder.
Preview.Builder previewBuilder = Preview.Builder()
    .setTargetAspectRatio(screenAspectRatio)
    .setTargetRotation(rotation);

// Use Camera2Interop.Extender to set the video call stream use case.
Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId);

// Bind the Preview UseCase and the corresponding CameraSelector.
Preview preview = previewBuilder.build()
Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)

추가 리소스

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

Codelab

  • CameraX 시작하기
  • 코드 샘플

  • CameraX 샘플 앱