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