0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Compose でカメラ映像を表示する - CameraXViewfinder

Posted at

はじめに

CameraXViewfinder

CameraX を Compose で使う場合、従来は PreviewView を AndroidView でラップする必要がありました。
が、CameraX 1.5.0-alpha から CameraXViewfinder という専用の Composable が生えていて
カメラプレビューを表示できるようになった様子。

CameraXViewfinder

@Composable
fun CameraXViewfinder(
    surfaceRequest: SurfaceRequest,
    modifier: Modifier = Modifier,
    implementationMode: ImplementationMode = ImplementationMode.EXTERNAL,
    coordinateTransformer: MutableCoordinateTransformer? = null
)
引数 説明
surfaceRequest Preview が発行する SurfaceRequest(カメラ映像の描画リクエスト)
implementationMode EXTERNAL(低遅延) or EMBEDDED(互換性重視)
coordinateTransformer タップ座標をカメラの座標系に変換するオブジェクト

コード

class CameraViewModel(context: Context) : ViewModel() {
    private val _surfaceRequests = MutableStateFlow<SurfaceRequest?>(null)
    val surfaceRequests: StateFlow<SurfaceRequest?> get() = _surfaceRequests.asStateFlow()

    val cameraProviderFuture = ProcessCameraProvider.getInstance(context)

    init {
        cameraProviderFuture.addListener({
            val cameraProvider = cameraProviderFuture.get()
            val preview = Preview.Builder().build().also {
                it.setSurfaceProvider { newSurfaceRequest ->
                    _surfaceRequests.value = newSurfaceRequest
                }
            }

            val imageCapture = ImageCapture.Builder().build()
            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            try {
                cameraProvider.unbindAll()
                cameraProvider.bindToLifecycle(
                    ProcessLifecycleOwner.get(),
                    cameraSelector,
                    preview,
                    imageCapture
                )
            } catch (exc: Exception) {
                exc.printStackTrace()
            }
        }, ContextCompat.getMainExecutor(context))
    }
}
@Composable
fun MyCameraScreen(viewModel: CameraViewModel) {
    val currentSurfaceRequest: SurfaceRequest? by viewModel.surfaceRequests.collectAsState()
    val coordinateTransformer = remember { MutableCoordinateTransformer() }

    currentSurfaceRequest?.let { surfaceRequest ->
        CameraXViewfinder(
            surfaceRequest = surfaceRequest,
            implementationMode = ImplementationMode.EXTERNAL,
            modifier = Modifier.pointerInput(Unit) {
                detectTapGestures {
                    with(coordinateTransformer) {
                        val surfaceCoords = it.transform()
                        println("Tap at (${surfaceCoords.x}, ${surfaceCoords.y})")
                    }
                }
            },
            coordinateTransformer = coordinateTransformer
        )
    }
}

カメラ画像を画面に表示する ってことをやっていて

  • Preview を用意
  • CameraProvider にバインド
  • Previewから発行されるSurfaceRequest を Composableに食わせる

という手順で描画が可能。

libs は こちら

androidx-camera-core = { group = "androidx.camera", name = "camera-core", version.ref = "camerax" }
androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "camerax" }
androidx-camera-compose = { group = "androidx.camera", name = "camera-compose", version.ref = "camerax" }
androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camerax" }

今回書いたコードだと、再コンポーズ で CameraXViewfinder 自体の 表示 非表示切り替えたりすると描画がうまくいかないことがある。
適切なlifeCycle の管理が必要そう。

SurfaceRequestとは

やや独自解釈が入ってるかもですが、Previewの仕組みとして

  • 描画側 : CameraDevice(Camera2 API)
  • 映像の受け皿 : Surface(SurfaceTexture, SurfaceView など)
  • 表示処理: PreviewView / CameraXViewfinder

と役割が分かれていて、以下流れがある。

  • CameraDevice がSurface にカメラ映像を書き込む
  • Surface は描画された映像を保持し、映像をPreviewViewやCameraXViewfinder に受け渡す
  • CameraXViewfinder や PreviewView が Surface を受け取り、画面に表示する

Previewユースケースは解像度などの表示要素の設定値を持っていて、自身の設定値からSurfaceを描画する情報をまとめて 描画のRequestを発行する。 これがSurfaceRequest。
Preview と CameraXViewfinderの橋渡し的オブジェクト。

implementationModeの違い

Mode 内部の仕組み メリット デメリット
EXTERNAL SurfaceView を使用 低遅延 & 省電力 一部の端末で動作が不安定になる可能性
EMBEDDED TextureView を使用 端末互換性が高い 若干の遅延が発生することがある

特に理由がなければ EXTERNAL でよいはず。

内部実装的には 以下となっていて

when (implementationMode) {
    ImplementationMode.EXTERNAL -> {
        AndroidExternalSurface(modifier = surfaceModifier, onInit = onInit)
    }
    ImplementationMode.EMBEDDED -> {
        val displayRotationDegrees =
            key(LocalConfiguration.current) {
                surfaceRotationToRotationDegrees(LocalView.current.display.rotation)
            }

        // For TextureView, correct the orientation to match the display rotation.
        val correctionMatrix = remember { Matrix() }

        transformationInfo.let {
            correctionMatrix.setFrom(
                SurfaceTransformationUtil.getTextureViewCorrectionMatrix(
                    displayRotationDegrees,
                    resolution
                )
            )
        }

        AndroidEmbeddedExternalSurface(
            modifier = surfaceModifier,
            transform = correctionMatrix,
            onInit = onInit
        )
    }
}

それぞれ SurfaceView と TextureView を呼び出している。
EXTERNALEMBEDDED の違いはほぼ このViewの違いと言って良さそう。
蛇足で、たしかCameraController (CameraProvider の簡易版のようなもの)は TextureView だったはず。

終わり

アーティファクト構成が少しややこしいのですが
この記事では androidx.camera:camera-*:1.5.0-alpha* のアーティファクトに入っているトップレベルの viewfinder の Composable を使ってます。
コレとは別にandroidx.camera:camera-viewfinder-compose:1.0.0-alpha*があるのですがこれは CameraXViewfinderが参照している ViewFinder などが入ったComponentを空間になります。
一応最新のメンテ状況は androidx.camera:camera-:1.5.0-alpha の方が更新がされている様子です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?