Monday, March 4, 2024

AOSP 기반 안드로이드 카메라 시스템의 동작 원리

I. 서론: 스마트폰 카메라, 그 이면의 복잡성

오늘날 스마트폰을 선택하는 가장 중요한 기준 중 하나는 단연 카메라 성능입니다. 우리는 일상적으로 스마트폰을 꺼내 들어 소중한 순간을 기록하고, 터치 한 번으로 고품질의 사진과 동영상을 촬영합니다. 이처럼 간단해 보이는 사용자 경험의 이면에는, 안드로이드 운영체제(OS)의 정교하고 복잡한 소프트웨어 및 하드웨어 아키텍처가 자리 잡고 있습니다. 이 아키텍처의 핵심을 이해하는 것은 단순히 더 나은 카메라 앱을 만드는 것을 넘어, 안드로이드 시스템 전체의 동작 방식을 꿰뚫어 보는 열쇠가 됩니다.

이 글에서는 안드로이드 카메라 시스템의 근간을 이루는 세 가지 핵심 요소인 AOSP(Android Open Source Project), Camera2 API, 그리고 HAL3(Hardware Abstraction Layer 3)에 대해 심도 있게 분석합니다. 우리는 AOSP가 어떻게 안드로이드 생태계의 토대를 마련하는지부터 시작하여, 개발자가 하드웨어를 직접 제어할 수 있게 해주는 강력한 도구인 Camera2 API의 구조와 동작 방식을 살펴볼 것입니다. 마지막으로, 소프트웨어와 하드웨어 사이의 필수적인 다리 역할을 하는 HAL3의 내부를 들여다보며, 이 세 요소가 어떻게 유기적으로 결합하여 우리가 매일 사용하는 카메라 기능을 구현하는지 그 원리를 파헤쳐 봅니다.

AOSP: 안드로이드 혁신의 출발점

AOSP(Android Open Source Project)는 Google이 주도하여 개발하고 유지 관리하는 안드로이드의 오픈 소스 버전입니다. 이는 리눅스 커널 위에 구축된 운영 체제, 미들웨어, 그리고 핵심 애플리케이션의 집합체로, 전 세계의 수많은 스마트폰, 태블릿, 웨어러블 기기 및 기타 스마트 디바이스의 기반이 됩니다. AOSP의 가장 큰 의의는 '개방성'에 있습니다. 삼성, LG, 퀄컴과 같은 기기 제조사(OEM)나 칩셋 제조업체들은 AOSP의 소스 코드를 자유롭게 가져와 자신들의 하드웨어에 맞게 수정하고 최적화하여 독자적인 안드로이드 버전을 만들 수 있습니다. 이러한 유연성 덕분에 안드로이드는 파편화라는 비판 속에서도 폭발적인 성장을 이루며 세계에서 가장 널리 사용되는 모바일 운영 체제가 될 수 있었습니다.

카메라 시스템의 관점에서 AOSP는 표준화된 프레임워크와 인터페이스를 제공합니다. 즉, 애플리케이션 개발자들이 어떤 제조사의 기기에서든 일관된 방식으로 카메라 하드웨어에 접근할 수 있도록 하는 기본 틀을 마련하는 것입니다. AOSP는 애플리케이션 계층에서 사용하는 Camera2 API부터 하드웨어 드라이버와 통신하는 HAL(Hardware Abstraction Layer)에 이르기까지, 카메라 스택의 모든 구성 요소를 포함하고 있습니다. 따라서 AOSP를 이해하는 것은 안드로이드 카메라가 어떻게 작동하는지 이해하기 위한 첫걸음이라 할 수 있습니다.

Camera2 API: 개발자에게 주어진 하드웨어 제어권

Camera2 API는 안드로이드 5.0 롤리팝(API 레벨 21)에서 처음 도입된 차세대 카메라 프로그래밍 인터페이스입니다. 이전 버전인 Camera1 API가 고수준의 추상화를 통해 사용법은 간단했지만 기능적으로 제한적이었던 것과 달리, Camera2 API는 개발자에게 카메라 하드웨어의 거의 모든 측면을 직접 제어할 수 있는 전례 없는 수준의 권한을 부여합니다.

이 API는 단순한 '사진 찍기' 명령을 내리는 방식에서 벗어나, '파이프라인' 개념을 도입했습니다. 개발자는 셔터 속도, ISO 감도, 초점 거리, 화이트 밸런스, 렌즈 조리개 값 등 각 프레임에 대한 세부적인 설정을 '요청(Request)'의 형태로 카메라 하드웨어에 전달할 수 있습니다. 카메라는 이 요청을 처리하고, 결과 이미지 데이터와 함께 처리 과정에서 수집된 상세한 메타데이터를 '결과(Result)'로 반환합니다. 이러한 프레임 단위의 정밀한 제어는 HDR(High Dynamic Range), ZSL(Zero Shutter Lag), RAW 이미지 캡처, 고속 연사 촬영과 같은 고급 사진 기술을 애플리케이션 수준에서 구현할 수 있는 길을 열었습니다. Camera2 API는 복잡하지만, 그만큼 강력한 성능과 유연성을 제공하여 전문적인 카메라 앱 개발의 필수 요소로 자리 잡았습니다.

HAL3: 소프트웨어와 하드웨어의 통역사

HAL3(Hardware Abstraction Layer 3)는 안드로이드 프레임워크(Camera2 API가 속한)와 실제 카메라 하드웨어(이미지 센서, 렌즈, 이미지 처리 프로세서 등) 사이를 중계하는 핵심 소프트웨어 계층입니다. HAL의 근본적인 목적은 '추상화'입니다. 안드로이드 프레임워크는 세상에 존재하는 수많은 종류의 카메라 하드웨어의 구체적인 동작 방식을 알 필요가 없습니다. 대신, HAL이라는 표준화된 인터페이스 규격에 맞춰 "사진을 찍어라" 또는 "초점을 이 지점에 맞춰라"와 같은 명령만 내리면 됩니다. 그러면 각 하드웨어 제조사가 자신의 하드웨어에 맞게 구현한 HAL이 그 명령을 해석하여 실제 하드웨어를 구동하는 역할을 수행합니다.

특히 HAL3는 Camera2 API와 완벽하게 호응하도록 설계되었습니다. Camera2 API가 파이프라인 기반의 비동기식 요청/결과 모델을 사용하는 것처럼, HAL3 역시 동일한 모델을 따릅니다. 프레임워크로부터 캡처 요청(Capture Request)을 받으면, HAL3는 이를 하드웨어 파이프라인에 전달하고, 처리가 완료되면 이미지 데이터와 메타데이터를 프레임워크로 다시 올려보냅니다. 이처럼 Camera2 API와 HAL3는 긴밀하게 연결된 한 쌍으로, 이 둘의 상호작용을 이해하는 것이 안드로이드 카메라 시스템의 핵심을 파악하는 길입니다.

II. Camera2 API의 구조와 핵심 구성요소

Camera2 API는 처음 접하는 개발자에게 다소 복잡하고 생소하게 느껴질 수 있습니다. 이는 기존 Camera1 API의 단순한 접근 방식과 달리, 상태(State)와 세션(Session) 기반의 비동기적 파이프라인 모델을 채택했기 때문입니다. 하지만 각 구성요소의 역할과 상호작용을 이해하면, 그 강력함을 온전히 활용할 수 있게 됩니다.

주요 클래스와 그들의 상호작용

Camera2 API는 여러 클래스가 유기적으로 상호작용하며 동작합니다. 전체적인 흐름은 다음과 같습니다.

  1. CameraManager: 시스템의 카메라 장치들을 탐색하고 연결하는 시작점입니다.
  2. CameraCharacteristics: 특정 카메라 장치가 지원하는 기능과 속성의 집합입니다.
  3. CameraDevice: 연결된 하나의 카메라 하드웨어를 대표하는 객체입니다. 캡처 세션을 생성하는 역할을 합니다.
  4. Surface: 카메라가 생성한 이미지 데이터를 수신할 목적지입니다. (예: 화면 표시를 위한 SurfaceView, 이미지 저장을 위한 ImageReader의 Surface)
  5. CameraCaptureSession: 카메라 장치와 하나 이상의 Surface를 연결하여 구성된 세션입니다. 캡처 요청을 제출하고 관리하는 핵심 객체입니다.
  6. CaptureRequest: 단일 프레임을 캡처하기 위한 모든 설정(노출, 초점, 모드 등)을 담고 있는 불변 객체입니다.
  7. CaptureResult: 캡처 요청이 처리된 후, 하드웨어로부터 반환된 결과입니다. 이미지 데이터 자체는 Surface로 전달되고, CaptureResult는 해당 이미지의 메타데이터를 담고 있습니다.

이 클래스들이 어떻게 상호작용하며 미리보기(Preview)와 사진 촬영(Still Capture)을 구현하는지 단계별로 살펴보겠습니다.

Camera2 API Simplified Diagram

(이미지 출처: Android Developers 공식 문서)

1. 카메라 장치 열기 (Opening a Camera Device)

모든 작업은 Context.getSystemService(Context.CAMERA_SERVICE)를 통해 CameraManager 인스턴스를 얻는 것에서 시작됩니다.


CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
String[] cameraIdList = manager.getCameraIdList(); // 시스템의 모든 카메라 ID 목록 가져오기

getCameraIdList()를 통해 후면 카메라, 전면 카메라, 외부 카메라 등의 ID를 얻을 수 있습니다. 특정 카메라(예: 첫 번째 후면 카메라)를 선택했다면, 해당 카메라의 특성을 CameraCharacteristics를 통해 조회합니다.


String backCameraId = ...; // 후면 카메라 ID 선택
CameraCharacteristics characteristics = manager.getCameraCharacteristics(backCameraId);

CameraCharacteristics는 해당 카메라가 지원하는 해상도, 프레임 속도, 초점 모드, 하드웨어 지원 수준(INFO_SUPPORTED_HARDWARE_LEVEL) 등 방대한 정보를 담고 있는 '사양서'와 같습니다. 이 정보를 바탕으로 앱은 최적의 스트림(해상도, 포맷)을 구성할 수 있습니다.

마지막으로 manager.openCamera()를 호출하여 실제 카메라 장치와의 연결을 시도합니다. 이 과정은 비동기적으로 처리되므로, 결과를 수신할 CameraDevice.StateCallback을 콜백으로 전달해야 합니다.


manager.openCamera(backCameraId, new CameraDevice.StateCallback() {
    @Override
    public void onOpened(@NonNull CameraDevice camera) {
        // 카메라가 성공적으로 열렸을 때 호출됨
        mCameraDevice = camera;
        createCameraPreviewSession(); // 다음 단계: 캡처 세션 생성
    }

    @Override
    public void onDisconnected(@NonNull CameraDevice camera) {
        // 카메라 연결이 끊어졌을 때
        camera.close();
        mCameraDevice = null;
    }

    @Override
    public void onError(@NonNull CameraDevice camera, int error) {
        // 오류 발생 시
        camera.close();
        mCameraDevice = null;
    }
}, backgroundHandler); // UI 스레드를 막지 않기 위해 별도 핸들러 사용

2. 캡처 세션 생성 (Creating a Capture Session)

CameraDevice가 성공적으로 열리면, 이제 이미지 데이터를 받을 목적지(Surface)를 지정하고 CameraCaptureSession을 생성해야 합니다. 예를 들어, 화면에 미리보기를 표시하고, 버튼을 누르면 사진을 저장하고 싶다면 최소 두 개의 Surface가 필요합니다. 하나는 SurfaceViewTextureView에서 얻은 Surface이고, 다른 하나는 ImageReader에서 얻은 Surface입니다.


Surface previewSurface = surfaceHolder.getSurface(); // SurfaceView로부터
mImageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 2);
Surface captureSurface = mImageReader.getSurface();

List<Surface> outputSurfaces = Arrays.asList(previewSurface, captureSurface);

mCameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
    @Override
    public void onConfigured(@NonNull CameraCaptureSession session) {
        // 세션 구성이 성공적으로 완료됨
        mCaptureSession = session;
        // 이제 캡처 요청을 보낼 준비가 됨
        sendPreviewRequest();
    }

    @Override
    public void onConfigureFailed(@NonNull CameraCaptureSession session) {
        // 세션 구성 실패
    }
}, backgroundHandler);

createCaptureSession() 역시 비동기적으로 동작하며, 성공 시 onConfigured 콜백에서 활성화된 CameraCaptureSession 객체를 전달받습니다. 이 세션 객체는 이제 지정된 Surface들로 이미지 데이터를 보낼 준비를 마친 상태입니다.

3. 캡처 요청 전송 (Submitting Capture Requests)

세션이 준비되면, CaptureRequest를 만들어 세션에 제출함으로써 카메라 하드웨어가 실제로 작업을 수행하도록 할 수 있습니다. 요청은 크게 두 가지 종류로 나뉩니다.

  • 반복 요청 (Repeating Request): setRepeatingRequest()를 사용하며, 주로 실시간 미리보기를 위해 사용됩니다. 세션은 중지 명령이 있을 때까지 주어진 요청을 계속해서 반복 처리하여 초당 30~60 프레임의 이미지를 생성하고 Surface로 보냅니다.
  • 단일 요청 (Single Request): capture()를 사용하며, 사진 촬영과 같이 한 번의 캡처가 필요할 때 사용됩니다.

요청을 생성하기 위해서는 CameraDevice로부터 CaptureRequest.Builder를 받아와야 합니다. 빌더는 특정 목적에 맞는 템플릿(TEMPLATE_PREVIEW, TEMPLATE_STILL_CAPTURE 등)을 기반으로 생성됩니다.


// 미리보기 요청 생성 및 전송
CaptureRequest.Builder previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
previewRequestBuilder.addTarget(previewSurface); // 이 요청의 결과는 previewSurface로 보낸다
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // 자동 초점 설정

CaptureRequest previewRequest = previewRequestBuilder.build();
mCaptureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler);

위 코드는 연속 자동 초점 모드가 활성화된 미리보기 요청을 생성하고, 이를 반복적으로 실행하도록 세션에 명령합니다. 카메라 하드웨어는 이제 매 프레임마다 이미지를 생성하여 previewSurface로 보내므로, 사용자는 화면에서 실시간 영상을 볼 수 있게 됩니다.

사진 촬영 요청은 TEMPLATE_STILL_CAPTURE 템플릿을 사용하며, 일반적으로 더 높은 품질의 이미지를 얻기 위한 설정을 포함합니다.


// 사진 촬영 요청 생성 및 전송
CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(captureSurface); // 이번엔 결과를 captureSurface(ImageReader)로 보낸다
// 필요한 설정 추가 (예: 플래시, 손떨림 방지 등)
captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);

mCaptureSession.capture(captureBuilder.build(), captureCallback, backgroundHandler);

4. 결과 수신 (Receiving Results)

요청을 보낼 때 함께 전달한 CameraCaptureSession.CaptureCallback은 캡처 프로세스의 각 단계에서 호출됩니다.


private CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
    @Override
    public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
        super.onCaptureStarted(session, request, timestamp, frameNumber);
        // 프레임 노출 시작 시점
    }

    @Override
    public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
        super.onCaptureProgressed(session, request, partialResult);
        // 캡처 진행 중, 부분적인 메타데이터 사용 가능
        process(partialResult);
    }

    @Override
    public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
        super.onCaptureCompleted(session, request, result);
        // 캡처 완료, 모든 메타데이터 사용 가능
        process(result);
    }

    @Override
    public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
        super.onCaptureFailed(session, request, failure);
        // 캡처 실패
    }

    private void process(CaptureResult result) {
        // 결과 메타데이터를 사용하여 UI 업데이트 등의 작업 수행
        // 예: 초점 상태(AF_STATE), 노출 상태(AE_STATE) 확인
        Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
        if (afState != null) {
            // ...
        }
    }
};

onCaptureCompleted 콜백에서 받은 TotalCaptureResult 객체에는 해당 프레임을 캡처할 때 사용된 최종 센서 감도(ISO), 노출 시간, 초점 거리, 화이트 밸런스 값 등 수많은 정보가 담겨 있습니다. 앱은 이 정보를 활용하여 현재 카메라의 상태를 사용자에게 표시하거나, 후처리 알고리즘에 적용할 수 있습니다.

한편, 이미지 데이터 자체는 이 콜백으로 전달되지 않고, 요청 생성 시 addTarget()으로 지정했던 Surface로 직접 전달됩니다. ImageReader를 사용했다면 ImageReader.OnImageAvailableListener를 통해 캡처된 이미지 데이터에 접근하여 파일로 저장하거나 추가적인 처리를 할 수 있습니다.

III. HAL3: 안드로이드 카메라 프레임워크와 하드웨어의 가교

애플리케이션 개발자가 Camera2 API를 통해 내린 명령은 어떻게 실제 카메라 하드웨어를 움직이게 할까요? 그 비밀은 바로 하드웨어 추상화 계층, 즉 HAL에 있습니다. HAL은 안드로이드가 특정 하드웨어에 종속되지 않고 다양한 기기에서 동작할 수 있도록 하는 핵심적인 설계 철학을 담고 있습니다.

HAL의 역할과 필요성

전 세계에는 수많은 칩셋 제조사(퀄컴, 미디어텍, 삼성 엑시노스 등)와 카메라 센서 제조사(소니, 삼성 등)가 있습니다. 이들이 만드는 하드웨어는 제각기 다른 레지스터, 명령어, 데이터 형식을 사용합니다. 만약 안드로이드 프레임워크가 이 모든 하드웨어의 동작 방식을 직접 알아야 한다면, 새로운 하드웨어가 출시될 때마다 안드로이드 OS 전체를 수정해야 하는 끔찍한 상황이 발생할 것입니다.

HAL은 이러한 문제를 해결하기 위해 도입된 '인터페이스' 또는 '계약'입니다. 안드로이드 프레임워크는 HAL이 정의하는 표준 함수들(예: `open_camera`, `process_capture_request`)만 호출합니다. 그러면 각 하드웨어 제조사(주로 칩셋 제조사)가 자신의 하드웨어에 맞게 이 함수들의 실제 동작을 구현하여 라이브러리(.so 파일) 형태로 제공합니다. 이 라이브러리가 바로 HAL 구현체입니다.

이러한 구조 덕분에 안드로이드 프레임워크는 하드웨어의 복잡한 내부 동작으로부터 완벽하게 분리됩니다. 구글은 AOSP를 통해 표준 HAL 인터페이스를 발전시키고, 제조사들은 그에 맞춰 자신의 드라이버와 HAL 구현체를 개발하기만 하면 됩니다. 이는 마치 USB 규격과도 같습니다. 컴퓨터 제조사는 USB 포트 규격만 맞추면 되고, 키보드/마우스 제조사는 USB 플러그 규격만 맞추면, 어떤 회사의 제품이든 서로 연결하여 사용할 수 있는 것과 같은 원리입니다.

HAL1에서 HAL3로의 진화

카메라 HAL은 Camera API의 발전에 맞춰 함께 진화해왔습니다.

  • HAL1: 구버전 Camera1 API와 함께 사용되던 HAL입니다. 동기식(Synchronous)이며, 프레임워크가 '미리보기 프레임 하나 줘'라고 요청하면 HAL이 프레임 데이터를 밀어주는(push) 단순한 구조였습니다. 제어가 단순했지만, 프레임 단위의 세부 설정 변경이 불가능하고 파이프라인 지연 시간(latency)이 길다는 단점이 있었습니다.
  • HAL2: HAL1의 확장 버전으로, ZSL(Zero Shutter Lag)과 같은 제한적인 기능을 추가했지만, 근본적인 아키텍처는 HAL1과 유사했습니다.
  • HAL3: Camera2 API와 함께 등장한 혁신적인 버전입니다. Camera2 API와 마찬가지로 비동기식(Asynchronous) 요청-결과 파이프라인 모델을 채택했습니다. 프레임워크가 캡처 요청 큐(queue)를 HAL에 보내면, HAL은 자신의 파이프라인에서 이 요청들을 순서대로 처리하고, 각 요청에 대한 결과(메타데이터)와 이미지 버퍼를 비동기적으로 프레임워크에 반환합니다. 이 구조는 프레임 간의 설정이 중첩(overlap)되어 처리될 수 있게 하여 처리량(throughput)을 극대화하고, Camera2 API의 모든 강력한 기능을 하드웨어 수준에서 뒷받침합니다.

HAL3의 내부 동작과 파이프라인

Camera2 API를 통해 앱이 session.capture()를 호출하면, 내부적으로 어떤 일이 일어나는지 따라가 보겠습니다.

  1. 프레임워크 계층 (Framework Layer):
    • CameraCaptureSessionCaptureRequest를 받아 내부 큐에 넣습니다.
    • 안드로이드 시스템의 핵심 프로세스인 CameraService가 이 요청을 받습니다. CameraService는 여러 앱이 동시에 카메라에 접근하려고 할 때 이를 중재하고, 현재 활성화된 앱과 HAL 사이의 통신을 관리합니다.
    • CameraService는 CaptureRequest에 담긴 설정을 HAL이 이해할 수 있는 형식의 메타데이터로 변환합니다.
    • Binder IPC(Inter-Process Communication)를 통해 HAL 프로세스에 이 요청을 전달합니다. 구체적으로는 HAL 인터페이스의 processCaptureRequest() 함수를 호출합니다.
  2. HAL 계층 (Hardware Abstraction Layer):
    • HAL 구현체는 프레임워크로부터 processCaptureRequest() 호출을 받습니다. 이 요청에는 프레임 번호(frameNumber)와 설정 메타데이터가 포함됩니다.
    • HAL은 이 메타데이터를 해석하여 실제 하드웨어 블록들을 제어하는 명령어로 변환합니다. 예를 들어, CaptureRequest.SENSOR_SENSITIVITY 값을 받으면, 이미지 센서(Image Sensor)의 아날로그/디지털 게인(gain)을 조절하는 레지스터 값을 설정합니다. CaptureRequest.LENS_FOCUS_DISTANCE 값을 받으면, 렌즈 액추에이터(Lens Actuator)를 구동하여 렌즈의 위치를 조정합니다.
    • 이러한 설정들은 카메라 하드웨어의 ISP(Image Signal Processor) 파이프라인에 적용됩니다. ISP는 센서로부터 들어온 RAW 데이터를 받아 노이즈 제거, 색 보정(White Balancing), 디모자이킹(Demosaicing), 샤프닝, 인코딩(JPEG/YUV) 등 복잡한 이미지 처리 과정을 수행하는 하드웨어입니다.
    • 설정이 완료되면, HAL은 지정된 프레임 번호에 맞춰 센서 노출을 시작하도록 명령합니다.
    • 노출이 끝나고 이미지 데이터가 ISP 파이프라인을 거쳐 처리되면, HAL은 두 가지 종류의 결과를 프레임워크로 다시 보냅니다.
      1. 이미지 버퍼 (Image Buffer): 처리된 이미지 데이터(YUV, JPEG 등)는 CaptureRequest 생성 시 지정된 Surface에 직접 기록됩니다. 이는 그래픽 버퍼(Gralloc Buffer)를 통해 효율적으로 처리되며, 데이터 복사를 최소화합니다.
      2. 결과 메타데이터 (Result Metadata): HAL은 프레임워크가 제공한 콜백 인터페이스의 processCaptureResult() 함수를 호출하여 결과 메타데이터를 전달합니다. 이 메타데이터에는 실제로 적용된 노출 시간, ISO 값, 렌즈 위치, 3A(Auto Exposure, Auto Focus, Auto White Balance) 알고리즘의 현재 상태 등 TotalCaptureResult를 구성하는 모든 정보가 포함됩니다. 이 메타데이터에도 요청과 동일한 frameNumber가 태그되어, 프레임워크가 어떤 요청에 대한 결과인지 명확히 알 수 있게 합니다.
  3. 다시 프레임워크 계층으로:
    • CameraService는 HAL로부터 받은 결과 메타데이터를 TotalCaptureResult 객체로 포장합니다.
    • 처음 요청을 보냈던 앱의 CameraCaptureSession.CaptureCallbackonCaptureCompleted 메소드를 호출하여 이 TotalCaptureResult를 전달합니다.
    • 동시에, 이미지 버퍼는 Surface를 통해 앱의 ImageReaderSurfaceView로 전달됩니다.

이처럼 HAL3는 Camera2 API와 완벽하게 대칭되는 파이프라인 구조를 가짐으로써, 프레임워크와 하드웨어 간의 효율적이고 유연한 통신을 가능하게 합니다. 개발자가 Camera2 API를 통해 프레임 단위로 설정을 변경하면, 그 요청이 거의 그대로 HAL3를 거쳐 하드웨어 파이프라인에 직접 반영되는 것입니다.

IV. AOSP에서 카메라 시스템 빌드와 디버깅

AOSP 소스 코드를 직접 다루는 것은 안드로이드 카메라 시스템의 가장 깊은 곳까지 이해할 수 있는 기회를 제공합니다. 이는 단순히 앱을 개발하는 것을 넘어, 프레임워크를 수정하거나 특정 하드웨어에 맞는 새로운 HAL을 구현하는 등 시스템 레벨의 개발을 가능하게 합니다.

AOSP 소스 코드 구조 탐색

AOSP의 방대한 소스 코드 트리에서 카메라와 관련된 주요 디렉토리는 다음과 같습니다.

  • /frameworks/av/camera/

    이곳은 카메라 프레임워크의 핵심 코드가 위치하는 곳입니다.

    • /frameworks/av/camera/aidl/: 최신 안드로이드에서 사용되는 AIDL(Android Interface Definition Language) 기반의 HAL 인터페이스 정의가 있습니다.
    • /frameworks/av/camera/cameraserver/: 시스템 부팅 시 실행되는 cameraserver 프로세스의 소스 코드입니다. CameraService가 이 프로세스 내에서 동작합니다.
    • /frameworks/av/services/camera/: CameraService의 주요 로직이 구현되어 있습니다. 앱의 요청을 받아 HAL로 전달하고, HAL의 콜백을 받아 앱으로 전달하는 모든 중계 작업이 여기서 이루어집니다.
    • /frameworks/base/core/java/android/hardware/camera2/: 애플리케이션 개발자가 직접 사용하는 Camera2 API 클래스(CameraManager, CameraDevice 등)의 Java 소스 코드입니다.
  • /hardware/interfaces/camera/

    카메라 HAL 인터페이스의 공식적인 정의 파일이 위치합니다. 과거에는 HIDL(Hardware Interface Definition Language)을 사용했으나, 최신 버전에서는 AIDL로 전환되고 있습니다. 이 디렉토리의 .hal 또는 .aidl 파일들은 프레임워크와 HAL 구현체가 따라야 하는 '문법'을 정의합니다.

  • /vendor/

    이 디렉토리는 각 하드웨어 제조사(Vendor)의 코드가 위치하는 공간입니다. 예를 들어, 구글 픽셀 폰의 AOSP라면 /vendor/google/ 디렉토리 아래에, 퀄컴 칩셋을 위한 코드라면 /vendor/qcom/ 디렉토리 아래에 카메라 HAL 구현체와 관련 드라이버들이 위치하게 됩니다. 실제 하드웨어를 제어하는 코드는 대부분 이곳에서 찾을 수 있습니다.

개발 환경 설정과 빌드

AOSP를 빌드하기 위해서는 리눅스 환경과 수백 GB의 디스크 공간, 그리고 많은 양의 RAM이 필요합니다. 기본적인 과정은 다음과 같습니다.

  1. 소스 코드 다운로드: `repo` 도구를 사용하여 특정 안드로이드 버전의 AOSP 소스 코드를 다운로드합니다. (repo init -u ..., repo sync)
  2. 빌드 환경 설정: source build/envsetup.sh 스크립트를 실행하여 빌드 관련 환경 변수와 함수를 로드합니다.
  3. 타겟 설정: lunch 명령어를 사용하여 빌드할 타겟 제품(예: 픽셀폰의 경우 `aosp_oriole-userdebug`)을 선택합니다. `userdebug` 빌드는 루트 권한 획득과 디버깅이 용이하여 개발에 적합합니다.
  4. 빌드 실행: m 또는 make -jN (N은 CPU 코어 수) 명령어로 전체 안드로이드 시스템 이미지를 빌드합니다. 빌드는 수십 분에서 수 시간까지 소요될 수 있습니다.
  5. 플래싱: 빌드가 완료되면 생성된 이미지 파일들(boot.img, system.img 등)을 fastboot 도구를 사용하여 실제 기기에 설치(플래싱)합니다.

카메라 HAL이나 프레임워크의 일부 코드를 수정한 후에는 전체 빌드 대신 해당 모듈만 개별적으로 빌드(예: m camera.provider@2.7-service-google)하여 시간과 자원을 절약할 수 있습니다.

디버깅 도구와 기법

복잡한 카메라 시스템의 문제를 해결하기 위해서는 적절한 도구를 사용하는 것이 필수적입니다.

1. Logcat

logcat은 가장 기본적이면서도 강력한 디버깅 도구입니다. 안드로이드 시스템의 모든 구성 요소는 로그 메시지를 출력하며, logcat을 통해 이를 실시간으로 확인할 수 있습니다. 카메라 관련 문제를 디버깅할 때는 특정 태그(tag)를 필터링하여 보는 것이 유용합니다.


# adb logcat -s CameraService CamDevSession Camera3-Device
  • CameraService: CameraService의 주요 동작과 상태 변화 로그
  • CamDevSession: CameraDevice와 CameraCaptureSession의 프레임워크 측 동작 로그
  • Camera3-Device, Camera3-Session: HAL3 구현체의 동작을 추적하는 데 사용되는 표준 태그. HAL 개발 시 이 태그들을 사용하여 로그를 남기면 디버깅이 용이합니다.

시스템 속성(property)을 변경하여 로그의 상세 수준을 조절할 수도 있습니다.


# adb shell setprop log.tag.Camera3-Device VERBOSE

위 명령은 Camera3-Device 태그의 로그 레벨을 VERBOSE로 설정하여 더욱 상세한 정보를 출력하게 합니다.

2. dumpsys

dumpsys는 현재 실행 중인 시스템 서비스의 내부 상태를 덤프하여 보여주는 강력한 명령어입니다. 카메라 시스템의 현재 상태를 확인하려면 다음 명령어를 사용합니다.


# adb shell dumpsys media.camera

이 명령어의 출력 결과에는 다음과 같은 유용한 정보가 포함됩니다.

  • 현재 시스템에 존재하는 카메라 장치 목록과 그 특성(CameraCharacteristics)
  • 각 카메라 장치의 현재 상태 (UNOPENED, OPEN, ACTIVE)
  • 현재 카메라를 사용하고 있는 앱(클라이언트)의 정보
  • 진행 중인 캡처 세션의 정보
  • 최근 발생한 오류 이력

카메라가 동작하지 않거나 특정 앱이 카메라를 점유하고 놓아주지 않는 등의 문제를 진단할 때 매우 유용합니다.

3. CTS와 VTS

  • CTS (Compatibility Test Suite): 안드로이드 호환성 테스트 모음입니다. CTS에는 Camera2 API의 모든 기능이 사양에 맞게 올바르게 동작하는지 검증하는 수많은 테스트 케이스가 포함되어 있습니다. 제조사는 자신의 기기가 안드로이드와 호환된다는 것을 증명하기 위해 반드시 CTS를 통과해야 합니다. 앱 개발자 또한 CTS를 통해 특정 기기가 Camera2 API의 특정 기능을 신뢰성 있게 지원하는지 간접적으로 확인할 수 있습니다.
  • VTS (Vendor Test Suite): HAL과 커널 드라이버 수준을 테스트하는 도구입니다. VTS는 HAL 인터페이스가 사양에 맞게 정확히 구현되었는지 검증합니다. HAL 개발자는 VTS를 통해 자신의 구현체가 프레임워크와 문제없이 통신할 수 있음을 보장할 수 있습니다.

새로운 카메라 HAL을 개발하거나 기존 HAL을 수정했다면, CTS와 VTS를 실행하여 변경 사항이 다른 부분에 예기치 않은 문제를 일으키지 않았는지 반드시 확인해야 합니다.

V. 결론: 끊임없이 진화하는 안드로이드 카메라

지금까지 AOSP를 기반으로 한 안드로이드 카메라 시스템의 핵심, Camera2 API와 HAL3의 구조와 동작 원리를 깊이 있게 살펴보았습니다. AOSP가 제공하는 개방적인 토대 위에서, Camera2 API는 개발자에게 하드웨어를 직접 제어하는 강력한 힘을 부여했고, HAL3는 이 모든 것을 가능하게 하는 견고하고 유연한 다리 역할을 수행하고 있음을 확인했습니다.

이 세 요소가 공유하는 비동기 파이프라인 아키텍처는 현대 컴퓨테이셔널 포토그래피(Computational Photography) 기술의 근간이 됩니다. 여러 프레임을 빠르게 캡처하여 합성하는 HDR+나 야간 모드, 여러 카메라(광각, 망원)의 이미지를 실시간으로 융합하는 기능 등은 모두 Camera2 API와 HAL3의 정교한 프레임 제어 능력이 없었다면 구현하기 어려웠을 것입니다.

안드로이드 카메라 스택은 AIDL 기반의 새로운 HAL 인터페이스 도입, 다중 카메라 API의 개선, AI 기술과의 접목 등을 통해 지금도 끊임없이 발전하고 있습니다. 이 복잡하고 정교한 시스템을 이해하는 것은 때로는 도전적인 과제일 수 있습니다. 하지만 그 내부 동작 원리를 파악했을 때, 개발자는 단순히 주어진 API를 사용하는 것을 넘어, 하드웨어의 잠재력을 최대한으로 이끌어내는 혁신적인 카메라 경험을 창조할 수 있게 될 것입니다. 이 글이 안드로이드 카메라 시스템의 깊이를 탐험하고자 하는 모든 개발자에게 유용한 나침반이 되기를 바랍니다.


0 개의 댓글:

Post a Comment