1 /*
2  * Copyright 2025 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 
17 package androidx.camera.core.internal
18 
19 import android.media.MediaCodec
20 import android.util.Range
21 import androidx.camera.core.Logger
22 import androidx.camera.core.impl.CaptureConfig
23 import androidx.camera.core.impl.DeferrableSurface
24 import androidx.camera.core.impl.SessionConfig.OutputConfig
25 
26 /**
27  * Modifier for the FPS range for preview-only scenarios in high-speed camera sessions.
28  *
29  * This class adjusts the FPS range of a repeating capture request when only a preview surface is
30  * active in a high-speed camera session. This is done to improve device compatibility by ensuring a
31  * lower bound of 30 FPS before recording starts.
32  *
33  * High-speed sessions typically allow only one preview and one video surface. This modifier ensures
34  * that when only the preview is active, the FPS range is adjusted to [30, high-speed fps], which is
35  * guaranteed to be supported.
36  *
37  * See b/405045641 for more detail.
38  */
39 public class HighSpeedFpsModifier {
40 
41     private companion object {
42         private const val TAG = "HighSpeedFpsModifier"
43         private const val PREVIEW_ONLY_FPS_LOWER = 30
44     }
45 
46     /**
47      * Modifies the FPS range for a preview-only repeating capture request in a high-speed camera
48      * session.
49      *
50      * This method checks if the current output configuration includes a video surface and if the
51      * repeating capture request does not. If these conditions are met, and the FPS range is a fixed
52      * high-speed range (e.g., [120, 120]), it adjusts the range to [30, high-speed fps].
53      *
54      * @param outputConfigs The collection of output configurations.
55      * @param repeatingConfigBuilder The builder for the repeating capture configuration.
56      */
modifyFpsForPreviewOnlyRepeatingnull57     public fun modifyFpsForPreviewOnlyRepeating(
58         outputConfigs: Collection<OutputConfig>,
59         repeatingConfigBuilder: CaptureConfig.Builder
60     ) {
61         if (
62             outputConfigs.size == 2 &&
63                 outputConfigs.hasVideoSurface() &&
64                 !repeatingConfigBuilder.hasVideoSurface()
65         ) {
66             repeatingConfigBuilder.expectedFrameRateRange
67                 ?.takeIf { it.isHighSpeedFixedFps() }
68                 ?.let { repeatingConfigBuilder.setExpectedFrameRateRange(it.toPreviewOnlyRange()) }
69         }
70     }
71 
Rangenull72     private fun Range<Int>.isHighSpeedFixedFps(): Boolean = upper >= 120 && lower == upper
73 
74     private fun Range<Int>.toPreviewOnlyRange(): Range<Int> {
75         return Range(PREVIEW_ONLY_FPS_LOWER, upper).also {
76             Logger.d(TAG, "Modified high-speed FPS range from $this to $it")
77         }
78     }
79 
<lambda>null80     private fun Collection<OutputConfig>.hasVideoSurface(): Boolean = any {
81         it.surface.isVideoSurface()
82     }
83 
hasVideoSurfacenull84     private fun CaptureConfig.Builder.hasVideoSurface(): Boolean =
85         surfaces.any { it.isVideoSurface() }
86 
DeferrableSurfacenull87     private fun DeferrableSurface.isVideoSurface(): Boolean =
88         containerClass == MediaCodec::class.java
89 }
90