https://developer.android.com/training/camera2
1. Camera2란?
Camera2란 안드로이드 기기에서 카메라 제어를 하는 API를 말하며 기존 Camera API를 대체하는 API이다
2. 카메라 기본개념
카메라 캡쳐 세션과 리퀘스트에 대해서 배워보도록 하자
하나의 안드로이드 디바이스는 여러개의 카메라를 가질 수 있다. 각각의 카메라는 CameraDevice라 정의된다.
CameraDevice는 하나이상의 stream output을 동시에 출력 할 수 있다.
아래그림처 display에 출력하게 최적화 되도록 된 preview 스트림과, 사진을 찍기위한 stream, 영상을 찍기위한 stream이 동시에 나오는 것을 볼 수 있다. 각각의 스트림은 병렬로 진행되며 각기 다른 파이프라인을 가진다.
이러한 병렬 처리는 각각 performance limit을 가진다. 이게 무슨말이냐 하면 cpu, gpu 또는 다른 processor가 파이프라인을 처리하는데, 만약 파이프라인의 한계를 넘게 되면 프레임을 drop시켜버린다.
각각의 파이프라인은 고유의 ouput format을 가진다. 카메라로부터 나오는 raw data는 자동적으로 각각의 파이프라인에 맞는 output format으로 변경된다.
3. Camera Capture Sessions and Requests 개념
CameraDevice를 사용하려면 먼저 CameraDevice를 이용하여 CameraCaptureSession을 만들어야 한다.
CameraCaptureSession은 raw frame을 CameraDevice에 전달 해 준다.
CameraCaptureSession은 자기들 만든 CameraDevice에 종속된다.
- 이렇게 형성된 configuration에는 autofocus, apeture, effects, exposure같은 카메라 속성들도 전달된다
- 하드웨어 제약에 의해서 하나의 카메라 센서에는 하나의 configuration만이 active된다. 활성화된 configuration을 active configuration이라고 한다
- CameraCaptureSession은 CameraDevice에 필요한 모든 pipeline을 이미 가지고 있다. 한번 session이 생성되면 여기서 pipeline을 add나 remove할 수 없다
- CameraCapureSession은 CaptureReuqest Queue을 관리한다. 그리고 이 리퀘스트 큐가 결국 active configuration이 된다
- CaptureRequest는 configuration을 Queue에다 추가하고, 하나이상의 파이프라인을 선택한다. 선택된 파이프라인은 CameraDevice로 부터 나온 frame을 전달받는다. CaptureSession이 살아있는동안 수많은 CaptureRequest를 보낼 수 있다. 각각의 CaptureReuqest는 active configuration을 바꿀 수 있고, 파이프라인도 선택 할 수 있다.
4. CameraCaptureSession 만들기
세션을 만들기전에 한가지 준비물이 있다. 바로 프레임을 받아올 Target Surface이다.
안드로이드에서는 다음과 같이 네가지의 Surface를 추천한다
- SurfaceView, 이미지를 유저에게 바로 보여줄 때 사용한다
- ImageReader, 이미지를 하나씩 받아 프레임단위로 무언가를 처리하거나 분석할 때 사용한다
- RenderScript.Allocation, 병렬적으로 무언가를 처리하고 싶을 때 사용한다
- OpenGL Texture or TextureView, gpu를 사용하는 opengl뷰를 사용한다. 관리적인 측면에서 추천하지 않는다
아래 예제는 SurfaceView와 ImageReader를 사용한 예제이다
kotlin
val surfaceView = findViewById<SurfaceView>(...)
val imageReader = ImageReader.newInstance(...)
val previewSurface = surfaceView.holder.surface
val imReaderSurface = imageReader.surface
val targets = listOf(previewSurface, imReaderSurface)
// Create a capture session using the predefined targets; this also involves
// defining the session state callback to be notified of when the session is
// ready
cameraDevice.createCaptureSession(targets, object: CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
// Do something with `session`
}
// Omitting for brevity...
override fun onConfigureFailed(session: CameraCaptureSession) = Unit
}, null) // null can be replaced with a Handler, falls back to current thread's Looper
java
Surface surfaceView = findViewById<SurfaceView>(...);
ImageReader imageReader = ImageReader.newInstance(...);
// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
Surface previewSurface = surfaceView.getHolder().getSurface();
Surface imageSurface = imageReader.getSurface();
List<Surface> targets = Arrays.asList(previewSurface, imageSurface);
// Create a capture session using the predefined targets; this also involves defining the
// session state callback to be notified of when the session is ready
cameraDevice.createCaptureSession(targets, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
// Do something with `session`
}
// Omitting for brevity...
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {}
}, null); // null can be replaced with a Handler, falls back to current thread's Looper
세션 생성에 관한 더 자세한 내용은 아래 링크를 참고
5. CaptureRequest 만들기
각 프레임마다 사용되는 configuration들은 CaptureRequest에 encoded 되고 카메라로 보내지게 된다.
캡처리퀘스트를 만들 때 아래 사항만 기억하면 된다
- 템플릿
- 타겟버퍼
CaptureRequest를 생성하기 위해서는 predefined 된 template이 필요하다. TEMPLATE_MANUAL을 사용하면 full control이 가능하다.
템플릿을 선택하였다면, 다음으로는 리퀘스트에 사용될 하나이상의 output target buffer을 제공해야 한다
캡처 리퀘스트는 builder pattern을 사용한다. 그리고 개발자에게 다양한 옵션을 제공한다. 예를 들면 auto exposure, auto focus, lens aperture 라던지. 이러한 옵션을 사용하기 전에, CameraCharacteristics.getAvailableCaptureRequestKeys() 함수를 이용해서 카메라가 해당 옵션을 지원하는지 확인해야 된다.
아래 예제는 SurfaceViewd에 프리뷰용 템플릿을 제공해주는 리퀘스트를 생성하는 예제이다.
kotlin
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback
val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureRequest.addTarget(previewSurface)
java
CameraCaptureSession session = ...; // from CameraCaptureSession.StateCallback
CaptureRequest.Builder captureRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequest.addTarget(previewSurface);
캡처 리퀘스트가 생성되었으면 이제 세션을 통해 캡쳐리퀘스트를 전송한다
kotlin
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ... // from CameraDevice.createCaptureRequest()
// 첫번째 null인자는 캡처리퀘스트에 대한 콜백이다. 원하면 정의하면 된다.
// 두번째 null인자는 핸들러로 정의된 콜백이다. 원하면 정의하자
session.capture(captureRequest.build(), null, null)
java
CameraCaptureSession session = ...; // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...; // from CameraDevice.createCaptureRequest()
// 첫번째 null인자는 캡처리퀘스트에 대한 콜백이다. 원하면 정의하면 된다.
// 두번째 null인자는 핸들러로 정의된 콜백이다. 원하면 정의하자
session.capture(captureRequest.build(), null, null);
위 예제는 단 한장의 리퀘스트만 날리는 예제이다.
그런데 나는 카메라의 프리뷰를 보고싶은데 한장만 날리면 달랑 하나의 프레임만 날라온다.
리퀘스트를 연속해서 계속 날리려면 어떻게 해야 될까? setRepeatingRequest를 이용하면 된다.
kotlin
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ... // from CameraDevice.createCaptureRequest()
// This will keep sending the capture request as frequently as possible until
// the
// session is torn down or session.stopRepeating() is called
session.setRepeatingRequest(captureRequest.build(), null, null)
java
CameraCaptureSession session = ...; // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...; // from CameraDevice.createCaptureRequest()
// This will keep sending the capture request as frequently as possible until the
// session is torn down or session.stopRepeating() is called
session.setRepeatingRequest(captureRequest.build(), null, null);
이렇게 하면 타겟버퍼(서페이스)에 프리뷰를 띄울 수 있다!
그런데 공부를 하다보면 CaptureRequests 라는놈도 있다. 얘는 비디오 촬영에 사용된다.
프리뷰와 비디오는 비슷하지만 다른개념인걸 기억하자.
Interleaving CaptureReuqests, 프리뷰 사용중에 캡쳐리퀘스트 보내기
프리뷰를 위해 repeating capture request를 날리는 도중에 사진을 찍고 싶으면?
repeating reuqest를 stop 할 필요 없이, Interleaving CaptureReuqests를 이용하면 된다.
kotlin
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback
// Create the repeating request and dispatch it
val repeatingRequest = session.device.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW)
repeatingRequest.addTarget(previewSurface)
session.setRepeatingRequest(repeatingRequest.build(), null, null)
// Some time later...
// Create the single request and dispatch it
// NOTE: This may disrupt the ongoing repeating request momentarily
val singleRequest = session.device.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE)
singleRequest.addTarget(imReaderSurface)
session.capture(singleRequest.build(), null, null)
java
CameraCaptureSession session = ...; // from CameraCaptureSession.StateCallback
// Create the repeating request and dispatch it
CaptureRequest.Builder repeatingRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
repeatingRequest.addTarget(previewSurface);
session.setRepeatingRequest(repeatingRequest.build(), null, null);
// Some time later...
// Create the single request and dispatch it
// NOTE: This may disrupt the ongoing repeating request momentarily
CaptureRequest.Builder singleRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
singleRequest.addTarget(imReaderSurface);
session.capture(singleRequest.build(), null, null);
위 코드에는 그런데 한가지 단점이 있다.
위그럼에서 B가 시작되기전 A와 B가 끝난후 A이 사이의 latency가 얼마나 될지 모른다는 점이다. 그래서 일부 프레임들이 스킵되는 문제가 있다.
이를 해결하기 위해서는 다음과 같이 하면 된다
- A의 target buffer와 B의 target buffer를 일치시킨다. 즉 위 코드에서 캡쳐 리퀘스트의 타겟을 singleRequest.addTarget(previewSurface)로 하면 된다
- zero shutter lag 템플릿을 이용한다. 이 템플릿은 위와같은 상황이 고려된 템플릿이다
'Android > 안드로이드 기본지식' 카테고리의 다른 글
[Android] Recycler View 사용하기 (0) | 2022.06.15 |
---|---|
[Android] Service 배우기 (0) | 2022.05.25 |
[Android] Canvas를 이용하여 UI 그리기 (0) | 2022.04.14 |
[Android] Custom View 만들기 (0) | 2022.04.13 |
[Android] Content Provider 컨텐트 프로바이더 (0) | 2022.04.05 |
댓글