Committed by
GitHub
Improved VirtualBackgroundVideoProcessor and VirtualBackgroundTransformer (#731)
* Made blur radius dynamically changeable Added lazy initialization of Segmenter Removed blurring in shader if radius less or equals 0 Configured in sample Target aspect ratio for ImageAnalysis * make blurRadius a constructor variable Also keeps the original API to avoid any changes. * Remove unneeded updateBlurRadius method * Fix compile * changeset * fix compile --------- Co-authored-by: davidliu <davidliu@deviange.net>
正在显示
7 个修改的文件
包含
82 行增加
和
19 行删除
.changeset/poor-coins-tease.md
0 → 100644
| @@ -51,6 +51,13 @@ class MainActivity : AppCompatActivity() { | @@ -51,6 +51,13 @@ class MainActivity : AppCompatActivity() { | ||
| 51 | track?.addRenderer(renderer) | 51 | track?.addRenderer(renderer) |
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | + findViewById<Button>(R.id.buttonIncreaseBlur).setOnClickListener { | ||
| 55 | + viewModel.increaseBlur() | ||
| 56 | + } | ||
| 57 | + findViewById<Button>(R.id.buttonDecreaseBlur).setOnClickListener { | ||
| 58 | + viewModel.decreaseBlur() | ||
| 59 | + } | ||
| 60 | + | ||
| 54 | requestNeededPermissions { | 61 | requestNeededPermissions { |
| 55 | viewModel.startCapture() | 62 | viewModel.startCapture() |
| 56 | } | 63 | } |
| @@ -22,6 +22,8 @@ import androidx.annotation.OptIn | @@ -22,6 +22,8 @@ import androidx.annotation.OptIn | ||
| 22 | import androidx.appcompat.content.res.AppCompatResources | 22 | import androidx.appcompat.content.res.AppCompatResources |
| 23 | import androidx.camera.camera2.interop.ExperimentalCamera2Interop | 23 | import androidx.camera.camera2.interop.ExperimentalCamera2Interop |
| 24 | import androidx.camera.core.ImageAnalysis | 24 | import androidx.camera.core.ImageAnalysis |
| 25 | +import androidx.camera.core.resolutionselector.AspectRatioStrategy | ||
| 26 | +import androidx.camera.core.resolutionselector.ResolutionSelector | ||
| 25 | import androidx.lifecycle.AndroidViewModel | 27 | import androidx.lifecycle.AndroidViewModel |
| 26 | import androidx.lifecycle.MutableLiveData | 28 | import androidx.lifecycle.MutableLiveData |
| 27 | import androidx.lifecycle.ProcessLifecycleOwner | 29 | import androidx.lifecycle.ProcessLifecycleOwner |
| @@ -52,15 +54,23 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { | @@ -52,15 +54,23 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { | ||
| 52 | eglBase = eglBase, | 54 | eglBase = eglBase, |
| 53 | ), | 55 | ), |
| 54 | ) | 56 | ) |
| 55 | - | ||
| 56 | - val processor = VirtualBackgroundVideoProcessor(eglBase, Dispatchers.IO).apply { | 57 | + private var blur = 16f |
| 58 | + private val processor = VirtualBackgroundVideoProcessor(eglBase, Dispatchers.IO, initialBlurRadius = blur).apply { | ||
| 57 | val drawable = AppCompatResources.getDrawable(application, R.drawable.background) as BitmapDrawable | 59 | val drawable = AppCompatResources.getDrawable(application, R.drawable.background) as BitmapDrawable |
| 58 | backgroundImage = drawable.bitmap | 60 | backgroundImage = drawable.bitmap |
| 59 | } | 61 | } |
| 60 | 62 | ||
| 61 | private var cameraProvider: CameraCapturerUtils.CameraProvider? = null | 63 | private var cameraProvider: CameraCapturerUtils.CameraProvider? = null |
| 62 | 64 | ||
| 63 | - private var imageAnalysis = ImageAnalysis.Builder().build() | 65 | + private var imageAnalysis = ImageAnalysis.Builder() |
| 66 | + .setResolutionSelector( | ||
| 67 | + ResolutionSelector.Builder() | ||
| 68 | + // LocalVideoTrack has default aspect ratio 16:9 VideoPreset169.H720 | ||
| 69 | + // ImageAnalysis of CameraX has default aspect ratio 4:3 | ||
| 70 | + .setAspectRatioStrategy(AspectRatioStrategy.RATIO_16_9_FALLBACK_AUTO_STRATEGY) | ||
| 71 | + .build(), | ||
| 72 | + ) | ||
| 73 | + .build() | ||
| 64 | .apply { setAnalyzer(Dispatchers.IO.asExecutor(), processor.imageAnalyzer) } | 74 | .apply { setAnalyzer(Dispatchers.IO.asExecutor(), processor.imageAnalyzer) } |
| 65 | 75 | ||
| 66 | init { | 76 | init { |
| @@ -99,4 +109,14 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { | @@ -99,4 +109,14 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { | ||
| 99 | processor.enabled = newState | 109 | processor.enabled = newState |
| 100 | return newState | 110 | return newState |
| 101 | } | 111 | } |
| 112 | + | ||
| 113 | + fun decreaseBlur() { | ||
| 114 | + blur -= 5 | ||
| 115 | + processor.updateBlurRadius(blur) | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + fun increaseBlur() { | ||
| 119 | + blur += 5 | ||
| 120 | + processor.updateBlurRadius(blur) | ||
| 121 | + } | ||
| 102 | } | 122 | } |
| @@ -17,4 +17,21 @@ | @@ -17,4 +17,21 @@ | ||
| 17 | android:layout_margin="10dp" | 17 | android:layout_margin="10dp" |
| 18 | android:text="Disable" /> | 18 | android:text="Disable" /> |
| 19 | 19 | ||
| 20 | + <Button | ||
| 21 | + android:id="@+id/buttonIncreaseBlur" | ||
| 22 | + android:layout_width="wrap_content" | ||
| 23 | + android:layout_height="wrap_content" | ||
| 24 | + android:layout_gravity="end" | ||
| 25 | + android:layout_margin="10dp" | ||
| 26 | + android:text="Blur more" /> | ||
| 27 | + | ||
| 28 | + <Button | ||
| 29 | + android:id="@+id/buttonDecreaseBlur" | ||
| 30 | + android:layout_width="wrap_content" | ||
| 31 | + android:layout_gravity="end" | ||
| 32 | + android:layout_height="wrap_content" | ||
| 33 | + android:layout_marginTop="64dp" | ||
| 34 | + android:layout_marginEnd="10dp" | ||
| 35 | + android:text="Blur less" /> | ||
| 36 | + | ||
| 20 | </FrameLayout> | 37 | </FrameLayout> |
| @@ -37,8 +37,8 @@ import java.nio.ByteBuffer | @@ -37,8 +37,8 @@ import java.nio.ByteBuffer | ||
| 37 | * Blurs the background of the camera video stream. | 37 | * Blurs the background of the camera video stream. |
| 38 | */ | 38 | */ |
| 39 | class VirtualBackgroundTransformer( | 39 | class VirtualBackgroundTransformer( |
| 40 | - val blurRadius: Float = 16f, | ||
| 41 | - val downSampleFactor: Int = 2, | 40 | + var blurRadius: Float = 16f, |
| 41 | + var downSampleFactor: Int = 2, | ||
| 42 | ) : RendererCommon.GlDrawer { | 42 | ) : RendererCommon.GlDrawer { |
| 43 | 43 | ||
| 44 | data class MaskHolder(val width: Int, val height: Int, val buffer: ByteBuffer) | 44 | data class MaskHolder(val width: Int, val height: Int, val buffer: ByteBuffer) |
| @@ -54,7 +54,7 @@ class VirtualBackgroundTransformer( | @@ -54,7 +54,7 @@ class VirtualBackgroundTransformer( | ||
| 54 | 54 | ||
| 55 | private lateinit var downSampler: ResamplerShader | 55 | private lateinit var downSampler: ResamplerShader |
| 56 | 56 | ||
| 57 | - var backgroundImageStateLock = Any() | 57 | + private var backgroundImageStateLock = Any() |
| 58 | var backgroundImage: Bitmap? = null | 58 | var backgroundImage: Bitmap? = null |
| 59 | set(value) { | 59 | set(value) { |
| 60 | if (value == field) { | 60 | if (value == field) { |
| @@ -66,7 +66,7 @@ class VirtualBackgroundTransformer( | @@ -66,7 +66,7 @@ class VirtualBackgroundTransformer( | ||
| 66 | backgroundImageNeedsUploading = true | 66 | backgroundImageNeedsUploading = true |
| 67 | } | 67 | } |
| 68 | } | 68 | } |
| 69 | - var backgroundImageNeedsUploading = false | 69 | + private var backgroundImageNeedsUploading = false |
| 70 | 70 | ||
| 71 | // For double buffering the final mask | 71 | // For double buffering the final mask |
| 72 | private var readMaskIndex = 0 // Index for renderFrame to read from | 72 | private var readMaskIndex = 0 // Index for renderFrame to read from |
| @@ -250,6 +250,7 @@ class VirtualBackgroundTransformer( | @@ -250,6 +250,7 @@ class VirtualBackgroundTransformer( | ||
| 250 | } | 250 | } |
| 251 | 251 | ||
| 252 | override fun release() { | 252 | override fun release() { |
| 253 | + if (!initialized) return | ||
| 253 | compositeShader.release() | 254 | compositeShader.release() |
| 254 | blurShader.release() | 255 | blurShader.release() |
| 255 | boxBlurShader.release() | 256 | boxBlurShader.release() |
| @@ -49,17 +49,27 @@ import java.util.concurrent.Semaphore | @@ -49,17 +49,27 @@ import java.util.concurrent.Semaphore | ||
| 49 | * By default, blurs the background of the video stream. | 49 | * By default, blurs the background of the video stream. |
| 50 | * Setting [backgroundImage] will use the provided image instead. | 50 | * Setting [backgroundImage] will use the provided image instead. |
| 51 | */ | 51 | */ |
| 52 | -class VirtualBackgroundVideoProcessor(private val eglBase: EglBase, dispatcher: CoroutineDispatcher = Dispatchers.Default) : NoDropVideoProcessor() { | 52 | +class VirtualBackgroundVideoProcessor( |
| 53 | + private val eglBase: EglBase, | ||
| 54 | + dispatcher: CoroutineDispatcher = Dispatchers.Default, | ||
| 55 | + initialBlurRadius: Float = 16f, | ||
| 56 | +) : NoDropVideoProcessor() { | ||
| 53 | 57 | ||
| 54 | private var targetSink: VideoSink? = null | 58 | private var targetSink: VideoSink? = null |
| 55 | - private val segmenter: Segmenter | 59 | + private val segmenter: Segmenter by lazy { |
| 60 | + val options = | ||
| 61 | + SelfieSegmenterOptions.Builder() | ||
| 62 | + .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE) | ||
| 63 | + .build() | ||
| 64 | + Segmentation.getClient(options) | ||
| 65 | + } | ||
| 56 | 66 | ||
| 57 | private var lastRotation = 0 | 67 | private var lastRotation = 0 |
| 58 | private var lastWidth = 0 | 68 | private var lastWidth = 0 |
| 59 | private var lastHeight = 0 | 69 | private var lastHeight = 0 |
| 60 | private val surfaceTextureHelper = SurfaceTextureHelper.create("BitmapToYUV", eglBase.eglBaseContext) | 70 | private val surfaceTextureHelper = SurfaceTextureHelper.create("BitmapToYUV", eglBase.eglBaseContext) |
| 61 | private val surface = Surface(surfaceTextureHelper.surfaceTexture) | 71 | private val surface = Surface(surfaceTextureHelper.surfaceTexture) |
| 62 | - private val backgroundTransformer = VirtualBackgroundTransformer() | 72 | + private val backgroundTransformer = VirtualBackgroundTransformer(blurRadius = initialBlurRadius) |
| 63 | private val eglRenderer = EglRenderer(VirtualBackgroundVideoProcessor::class.java.simpleName) | 73 | private val eglRenderer = EglRenderer(VirtualBackgroundVideoProcessor::class.java.simpleName) |
| 64 | .apply { | 74 | .apply { |
| 65 | init(eglBase.eglBaseContext, EglBase.CONFIG_PLAIN, backgroundTransformer) | 75 | init(eglBase.eglBaseContext, EglBase.CONFIG_PLAIN, backgroundTransformer) |
| @@ -88,12 +98,6 @@ class VirtualBackgroundVideoProcessor(private val eglBase: EglBase, dispatcher: | @@ -88,12 +98,6 @@ class VirtualBackgroundVideoProcessor(private val eglBase: EglBase, dispatcher: | ||
| 88 | private var backgroundImageNeedsUpdating = false | 98 | private var backgroundImageNeedsUpdating = false |
| 89 | 99 | ||
| 90 | init { | 100 | init { |
| 91 | - val options = | ||
| 92 | - SelfieSegmenterOptions.Builder() | ||
| 93 | - .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE) | ||
| 94 | - .build() | ||
| 95 | - segmenter = Segmentation.getClient(options) | ||
| 96 | - | ||
| 97 | // Funnel processing into a single flow that won't buffer, | 101 | // Funnel processing into a single flow that won't buffer, |
| 98 | // since processing may be slower than video capture. | 102 | // since processing may be slower than video capture. |
| 99 | scope.launch { | 103 | scope.launch { |
| @@ -167,7 +171,11 @@ class VirtualBackgroundVideoProcessor(private val eglBase: EglBase, dispatcher: | @@ -167,7 +171,11 @@ class VirtualBackgroundVideoProcessor(private val eglBase: EglBase, dispatcher: | ||
| 167 | } | 171 | } |
| 168 | } | 172 | } |
| 169 | 173 | ||
| 170 | - fun processFrame(frame: VideoFrame) { | 174 | + override fun setSink(sink: VideoSink?) { |
| 175 | + targetSink = sink | ||
| 176 | + } | ||
| 177 | + | ||
| 178 | + private fun processFrame(frame: VideoFrame) { | ||
| 171 | if (lastRotation != frame.rotation) { | 179 | if (lastRotation != frame.rotation) { |
| 172 | lastRotation = frame.rotation | 180 | lastRotation = frame.rotation |
| 173 | backgroundImageNeedsUpdating = true | 181 | backgroundImageNeedsUpdating = true |
| @@ -227,8 +235,8 @@ class VirtualBackgroundVideoProcessor(private val eglBase: EglBase, dispatcher: | @@ -227,8 +235,8 @@ class VirtualBackgroundVideoProcessor(private val eglBase: EglBase, dispatcher: | ||
| 227 | } | 235 | } |
| 228 | } | 236 | } |
| 229 | 237 | ||
| 230 | - override fun setSink(sink: VideoSink?) { | ||
| 231 | - targetSink = sink | 238 | + fun updateBlurRadius(blurRadius: Float) { |
| 239 | + backgroundTransformer.blurRadius = blurRadius | ||
| 232 | } | 240 | } |
| 233 | 241 | ||
| 234 | fun dispose() { | 242 | fun dispose() { |
| @@ -34,6 +34,11 @@ uniform float u_radius; | @@ -34,6 +34,11 @@ uniform float u_radius; | ||
| 34 | out vec4 fragColor; | 34 | out vec4 fragColor; |
| 35 | 35 | ||
| 36 | void main() { | 36 | void main() { |
| 37 | + if (u_radius <= 0.0) { | ||
| 38 | + fragColor = texture(u_texture, texCoords); | ||
| 39 | + return; | ||
| 40 | + } | ||
| 41 | + | ||
| 37 | float sigma = u_radius; | 42 | float sigma = u_radius; |
| 38 | float twoSigmaSq = 2.0 * sigma * sigma; | 43 | float twoSigmaSq = 2.0 * sigma * sigma; |
| 39 | float totalWeight = 0.0; | 44 | float totalWeight = 0.0; |
-
请 注册 或 登录 后发表评论