이 페이지에서는 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
의 기본 UseCase
는 Preview
, 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
의 인스턴스를 각각 하나씩 동시에 사용할 수 있습니다. 또한 다음 사항에 유의하세요.
- 모든 사용 사례가 단독으로 작동할 수 있습니다. 예를 들어, 앱은 미리보기를 사용하지 않고 동영상을 녹화할 수 있습니다.
- 확장 프로그램을 사용하는 경우
ImageCapture
및Preview
조합만 작동이 보장됩니다. OEM 구현에 따라ImageAnalysis
를 추가하지 못할 수도 있습니다.VideoCapture
사용 사례에 확장 프로그램을 사용할 수 없습니다. 자세한 내용은 확장 프로그램 참조 문서를 확인하세요. - 카메라 기능에 따라 일부 카메라는 낮은 해상도 모드에서 조합을 지원할 수 있지만, 일부 높은 해상도에서는 동일한 조합을 지원할 수 없습니다.
- 카메라 하드웨어 수준이
FULL
이하인 기기에서Preview
,VideoCapture
및ImageCapture
또는ImageAnalysis
를 결합하면 CameraX가Preview
및VideoCapture
의 카메라의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 속성을 작성하도록 선택할 수도 있습니다.
Camera2CameraControl
을 사용하면 기본CaptureRequest
에 속성(예: 자동 초점 모드)을 설정할 수 있습니다.Camera2Interop.Extender
를 사용하여 CameraXUseCase
를 확장합니다. 이렇게 하면Camera2CameraControl
과 같이 CaptureRequest에 속성을 설정할 수 있습니다. 스트림 사용 사례를 설정하여 사용 시나리오에 맞게 카메라를 최적화하는 등의 추가 관리도 할 수 있습니다. 자세한 내용은 성능 개선을 위한 스트림 사용 사례 사용을 참고하세요.
다음 코드 샘플은 스트림 사용 사례를 사용하여 영상 통화에 맞게 최적화합니다.
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
코드 샘플