1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.jetpackcamera.core.camera 17 18 import android.annotation.SuppressLint 19 import android.util.Log 20 import androidx.camera.core.CompositionSettings 21 import androidx.camera.core.TorchState 22 import androidx.lifecycle.asFlow 23 import com.google.jetpackcamera.settings.model.DynamicRange 24 import com.google.jetpackcamera.settings.model.ImageOutputFormat 25 import com.google.jetpackcamera.settings.model.Stabilization 26 import kotlinx.coroutines.coroutineScope 27 import kotlinx.coroutines.flow.collectLatest 28 import kotlinx.coroutines.flow.filterNotNull 29 import kotlinx.coroutines.flow.first 30 import kotlinx.coroutines.flow.update 31 import kotlinx.coroutines.launch 32 33 private const val TAG = "ConcurrentCameraSession" 34 35 context(CameraSessionContext) 36 @SuppressLint("RestrictedApi") 37 internal suspend fun runConcurrentCameraSession( 38 sessionSettings: PerpetualSessionSettings.ConcurrentCamera, 39 useCaseMode: CameraUseCase.UseCaseMode 40 ) = coroutineScope { 41 val primaryLensFacing = sessionSettings.primaryCameraInfo.appLensFacing 42 val secondaryLensFacing = sessionSettings.secondaryCameraInfo.appLensFacing 43 Log.d( 44 TAG, 45 "Starting new concurrent camera session " + 46 "[primary: $primaryLensFacing, secondary: $secondaryLensFacing]" 47 ) 48 49 val initialTransientSettings = transientSettings 50 .filterNotNull() 51 .first() 52 53 val useCaseGroup = createUseCaseGroup( 54 cameraInfo = sessionSettings.primaryCameraInfo, 55 initialTransientSettings = initialTransientSettings, 56 stabilizePreviewMode = Stabilization.OFF, 57 stabilizeVideoMode = Stabilization.OFF, 58 aspectRatio = sessionSettings.aspectRatio, 59 targetFrameRate = TARGET_FPS_AUTO, 60 dynamicRange = DynamicRange.SDR, 61 imageFormat = ImageOutputFormat.JPEG, 62 useCaseMode = useCaseMode 63 ) 64 65 val cameraConfigs = listOf( 66 Pair( 67 sessionSettings.primaryCameraInfo.cameraSelector, 68 CompositionSettings.Builder() 69 .setAlpha(1.0f) 70 .setOffset(0.0f, 0.0f) 71 .setScale(1.0f, 1.0f) 72 .build() 73 ), 74 Pair( 75 sessionSettings.secondaryCameraInfo.cameraSelector, 76 CompositionSettings.Builder() 77 .setAlpha(1.0f) 78 .setOffset(2 / 3f - 0.1f, -2 / 3f + 0.1f) 79 .setScale(1 / 3f, 1 / 3f) 80 .build() 81 ) 82 ) 83 84 cameraProvider.runWithConcurrent(cameraConfigs, useCaseGroup) { concurrentCamera -> 85 Log.d(TAG, "Concurrent camera session started") 86 val primaryCamera = concurrentCamera.cameras.first { 87 it.cameraInfo.appLensFacing == sessionSettings.primaryCameraInfo.appLensFacing 88 } 89 90 launch { 91 processFocusMeteringEvents(primaryCamera.cameraControl) 92 } 93 94 launch { 95 processVideoControlEvents( 96 primaryCamera, 97 useCaseGroup.getVideoCapture(), 98 captureTypeSuffix = "DualCam" 99 ) 100 } 101 102 launch { 103 sessionSettings.primaryCameraInfo.torchState.asFlow().collectLatest { torchState -> 104 currentCameraState.update { old -> 105 old.copy(torchEnabled = torchState == TorchState.ON) 106 } 107 } 108 } 109 110 applyDeviceRotation(initialTransientSettings.deviceRotation, useCaseGroup) 111 processTransientSettingEvents( 112 primaryCamera, 113 useCaseGroup, 114 initialTransientSettings, 115 transientSettings 116 ) 117 } 118 } 119