여러 카메라 스트림 동시 사용

참고: 이 페이지에서는 Camera2 패키지를 다룹니다. 앱에 Camera2의 특정 하위 수준 기능이 필요하지 않다면 CameraX를 사용하는 것이 좋습니다. CameraX와 Camera2는 모두 Android 5.0(API 수준 21) 이상을 지원합니다.

카메라 애플리케이션은 두 개 이상의 프레임 스트림을 동시에 사용할 수 있습니다. 포함 스트림마다 다른 프레임 해상도나 픽셀이 필요한 경우도 있습니다. 형식으로 입력합니다. 몇 가지 일반적인 사용 사례는 다음과 같습니다.

  • 동영상 녹화: 미리보기용 스트림 1개, 인코딩 및 저장용 스트림 1개 할 수 있습니다.
  • 바코드 스캔: 미리보기용 스트림, 바코드 감지용 스트림
  • 컴퓨팅 사진: 미리보기용 스트림, 얼굴/장면 스트림 1개 있습니다

프레임을 처리할 때 적지 않은 성능 비용이 발생하며 곱하기만 하면 됩니다.

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)

자바

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);

타겟 표면을 올바르게 구성하면 이 코드는 최소 FPS를 충족하는 스트림은 StreamComfigurationMap.GetOutputMinFrameDuration(int, Size) 드림 및 StreamComfigurationMap.GetOutputStallDuration(int, Size) 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

자바

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)

자바

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)

자바

CameraCharacteristics characteristics = …;
   int outputFormat = …;  // such as ImageFormat.JPEG
   Size[] sizes = characteristics.get(
                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
                .getOutputSizes(outputFormat);

MAXIMUM 크기를 가져오려면 출력 크기를 영역으로 정렬하고 가장 큰 값을 반환합니다. 1개:

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 }
}

자바

 @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
}

자바

/** 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)

자바

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);

총정리

출력 유형, 출력 크기, 하드웨어 수준을 사용해 유효한 스트림 조합입니다. 다음 차트는 2014년 2월 1일 이후에 CameraDevice에서 지원하는 구성 LEGACY 하드웨어 수준입니다

타겟 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 이상)를 지원하는 기기는 최대 세 개의 여러 개의 동시 스트림을 스트리밍하려는 경우 성능 제한(예: 메모리, 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)

자바

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
  }
  ...
})

자바

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
            }
            ...
        });

다음을 호출하여 SurfaceView를 카메라 출력 크기와 일치하도록 강제할 수 있습니다. SurfaceHolder.setFixedSize() kubectl 명령어와 유사한 방식으로 Commons의 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)

자바

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)

자바

imageReader.setOnImageAvailableListener(listener -> {
            Image frame = listener.acquireNextImage();
            // Do something with "frame" here
            listener.close();
        }, null);

하드웨어 수준 LEGACY개가 가장 낮은 공통분모 기기를 타겟팅합니다. 다음과 같은 작업을 할 수 있습니다. 조건부 브랜치를 추가하고 출력 타겟 중 하나에 RECORD 크기를 사용합니다. 하드웨어 수준이 LIMITED인 기기에 노출 영역을 표시하거나 하드웨어 수준이 FULL인 기기의 경우 MAXIMUM 크기입니다.