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

CameraX における動画キャプチャのソリューションは、VideoCapture のユースケースです。
VideoCapture のユースケースを処理する仕組みを示す概念図。図 2 に示すように、CameraX 動画キャプチャには高度なアーキテクチャのコンポーネントがいくつか含まれています。
- 動画のソース用の
SurfaceProvider。 - 音声のソース用の
AudioSource。 - 動画 / 音声をエンコードして圧縮する 2 つのエンコーダ。
- 2 つのストリームを多重化するメディア マルチプレクサ。
- 結果を書き出すファイル セーバー。
VideoCapture API は複雑なキャプチャ エンジンを抽象化し、はるかにシンプルで簡単な API をアプリに提供します。
VideoCapture API の概要
VideoCapture は CameraX のユースケースであり、単独で、または他のユースケースと組み合わせて使用できます。具体的にサポートされる組み合わせはカメラのハードウェア機能によって異なりますが、Preview と VideoCapture はどのデバイスでも利用できるユースケースの組み合わせです。
VideoCapture API は、アプリと通信する次のオブジェクトで構成されています。
VideoCaptureは最上位のユースケース クラスです。VideoCaptureは、CameraSelectorと他の CameraX ユースケースを使ってLifecycleOwnerにバインドします。これらのコンセプトと使用法について詳しくは、CameraX のアーキテクチャをご覧ください。Recorderは、VideoCaptureと密結合された VideoOutput の実装です。Recorderは、動画と音声のキャプチャを実行するために使用されます。アプリはRecorderを通じて録画を作成します。PendingRecordingは録画を設定し、音声の有効化やイベント リスナーの設定などを行えるようにします。PendingRecordingを作成するには、Recorderを使用する必要があります。PendingRecordingは何も記録しません。Recordingが実際の録画を実行します。Recordingを作成するには、PendingRecordingを使用する必要があります。
図 3 は、これらのオブジェクト間の関係を示しています。
凡例:
QualitySelectorでRecorderを作成します。- いずれかの
OutputOptionsでRecorderを構成します。 - 必要に応じて、
withAudioEnabled()を使用して音声を有効にします。 VideoRecordEventリスナーでstart()を呼び出して、録画を開始します。- 録画を管理するには、
Recordingのpause()、resume()、stop()を使用します。 - イベント リスナー内で
VideoRecordEventsに応答します。
詳細な API リストは、ソースコード内の current.txt にあります。
VideoCapture API の使用
CameraX VideoCapture のユースケースをアプリに統合するには、次の手順を行います。
VideoCaptureをバインドする。- 録画を準備、設定する。
- ランタイム録画を開始、制御する。
以降のセクションでは、エンドツーエンドの録画セッションを開始するために各ステップで実行できる操作の概要を説明します。
VideoCapture をバインドする
VideoCapture ユースケースをバインドするには、次の手順を行います。
Recorderオブジェクトを作成する。VideoCaptureオブジェクトを作成する。Lifecycleにバインドする。
CameraX VideoCapture API はビルダーの設計パターンに従います。アプリは Recorder.Builder を使用して Recorder を作成します。また、QualitySelector オブジェクトを通じて、Recorder の動画解像度を設定することもできます。
CameraX Recorder は、動画解像度用にあらかじめ定義された次の Qualities に対応しています。
Quality.UHD: 4K Ultra HD 動画サイズ(2160p)Quality.QHD: クアッド HD 動画サイズ(1440p)。CameraX1.7.0-alpha02で導入Quality.FHD: フル HD 動画サイズ(1080p)Quality.HD: HD 動画サイズ(720p)Quality.SD: SD 動画サイズ(480p)
CameraX は、アプリで許可されている場合、他の解像度を選択することもできます。
それぞれの選択に対応する正確な動画サイズは、カメラとエンコーダの機能によって異なります。詳細については、CamcorderProfile のドキュメントをご覧ください。
アプリは、QualitySelector を作成することで解像度を設定できます。QualitySelector は、次のいずれかの方法で作成できます。
fromOrderedList()を使用して優先する解像度をいくつか提供し、優先する解像度がいずれもサポートされていない場合に使用する代替案を含めます。CameraX では、選択したカメラの機能に応じて最適な代替案を決定できます。詳細については、
QualitySelectorのFallbackStrategy specificationをご覧ください。たとえば次のコードで、録画で対応している最高解像度をリクエストし、リクエストの解像度がいずれもサポートされていない場合は、CameraX が Quality.SD 解像度に最も近い解像度を選択することを許可します。val qualitySelector = QualitySelector.fromOrderedList( listOf(Quality.UHD, Quality.QHD, 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.QHD, 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.QHD -> "QHD" Quality.FHD -> "FHD" Quality.HD -> "HD" Quality.SD -> "SD" else -> throw IllegalArgumentException() } }QualitySelector.getSupportedQualities()から返される機能は、VideoCaptureのユースケースまたはVideoCaptureとPreviewのユースケースの組み合わせで必ず動作するようになっています。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 を通じて録画オブジェクトを作成して、動画と音声のキャプチャを実行できます。アプリでは、次の手順で録画を作成します。
prepareRecording()でOutputOptionsを設定する。- (省略可)録音を有効にする。
start()を使用してVideoRecordEventリスナーを登録し、動画キャプチャを開始する。
start() 関数を呼び出すと、Recorder は Recording オブジェクトを返します。アプリは、この Recording オブジェクトを使用して、キャプチャの終了や、一時停止、再開などの他のアクションを実行できます。
Recorder は、一度に 1 つの Recording オブジェクトをサポートします。前の Recording オブジェクトで Recording.stop() または Recording.close() を呼び出すと、新しい録画を開始できます。
これらのステップを詳しく説明します。まず、アプリで Recorder.prepareRecording() を使用してレコーダーの OutputOptions を設定します。Recorder では、次の種類の OutputOptions がサポートされています。
FileDescriptorOutputOptions:FileDescriptorにキャプチャするために使用します。FileOutputOptions:Fileにキャプチャするために使用します。MediaStoreOutputOptions:MediaStoreにキャプチャするために使用します。
どのタイプの OutputOptions でも、setFileSizeLimit() を使用して最大ファイルサイズを設定できます。その他のオプションは、個々の出力タイプに応じて異なります。たとえば、ParcelFileDescriptor は FileDescriptorOutputOptions に固有です。
prepareRecording() は PendingRecording オブジェクトを返します。これは、対応する Recording オブジェクトの作成に使われる中間オブジェクトです。PendingRecording は、多くの場合は表示されない一時的なクラスであり、アプリでキャッシュに保存されることはほとんどありません。
アプリでは、次のように録画をさらに設定できます。
withAudioEnabled()で音声を有効にする。start(Executor, Consumer<VideoRecordEvent>)を使用して、録画イベントを受信するリスナーを登録する。PendingRecording.asPersistentRecording()を使用して、アタッチされている VideoCapture が別のカメラに再バインドされている状態でも、引き続き録画できるようにする。
録画を開始するには、PendingRecording.start() を呼び出します。CameraX は PendingRecording を Recording に変換し、録画リクエストをキューに入れて、新しく作成された 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);
ダイナミック レンジ(HDR 動画)
CameraX は、DynamicRange を使用して、HLG10、HDR10、HDR10+、Dolby Vision などの 10 ビット ハイ ダイナミック レンジ(HDR)動画の録画をサポートしています。
HDR 動画撮影を使用する手順は次のとおりです。
VideoCapabilities.getSupportedDynamicRanges()を使用して、カメラがサポートするダイナミック レンジをクエリします。- 省略可: カスタム
QualitySelectorを構成する場合は、getSupportedQualities(DynamicRange)を使用して、そのダイナミック レンジでサポートされている品質を確認します。 VideoCapture.BuilderをsetDynamicRange()で構成します。
次の例では、カメラが HLG 10 ビット HDR 録画をサポートしている場合に、HLG 10 ビット HDR 録画を有効にします。
Kotlin
val videoCapabilities = Recorder.getVideoCapabilities(cameraInfo) val supportedHdrRanges = videoCapabilities.supportedDynamicRanges .filter { it != DynamicRange.SDR } val recorder = Recorder.Builder().build() val videoCapture = VideoCapture.Builder(recorder) .apply { if (supportedHdrRanges.contains(DynamicRange.HLG_10_BIT)) { setDynamicRange(DynamicRange.HLG_10_BIT) } } .build()
Java
VideoCapabilities videoCapabilities = Recorder.getVideoCapabilities(cameraInfo); Set<DynamicRange> supportedRanges = videoCapabilities.getSupportedDynamicRanges(); Recorder recorder = new Recorder.Builder().build(); VideoCapture.Builder<Recorder> builder = new VideoCapture.Builder<>(recorder); if (supportedRanges.contains(DynamicRange.HLG_10_BIT)) { builder.setDynamicRange(DynamicRange.HLG_10_BIT); } VideoCapture<Recorder> videoCapture = builder.build();
動画の手ぶれ補正
CameraX は、動画撮影中の手ぶれやジッターを軽減する動画の手ぶれ補正をサポートしています。
動画の手ぶれ補正を有効にするには、次の手順を行います。
VideoCapabilities.isStabilizationSupported()を使用して、カメラデバイスが手ぶれ補正をサポートしているかどうかを確認します。setVideoStabilizationEnabled(true)を使用してVideoCapture.Builderで有効にします。
次の例では、カメラが手ぶれ補正をサポートしている場合に手ぶれ補正を有効にしています。
Kotlin
val videoCapabilities = Recorder.getVideoCapabilities(cameraInfo) val videoCapture = VideoCapture.Builder(recorder) .setVideoStabilizationEnabled(videoCapabilities.isStabilizationSupported) .build()
Java
VideoCapabilities videoCapabilities = Recorder.getVideoCapabilities(cameraInfo); VideoCapture<Recorder> videoCapture = new VideoCapture.Builder<>(recorder) .setVideoStabilizationEnabled(videoCapabilities.isStabilizationSupported()) .build();
アクティブな録画を制御する
進行中の Recording を一時停止、再開、停止するには、次の方法を使用します。
pause: 現在アクティブな録画を一時停止します。resume(): 一時停止していたアクティブな録画を再開します。stop(): 録画を終了し、関連する録画オブジェクトをフラッシュします。mute(): 現在の録画をミュートまたはミュート解除します。
録画が一時停止状態かアクティブ状態かにかかわらず、stop() を呼び出すと Recording を終了できます。
PendingRecording.start() に EventListener を登録している場合、Recording は VideoRecordEvent を使用して通信を行います。
VideoRecordEvent.EVENT_TYPE_STATUSは、現在のファイルサイズや録画された期間などの統計を記録するために使用されます。VideoRecordEvent.EVENT_TYPE_FINALIZEは結果の記録に使用され、最終ファイルの URI や関連するエラーなどの情報を含みます。
録画セッションが完了したことを示す EVENT_TYPE_FINALIZE をアプリで受信したら、OutputOptions で指定された場所からキャプチャした動画にアクセスできます。
リアルタイムの音声振幅と状態
録音中、VideoRecordEvent.Status イベントは AudioStats を介してリアルタイムの音声統計情報を提供します。
AudioStats.getAudioAmplitude()は、現在の音声のピーク振幅を0.0~1.0の範囲で返します。この値を使用して、UI メーターやオーディオ波形ビジュアライザーを駆動できます。AudioStats.getAudioState()は、音声ストリームの現在のステータス(AUDIO_STATE_ACTIVE、AUDIO_STATE_MUTED、AUDIO_STATE_SOURCE_SILENCEDなど)を示します。
次の例では、ステータス イベントから音声振幅を読み取ります。
Kotlin
when (event) { is VideoRecordEvent.Status -> { val audioStats = event.recordingStats.audioStats if (audioStats.hasAudio()) { val amplitude = audioStats.audioAmplitude updateAudioWaveform(amplitude) } } // ... }
Java
if (event instanceof VideoRecordEvent.Status) { VideoRecordEvent.Status status = (VideoRecordEvent.Status) event; AudioStats audioStats = status.getRecordingStats().getAudioStats(); if (audioStats.hasAudio()) { double amplitude = audioStats.getAudioAmplitude(); updateAudioWaveform(amplitude); } }
高速動画とスローモーション動画
CameraX は、120、240、480 fps などの高速動画撮影をサポートしています。これにより、次の 2 つのユースケースが可能になります。
- 高フレームレート動画(プレーヤー処理のスローモーション): CameraX は、高フレームレート(120 fps など)でフレームをキャプチャして保存します。動画プレーヤーは再生速度を制御し、ユーザーは通常速度とスローモーションを切り替えたり、フレーム単位の分析を行ったりできます。
- すぐに再生できるスローモーション動画: CameraX は高速ストリームを処理し、フレームのタイムスタンプを調整した標準の 30 fps 動画ファイルを出力します。スローモーション効果は「焼き付け」られるため、特別な動画プレーヤーのサポートがなくても、標準の動画プレーヤーで動画が自動的にスローモーションで再生されます。たとえば、120 fps で撮影した動画を 30 fps で再生すると、1/4 倍速のスローモーション動画になります。
高速機能を確認する
高速動画撮影には、ハードウェア サポートと、デバイス メーカーによる対応する CamcorderProfile 定義(CamcorderProfile.QUALITY_HIGH_SPEED_1080P や QUALITY_HIGH_SPEED_720P など)が必要です。
高速録画を構成する前に、Recorder.getHighSpeedVideoCapabilities(CameraInfo) を使用して高速機能をクエリします。
Kotlin
val highSpeedCapabilities = Recorder.getHighSpeedVideoCapabilities(cameraInfo) if (highSpeedCapabilities == null) { // Camera does not support high-speed video capture return } val supportedQualities = highSpeedCapabilities.getSupportedQualities(DynamicRange.SDR)
Java
VideoCapabilities highSpeedCapabilities = Recorder.getHighSpeedVideoCapabilities(cameraInfo); if (highSpeedCapabilities == null) { // Camera does not support high-speed video capture return; } List<Quality> supportedQualities = highSpeedCapabilities.getSupportedQualities(DynamicRange.SDR);
HighSpeedVideoSessionConfig を構成する
高速キャプチャを使用するには、HighSpeedVideoSessionConfig で VideoCapture と Preview のユースケースを組み合わせます。セッションを構成する手順は次のとおりです。
VideoCaptureユースケースとPreviewユースケースを作成します。VideoCaptureユースケースでHighSpeedVideoSessionConfig.Builderを初期化し、Previewをアタッチします。CameraInfo.getSupportedFrameRateRanges(SessionConfig)を使用して、サポートされているフレームレート範囲をクエリし、選択した範囲を設定します。- 高フレームレートの動画か、すぐに再生できるスローモーション動画のいずれかを選択します。
- 高フレームレート動画を作成するには、
setSlowMotionEnabled(false)(デフォルト)のままにします。 - すぐに再生できるスローモーション動画を作成するには、
setSlowMotionEnabled(true)を呼び出します。
- 高フレームレート動画を作成するには、
cameraProvider.bindToLifecycle()を使用してセッション構成をバインドします。
次の例では、再生可能なスローモーション セッションを構成してバインドしています。
Kotlin
val preview = Preview.Builder().build() val recorder = Recorder.Builder() .setQualitySelector(QualitySelector.from(supportedQualities.first())) .build() val videoCapture = VideoCapture.withOutput(recorder) // Build HighSpeedVideoSessionConfig val sessionConfigBuilder = HighSpeedVideoSessionConfig.Builder(videoCapture) .setPreview(preview) // Enable automatic slow-motion playback at 30 fps .setSlowMotionEnabled(true) // Query and set a supported high-speed frame rate range (such as 120 or 240 fps). // The set is non-empty because getHighSpeedVideoCapabilities() returned non-null earlier. val supportedFpsRanges = cameraInfo.getSupportedFrameRateRanges(sessionConfigBuilder.build()) sessionConfigBuilder.setFrameRateRange(supportedFpsRanges.first()) val sessionConfig = sessionConfigBuilder.build() // Bind session config to lifecycle cameraProvider.bindToLifecycle( lifecycleOwner, CameraSelector.DEFAULT_BACK_CAMERA, sessionConfig )
Java
Preview preview = new Preview.Builder().build(); Recorder recorder = new Recorder.Builder() .setQualitySelector(QualitySelector.from(supportedQualities.get(0))) .build(); VideoCapture<Recorder> videoCapture = VideoCapture.withOutput(recorder); HighSpeedVideoSessionConfig.Builder sessionConfigBuilder = new HighSpeedVideoSessionConfig.Builder(videoCapture) .setPreview(preview) .setSlowMotionEnabled(true); // The set is non-empty because getHighSpeedVideoCapabilities() returned non-null earlier. Set<Range<Integer>> supportedFpsRanges = cameraInfo.getSupportedFrameRateRanges(sessionConfigBuilder.build()); sessionConfigBuilder.setFrameRateRange(supportedFpsRanges.iterator().next()); HighSpeedVideoSessionConfig sessionConfig = sessionConfigBuilder.build(); cameraProvider.bindToLifecycle( lifecycleOwner, CameraSelector.DEFAULT_BACK_CAMERA, sessionConfig );
CameraX 1.7.0-alpha02 で導入された Kotlin DSL を使用して、高速セッションを簡潔に構成することもできます。
val sessionConfig = highSpeedVideoSessionConfig(videoCapture) {
this.preview = preview
isSlowMotionEnabled = true
frameRateRange = supportedFpsRanges.first()
}
cameraProvider.bindToLifecycle(
lifecycleOwner,
CameraSelector.DEFAULT_BACK_CAMERA,
sessionConfig
)
高速動画またはスローモーション動画を撮影する
HighSpeedVideoSessionConfig をバインドしたら、標準の動画録画と同じ方法で VideoCapture を使用して録画を開始します。
Kotlin
val recording = videoCapture.output .prepareRecording(context, mediaStoreOutput) .withAudioEnabled() .start(ContextCompat.getMainExecutor(context)) { event -> when (event) { is VideoRecordEvent.Start -> Log.d(TAG, "Recording started") is VideoRecordEvent.Finalize -> { if (!event.hasError()) { Log.d(TAG, "Video saved: ${event.outputResults.outputUri}") } } } }
Java
Recording recording = videoCapture.getOutput() .prepareRecording(context, mediaStoreOutput) .withAudioEnabled() .start(ContextCompat.getMainExecutor(context), event -> { if (event instanceof VideoRecordEvent.Start) { Log.d(TAG, "Recording started"); } else if (event instanceof VideoRecordEvent.Finalize) { VideoRecordEvent.Finalize finalize = (VideoRecordEvent.Finalize) event; if (!finalize.hasError()) { Log.d(TAG, "Video saved: " + finalize.getOutputResults().getOutputUri()); } } });
参考情報
CameraX について詳しくは、以下の参考情報をご確認ください。
- CameraX のスタートガイドの Codelab
- CameraX の公式サンプルアプリ
- 最新の CameraX Video Capture API リスト
- CameraX のリリースノート
- CameraX のソースコード
- CameraX 1.5 での高速撮影とスローモーション動画