HDR 動画キャプチャ

注: このページでは、Camera2 パッケージについて言及しています。アプリで Camera2 の特定の低レベル機能が必要な場合を除き、CameraX の使用をおすすめします。CameraX と Camera2 は、どちらも Android 5.0(API レベル 21)以降に対応しています。

Camera2 API はハイ ダイナミック レンジ(HDR)動画キャプチャをサポートしており、カメラを使用して HDR 動画コンテンツをプレビュー、録画できます。標準ダイナミック レンジ(SDR)と比較して、HDR はより幅広い色に対応し、輝度コンポーネントのダイナミック レンジを拡大します(現在の 100 cd/m2 から 1,000 cd/m2 まで)。これにより、動画の品質が現実の世界に近づき、色彩が豊かになり、明るい部分はより明るく、暗い部分はより暗くなります。

HDR 動画で夕日をより鮮やかに、細部まで撮影する方法をご覧ください。

SDR 画質で撮影された夕日 HDR 動画の画質で撮影された夕日
図 1. SDR(上)と HDR(下)の動画の画質の比較。

デバイスの前提条件

すべての Android デバイスが HDR 動画キャプチャをサポートしているわけではありません。アプリで HDR 動画を撮影する前に、デバイスが次の前提条件を満たしているかどうかを確認します。

  • Android 13(API レベル 33)をターゲットとしている。
  • 10 ビット以上対応のカメラセンサーが搭載されている。HDR サポートの詳細については、HDR サポートを確認するをご覧ください。

すべてのデバイスが前提条件を満たしているわけではないため、アプリで HDR 動画キャプチャを設定する際に、別のコードパスを追加できます。これにより、互換性のないデバイスではアプリが SDR にフォールバックします。また、SDR の UI オプションを追加することも検討してください。ユーザーは、動画撮影のニーズに応じて SDR と HDR を切り替えることができます。

HDR キャプチャ アーキテクチャ

次の図は、HDR キャプチャ アーキテクチャの主なコンポーネントを示しています。

HDR キャプチャのアーキテクチャ図。
図 2. HDR キャプチャのアーキテクチャ図。

カメラデバイスが HDR でフレームをキャプチャすると、Camera2 フレームワークは、処理されたカメラセンサーの出力を保存するバッファを割り当てます。また、HDR プロファイルで必要な場合は、それぞれの HDR メタデータも添付します。次に、Camera2 フレームワークは、図に示すように、CaptureRequest で参照されている出力サーフェス(ディスプレイや動画エンコーダなど)の入力済みバッファをキューに入れます。

HDR のサポートを確認する

アプリで HDR 動画をキャプチャする前に、選択した HDR プロファイルがデバイスでサポートされているかどうかを確認します。

CameraManager getCameraCharacteristics() メソッドを使用して CameraCharacteristics インスタンスを取得します。このインスタンスをクエリして、デバイスの HDR 機能を確認できます。

デバイスが HLG10 をサポートしているかどうかを確認する手順は次のとおりです。HLG10 は、デバイス メーカーが 10 ビット出力のカメラでサポートする必要があるベースライン HDR 標準です。

  1. まず、デバイスが 10 ビット プロファイル(HLG10 のビット深度)をサポートしているかどうかを確認します。

    private fun isTenBitProfileSupported(cameraId: String): Boolean {
        val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)
        val availableCapabilities = cameraCharacteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
        for (capability in availableCapabilities!!) {
            if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT) {
                return true
            }
        }
        return false
    }
    
  2. 次に、デバイスが HLG10(または別のサポートされているプロファイル)をサポートしているかどうかを確認します。

    @RequiresApi(api = 33)
    private fun isHLGSupported(cameraId: String): Boolean {
      if (isTenBitProfileSupported(cameraId)) {
        Val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)
        val availableProfiles = cameraCharacteristics
        .get(CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES)!!
        .getSupportedProfiles()
    
        // Checks for the chosen profile, in this case HLG10
        return availableProfiles.contains(DynamicRangeProfiles.HLG10)
      }
      return false;
    }
    

デバイスが HDR をサポートしている場合、isHLGSupported() は常に true を返します。詳細については、CameraCharacteristics リファレンス ドキュメントをご覧ください。

HDR キャプチャをセットアップする

デバイスが HDR をサポートしていることを確認したら、カメラから未加工の HDR 動画ストリームをキャプチャするようにアプリを設定します。setDynamicRangeProfile() を使用して、デバイスでサポートされている HDR プロファイルをストリームの OutputConfiguration に提供します。このプロファイルは、作成時に CameraCaptureSession に渡されます。サポートされている HDR プロファイルのリストをご覧ください。

次のコードサンプルでは、setupSessionDynamicRangeProfile() はまず、デバイスが Android 13 を搭載していることを確認します。次に、デバイスでサポートされている HDR プロファイルを OutputConfiguration として CameraCaptureSession を設定します。

  /**
   * Creates a [CameraCaptureSession] with a dynamic range profile.
   */
  private fun setupSessionWithDynamicRangeProfile(
      dynamicRange: Long,
      device: CameraDevice,
      targets: List<Surface>,
      handler: Handler? = null,
      stateCallback: CameraCaptureSession.StateCallback
  ): Boolean {
      if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
          val outputConfigs = mutableListOf<OutputConfiguration>()
          for (target in targets) {
              val outputConfig = OutputConfiguration(target)
              //sets the dynamic range profile, for example DynamicRangeProfiles.HLG10
              outputConfig.setDynamicRangeProfile(dynamicRange)
              outputConfigs.add(outputConfig)
          }
          device.createCaptureSessionByOutputConfigurations(
              outputConfigs, stateCallback, handler
          )
          return true
      } else {
          device.createCaptureSession(targets, stateCallback, handler)
          return false
      }
  }

カメラアプリは、カメラを初期化するときに、録画をプレビューするために繰り返し CaptureRequest を送信します。

session.setRepeatingRequest(previewRequest, null, cameraHandler)

また、動画の録画を開始するには、次のようにします。

// Start recording repeating requests, which stops the ongoing preview
//  repeating requests without having to explicitly call
//  `session.stopRepeating`
session.setRepeatingRequest(recordRequest,
        object : CameraCaptureSession.CaptureCallback() {
    override fun onCaptureCompleted(session: CameraCaptureSession,
            request: CaptureRequest, result: TotalCaptureResult) {
        if (currentlyRecording) {
            encoder.frameAvailable()
        }
    }
}, cameraHandler)

HDR カメラ ストリームをエンコードする

HDR カメラ ストリームをエンコードしてファイルをディスクに書き込むには、MediaCodec を使用します。

まず、未加工の動画データを格納するバッファにマッピングされる出力 Surface を取得します。MediaCodec には createInputSurface() を使用します。

MediaCodec を初期化するには、アプリは、指定されたコーデック プロファイル、色空間、色範囲、転送関数を使用して MediaFormat を作成する必要があります。

val mimeType = when {
    dynamicRange == DynamicRangeProfiles.STANDARD -> MediaFormat.MIMETYPE_VIDEO_AVC
    dynamicRange < DynamicRangeProfiles.PUBLIC_MAX ->
            MediaFormat.MIMETYPE_VIDEO_HEVC
    else -> throw IllegalArgumentException("Unknown dynamic range format")
}

val codecProfile = when {
    dynamicRange == DynamicRangeProfiles.HLG10 ->
            MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10
    dynamicRange == DynamicRangeProfiles.HDR10 ->
            MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10
    dynamicRange == DynamicRangeProfiles.HDR10_PLUS ->
            MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10Plus
    else -> -1
}
// Failing to correctly set color transfer causes quality issues
// for example, washout and color clipping
val transferFunction = when (codecProfile) {
    MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10 ->
            MediaFormat.COLOR_TRANSFER_HLG
    MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10 ->
            MediaFormat.COLOR_TRANSFER_ST2084
    MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10Plus ->
            MediaFormat.COLOR_TRANSFER_ST2084
    else -> MediaFormat.COLOR_TRANSFER_SDR_VIDEO
}

val format = MediaFormat.createVideoFormat(mimeType, width, height)

// Set some properties.  Failing to specify some of these can cause the MediaCodec
// configure() call to throw an exception.
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
        MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate)
format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate)
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL)

if (codecProfile != -1) {
    format.setInteger(MediaFormat.KEY_PROFILE, codecProfile)
    format.setInteger(MediaFormat.KEY_COLOR_STANDARD,
            MediaFormat.COLOR_STANDARD_BT2020)
    format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED)
    format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, transferFunction)
    format.setFeatureEnabled(MediaCodecInfo.CodecCapabilities.FEATURE_HdrEditing,
            true)
}

mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)

実装の詳細については、Camera2Video サンプルアプリの EncoderWrapper.kt をご覧ください。

HDR 形式

Android 13 以降、10 ビット出力機能を備えたカメラデバイスは、HDR キャプチャと再生で HLG10 をサポートする必要があります。さらに、デバイス メーカーは、HDR キャプチャ アーキテクチャを使用して、任意の HDR 形式を有効にできます。

次の表に、利用可能な HDR 形式と、HDR 動画撮影におけるその機能を示します。

形式 伝達関数(TF) メタデータ コーデック ビット深度
HLG10 HLG いいえ HEVC 10 ビット
HDR10 PQ 静的 HEVC 10 ビット
HDR10+ PQ 動的 HEVC 10 ビット
ドルビー ビジョン 8.4 HLG 動的 HEVC 10 ビット

リソース

HDR 動画撮影機能を備えたアプリの実例については、GitHub の Camera2Video サンプルをご覧ください。