Open7
AndroidのCameraについて調べる

最近Compose対応が入ったという話(2025/02/17現在1.5.0-alpha05)
公式サンプルアプリ

PreviewViewをComposeで使うときはAndroidViewでラップしてなんやかんやが必要だったけど、ViewModelでSurfaceRequestを管理してCameraXViewFinderに渡すだけでよくなった。
class CameraPreviewViewModel : ViewModel() {
// Used to set up a link between the Camera and your UI.
private val _surfaceRequest = MutableStateFlow<SurfaceRequest?>(null)
val surfaceRequest: StateFlow<SurfaceRequest?> = _surfaceRequest
private val cameraPreviewUseCase = Preview.Builder().build().apply {
setSurfaceProvider { newSurfaceRequest ->
_surfaceRequest.update { newSurfaceRequest }
}
}
suspend fun bindToCamera(appContext: Context, lifecycleOwner: LifecycleOwner) {
val processCameraProvider = ProcessCameraProvider.awaitInstance(appContext)
processCameraProvider.bindToLifecycle(
lifecycleOwner, DEFAULT_FRONT_CAMERA, cameraPreviewUseCase
)
// Cancellation signals we're done with the camera
try { awaitCancellation() } finally { processCameraProvider.unbindAll() }
}
}
@Composable
fun CameraPreviewContent(
viewModel: CameraPreviewViewModel,
modifier: Modifier = Modifier,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current
) {
val surfaceRequest by viewModel.surfaceRequest.collectAsStateWithLifecycle()
val context = LocalContext.current
LaunchedEffect(lifecycleOwner) {
viewModel.bindToCamera(context.applicationContext, lifecycleOwner)
}
surfaceRequest?.let { request ->
CameraXViewfinder(
surfaceRequest = request,
modifier = modifier
)
}
}

カメラやMediaCodecで毎度出てくるSurfaceくん、一体何なんだという疑問がある。
一旦CameraとSurfaceの関係を調べる。

FHD60fpsのプレビューを表示しようとすると
1920 * 1080 * 3bytes * 60fps = 373,248,000
1920 * 1080 * 4bytes * 60fps = 497,664,000
実際にどれくらいメモリのアロケーションが発生してるかはプロファイラ使わないことにはわからないけどざっくり計算ですごい。
LPDDR4: 最大3.2Gbps
LPDDR5: 最大6.4Gbps
LPDDR5X: 最大8.533Gbps(9.6Gbps, 10.7Gbpsとかもある)

メモリ帯域を計測するにはどうすればいいのとGeminiに聞いたので試してみる
#include <iostream>
#include <chrono>
// メモリ帯域計測関数
double measureMemoryBandwidth(void* buffer, size_t size) {
auto start = std::chrono::high_resolution_clock::now();
// メモリ読み書き処理
for (size_t i = 0; i < size; i += 64) { // 例:64バイトずつアクセス
volatile char* p = static_cast<volatile char*>(buffer) + i;
char dummy = *p; // 読み込み
*p = dummy; // 書き込み
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
double elapsed_time = duration.count() / 1000.0; // 秒単位
double bandwidth = (size * 2) / elapsed_time; // MB/s単位 (読み書き2回分)
return bandwidth;
}
// JNIインターフェース
extern "C" JNIEXPORT double JNICALL
Java_com_example_myapp_MainActivity_measureBandwidth(JNIEnv* env, jobject thiz, jobject buffer, jlong size) {
void* nativeBuffer = env->GetDirectBufferAddress(buffer);
return measureMemoryBandwidth(nativeBuffer, size);
}
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024); // 1MBのバッファ
double bandwidth = measureBandwidth(buffer, buffer.capacity()); // ネイティブ関数呼び出し

ImageAnalysis.Analyzerで読み取ったQRコードのBoundingBoxをCameraXViewfinder上にうまいこと表示したい

Rotationが0のときだけしか一致してくれないのでどっかで回転させる必要がありそう
ログインするとコメントできます