참고: 이 페이지에서는 Camera2 패키지에 관해 다룹니다. 앱에 Camera2의 특정 하위 수준 기능이 필요한 경우가 아니라면 CameraX를 사용하는 것이 좋습니다. CameraX와 Camera2는 모두 Android 5.0(API 수준 21) 이상을 지원합니다.
카메라 애플리케이션은 둘 이상의 프레임 스트림을 동시에 사용할 수 있습니다. 스트림마다 다른 프레임 해상도나 픽셀 형식이 필요한 경우도 있습니다. 몇 가지 일반적인 사용 사례는 다음과 같습니다.
- 동영상 녹화: 미리보기용 스트림과 인코딩되어 파일에 저장되는 스트림.
- 바코드 스캔: 미리보기용 스트림과 바코드 감지용 스트림.
- 컴퓨팅 사진: 미리보기용 스트림과 얼굴/장면 감지용 스트림
프레임을 처리하는 데는 적지 않은 성능 비용이 발생하며, 병렬 스트림 또는 파이프라인 처리를 수행할 때는 이 비용이 곱해집니다.
CPU, GPU, DSP와 같은 리소스는 프레임워크의 재처리 기능을 활용할 수 있지만 메모리와 같은 리소스는 선형적으로 증가합니다.
요청당 여러 대상
여러 카메라 스트림을 단일 CameraCaptureRequest
로 결합할 수 있습니다.
다음 코드 스니펫은 카메라 미리보기용 스트림과 이미지 처리를 위한 다른 스트림으로 카메라 세션을 설정하는 방법을 보여줍니다.
Kotlin
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback // You will use the preview capture template for the combined streams // because it is optimized for low latency; for high-quality images, use // TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD val requestTemplate = CameraDevice.TEMPLATE_PREVIEW val combinedRequest = session.device.createCaptureRequest(requestTemplate) // Link the Surface targets with the combined request combinedRequest.addTarget(previewSurface) combinedRequest.addTarget(imReaderSurface) // In this simple case, the SurfaceView gets updated automatically. ImageReader // has its own callback that you have to listen to in order to retrieve the // frames so there is no need to set up a callback for the capture request session.setRepeatingRequest(combinedRequest.build(), null, null)
Java
CameraCaptureSession session = …; // from CameraCaptureSession.StateCallback // You will use the preview capture template for the combined streams // because it is optimized for low latency; for high-quality images, use // TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD CaptureRequest.Builder combinedRequest = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // Link the Surface targets with the combined request combinedRequest.addTarget(previewSurface); combinedRequest.addTarget(imReaderSurface); // In this simple case, the SurfaceView gets updated automatically. ImageReader // has its own callback that you have to listen to in order to retrieve the // frames so there is no need to set up a callback for the capture request session.setRepeatingRequest(combinedRequest.build(), null, null);
타겟 노출 영역을 올바르게 구성하면 이 코드는 StreamComfigurationMap.GetOutputMinFrameDuration(int, Size)
및 StreamComfigurationMap.GetOutputStallDuration(int, Size)
에 의해 결정된 최소 FPS를 충족하는 스트림만 생성합니다.
실제 성능은 기기마다 다르지만 Android는 출력 유형, 출력 크기 및 하드웨어 수준의 세 가지 변수에 따라 특정 조합을 지원하기 위한 몇 가지 보증을 제공합니다.
지원되지 않는 변수 조합을 사용하면 낮은 프레임 속도에서 작동할 수 있습니다. 그렇지 않으면 실패 콜백 중 하나를 트리거합니다.
createCaptureSession
문서에서는 보장되는 방식을 설명합니다.
출력 유형
출력 유형은 프레임이 인코딩되는 형식을 나타냅니다. 가능한 값은 PRIV, YUV, JPEG, RAW입니다. createCaptureSession
문서에서 이를 설명합니다.
애플리케이션의 출력 유형을 선택할 때 호환성 극대화가 목표라면 프레임 분석에는 ImageFormat.YUV_420_888
를 사용하고 스틸 이미지에는 ImageFormat.JPEG
를 사용하세요. 미리보기 및 녹화 시나리오의 경우 SurfaceView
, TextureView
, MediaRecorder
, MediaCodec
, RenderScript.Allocation
를 사용할 가능성이 높습니다. 이러한 경우 이미지 형식을 지정하지 마세요. 호환성을 위해 내부적으로 사용되는 실제 형식과 관계없이 ImageFormat.PRIVATE
로 계산됩니다. CameraCharacteristics
가 지정된 기기에서 지원하는 형식을 쿼리하려면 다음 코드를 사용하세요.
Kotlin
val characteristics: CameraCharacteristics = ... val supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).outputFormats
Java
CameraCharacteristics characteristics = …; int[] supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputFormats();
출력 크기
사용 가능한 모든 출력 크기는 StreamConfigurationMap.getOutputSizes()
에 의해 나열되지만 호환성과 관련된 것은 PREVIEW
및 MAXIMUM
두 가지뿐입니다. 크기는 상한값으로 기능합니다. PREVIEW
크기의 항목이 작동하면 PREVIEW
보다 작은 크기도 작동합니다. 이는 MAXIMUM
의 경우에도 마찬가지입니다. CameraDevice
문서에서 이러한 크기를 설명합니다.
사용 가능한 출력 크기는 형식 선택에 따라 다릅니다. CameraCharacteristics
와 형식이 주어지면 다음과 같이 사용 가능한 출력 크기를 쿼리할 수 있습니다.
Kotlin
val characteristics: CameraCharacteristics = ... val outputFormat: Int = ... // such as ImageFormat.JPEG val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat)
Java
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
카메라 미리보기 및 녹화 사용 사례에서 타겟 클래스를 사용하여 지원되는 크기를 결정합니다. 형식은 카메라 프레임워크 자체에서 처리합니다.
Kotlin
val characteristics: CameraCharacteristics = ... val targetClass: Class <T> = ... // such as SurfaceView::class.java val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(targetClass)
Java
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
MAXIMUM
크기를 가져오려면 출력 크기를 영역으로 정렬하고 가장 큰 크기를 반환합니다.
Kotlin
fun <T>getMaximumOutputSize( characteristics: CameraCharacteristics, targetClass: Class <T>, format: Int? = null): Size { val config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) // If image format is provided, use it to determine supported sizes; or else use target class val allSizes = if (format == null) config.getOutputSizes(targetClass) else config.getOutputSizes(format) return allSizes.maxBy { it.height * it.width } }
Java
@RequiresApi(api = Build.VERSION_CODES.N) <T> Size getMaximumOutputSize(CameraCharacteristics characteristics, Class <T> targetClass, Integer format) { StreamConfigurationMap config = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); // If image format is provided, use it to determine supported sizes; else use target class Size[] allSizes; if (format == null) { allSizes = config.getOutputSizes(targetClass); } else { allSizes = config.getOutputSizes(format); } return Arrays.stream(allSizes).max(Comparator.comparing(s -> s.getHeight() * s.getWidth())).get(); }
PREVIEW
은 기기의 화면 해상도에 맞는 최적의 크기 또는 1080p (1920x1080) 중 더 작은 크기를 나타냅니다. 가로세로 비율은 화면의 가로세로 비율과 정확히 일치하지 않을 수 있으므로 전체 화면 모드로 표시하려면 레터박스 또는 스트림에 자르기를 적용해야 할 수 있습니다. 적절한 미리보기 크기를 얻으려면 디스플레이가 회전될 수 있다는 점을 고려하면서 사용 가능한 출력 크기를 디스플레이 크기와 비교합니다.
다음 코드는 크기 비교를 좀 더 쉽게 하는 도우미 클래스 SmartSize
를 정의합니다.
Kotlin
/** Helper class used to pre-compute shortest and longest sides of a [Size] */ class SmartSize(width: Int, height: Int) { var size = Size(width, height) var long = max(size.width, size.height) var short = min(size.width, size.height) override fun toString() = "SmartSize(${long}x${short})" } /** Standard High Definition size for pictures and video */ val SIZE_1080P: SmartSize = SmartSize(1920, 1080) /** Returns a [SmartSize] object for the given [Display] */ fun getDisplaySmartSize(display: Display): SmartSize { val outPoint = Point() display.getRealSize(outPoint) return SmartSize(outPoint.x, outPoint.y) } /** * Returns the largest available PREVIEW size. For more information, see: * https://d.android.com/reference/android/hardware/camera2/CameraDevice */ fun <T>getPreviewOutputSize( display: Display, characteristics: CameraCharacteristics, targetClass: Class <T>, format: Int? = null ): Size { // Find which is smaller: screen or 1080p val screenSize = getDisplaySmartSize(display) val hdScreen = screenSize.long >= SIZE_1080P.long || screenSize.short >= SIZE_1080P.short val maxSize = if (hdScreen) SIZE_1080P else screenSize // If image format is provided, use it to determine supported sizes; else use target class val config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!! if (format == null) assert(StreamConfigurationMap.isOutputSupportedFor(targetClass)) else assert(config.isOutputSupportedFor(format)) val allSizes = if (format == null) config.getOutputSizes(targetClass) else config.getOutputSizes(format) // Get available sizes and sort them by area from largest to smallest val validSizes = allSizes .sortedWith(compareBy { it.height * it.width }) .map { SmartSize(it.width, it.height) }.reversed() // Then, get the largest output size that is smaller or equal than our max size return validSizes.first { it.long <= maxSize.long && it.short <= maxSize.short }.size }
Java
/** Helper class used to pre-compute shortest and longest sides of a [Size] */ class SmartSize { Size size; double longSize; double shortSize; public SmartSize(Integer width, Integer height) { size = new Size(width, height); longSize = max(size.getWidth(), size.getHeight()); shortSize = min(size.getWidth(), size.getHeight()); } @Override public String toString() { return String.format("SmartSize(%sx%s)", longSize, shortSize); } } /** Standard High Definition size for pictures and video */ SmartSize SIZE_1080P = new SmartSize(1920, 1080); /** Returns a [SmartSize] object for the given [Display] */ SmartSize getDisplaySmartSize(Display display) { Point outPoint = new Point(); display.getRealSize(outPoint); return new SmartSize(outPoint.x, outPoint.y); } /** * Returns the largest available PREVIEW size. For more information, see: * https://d.android.com/reference/android/hardware/camera2/CameraDevice */ @RequiresApi(api = Build.VERSION_CODES.N) <T> Size getPreviewOutputSize( Display display, CameraCharacteristics characteristics, Class <T> targetClass, Integer format ){ // Find which is smaller: screen or 1080p SmartSize screenSize = getDisplaySmartSize(display); boolean hdScreen = screenSize.longSize >= SIZE_1080P.longSize || screenSize.shortSize >= SIZE_1080P.shortSize; SmartSize maxSize; if (hdScreen) { maxSize = SIZE_1080P; } else { maxSize = screenSize; } // If image format is provided, use it to determine supported sizes; else use target class StreamConfigurationMap config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (format == null) assert(StreamConfigurationMap.isOutputSupportedFor(targetClass)); else assert(config.isOutputSupportedFor(format)); Size[] allSizes; if (format == null) { allSizes = config.getOutputSizes(targetClass); } else { allSizes = config.getOutputSizes(format); } // Get available sizes and sort them by area from largest to smallest List <Size> sortedSizes = Arrays.asList(allSizes); List <SmartSize> validSizes = sortedSizes.stream() .sorted(Comparator.comparing(s -> s.getHeight() * s.getWidth())) .map(s -> new SmartSize(s.getWidth(), s.getHeight())) .sorted(Collections.reverseOrder()).collect(Collectors.toList()); // Then, get the largest output size that is smaller or equal than our max size return validSizes.stream() .filter(s -> s.longSize <= maxSize.longSize && s.shortSize <= maxSize.shortSize) .findFirst().get().size; }
지원되는 하드웨어 수준 확인
런타임 시 사용 가능한 기능을 확인하려면 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
를 사용하여 지원되는 하드웨어 수준을 확인합니다.
CameraCharacteristics
객체를 사용하면 단일 구문으로 하드웨어 수준을 가져올 수 있습니다.
Kotlin
val characteristics: CameraCharacteristics = ... // Hardware level will be one of: // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 val hardwareLevel = characteristics.get( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
Java
CameraCharacteristics characteristics = ...; // Hardware level will be one of: // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 Integer hardwareLevel = characteristics.get( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
모든 조각 맞추기
출력 유형, 출력 크기 및 하드웨어 수준을 사용하여 유효한 스트림 조합을 확인할 수 있습니다. 다음 차트는 LEGACY
하드웨어 수준의 CameraDevice
에서 지원되는 구성의 스냅샷입니다.
타겟 1 | 타겟 2 | 타겟 3 | 샘플 사용 사례 | |||
---|---|---|---|---|---|---|
유형 | 최대 크기 | 유형 | 최대 크기 | 유형 | 최대 크기 | |
PRIV |
MAXIMUM |
간단한 미리보기, GPU 동영상 처리 또는 미리보기가 없는 동영상 녹화 | ||||
JPEG |
MAXIMUM |
뷰파인더 없는 정지 이미지를 캡처합니다. | ||||
YUV |
MAXIMUM |
인앱 동영상/이미지 처리 | ||||
PRIV |
PREVIEW |
JPEG |
MAXIMUM |
표준 정지 이미지. | ||
YUV |
PREVIEW |
JPEG |
MAXIMUM |
인앱 처리 및 스틸 캡처 | ||
PRIV |
PREVIEW |
PRIV |
PREVIEW |
표준 녹화 | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
미리보기 및 인앱 처리 | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
미리보기 및 인앱 처리 | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
JPEG |
MAXIMUM |
스틸 캡처 및 인앱 처리 |
LEGACY
은 가장 낮은 하드웨어 수준입니다. 이 표는 Camera2 (API 수준 21 이상)를 지원하는 모든 기기가 올바른 구성을 사용하여 최대 3개의 동시 스트림을 출력할 수 있으며 메모리, CPU 또는 열 제약 조건과 같은 오버헤드 제한 성능이 너무 많지 않은 경우 이를 보여줍니다.
앱은 출력 버퍼 타겟팅도 구성해야 합니다. 예를 들어 LEGACY
하드웨어 수준의 기기를 타겟팅하려면 두 개의 타겟 출력 표면을 설정할 수 있습니다. 하나는 ImageFormat.PRIVATE
을 사용하고 다른 하나는 ImageFormat.YUV_420_888
를 사용합니다. 이는 PREVIEW
크기를 사용하는 동안 지원되는 조합입니다. 이 주제의 앞부분에서 정의한 함수를 사용하여 카메라 ID에 필요한 미리보기 크기를 가져오려면 다음 코드가 필요합니다.
Kotlin
val characteristics: CameraCharacteristics = ... val context = this as Context // assuming you are inside of an activity val surfaceViewSize = getPreviewOutputSize( context, characteristics, SurfaceView::class.java) val imageReaderSize = getPreviewOutputSize( context, characteristics, ImageReader::class.java, format = ImageFormat.YUV_420_888)
Java
CameraCharacteristics characteristics = ...; Context context = this; // assuming you are inside of an activity Size surfaceViewSize = getPreviewOutputSize( context, characteristics, SurfaceView.class); Size imageReaderSize = getPreviewOutputSize( context, characteristics, ImageReader.class, format = ImageFormat.YUV_420_888);
제공된 콜백을 사용하여 SurfaceView
가 준비될 때까지 기다려야 합니다.
Kotlin
val surfaceView = findViewById <SurfaceView>(...) surfaceView.holder.addCallback(object : SurfaceHolder.Callback { override fun surfaceCreated(holder: SurfaceHolder) { // You do not need to specify image format, and it will be considered of type PRIV // Surface is now ready and you could use it as an output target for CameraSession } ... })
Java
SurfaceView surfaceView = findViewById <SurfaceView>(...); surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) { // You do not need to specify image format, and it will be considered of type PRIV // Surface is now ready and you could use it as an output target for CameraSession } ... });
SurfaceHolder.setFixedSize()
를 호출하여 SurfaceView
를 강제로 카메라 출력 크기와 일치시킬 수도 있고, GitHub의 카메라 샘플에 있는 일반 모듈의 AutoFitSurfaceView
과 유사한 접근 방식을 취할 수도 있습니다. 이 모듈은 가로세로 비율과 사용 가능한 공간을 모두 고려하여 절대 크기를 설정하고 활동 변경이 트리거될 때 자동으로 조정됩니다.
ImageReader
의 다른 노출 영역을 원하는 형식으로 설정하는 것이 더 쉽습니다. 기다릴 콜백이 없기 때문입니다.
Kotlin
val frameBufferCount = 3 // just an example, depends on your usage of ImageReader val imageReader = ImageReader.newInstance( imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888, frameBufferCount)
Java
int frameBufferCount = 3; // just an example, depends on your usage of ImageReader ImageReader imageReader = ImageReader.newInstance( imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888, frameBufferCount);
ImageReader
와 같은 차단 타겟 버퍼를 사용할 때는 사용 후 프레임을 삭제합니다.
Kotlin
imageReader.setOnImageAvailableListener({ val frame = it.acquireNextImage() // Do something with "frame" here it.close() }, null)
Java
imageReader.setOnImageAvailableListener(listener -> { Image frame = listener.acquireNextImage(); // Do something with "frame" here listener.close(); }, null);
LEGACY
하드웨어 수준은 가장 낮은 공통분모 기기를 타겟팅합니다. 조건부 분기를 추가하고 하드웨어 수준이 LIMITED
인 기기의 출력 타겟 노출 영역 중 하나에 RECORD
크기를 사용하거나 하드웨어 수준이 FULL
인 기기의 경우 MAXIMUM
크기로 늘릴 수도 있습니다.