1 /* 2 * Copyright 2022 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.camera2.pipe 18 19 import android.view.Surface 20 import androidx.annotation.GuardedBy 21 import androidx.annotation.RestrictTo 22 import androidx.camera.camera2.pipe.CameraSurfaceManager.SurfaceListener 23 import androidx.camera.camera2.pipe.CameraSurfaceManager.SurfaceToken 24 import androidx.camera.camera2.pipe.core.Log 25 import kotlinx.atomicfu.atomic 26 27 /** 28 * CameraSurfaceManager is a utility class that manages the lifetime of [Surface]s being used in 29 * CameraPipe. It is a singleton, and has the same lifetime of CameraPipe, meaning that it manages 30 * the lifetime of [Surface]s across multiple [CameraGraph] instances. 31 * 32 * Users of CameraSurfaceManager would register a [SurfaceListener] to receive updates when a 33 * [Surface] is in use ("active") or no longer in use ("inactive") at CameraPipe. 34 * 35 * These callbacks are managed by acquiring and closing "usage tokens" for a given Surface. 36 * acquiring the first token causes [SurfaceListener.onSurfaceActive] to be invoked on all 37 * registered listeners. When a surface is no longer being used, the [SurfaceToken] must be closed. 38 * After all outstanding tokens have been closed, the listeners receive 39 * SurfaceListener.onSurfaceInactive] for that surface. 40 * 41 * If the same [Surface] is used in a subsequent [CameraGraph], it will be issued a different token. 42 * Essentially each token means a single use on a [Surface]. 43 */ 44 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 45 public class CameraSurfaceManager { 46 47 private val lock = Any() 48 49 @GuardedBy("lock") private val useCountMap: MutableMap<Surface, Int> = mutableMapOf() 50 51 @GuardedBy("lock") private val listeners: MutableSet<SurfaceListener> = mutableSetOf() 52 53 /** 54 * A new [SurfaceToken] is issued when a [Surface] is registered in CameraSurfaceManager. When 55 * all [SurfaceToken]s issued for a [Surface] is closed, the [Surface] is considered "inactive". 56 */ 57 public inner class SurfaceToken(internal val surface: Surface) : AutoCloseable { 58 private val debugId = surfaceTokenDebugIds.incrementAndGet() 59 private val closed = atomic(false) 60 closenull61 override fun close() { 62 if (closed.compareAndSet(expect = false, update = true)) { 63 Log.debug { "SurfaceToken $this closed" } 64 onTokenClosed(this) 65 } 66 } 67 toStringnull68 override fun toString(): String = "SurfaceToken-$debugId" 69 } 70 71 public interface SurfaceListener { 72 /** 73 * Called when a [Surface] is in use by a [CameraGraph]. Calling [CameraGraph.setSurface] 74 * will cause [onSurfaceActive] to be called on any currently registered listener. The 75 * surface will remain active until the [CameraGraph] is closed AND any underlying usage has 76 * been released (Normally this means that it will remain in use until the camera device is 77 * closed, or until the CaptureSession that uses it is replaced). 78 */ 79 public fun onSurfaceActive(surface: Surface) 80 81 /** 82 * Called when a [Surface] is considered "inactive" and no longer in use by [CameraGraph]. 83 * This can happen under a few different scenarios: 84 * 1. A [Surface] is unset or replaced in a [CameraGraph]. 85 * 2. A CaptureSession is closed or fails to configure and the [CameraGraph] has been 86 * closed. 87 * 3. [CameraGraph] is closed, and the [Surface] isn't not in use by some other camera 88 * subsystem. 89 */ 90 public fun onSurfaceInactive(surface: Surface) 91 } 92 93 /** 94 * Adds a [SurfaceListener] to receive [Surface] lifetime updates. When a listener is added, it 95 * will receive [SurfaceListener.onSurfaceActive] for all active Surfaces. 96 */ addListenernull97 public fun addListener(listener: SurfaceListener) { 98 val activeSurfaces = 99 synchronized(lock) { 100 listeners.add(listener) 101 useCountMap.filter { it.value > 0 }.keys 102 } 103 104 activeSurfaces.forEach { listener.onSurfaceActive(it) } 105 } 106 107 /** Removes a [SurfaceListener] to stop receiving [Surface] lifetime updates. */ removeListenernull108 public fun removeListener(listener: SurfaceListener) { 109 synchronized(lock) { listeners.remove(listener) } 110 } 111 registerSurfacenull112 internal fun registerSurface(surface: Surface): AutoCloseable { 113 if (!surface.isValid) { 114 Log.warn { "registerSurface: Surface $surface isn't valid!" } 115 } 116 val surfaceToken: SurfaceToken 117 var listenersToInvoke: List<SurfaceListener>? = null 118 119 synchronized(lock) { 120 surfaceToken = SurfaceToken(surface) 121 val newUseCount = (useCountMap[surface] ?: 0) + 1 122 useCountMap[surface] = newUseCount 123 if (DEBUG) { 124 Log.debug { 125 "registerSurface: surface=$surface, " + 126 "surfaceToken=$surfaceToken, newUseCount=$newUseCount" + 127 (if (DEBUG) " from ${Log.readStackTrace()}" else "") 128 } 129 } 130 131 if (newUseCount == 1) { 132 Log.debug { "$surface for $surfaceToken is active" } 133 listenersToInvoke = listeners.toList() 134 } 135 } 136 137 listenersToInvoke?.forEach { it.onSurfaceActive(surface) } 138 return surfaceToken 139 } 140 onTokenClosednull141 internal fun onTokenClosed(surfaceToken: SurfaceToken) { 142 val surface: Surface 143 var listenersToInvoke: List<SurfaceListener>? = null 144 145 synchronized(lock) { 146 surface = surfaceToken.surface 147 val useCount = useCountMap[surface] 148 checkNotNull(useCount) { "Surface $surface ($surfaceToken) has no use count" } 149 val newUseCount = useCount - 1 150 useCountMap[surface] = newUseCount 151 152 if (DEBUG) { 153 Log.debug { 154 "onTokenClosed: surface=$surface, " + 155 "surfaceToken=$surfaceToken, newUseCount=$newUseCount" + 156 (if (DEBUG) " from ${Log.readStackTrace()}" else "") 157 } 158 } 159 if (newUseCount == 0) { 160 Log.debug { "$surface for $surfaceToken is inactive" } 161 listenersToInvoke = listeners.toList() 162 useCountMap.remove(surface) 163 } 164 } 165 166 listenersToInvoke?.forEach { it.onSurfaceInactive(surface) } 167 } 168 169 public companion object { 170 public const val DEBUG: Boolean = false 171 172 internal val surfaceTokenDebugIds = atomic(0) 173 } 174 } 175