CameraX (1) - Android CameraAPI 및 CameraX 소개
카메라
Android 플랫폼은 기기 카메라 하드웨어에 대한 광범위한 제어 권한을 제공하여 개발자가 다양한 카메라 기능을 자유롭게 구현할 수 있도록 지원합니다. 이를 통해 앱 내에서 고화질 사진 촬영 또는 동영상 녹화와 같은 기능을 수행할 수 있습니다. 카메라 기능을 구현하기 위해 Android 플랫폼이 제공하는 주요 API는 다음과 같습니다.
Camera (Legacy)
카메라 하드웨어에 액세스할 수 있는 초기의 API입니다. android.hardware.Camera 클래스를 통해 기능을 구현할 수 있지만, 현재는 Deprecated 상태입니다. Google에서는
성능 이슈 대응과 최신 기기의 기능을 제공하는 Camera2나 CameraX 사용을 권장하고 있습니다.
Camera2
Camera2 는 기존 Camera API를 대체하는 저수준의 API입니다. 기존 Camera보다 훨씬 더 유연한 구조와 향상된 성능을 제공하며, 수동 초점이나 노출 제어와 같은 정교한 하드웨어 조작이
가능합니다.
하지만 구현 방식이 매우 복잡하다는 단점이 존재하며, 카메라 연결부터 세션 관리, 리소스 해제까지의 모든 처리를 개발자가 직접 수행해야만 합니다.
CameraX
CameraX 는 Camera2 API를 기반으로 구축된 Jetpack 라이브러리입니다. Camera2의 강력함은 유지하면서 복잡함과 기기별 호환성(파편화) 문제를 해결하여, 개발자가 훨씬 손쉽게
기능을 구현할 수 있도록 도와줍니다.
추가적으로 Lifecycle을 인식하여 카메라 자원의 관리를 자동화하고, 기기 회전이나 센서 방향에 따른 이미지 회전 연산과 같은 까다로운 작업을 내부적으로 처리해 주어 일관된 동작을 보장합니다.
현재 안드로이드 카메라 개발의 표준은 Camera2와 이를 기반으로 편의성을 높인 CameraX입니다. 본 시리즈에서는 구글에서 권장하는 CameraX를 먼저 집중적으로 다룬 후, 추후 심화 과정으로 Camera2에 대해 알아보겠습니다.
CameraIntent
일부 요구사항 중에서는 정교한 기능 없이 기본적인 카메라 촬영만이 필요한 경우가 존재합니다. Android는 이러한 상황을 위해 기기의 기본 카메라 앱에 작업을 위임할 수 있도록 Intent를 제공하고 있습니다. 이 방식은 별도의 카메라 로직을 구현할 필요가 없어 가장 쉽고 안정적입니다.
CameraX
앞서 소개했듯이, CameraX는 개발자가 카메라 기능을 빠르고 효율적으로 구현할 수 있도록 돕는 Jetpack 라이브러리입니다. 기존 API의 복잡성을 해결하기 위해 다음과 같은 강력한 특징들을 제공합니다.
생명주기 관리
CameraX의 가장 큰 장점은 수명 주기를 자동으로 관리한다는 점입니다.** 기존 Camera2 API에서는 onResume이나 onPause 단계에서 개발자가 직접 카메라 리소스를 해제하고 재연결하는 방어 코드를
작성해야 했습니다. CameraX는 LifecycleOwner 에 바인딩되어 앱의 수명 주기에 맞춰 카메라 세션의 시작과 종료를 자동으로 처리합니다. 이를 통해 불필요한 보일러플레이트 코드를 줄이고,
메모리 누수와 같은 위험성으로부터 앱을 보호해 줍니다.
일관성 보장
Android 디바이스들은 제조사나 기기 모델마다 화면 비율, 센서 방향, 회전 처리가 달라 복잡함의 원인이 되었습니다. CameraX는 이러한 하드웨어적 차이를 라이브러리 내부에서 보정하여 다양한 디바이스에서 일관된 동작을 보장합니다.
익스텐션 지원
Android 기기 하드웨어의 기능을 최대한 활용할 수 있도록 Extensions 를 지원합니다. 이를 통해 기기 제조사의 기본 카메라 앱에서만 제공하던 HDR, 야간 모드, 얼굴 보정 등의 특수
효과를 서드파티 앱에서도 API를 통해 손쉽게 적용할 수 있습니다.
유즈케이스 기반 설계
개발자가 복잡한 파이프라인 구성보다 비즈니스 로직에 집중할 수 있도록 UseCase 중심의 설계를 채택했습니다. 미리보기(Preview), 이미지 분석(Image Analysis), 이미지 캡처(
Image Capture), 동영상 캡처(Video Capture) 등 목적에 맞는 UseCase를 조립하여 개발할 수 있습니다.
접근방식
CameraX를 사용하기 위해서는 두가지 방식중 하나를 채택하여야합니다. 세밀한 제어가 필요한 경우에는 CameraProvider 를 사용하여 컴포넌트를 조립하고, 빠른 구현이 목표라면 CameraController 를 사용하여 통합된 기능을 구축할 수 있습니다.
CameraProvider
CameraProvider는 카메라를 액세스하거나 관련 정보를 조회하는 기능을 제공하는 핵심 코어 컴포넌트로, 카메라 하드웨어와 애플리케이션을 연결하는 역할을 합니다. 사용 가능한 카메라 목록 가져오기,
카메라의 미리보기 화면 설정하기, 그리고 이미지 캡처하기와 같은 작업을 수행할 수 있습니다.
CameraProvider를 실제 코드에서 사용하기 위해서는 ProcessCameraProvider를 사용해야만 합니다. ProcessCameraProvider는 앱의 생명주기와 결합하여 동작되는
CameraProvider의 구현체입니다.
구조적으로
ProcessCameraProvider클래스는LifecycleCameraProvider를 구현하고,LifecycleCameraProvider는 다시CameraProvider를 상속합니다.
ProcessCameraProvider → LifecycleCameraProvider → CameraProvider
따라서 일반적으로 CameraProvider를 사용한다는 것은 ProcessCameraProvider 클래스를 통해 접근함을 의미합니다. 해당 클래스에는 생명주기를 바인딩할 수 있는
bindToLifecycle() 메서드가 존재합니다. 이 메서드는 CameraSelector, UseCase 등의 정보를 파라미터로 받아 생명주기에 바인딩된 카메라 세션을 구성합니다.
CameraSelector는 애플리케이션이 사용하고자 하는 카메라 하드웨어를 결정하는 컴포넌트로 이후에 자세히 설명됩니다.
CameraController
CameraController는 앞서 소개한 CameraProvider와 UseCase 관리 로직을 내부적으로 캡슐화한 통합 컨트롤러입니다. 개발자가 CameraProvider와 UseCase를
직접 다루는 복잡함을 줄이기 위해 제공되었으며, 내부적으로 CameraProvider 초기화, UseCase 생성 및 바인딩, 그리고 CameraSelector 관리를 모두 수행합니다.
CameraController를 사용하는 이유는 매번 파이프라인을 처음부터 조립하는 것이 비효율적일 수 있기 때문입니다. CameraController는 이러한 보일러플레이트 코드를 제거하고, 인스턴스 생성만으로
즉시 카메라 기능을 사용할 수 있도록 돕습니다.
CameraController는 기본적으로 Preview, ImageCapture 그리고 ImageAnalysis 기능이 활성화 되어있으며, 터치이벤트 수신을 통한 포커스와 줌 기능 처리를 지원해주고 있습니다. ( Preview, ImageCapture, ImageAnalysis 모두 UseCase의 요소로 하단에서 이어서 설명합니다.)
LifecycleCameraController은 앞서 짧게 설명한 LifecycleCameraProvider과 마찬가지로 CameraController를 상속하고 있는 클래스입니다.
LifecycleCameraController 클래스는 카메라 초기화 처리를 도와주고 LifecycleOwner에 바인딩을 하여 생명주기 바탕으로 제어할 수 있다는 장점이 존재합니다. 일반적으로 해당 클래스를
사용해 CameraController를 초기화하여 생성합니다.
UseCase
CameraX 아키텍처의 핵심은 UseCase입니다. UseCase는 카메라가 수행해야 할 개별 작업을 캡슐화한 클래스로 개발자는 필요한 UseCase를 선택하여 파이프라인을 구성합니다. CameraX는 총 4가지의 UseCase를 지원합니다.
Preview
Preview는 카메라 앱의 가장 기본이 되는 UseCase로 카메라 센서가 포착한 이미지를 화면에 표시하기 위해 실시간 데이터 스트림을 생성합니다. 이때 중요한 점은 Preview가 직접 UI에 그림을
그리는 것이 아니라는 것입니다. 단지, Preview는 생성된 연속적인 이미지 프레임을 파이프라인으로 흘려보내는 역할만 수행합니다.
Preview UseCase는 SurfaceProvider 인터페이스를 통해 이미지 프레임 데이터를 받아줄 대상을 찾습니다. SurfaceProvider는 Surface 객체를 제공하는데,
Surface란 연속적인 이미지 프레임이 그려지는 메모리 버퍼를 의미합니다. Preview UseCase는 setSurfaceProvider() 메서드를 통해 해당 Surface와 연결됩니다.
ImageCapture
ImageCapture는 고해상도의 정지 영상 촬영을 담당하는 UseCase입니다. ImageCapture는 현재 UI상에 보이는 이미지를 캡쳐하는 것이 아닌, 별도의 이미지 파이프라인을 사용하여 단
한장의 이미지를 출력합니다.
해당 UseCase는 사진 촬영을 넘어 촬영된 이미지를 메모리, 파일 또는 MediaStore로 저장하며 이때 이미지 메타 데이터들을 함께 기록합니다. 추가적으로 촬영된 이미지에 대하여 화면 비율을 변경하거나 회전시키는 후처리 작업 파이프라인을 구성할 수 있습니다.
ImageAnalysis
ImageAnalysis는 카메라 데이터 스트림의 이미지 버퍼에 직접 접근하여 CPU 연산을 수행할 수 있게 해주는 UseCase입니다.
단순히 화면에 보여주도록 유도하는 Preview와 달리, ImageAnalysis는 내부적으로 ImageReader를 통해 렌더링된 이미지 데이터를 획득합니다. 이를 통해 앱은 QR 코드 인식, 얼굴
감지, MLKit을 활용한 객체 분류 등과 같은 컴퓨터 비전 작업을 실시간으로 수행할 수 있습니다.
VideoCapture
VideoCapture는 비디오 및 오디오 스트림을 캡처하여 파일로 저장하거나 네트워크로 스트리밍하는 기능을 제공하는 UseCase입니다. VideoCapture는 단순히 연속된 이미지를 찍는 것 뿐만
아니라 내부적으로 오디오와 비디오 데이터를 압축하고 합치는 인코딩 및 먹싱 과정을 거쳐 최종 미디어 파일을 생성합니다.
또한 기기마다 지원하는 하드웨어 스펙이 다른 점을 고려하여 QualitySelector를 통해 SD, HD, FHD, 4K 등 원하는 품질의 비디오를 유연하게 생성할 수 있도록 지원합니다.
PreviewView
PreviewView는 UseCase는 아니지만, Preview UseCase로부터 전달받은 데이터 스트림을 실제 화면에 렌더링하는 역할을 수행합니다. 이때 단순히 화면에 렌더링하는 것이 아니라 전달되는
이미지를 적절한 형태로 변경시켜줍니다. 예를 들어 화면비나 회전 처리를 수행할 할때 이미지 좌표계를 변경하기 위한 행렬 연산이 필요하지만, PreviewView는 이러한 Scaling, Rotation,
Cropping 같은 요구사항에 대하여 내부적으로 처리하여 렌더링 처리를 수행합니다.
이번 포스트에서는 Camera API의 진화 과정과 CameraX의 핵심 구조에 대해 알아보았습니다. Camera API는 내용이 방대하기 때문에, 이어지는 포스팅에서는 각 컴포넌트의 상세한 기능과 실제 코드 구현 방법에 대해 다루어 보겠습니다.
CameraX에 대한 추가적인 정보는 공식문서를 통해 획득할 수 있습니다.