CameraX 동영상 캡처 아키텍처

일반적으로 캡처 시스템은 동영상 및 오디오 스트림을 녹화하여 압축한 후 두 스트림을 다중화하고 결과로 생성된 스트림을 디스크에 씁니다.

동영상 및 오디오 캡처 시스템의 개념 다이어그램
그림 1. 동영상 및 오디오 캡처 시스템의 개념 다이어그램

CameraX에서 동영상 캡처 솔루션은 VideoCapture 사용 사례입니다.

CameraX가 동영상 캡처 사용 사례를 처리하는 방법을 보여주는 개념 다이어그램
그림 2. CameraX가 VideoCapture 사용 사례를 처리하는 방법을 보여주는 개념 다이어그램

그림 2와 같이 CameraX 동영상 캡처에는 상위 수준의 몇 가지 아키텍처 구성요소가 포함됩니다.

  • 동영상 소스용 SurfaceProvider
  • 오디오 소스용 AudioSource
  • 동영상/오디오를 인코딩하고 압축하는 2개의 인코더
  • 2개의 스트림을 결합하는 미디어 Muxer
  • 결과를 기록할 수 있는 File Saver

VideoCapture API는 복잡한 캡처 엔진을 추상화하고 애플리케이션에 훨씬 단순하고 간단한 API를 제공합니다.

VideoCapture API 개요

VideoCapture는 그 자체로도 잘 작동하고 다른 사용 사례와 결합할 수도 있는 CameraX 사용 사례입니다. 지원되는 조합은 카메라 하드웨어 기능에 따라 다르지만, PreviewVideoCapture는 모든 기기에서 유효한 사용 사례 조합입니다.

VideoCapture API는 애플리케이션과 통신하는 다음 객체로 구성됩니다.

  • VideoCapture는 최상위 사용 사례 클래스입니다. VideoCaptureCameraSelector 및 다른 CameraX UseCases와 함께 LifecycleOwner에 바인딩됩니다. 이러한 개념 및 사용법에 관한 자세한 내용은 CameraX 아키텍처를 참고하세요.
  • RecorderVideoCapture와 긴밀하게 결합된 VideoOutput의 구현입니다. Recorder는 동영상 및 오디오 캡처를 실행하는 데 사용됩니다. 애플리케이션은 Recorder로부터 녹화 파일을 만듭니다.
  • PendingRecording은 오디오를 사용 설정하고 이벤트 리스너를 설정하는 등의 옵션을 제공하여 녹화를 구성합니다. PendingRecording을 만들려면 Recorder를 사용해야 합니다. PendingRecording은 아무것도 녹화하지 않습니다.
  • Recording은 실제 녹화를 실행합니다. Recording을 만들려면 PendingRecording을 사용해야 합니다.

그림 3은 이러한 객체 간의 관계를 보여줍니다.

동영상 캡처 사용 사례에서 발생하는 상호작용을 보여주는 다이어그램
그림 3. VideoCapture 사용 사례에서 발생하는 상호작용을 보여주는 다이어그램

범례:

  1. QualitySelectorRecorder를 만듭니다.
  2. OutputOptions 중 하나로 Recorder를 구성합니다.
  3. 필요한 경우 withAudioEnabled()로 오디오를 사용 설정합니다.
  4. VideoRecordEvent 리스너로 start()를 호출하여 녹화를 시작합니다.
  5. Recording에서 pause()/resume()/stop()을 사용하여 녹화를 제어합니다.
  6. 이벤트 리스너 내에서 VideoRecordEvents에 응답합니다.

자세한 API 목록은 소스 코드 내 current.txt에 있습니다.

VideoCapture API 사용

CameraX VideoCapture 사용 사례를 앱에 통합하려면 다음을 따르세요.

  1. VideoCapture를 바인딩합니다.
  2. 녹화를 준비하고 구성합니다.
  3. 런타임 녹화를 시작하고 제어합니다.

다음 섹션에서는 엔드 투 엔드 녹화 세션을 가져오기 위해 각 단계에서 할 수 있는 작업을 설명합니다.

VideoCapture 바인딩

VideoCapure 사용 사례를 바인딩하려면 다음을 따르세요.

  1. Recorder 객체를 만듭니다.
  2. VideoCapture 객체를 만듭니다.
  3. Lifecycle에 바인딩합니다.

CameraX VideoCapture API는 빌더 디자인 패턴을 따릅니다. 애플리케이션은 Recorder.Builder를 사용하여 Recorder를 만듭니다. QualitySelector 객체를 통해 Recorder의 동영상 해상도를 구성할 수도 있습니다.

CameraX Recorder는 다음과 같은 사전 정의된 동영상 해상도 Qualities를 지원합니다.

  • Quality.UHD: 4K Ultra HD 동영상 크기(2,160p)
  • Quality.FHD - 풀 HD 동영상 크기(1080p)
  • Quality.HD - HD 동영상 크기(720p)
  • Quality.SD - SD 동영상 크기(480p)

참고로 CameraX는 앱에서 승인한 경우 다른 해상도를 선택할 수도 있습니다.

각 선택 항목의 정확한 동영상 크기는 카메라와 인코더 기능에 따라 다릅니다. 자세한 내용은 CamcorderProfile 문서를 참고하세요.

애플리케이션은 QualitySelector를 만들어 해상도를 구성할 수 있습니다. 다음 메서드 중 하나를 사용하여 QualitySelector를 만들 수 있습니다.

  • fromOrderedList()를 사용하여 선호하는 해상도를 제공하고, 선호하는 해상도가 지원되지 않는 경우 사용할 대체 전략을 포함합니다.

    CameraX는 선택된 카메라의 기능에 따라 최적의 대체 전략을 결정할 수 있습니다. 자세한 내용은 QualitySelectorFallbackStrategy specification을 참고하세요. 예를 들어 다음 코드는 지원되는 가장 높은 녹화 해상도를 요청하고, 요청한 해상도가 지원되지 않으면 CameraX가 Quality.SD 해상도에 가장 가까운 해상도를 선택하도록 승인합니다.

    val qualitySelector = QualitySelector.fromOrderedList(
             listOf(Quality.UHD, Quality.FHD, Quality.HD, Quality.SD),
             FallbackStrategy.lowerQualityOrHigherThan(Quality.SD))
    
  • 먼저 카메라 기능을 쿼리하고 QualitySelector::from()을 사용하여 지원되는 해상도 중에서 선택합니다.

    val cameraInfo = cameraProvider.availableCameraInfos.filter {
        Camera2CameraInfo
        .from(it)
        .getCameraCharacteristic(CameraCharacteristics.LENS\_FACING) == CameraMetadata.LENS_FACING_BACK
    }
    
    val supportedQualities = QualitySelector.getSupportedQualities(cameraInfo[0])
    val filteredQualities = arrayListOf (Quality.UHD, Quality.FHD, Quality.HD, Quality.SD)
                           .filter { supportedQualities.contains(it) }
    
    // Use a simple ListView with the id of simple_quality_list_view
    viewBinding.simpleQualityListView.apply {
        adapter = ArrayAdapter(context,
                               android.R.layout.simple_list_item_1,
                               filteredQualities.map { it.qualityToString() })
    
        // Set up the user interaction to manually show or hide the system UI.
        setOnItemClickListener { _, _, position, _ ->
            // Inside View.OnClickListener,
            // convert Quality.* constant to QualitySelector
            val qualitySelector = QualitySelector.from(filteredQualities[position])
    
            // Create a new Recorder/VideoCapture for the new quality
            // and bind to lifecycle
            val recorder = Recorder.Builder()
                .setQualitySelector(qualitySelector).build()
    
             // ...
        }
    }
    
    // A helper function to translate Quality to a string
    fun Quality.qualityToString() : String {
        return when (this) {
            Quality.UHD -> "UHD"
            Quality.FHD -> "FHD"
            Quality.HD -> "HD"
            Quality.SD -> "SD"
            else -> throw IllegalArgumentException()
        }
    }
    
    

    QualitySelector.getSupportedQualities()에서 반환되는 기능은 VideoCapture 사용 사례 또는 VideoCapturePreview 사용 사례의 조합에서 확실히 작동합니다. ImageCapture 또는 ImageAnalysis 사용 사례와 바인딩해야 할 때 요청된 카메라에서 필요한 조합이 지원되지 않으면 CameraX가 바인딩에 실패할 수 있습니다.

QualitySelector를 만든 후에는 애플리케이션이 VideoCapture 객체를 만들고 바인딩을 실행할 수 있습니다. 이 바인딩은 다른 사용 사례와 동일합니다.

val recorder = Recorder.Builder()
    .setExecutor(cameraExecutor).setQualitySelector(qualitySelector)
    .build()
val videoCapture = VideoCapture.withOutput(recorder)

try {
    // Bind use cases to camera
    cameraProvider.bindToLifecycle(
            this, CameraSelector.DEFAULT_BACK_CAMERA, preview, videoCapture)
} catch(exc: Exception) {
    Log.e(TAG, "Use case binding failed", exc)
}

bindToLifecycle()Camera 객체를 반환합니다. 확대/축소, 노출 등 카메라 출력을 제어하는 방법에 관한 자세한 내용은 이 가이드를 참고하세요.

Recorder는 시스템에 가장 적합한 형식을 선택합니다. 가장 일반적인 동영상 코덱은 컨테이너 형식 MPEG-4를 사용하는 H.264 AVC입니다.

녹화 구성 및 만들기

애플리케이션은 Recorder에서 동영상 및 오디오 캡처를 실행하는 녹화 객체를 만들 수 있습니다. 애플리케이션은 다음을 실행하여 녹화 파일을 만듭니다.

  1. prepareRecording()으로 OutputOptions를 구성합니다.
  2. (선택사항) 오디오 녹음을 사용 설정합니다.
  3. start()를 사용하여 VideoRecordEvent 리스너를 등록하고 동영상 캡처를 시작합니다.

start() 함수를 호출하면 RecorderRecording 객체를 반환합니다. 애플리케이션은 이 Recording 객체를 사용하여 캡처를 마치거나 일시중지 또는 다시 시작 등 다른 작업을 실행할 수 있습니다.

RecorderRecording 객체를 한 번에 하나씩 지원합니다. 이전 Recording 객체에서 Recording.stop() 또는 Recording.close()를 호출한 후에 새 녹화를 시작할 수 있습니다.

이러한 단계에 관해 더 자세히 살펴보겠습니다. 먼저 애플리케이션은 Recorder.prepareRecording()으로 Recorder의 OutputOptions를 구성합니다. Recorder는 다음 유형의 OutputOptions를 지원합니다.

모든 OutputOptions 유형을 사용하면 setFileSizeLimit()으로 최대 파일 크기를 설정할 수 있습니다. 다른 옵션은 개별 출력 유형에 따라 다릅니다(예: FileDescriptorOutputOptions의 경우 ParcelFileDescriptor).

prepareRecording()PendingRecording 객체를 반환합니다. 이 객체는 대응되는 Recording 객체를 생성하는 데 사용되는 중간 객체입니다. PendingRecording은 대부분의 경우 표시되지 않으며 앱에서 캐시되는 경우가 거의 없는 일시적인 클래스입니다.

애플리케이션은 다음과 같이 녹화를 추가로 구성할 수 있습니다.

  • withAudioEnabled()로 오디오를 사용 설정합니다.
  • start(Executor, Consumer<VideoRecordEvent>)를 사용하여 동영상 녹화 이벤트를 수신하는 리스너를 등록합니다.
  • 연결된 VideoCapture가 다른 카메라에 다시 결합되는 동안 PendingRecording.asPersistentRecording()을 사용하여 녹화가 계속 녹화되도록 허용합니다.

녹화를 시작하려면 PendingRecording.start()를 호출합니다. CameraX는 PendingRecordingRecording으로 변환하고 녹화 요청을 큐에 추가한 다음 새로 만들어진 Recording 객체를 애플리케이션에 반환합니다. 상응하는 카메라 기기에서 녹화가 시작되면 CameraX는 VideoRecordEvent.EVENT_TYPE_START 이벤트를 전송합니다.

다음 예는 동영상과 오디오를 MediaStore 파일에 녹화하는 방법을 보여줍니다.

// Create MediaStoreOutputOptions for our recorder
val name = "CameraX-recording-" +
        SimpleDateFormat(FILENAME_FORMAT, Locale.US)
                .format(System.currentTimeMillis()) + ".mp4"
val contentValues = ContentValues().apply {
   put(MediaStore.Video.Media.DISPLAY_NAME, name)
}
val mediaStoreOutput = MediaStoreOutputOptions.Builder(this.contentResolver,
                              MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
                              .setContentValues(contentValues)
                              .build()

// 2. Configure Recorder and Start recording to the mediaStoreOutput.
val recording = videoCapture.output
                .prepareRecording(context, mediaStoreOutput)
                .withAudioEnabled()
                .start(ContextCompat.getMainExecutor(this), captureListener)

카메라 미리보기는 기본적으로 전면 카메라에 미러링되지만 VideoCapture로 녹화된 동영상은 기본적으로 미러링되지 않습니다. CameraX 1.3을 사용하면 이제 동영상 녹화를 미러링할 수 있으므로 전면 카메라 미리보기와 녹화된 동영상이 일치합니다.

MirrorMode 옵션 세 가지는 MIRROR_MODE_OFF, MIRROR_MODE_ON, MIRROR_MODE_ON_FRONT_ONLY입니다. 카메라 미리보기와 일치하려면 MIROR_MODE_ON_FRONT_ONLY를 사용하는 것이 좋습니다. 즉, 미러링이 후면 카메라에는 사용 설정되지 않지만 전면 카메라에는 사용 설정됩니다. MirrorMode에 관한 자세한 내용은 MirrorMode constants를 참고하세요.

다음 코드 스니펫은 MIRROR_MODE_ON_FRONT_ONLY를 사용하여 VideoCapture.Builder.setMirrorMode()를 호출하는 방법을 보여줍니다. 자세한 내용은 setMirrorMode()를 참고하세요.

Kotlin


val recorder = Recorder.Builder().build()

val videoCapture = VideoCapture.Builder(recorder)
    .setMirrorMode(MIRROR_MODE_ON_FRONT_ONLY)
    .build()

useCases.add(videoCapture);

Java


Recorder.Builder builder = new Recorder.Builder();
if (mVideoQuality != QUALITY_AUTO) {
    builder.setQualitySelector(
        QualitySelector.from(mVideoQuality));
}
  VideoCapture<Recorder> videoCapture = new VideoCapture.Builder<>(builder.build())
      .setMirrorMode(MIRROR_MODE_ON_FRONT_ONLY)
      .build();
    useCases.add(videoCapture);

활성 상태의 녹화 제어하기

다음 메서드를 사용하여 진행 중인 Recording을 일시중지, 다시 시작, 중지할 수 있습니다.

  • pause: 현재 활성 상태인 녹화를 일시중지합니다.
  • resume(): 일시중지된 활성 상태의 녹화를 다시 시작합니다.
  • stop(): 녹화를 완료하고 연결된 녹화 객체를 플러시합니다.
  • mute(): 현재 녹화를 음소거하거나 음소거 해제합니다.

녹화가 일시중지 상태인지, 활성 상태인지와 관계없이 stop()을 호출하여 Recording을 종료할 수 있습니다.

PendingRecording.start()EventListener를 등록했다면 RecordingVideoRecordEvent를 사용하여 통신합니다.

  • VideoRecordEvent.EVENT_TYPE_STATUS는 현재 파일 크기 및 녹화된 기간과 같은 통계를 기록하는 데 사용됩니다.
  • VideoRecordEvent.EVENT_TYPE_FINALIZE는 결과를 기록하는 데 사용되며 관련 오류와 함께 최종 파일의 URI와 같은 정보를 포함합니다.

앱에서 녹화 세션이 성공했음을 나타내는 EVENT_TYPE_FINALIZE를 수신하면 OutputOptions에 지정된 위치에서 캡처된 동영상에 액세스할 수 있습니다.

추가 리소스

CameraX에 관해 자세히 알아보려면 다음 추가 리소스를 참고하세요.