はじめに
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 を呼び出している。
EXTERNAL
と EMBEDDED
の違いはほぼ この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 の方が更新がされている様子です。