CameraX 動画キャプチャ アーキテクチャ

キャプチャ システムでは通常、動画ストリームと音声ストリームを記録し、圧縮します。そしてこの 2 つのストリームを多重化し、そこで生成された結果のストリームをディスクに書き込みます。

動画と音声のキャプチャ システムの概念図
図 1. 動画と音声のキャプチャ システムの概念図。

CameraX における動画キャプチャのソリューションは、VideoCapture のユースケースです。

CameraX が動画キャプチャのユースケースを処理する仕組みを示す概念図
図 2. CameraX が VideoCapture のユースケースを処理する仕組みを示す概念図。

図 2 に示すように、CameraX 動画キャプチャには高度なアーキテクチャのコンポーネントがいくつか含まれています。

  • 動画のソース用の SurfaceProvider
  • 音声のソース用の AudioSource
  • 動画 / 音声をエンコードして圧縮する 2 つのエンコーダ。
  • 2 つのストリームを多重化するメディア マルチプレクサ。
  • 結果を書き出すファイル セーバー。

VideoCapture API は複雑なキャプチャ エンジンを抽象化し、はるかにシンプルで簡単な API をアプリに提供します。

VideoCapture API の概要

VideoCapture は CameraX のユースケースであり、単独で、または他のユースケースと組み合わせて使用できます。具体的にサポートされる組み合わせはカメラのハードウェア機能によって異なりますが、PreviewVideoCapture はどのデバイスでも利用できるユースケースの組み合わせです。

VideoCapture API は、アプリと通信する次のオブジェクトで構成されています。

  • VideoCapture は最上位のユースケース クラスです。VideoCapture は、CameraSelector と他の CameraX ユースケースを使って LifecycleOwner にバインドします。これらのコンセプトと使用法について詳しくは、CameraX のアーキテクチャをご覧ください。
  • Recorder は、VideoCapture と密結合された VideoOutput の実装です。Recorder は、動画と音声のキャプチャを実行するために使用されます。アプリは Recorder を通じて録画を作成します。
  • PendingRecording は録画を設定し、音声の有効化やイベント リスナーの設定などを行えるようにします。PendingRecording を作成するには、Recorder を使用する必要があります。PendingRecording は何も記録しません。
  • Recording が実際の録画を実行します。Recording を作成するには、PendingRecording を使用する必要があります。

図 3 は、これらのオブジェクト間の関係を示しています。

動画キャプチャのユースケースで発生するインタラクションを示す図
図 3. VideoCapture のユースケースで発生するインタラクションを示す図。

凡例:

  1. QualitySelectorRecorder を作成します。
  2. いずれかの OutputOptionsRecorder を構成します。
  3. 必要に応じて、withAudioEnabled() を使用して音声を有効にします。
  4. VideoRecordEvent リスナーで start() を呼び出して、録画を開始します。
  5. 録画を管理するには、Recordingpause()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 動画サイズ(2160p)
  • 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 で、システムに最適な形式が選択されます。最も一般的な動画コーデックは、H.264 AVC で、そのコンテナ形式は MPEG-4 です。

録画の設定と作成を行う

アプリは Recorder を通じて録画オブジェクトを作成して、動画と音声のキャプチャを実行できます。アプリでは、次の手順で録画を作成します。

  1. prepareRecording()OutputOptions を設定する。
  2. (省略可)録音を有効にする。
  3. start() を使用して VideoRecordEvent リスナーを登録し、動画キャプチャを開始する。

start() 関数を呼び出すと、RecorderRecording オブジェクトを返します。アプリは、この Recording オブジェクトを使用して、キャプチャの終了や、一時停止、再開などの他のアクションを実行できます。

Recorder は、一度に 1 つの Recording オブジェクトをサポートします。前の Recording オブジェクトで Recording.stop() または Recording.close() を呼び出すと、新しい録画を開始できます。

これらのステップを詳しく説明します。まず、アプリで Recorder.prepareRecording() を使用してレコーダーの OutputOptions を設定します。Recorder では、次の種類の OutputOptions がサポートされています。

  • FileDescriptorOutputOptions: FileDescriptor にキャプチャするために使用します。
  • FileOutputOptions: File にキャプチャするために使用します。
  • MediaStoreOutputOptions: MediaStore にキャプチャするために使用します。

どのタイプの OutputOptions でも、setFileSizeLimit() を使用して最大ファイルサイズを設定できます。その他のオプションは、個々の出力タイプに応じて異なります。たとえば、ParcelFileDescriptorFileDescriptorOutputOptions に固有です。

prepareRecording()PendingRecording オブジェクトを返します。これは、対応する Recording オブジェクトの作成に使われる中間オブジェクトです。PendingRecording は、多くの場合は表示されない一時的なクラスであり、アプリでキャッシュに保存されることはほとんどありません。

アプリでは、次のように録画をさらに設定できます。

  • withAudioEnabled() で音声を有効にする。
  • start(Executor, Consumer<VideoRecordEvent>) を使用して、録画イベントを受信するリスナーを登録する。
  • PendingRecording.asPersistentRecording() を使用して、アタッチされている VideoCapture が別のカメラに再バインドされている状態でも、引き続き録画できるようにする。

録画を開始するには、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 という 3 つのオプションがあります。カメラのプレビューに合わせるため、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 について詳しくは、以下の参考情報をご確認ください。