이 글은 제가 하이퍼커넥트에서 인턴으로 있으면서 카메라 앱에서 Camera2 API를 활용할 수 있게 구현하고 사내에서 간단히 발표했던 것을 재구성한 포스트입니다. 발표자료는 SlideShare에서 보실 수 있습니다. (공개하기 곤란한 슬라이드들은 삭제되었습니다)
스마트폰이 나온 이후 휴대폰 카메라의 존재감은 갈수록 커지고 있습니다. 제조사들도 사용자들의 요구에 맞춰 스마트폰 카메라 성능을 나날히 진화시켜 가고 있습니다. 여행을 갈 때도 요즘은 디지털 카메라를 따로 갖고 가지 않기도 합니다.
하지만 안드로이드는 안드로이드 1.0(API 1; 2008년 9월 23일)에 만들어진 API를 롤리팝 5.0(API 21, 2014년 11월 12일)에서 교체될 때까지 적은 기능만 추가된 채 상당히 오랫동안 써 왔습니다. 사실 Camera API에서 기능이라고 할 만한 게 추가된 건 얼굴 인식 뿐이었습니다.
기존 API는 안드로이드 초기 버전에서 만들어진 만큼 카메라가 지금 이렇게까지 발전할 거라고는 생각하지 않았고, 컴팩트 카메라처럼 간단한 기능들만을 제공했습니다. 이런 점을 상당 부분 개선한 API가 Camera2 API입니다.
혼란을 막기 위해 여기서부터는 예전 카메라 API를 Camera1 API라고 부르겠습니다.
새로운 API
Camera2 API는 롤리팝 5.0(API 21)에 등장했습니다. 롤리팝 이후에는 Camera1 API는 deprecate 되지만, 계속 사용할 수는 있습니다.
Camera2는 Camera1이 지원하지 않던 많은 기능들을 새로 지원합니다. 몇 개만 예를 들어 보자면,
- 3개 이상의 카메라를 쓸 수 있게 됐습니다. 파이 9.0(API 28) 이상에서는 여러 카메라를 동시에 쓸 수도 있습니다.
- DSLR에서 흔히 볼 수 있는 수동 컨트롤을 지원합니다. Camera1 API는 초점을 맞출 위치만 정해 줄 수 있었지만, Camera2부터는 초점 거리를 정하거나 노출 시간, ISO 등을 API에서 직접 설정해 줄 수 있습니다.
- 연사, RAW 지원 등이 추가됩니다.
파편화
하지만 새로운 API를 구현하기 전에 항상 생각해야 되는 것이 있습니다. 이게 과연 잘 될까입니다. 결론부터 말하자면 Camera2는 대부분의 경우 잘 되지만, 이상하게 동작하는 경우들이 없진 않습니다.
그래서 이거 쓸 만 할까요?
- Camera1이 더 이상 지원이 중단되었기도 하고,
- Camera2가 지원하는 기능 자체가 너무 강력하며,
- 후술할 새로운 동작 방식과 HAL(하드웨어 추상화 레이어)의 사용으로 Camera1보다 속도가 개선되었고,
- 이제는 롤리팝 5.0(API 21) 이상의 점유율이 87%를 넘어가서(2018년 7월 23일까지) 많은 유저들이 Camera2의 프로페셔널한 기능을 사용할 수 있기 때문에
저런 이슈가 있음에도 Camera2를 구현할 만한 가치는 충분히 있습니다. 다만 Camera2를 지원하지 않는 API 21 미만의 기기들이나 Camera2에 버그가 많아 Camera1을 사용해야 하는 기기들을 위해 아직은 2개의 로직을 구현해야 할 것 같습니다.
어떻게 바뀌었나요?
Camera1과 다르게 Camera2 API는 파이프라인 모델으로 만들어져 있습니다.
Camera1은 API가 모든 걸 비동기로 처리해서 설정을 변경하거나 명령을 내리면 일부 메서드를 제외하고는 언제 값이 반영되는지, 제대로 반영이 되긴 했는지 알 수 없었습니다. Camera2의 새 구조는 이 점을 상당 부분 개선합니다. 모든 것이 API 내부에서 동기로 처리되어 콜백을 이용해 피드백을 받을 수 있습니다.
동작 순서도를 보면 역시 Camera1에서는 못 보던 낯선 것들이 등장하는데요, 가장 낯선 부분이 아마 CaptureRequest
와 CameraCaptureSession
이 아닐까 싶습니다. 각 요소가 하는 일들은 다음과 같습니다.
CameraManager |
시스템 서비스로서, 사용 가능한 카메라와 카메라 기능들을 쿼리할 수 있고 카메라를 열 수 있습니다. |
CameraCharacteristics |
카메라의 속성들을 담고 있는 객체입니다. (Camera1의 properties와는 다릅니다 – 속성을 가져오는 것만 가능하고, 속성을 정하는 건 다른 방식으로 가능합니다) |
CameraDevice |
카메라 객체입니다. |
CaptureRequest |
사진 촬영이나 카메라 미리보기를 요청(request)하는 데 쓰이는 객체입니다. 카메라의 설정을 변경할 때도 관여합니다. |
CameraCaptureSession |
CaptureRequest 를 보내고 카메라 하드웨어에서 결과를 받을 수 있는 세션입니다. |
CaptureResult |
CaptureRequest 의 결과입니다. 이미지의 메타데이터도 가져올 수 있습니다. |
이렇게 Session에 CaptureRequest를 보내는 것으로 API가 동작합니다. 사진 촬영뿐만 아니라 미리보기(Preview)도 CaptureRequest를 연속적으로 보내는 식으로 작동합니다. 이 때 Request에 캡쳐 설정을 같이 보내게 됩니다.
위의 그림을 참고하면 여러 개의 Surface로 버퍼를 보내고 있는데, SurfaceView를 사용해 바로 미리보기를 보낼 수도 있고, SurfaceTexture나 RenderScript를 이용해 후처리를 하게 할 수도 있습니다. 특이한 점은 ImageReader나 MediaCodec으로 보내는 점인데, Camera2는 사진을 찍으면 바로 ByteArray를 주는 Camera1과는 달리 ImageReader로 Image를 줍니다.
여러 Surface로 보내는 게 가능하기 때문에, Camera1처럼 따로 버퍼의 Preview 크기나 Picture 크기를 정할 필요 없이 Surface의 크기에 맞춰 보냅니다. (다만 아직까지는 모든 Surface들의 크기의 높이 대 너비 비가 같지 않으면 이미지가 이상하게 늘어나는 버그가 많은 기기들에서 관찰됐습니다)
왜 이렇게 바뀌었나요?
카메라 작업들은 보통 꽤 시간이 걸리고, 동기되지 않아서 일어나는 문제들을 해결함과 동시에 속도 측면에서의 이점도 누릴 수 있기 때문입니다. 이미지가 카메라를 거쳐 기기가 사용 가능하도록 디코딩되기까지는 이미지 프로세싱이라는 일련의 과정들을 거칠 필요가 있습니다. 아래 그림을 봅시다.
이미지들이 설정 A로 잘 프로세싱되고 있는 모습을 볼 수 있습니다. 이미지 프로세싱에는 여러 단계가 있어서, 이런 식으로 여러 이미지가 동시에 처리될 수 있습니다.
Camera1은 전역적으로 설정을 적용합니다. 그런데 전역적으로 설정을 적용할 경우 만약 설정을 이미지가 프로세싱되고 있는 도중에 A에서 B로 바꾼다면 결과 이미지에서는 이렇게 A와 B가 섞여버리게 됩니다.
그래서 Camera1은 이렇게 이미지 하나가 전부 프로세싱되고 나서 설정을 새로 적용하고, 새 이미지를 프로세싱하고, 다시 설정을 새로 적용하고, …. 같은 식으로 처리합니다.
하지만 Camera2는 요청 자체에 설정을 첨부해서 보내기 때문에 Camera1과 같이 하나하나 처리할 필요가 없습니다. 각 단계마다 요청에 첨부된 설정을 확인하면 되기 때문입니다. 설정이 섞일 일도 없습니다.
물론 새 버전의 HAL을 활용하는 것도 있지만 이런 방식을 사용해서 Camera2는 Camera1보다 훨씬 빠른 이미지 처리가 가능해졌고, 최고 화질로 초당 30장의 사진을 찍는 연사도 가능하게 되었습니다. 기존에는 초당 1~3장 정도밖에 못 찍었던 것을 생각하면 굉장한 발전입니다.
참고로, Camera2는 많은 동작들에 Handler를 인수로 받아서 그 Handler에서 동기 작업을 합니다. (많은 분들이 Camera1의 작업들이 UI 스레드를 막는다는 걸 모르고 UI 스레드에서 Camera.open()
등의 작업을 하셔서 그런 게 아닐까 싶기도 합니다)
기존 앱에 Camera2 구현하기
일단 API와 관계없이 앱이 어떻게 동작하는지 단계별로 쪼갠 후, 그에 맞게 기존에 Camera를 핸들하던 클래스를 추상화해서 Camera1과 Camera2 로직을 구현하면 됩니다.
Camera1과 Camera2의 코드 차이는 이 슬라이드쇼의 32쪽부터 47쪽에 걸쳐 확인해 볼 수 있습니다!
하지만 추상화를 잘 하고 API 레벨을 체크해 21 이상이면 Camera2를 쓰게 하더라도 모든 기기가 Camera2를 잘 지원하는 건 아닙니다. 제조사가 HAL을 잘 구현했다면 잘 될 거고, 아니면 안 될 겁니다.
그렇다고 그런 소수의 기기들 때문에 Camera2가 제대로 지원되는 기기들이 좋은 기능들을 쓸 수 없게 되는 건 안타까운데요, Camera2의 좋은 기능들을 어떤 방식으로 제공해야 될까요?
일단 기기가 엄청나게 많다면 하나하나 테스트해 볼 수 있습니다. 화이트리스트를 만들어서 잘 되는 기기들을 넣으면 됩니다.
하지만 기기가 그렇게 많지 않거나 저렇게 테스트할 수 있는 환경이 없다면 사용자들에게 잘 되는지 물어보는 방법도 있습니다. 카카오톡의 ‘실험실‘ 기능처럼요.
API 21 이상이고 개발자가 테스트하지 않은 기기라면 사용자가 Camera2 기능들을 직접 사용해 볼 수 있도록 실험실에 ‘고급 카메라 기능 사용‘ 등의 항목을 넣어두는 것도 괜찮습니다. 해당 기기에서 많은 오류가 발생한다면 고쳐 보거나 고칠 수 없는 경우 지원을 하지 않으면 되고, 오류가 거의 발생하지 않았다면 화이트리스트에 추가하는 식입니다.
참고하면 좋은 자료
- Google I/O 2014 – Building great multi-media experiences on Android (30:00~) : 구현 자체에 도움을 주는 영상은 아니지만, 이 포스트에서는 꽤 많이 참고로 했습니다!
- googlesamples/android-Camera2Basic : Camera1을 하다가 Camera2를 하게 되면 도큐먼테이션을 봐도 이해가 안 되는 부분이 많을 수 있습니다. 도큐먼테이션을 읽는 것보다 샘플 프로젝트를 참고하는 편이 많은 도움이 됐습니다.
- Camera 2 APIはじめの一歩 (번역된 글)
- The least you can do with Camera2 API