참고: 이 페이지에서는 Camera2 패키지를 다룹니다. 앱에 Camera2의 특정 하위 수준 기능이 필요하지 않다면 CameraX를 사용하는 것이 좋습니다. CameraX와 Camera2는 모두 Android 5.0(API 수준 21) 이상을 지원합니다.
단일 Android 지원 기기에 여러 카메라가 있을 수 있습니다. 각 카메라는 CameraDevice이며 CameraDevice는 동시에 두 개 이상의 스트림을 출력할 수 있습니다.
이렇게 하는 한 가지 이유는 CameraDevice에서 나오는 순차적 카메라 프레임인 하나의 스트림이 뷰파인더 표시와 같은 특정 작업에 최적화되어 있는 반면 다른 스트림은 사진을 찍거나 동영상을 녹화하는 데 사용될 수 있기 때문입니다. 스트림은 카메라에서 나오는 원시 프레임을 한 번에 하나씩 처리하는 병렬 파이프라인 역할을 합니다.
병렬 처리는 CPU, GPU 또는 기타 프로세서에서 사용 가능한 처리 능력에 따라 성능 제한이 있을 수 있음을 시사합니다. 파이프라인이 수신 프레임을 따라갈 수 없으면 프레임을 삭제하기 시작합니다.
각 파이프라인에는 고유한 출력 형식이 있습니다. 수신되는 원시 데이터는 각 파이프라인과 연결된 암시적 로직에 의해 적절한 출력 형식으로 자동 변환됩니다. 이 페이지의 코드 샘플 전체에서 사용되는 CameraDevice는 구체적이지 않으므로 먼저 사용 가능한 모든 카메라를 열거한 후 진행합니다.
CameraDevice를 사용하여 해당 CameraDevice에 고유한 CameraCaptureSession를 만들 수 있습니다. CameraDevice는 CameraCaptureSession를 사용하여 각 원시 프레임의 프레임 구성을 수신해야 합니다. 구성은 자동 초점, 조리개, 효과, 노출과 같은 카메라 속성을 지정합니다. 하드웨어 제약으로 인해 카메라 센서에서는 한 번에 하나의 구성만 활성화되며 이를 활성 구성이라고 합니다.
하지만 스트림 사용 사례는 CameraDevice를 사용하여 캡처 세션을 스트리밍하는 이전 방식을 개선하고 확장하므로 특정 사용 사례에 맞게 카메라 스트림을 최적화할 수 있습니다. 예를 들어 영상 통화를 최적화할 때 배터리 수명을 개선할 수 있습니다.
CameraCaptureSession은 CameraDevice에 바인딩된 모든 가능한 파이프라인을 설명합니다. 세션이 생성되면 파이프라인을 추가하거나 삭제할 수 없습니다.
CameraCaptureSession는 활성 구성이 되는 CaptureRequest의 큐를 유지합니다.
CaptureRequest는 큐에 구성을 추가하고 사용 가능한 파이프라인 중 하나, 여러 개 또는 모두를 선택하여 CameraDevice에서 프레임을 수신합니다. 캡처 세션의 수명 동안 여러 캡처 요청을 보낼 수 있습니다. 각 요청은 활성 구성과 원시 이미지를 수신하는 출력 파이프라인 집합을 변경할 수 있습니다.
성능 개선을 위한 스트림 사용 사례 사용
스트림 사용 사례는 Camera2 캡처 세션의 성능을 개선하는 방법입니다. 하드웨어 기기에 매개변수를 조정할 수 있는 더 많은 정보를 제공하여 특정 작업에 더 나은 카메라 환경을 제공합니다.
이렇게 하면 카메라 기기가 각 스트림의 사용자 시나리오를 기반으로 카메라 하드웨어 및 소프트웨어 파이프라인을 최적화할 수 있습니다. 스트림 사용 사례에 관한 자세한 내용은 setStreamUseCase를 참고하세요.
스트림 사용 사례를 사용하면 CameraDevice.createCaptureRequest()에서 템플릿을 설정하는 것 외에도 특정 카메라 스트림이 사용되는 방식을 더 자세히 지정할 수 있습니다. 이를 통해 카메라 하드웨어는 특정 사용 사례에 적합한 품질 또는 지연 시간 절충을 기반으로 매개변수(예: 조정, 센서 모드 또는 카메라 센서 설정)를 최적화할 수 있습니다.
스트림 사용 사례는 다음과 같습니다.
DEFAULT: 기존의 모든 애플리케이션 동작을 다룹니다. 이는 스트림 사용 사례를 설정하지 않는 것과 같습니다.PREVIEW: : 뷰파인더 또는 인앱 이미지 분석에 권장됩니다.STILL_CAPTURE: 고품질 고해상도 캡처에 최적화되어 있으며 미리보기와 같은 프레임 속도를 유지하지 않습니다.VIDEO_RECORD: 기기에서 지원하고 애플리케이션에서 사용 설정한 경우 고품질 이미지 안정화를 비롯한 고품질 동영상 캡처에 최적화됩니다. 이 옵션은 최고 품질의 안정화 또는 기타 처리를 허용하기 위해 실시간에서 상당한 지연이 있는 출력 프레임을 생성할 수 있습니다.VIDEO_CALL: : 전력 소모가 우려되는 장기 실행 카메라 사용에 권장됩니다.PREVIEW_VIDEO_STILL: 소셜 미디어 앱 또는 단일 스트림 사용 사례에 권장됩니다. 다목적 스트림입니다.VENDOR_START: OEM 정의 사용 사례에 사용됩니다.
CameraCaptureSession 만들기
카메라 세션을 만들려면 앱에서 출력 프레임을 쓸 수 있는 출력 버퍼를 하나 이상 제공합니다. 각 버퍼는 파이프라인을 나타냅니다. 프레임워크가 기기의 내부 파이프라인을 구성하고 필요한 출력 타겟으로 프레임을 전송하기 위한 메모리 버퍼를 할당할 수 있도록 카메라를 사용하기 전에 이 작업을 실행해야 합니다.
다음 코드 스니펫은 두 개의 출력 버퍼(하나는 SurfaceView에 속하고 다른 하나는 ImageReader에 속함)를 사용하여 카메라 세션을 준비하는 방법을 보여줍니다. previewSurface에 PREVIEW 스트림 사용 사례를 추가하고 imReaderSurface에 STILL_CAPTURE 스트림 사용 사례를 추가하면 기기 하드웨어에서 이러한 스트림을 더욱 최적화할 수 있습니다.
// Retrieve the target surfaces, which might be coming from a number of places: // 1. SurfaceView, if you want to display the image directly to the user // 2. ImageReader, if you want to read each frame or perform frame-by-frame // analysis // 3. OpenGL Texture or TextureView, although discouraged for maintainability // reasons // 4. RenderScript.Allocation, if you want to do parallel processing val surfaceView = findViewById<SurfaceView>( // ... ) val imageReader = ImageReader.newInstance( // ... ) // Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated(). 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. // Setup Stream Use Case while setting up your Output Configuration. @RequiresApi(Build.VERSION_CODES.TIRAMISU) fun configureSession(device: CameraDevice, targets: List<Surface>) { val configs = mutableListOf<OutputConfiguration>() val streamUseCase = CameraMetadata .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL targets.forEach { val config = OutputConfiguration(it) config.streamUseCase = streamUseCase.toLong() configs.add(config) } // ... device.createCaptureSession(session) }
이 시점에서는 카메라의 활성 구성을 정의하지 않았습니다. 세션이 구성되면 캡처 요청을 만들고 전달하여 이 작업을 실행할 수 있습니다.
입력이 버퍼에 기록될 때 입력에 적용되는 변환은 각 타겟의 유형에 따라 결정되며, 이는 Surface여야 합니다. Android 프레임워크는 활성 구성의 원시 이미지를 각 타겟에 적합한 형식으로 변환하는 방법을 알고 있습니다. 변환은 특정 Surface의 픽셀 형식과 크기에 의해 제어됩니다.
프레임워크는 최선을 다하지만 일부 Surface 구성 조합이 작동하지 않아 세션이 생성되지 않거나 요청을 전달할 때 런타임 오류가 발생하거나 성능이 저하되는 등의 문제가 발생할 수 있습니다. 프레임워크는 기기, 표면, 요청 매개변수의 특정 조합을 보장합니다. 자세한 내용은 createCaptureSession() 문서를 참조하세요.
단일 CaptureRequests
각 프레임에 사용되는 구성은 CaptureRequest로 인코딩되어 카메라로 전송됩니다. 캡처 요청을 만들려면 사전 정의된 템플릿 중 하나를 사용하거나 TEMPLATE_MANUAL를 사용하여 완전히 제어할 수 있습니다. 템플릿을 선택할 때는 요청에 사용할 출력 버퍼를 하나 이상 제공해야 합니다. 사용하려는 캡처 세션에 이미 정의된 버퍼만 사용할 수 있습니다.
캡처 요청은 빌더 패턴을 사용하며 개발자에게 자동 노출, 자동 초점, 렌즈 조리개를 비롯한 다양한 옵션을 설정할 기회를 제공합니다.
필드를 설정하기 전에 CameraCharacteristics.getAvailableCaptureRequestKeys()를 호출하여 기기에서 특정 옵션을 사용할 수 있는지 확인하고, 사용 가능한 자동 노출 모드와 같은 적절한 카메라 특성을 확인하여 원하는 값이 지원되는지 확인합니다.
수정 없이 미리보기를 위해 설계된 템플릿을 사용하여 SurfaceView의 캡처 요청을 만들려면 CameraDevice.TEMPLATE_PREVIEW를 사용합니다.
val session: CameraCaptureSession = /* ... */ // from CameraCaptureSession.StateCallback val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) captureRequest.addTarget(previewSurface)
캡처 요청을 정의했으므로 이제 카메라 세션에 전달할 수 있습니다.
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback val captureRequest: CaptureRequest.Builder = ... // from CameraDevice.createCaptureRequest() // The first null argument corresponds to the capture callback, which you // provide if you want to retrieve frame metadata or keep track of failed capture // requests that can indicate dropped frames; the second null argument // corresponds to the Handler used by the asynchronous callback, which falls // back to the current thread's looper if null. session.capture(captureRequest.build(), null, null)
출력 프레임이 특정 버퍼에 배치되면 캡처 콜백이 트리거됩니다. 대부분의 경우, 포함된 프레임이 처리될 때 ImageReader.OnImageAvailableListener와 같은 추가 콜백이 트리거됩니다. 이 시점에서 지정된 버퍼에서 이미지 데이터를 가져올 수 있습니다.
CaptureRequest 반복
단일 카메라 요청은 간단하지만 실시간 미리보기나 동영상을 표시하는 데는 그다지 유용하지 않습니다. 이 경우 단일 프레임이 아닌 연속적인 프레임 스트림을 수신해야 합니다. 다음 코드 스니펫은 세션에 반복 요청을 추가하는 방법을 보여줍니다.
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback val captureRequest: CaptureRequest.Builder = ... // from CameraDevice.createCaptureRequest() // This keeps 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)
반복 캡처 요청은 카메라 기기가 제공된 CaptureRequest의 설정을 사용하여 이미지를 지속적으로 캡처하도록 합니다. 또한 Camera2 API를 사용하면 GitHub의 이 Camera2 샘플 저장소에 표시된 것처럼 반복되는 CaptureRequests를 전송하여 카메라에서 동영상을 캡처할 수 있습니다. 또한 GitHub의 Camera2 슬로 모션 동영상 샘플 앱에 표시된 것처럼 반복 버스트 CaptureRequests를 사용하여 고속(슬로 모션) 동영상을 캡처하여 슬로 모션 동영상을 렌더링할 수 있습니다.
CaptureRequests 인터리브 처리
반복 캡처 요청이 활성 상태인 동안 두 번째 캡처 요청을 보내려면(예: 뷰파인더를 표시하고 사용자가 사진을 캡처할 수 있도록 하려면) 진행 중인 반복 요청을 중지할 필요가 없습니다. 대신 반복 요청이 계속 실행되는 동안 반복되지 않는 캡처 요청을 실행합니다.
사용되는 모든 출력 버퍼는 세션이 처음 생성될 때 카메라 세션의 일부로 구성되어야 합니다. 반복 요청은 단일 프레임 또는 버스트 요청보다 우선순위가 낮으므로 다음 샘플이 작동할 수 있습니다.
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 can disrupt the ongoing repeating request momentarily. val singleRequest = session.device.createCaptureRequest( CameraDevice.TEMPLATE_STILL_CAPTURE ) singleRequest.addTarget(imReaderSurface) session.capture(singleRequest.build(), null, null)
하지만 이 접근 방식에는 단점이 있습니다. 단일 요청이 발생하는 시점을 정확히 알 수 없다는 것입니다. 다음 그림에서 A가 반복 캡처 요청이고 B가 단일 프레임 캡처 요청인 경우 세션은 다음과 같이 요청 대기열을 처리합니다.

요청 B가 활성화되기 전 A의 마지막 반복 요청과 A가 다시 사용되는 다음 시간 사이의 지연 시간은 보장되지 않으므로 일부 프레임이 건너뛰어질 수 있습니다. 이 문제를 완화하기 위해 할 수 있는 몇 가지 방법은 다음과 같습니다.
요청 A의 출력 타겟을 요청 B에 추가합니다. 이렇게 하면 B의 프레임이 준비되었을 때 A의 출력 타겟에 복사됩니다. 예를 들어 이는 동영상 스냅샷을 촬영할 때 안정적인 프레임 속도를 유지하는 데 필수적입니다. 위 코드에서 요청을 빌드하기 전에
singleRequest.addTarget(previewSurface)를 추가합니다.제로 셔터 지연과 같이 이 특정 시나리오에 맞게 설계된 템플릿 조합을 사용합니다.