Skip to content

Commit c3bef08

Browse files
binkosdavidliu
andauthored
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>
1 parent f694f42 commit c3bef08

7 files changed

Lines changed: 82 additions & 19 deletions

File tree

.changeset/poor-coins-tease.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"client-sdk-android": patch
3+
---
4+
5+
Make blurRadius in the VirtualBackgroundTransformer variable to allow for dynamically changing the value.

examples/virtual-background/src/main/java/io/livekit/android/selfie/MainActivity.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ class MainActivity : AppCompatActivity() {
5151
track?.addRenderer(renderer)
5252
}
5353

54+
findViewById<Button>(R.id.buttonIncreaseBlur).setOnClickListener {
55+
viewModel.increaseBlur()
56+
}
57+
findViewById<Button>(R.id.buttonDecreaseBlur).setOnClickListener {
58+
viewModel.decreaseBlur()
59+
}
60+
5461
requestNeededPermissions {
5562
viewModel.startCapture()
5663
}

examples/virtual-background/src/main/java/io/livekit/android/selfie/MainViewModel.kt

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import androidx.annotation.OptIn
2222
import androidx.appcompat.content.res.AppCompatResources
2323
import androidx.camera.camera2.interop.ExperimentalCamera2Interop
2424
import androidx.camera.core.ImageAnalysis
25+
import androidx.camera.core.resolutionselector.AspectRatioStrategy
26+
import androidx.camera.core.resolutionselector.ResolutionSelector
2527
import androidx.lifecycle.AndroidViewModel
2628
import androidx.lifecycle.MutableLiveData
2729
import androidx.lifecycle.ProcessLifecycleOwner
@@ -52,15 +54,23 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
5254
eglBase = eglBase,
5355
),
5456
)
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 {
5759
val drawable = AppCompatResources.getDrawable(application, R.drawable.background) as BitmapDrawable
5860
backgroundImage = drawable.bitmap
5961
}
6062

6163
private var cameraProvider: CameraCapturerUtils.CameraProvider? = null
6264

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()
6474
.apply { setAnalyzer(Dispatchers.IO.asExecutor(), processor.imageAnalyzer) }
6575

6676
init {
@@ -99,4 +109,14 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
99109
processor.enabled = newState
100110
return newState
101111
}
112+
113+
fun decreaseBlur() {
114+
blur -= 5
115+
processor.updateBlurRadius(blur)
116+
}
117+
118+
fun increaseBlur() {
119+
blur += 5
120+
processor.updateBlurRadius(blur)
121+
}
102122
}

examples/virtual-background/src/main/res/layout/activity_main.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,21 @@
1717
android:layout_margin="10dp"
1818
android:text="Disable" />
1919

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+
2037
</FrameLayout>

livekit-android-track-processors/src/main/java/io/livekit/android/track/processing/video/VirtualBackgroundTransformer.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ import java.nio.ByteBuffer
3737
* Blurs the background of the camera video stream.
3838
*/
3939
class VirtualBackgroundTransformer(
40-
val blurRadius: Float = 16f,
41-
val downSampleFactor: Int = 2,
40+
var blurRadius: Float = 16f,
41+
var downSampleFactor: Int = 2,
4242
) : RendererCommon.GlDrawer {
4343

4444
data class MaskHolder(val width: Int, val height: Int, val buffer: ByteBuffer)
@@ -54,7 +54,7 @@ class VirtualBackgroundTransformer(
5454

5555
private lateinit var downSampler: ResamplerShader
5656

57-
var backgroundImageStateLock = Any()
57+
private var backgroundImageStateLock = Any()
5858
var backgroundImage: Bitmap? = null
5959
set(value) {
6060
if (value == field) {
@@ -66,7 +66,7 @@ class VirtualBackgroundTransformer(
6666
backgroundImageNeedsUploading = true
6767
}
6868
}
69-
var backgroundImageNeedsUploading = false
69+
private var backgroundImageNeedsUploading = false
7070

7171
// For double buffering the final mask
7272
private var readMaskIndex = 0 // Index for renderFrame to read from
@@ -250,6 +250,7 @@ class VirtualBackgroundTransformer(
250250
}
251251

252252
override fun release() {
253+
if (!initialized) return
253254
compositeShader.release()
254255
blurShader.release()
255256
boxBlurShader.release()

livekit-android-track-processors/src/main/java/io/livekit/android/track/processing/video/VirtualBackgroundVideoProcessor.kt

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,27 @@ import java.util.concurrent.Semaphore
4949
* By default, blurs the background of the video stream.
5050
* Setting [backgroundImage] will use the provided image instead.
5151
*/
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() {
5357

5458
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+
}
5666

5767
private var lastRotation = 0
5868
private var lastWidth = 0
5969
private var lastHeight = 0
6070
private val surfaceTextureHelper = SurfaceTextureHelper.create("BitmapToYUV", eglBase.eglBaseContext)
6171
private val surface = Surface(surfaceTextureHelper.surfaceTexture)
62-
private val backgroundTransformer = VirtualBackgroundTransformer()
72+
private val backgroundTransformer = VirtualBackgroundTransformer(blurRadius = initialBlurRadius)
6373
private val eglRenderer = EglRenderer(VirtualBackgroundVideoProcessor::class.java.simpleName)
6474
.apply {
6575
init(eglBase.eglBaseContext, EglBase.CONFIG_PLAIN, backgroundTransformer)
@@ -88,12 +98,6 @@ class VirtualBackgroundVideoProcessor(private val eglBase: EglBase, dispatcher:
8898
private var backgroundImageNeedsUpdating = false
8999

90100
init {
91-
val options =
92-
SelfieSegmenterOptions.Builder()
93-
.setDetectorMode(SelfieSegmenterOptions.STREAM_MODE)
94-
.build()
95-
segmenter = Segmentation.getClient(options)
96-
97101
// Funnel processing into a single flow that won't buffer,
98102
// since processing may be slower than video capture.
99103
scope.launch {
@@ -167,7 +171,11 @@ class VirtualBackgroundVideoProcessor(private val eglBase: EglBase, dispatcher:
167171
}
168172
}
169173

170-
fun processFrame(frame: VideoFrame) {
174+
override fun setSink(sink: VideoSink?) {
175+
targetSink = sink
176+
}
177+
178+
private fun processFrame(frame: VideoFrame) {
171179
if (lastRotation != frame.rotation) {
172180
lastRotation = frame.rotation
173181
backgroundImageNeedsUpdating = true
@@ -227,8 +235,8 @@ class VirtualBackgroundVideoProcessor(private val eglBase: EglBase, dispatcher:
227235
}
228236
}
229237

230-
override fun setSink(sink: VideoSink?) {
231-
targetSink = sink
238+
fun updateBlurRadius(blurRadius: Float) {
239+
backgroundTransformer.blurRadius = blurRadius
232240
}
233241

234242
fun dispose() {

livekit-android-track-processors/src/main/java/io/livekit/android/track/processing/video/shader/BlurShader.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ uniform float u_radius;
3434
out vec4 fragColor;
3535
3636
void main() {
37+
if (u_radius <= 0.0) {
38+
fragColor = texture(u_texture, texCoords);
39+
return;
40+
}
41+
3742
float sigma = u_radius;
3843
float twoSigmaSq = 2.0 * sigma * sigma;
3944
float totalWeight = 0.0;

0 commit comments

Comments
 (0)