Committed by
GitHub
Fixes for video processing and examples (#495)
* Add utility class NoDropVideoProcessor to force video processing while not connected * Fix local video tracks not rendering processed frames
正在显示
7 个修改的文件
包含
108 行增加
和
7 行删除
.changeset/clean-ties-whisper.md
0 → 100644
.changeset/lucky-lamps-sneeze.md
0 → 100644
| @@ -31,6 +31,7 @@ import com.google.mlkit.vision.common.InputImage | @@ -31,6 +31,7 @@ import com.google.mlkit.vision.common.InputImage | ||
| 31 | import com.google.mlkit.vision.segmentation.Segmentation | 31 | import com.google.mlkit.vision.segmentation.Segmentation |
| 32 | import com.google.mlkit.vision.segmentation.Segmenter | 32 | import com.google.mlkit.vision.segmentation.Segmenter |
| 33 | import com.google.mlkit.vision.segmentation.selfie.SelfieSegmenterOptions | 33 | import com.google.mlkit.vision.segmentation.selfie.SelfieSegmenterOptions |
| 34 | +import io.livekit.android.room.track.video.NoDropVideoProcessor | ||
| 34 | import kotlinx.coroutines.CoroutineDispatcher | 35 | import kotlinx.coroutines.CoroutineDispatcher |
| 35 | import kotlinx.coroutines.CoroutineScope | 36 | import kotlinx.coroutines.CoroutineScope |
| 36 | import kotlinx.coroutines.cancel | 37 | import kotlinx.coroutines.cancel |
| @@ -41,13 +42,12 @@ import kotlinx.coroutines.sync.Mutex | @@ -41,13 +42,12 @@ import kotlinx.coroutines.sync.Mutex | ||
| 41 | import livekit.org.webrtc.EglBase | 42 | import livekit.org.webrtc.EglBase |
| 42 | import livekit.org.webrtc.SurfaceTextureHelper | 43 | import livekit.org.webrtc.SurfaceTextureHelper |
| 43 | import livekit.org.webrtc.VideoFrame | 44 | import livekit.org.webrtc.VideoFrame |
| 44 | -import livekit.org.webrtc.VideoProcessor | ||
| 45 | import livekit.org.webrtc.VideoSink | 45 | import livekit.org.webrtc.VideoSink |
| 46 | import livekit.org.webrtc.YuvHelper | 46 | import livekit.org.webrtc.YuvHelper |
| 47 | import java.io.ByteArrayOutputStream | 47 | import java.io.ByteArrayOutputStream |
| 48 | import java.nio.ByteBuffer | 48 | import java.nio.ByteBuffer |
| 49 | 49 | ||
| 50 | -class SelfieBitmapVideoProcessor(eglBase: EglBase, dispatcher: CoroutineDispatcher) : VideoProcessor { | 50 | +class SelfieBitmapVideoProcessor(eglBase: EglBase, dispatcher: CoroutineDispatcher) : NoDropVideoProcessor() { |
| 51 | 51 | ||
| 52 | private var targetSink: VideoSink? = null | 52 | private var targetSink: VideoSink? = null |
| 53 | private val segmenter: Segmenter | 53 | private val segmenter: Segmenter |
| @@ -138,6 +138,7 @@ class SelfieBitmapVideoProcessor(eglBase: EglBase, dispatcher: CoroutineDispatch | @@ -138,6 +138,7 @@ class SelfieBitmapVideoProcessor(eglBase: EglBase, dispatcher: CoroutineDispatch | ||
| 138 | frameBuffer.release() | 138 | frameBuffer.release() |
| 139 | frame.release() | 139 | frame.release() |
| 140 | 140 | ||
| 141 | + // Ready for segementation processing. | ||
| 141 | val inputImage = InputImage.fromBitmap(bitmap, 0) | 142 | val inputImage = InputImage.fromBitmap(bitmap, 0) |
| 142 | val task = segmenter.process(inputImage) | 143 | val task = segmenter.process(inputImage) |
| 143 | 144 | ||
| @@ -156,6 +157,7 @@ class SelfieBitmapVideoProcessor(eglBase: EglBase, dispatcher: CoroutineDispatch | @@ -156,6 +157,7 @@ class SelfieBitmapVideoProcessor(eglBase: EglBase, dispatcher: CoroutineDispatch | ||
| 156 | } | 157 | } |
| 157 | } | 158 | } |
| 158 | 159 | ||
| 160 | + // Prepare for creating the processed video frame. | ||
| 159 | if (lastRotation != rotationDegrees) { | 161 | if (lastRotation != rotationDegrees) { |
| 160 | surfaceTextureHelper?.setFrameRotation(rotationDegrees) | 162 | surfaceTextureHelper?.setFrameRotation(rotationDegrees) |
| 161 | lastRotation = rotationDegrees | 163 | lastRotation = rotationDegrees |
| @@ -175,6 +177,7 @@ class SelfieBitmapVideoProcessor(eglBase: EglBase, dispatcher: CoroutineDispatch | @@ -175,6 +177,7 @@ class SelfieBitmapVideoProcessor(eglBase: EglBase, dispatcher: CoroutineDispatch | ||
| 175 | } | 177 | } |
| 176 | 178 | ||
| 177 | if (canvas != null) { | 179 | if (canvas != null) { |
| 180 | + // Create the video frame. | ||
| 178 | canvas.drawBitmap(bitmap, Matrix(), Paint()) | 181 | canvas.drawBitmap(bitmap, Matrix(), Paint()) |
| 179 | surface.unlockCanvasAndPost(canvas) | 182 | surface.unlockCanvasAndPost(canvas) |
| 180 | } | 183 | } |
| @@ -21,17 +21,17 @@ import com.google.mlkit.vision.common.InputImage | @@ -21,17 +21,17 @@ import com.google.mlkit.vision.common.InputImage | ||
| 21 | import com.google.mlkit.vision.segmentation.Segmentation | 21 | import com.google.mlkit.vision.segmentation.Segmentation |
| 22 | import com.google.mlkit.vision.segmentation.Segmenter | 22 | import com.google.mlkit.vision.segmentation.Segmenter |
| 23 | import com.google.mlkit.vision.segmentation.selfie.SelfieSegmenterOptions | 23 | import com.google.mlkit.vision.segmentation.selfie.SelfieSegmenterOptions |
| 24 | +import io.livekit.android.room.track.video.NoDropVideoProcessor | ||
| 24 | import kotlinx.coroutines.CoroutineDispatcher | 25 | import kotlinx.coroutines.CoroutineDispatcher |
| 25 | import kotlinx.coroutines.CoroutineScope | 26 | import kotlinx.coroutines.CoroutineScope |
| 26 | import kotlinx.coroutines.channels.BufferOverflow | 27 | import kotlinx.coroutines.channels.BufferOverflow |
| 27 | import kotlinx.coroutines.flow.MutableSharedFlow | 28 | import kotlinx.coroutines.flow.MutableSharedFlow |
| 28 | import kotlinx.coroutines.launch | 29 | import kotlinx.coroutines.launch |
| 29 | import livekit.org.webrtc.VideoFrame | 30 | import livekit.org.webrtc.VideoFrame |
| 30 | -import livekit.org.webrtc.VideoProcessor | ||
| 31 | import livekit.org.webrtc.VideoSink | 31 | import livekit.org.webrtc.VideoSink |
| 32 | import java.nio.ByteBuffer | 32 | import java.nio.ByteBuffer |
| 33 | 33 | ||
| 34 | -class SelfieVideoProcessor(dispatcher: CoroutineDispatcher) : VideoProcessor { | 34 | +class SelfieVideoProcessor(dispatcher: CoroutineDispatcher) : NoDropVideoProcessor() { |
| 35 | 35 | ||
| 36 | private var targetSink: VideoSink? = null | 36 | private var targetSink: VideoSink? = null |
| 37 | private val segmenter: Segmenter | 37 | private val segmenter: Segmenter |
| 1 | +/* | ||
| 2 | + * Copyright 2024 LiveKit, Inc. | ||
| 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 io.livekit.android.annotations | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + * The target marked with this annotation is sensitive to the internal | ||
| 21 | + * code of WebRTC, and should be directly retested whenever WebRTC version | ||
| 22 | + * is upgraded. | ||
| 23 | + */ | ||
| 24 | +@Retention(AnnotationRetention.SOURCE) | ||
| 25 | +annotation class WebRTCSensitive |
| @@ -476,13 +476,20 @@ constructor( | @@ -476,13 +476,20 @@ constructor( | ||
| 476 | source.setVideoProcessor(videoProcessor) | 476 | source.setVideoProcessor(videoProcessor) |
| 477 | 477 | ||
| 478 | val surfaceTextureHelper = SurfaceTextureHelper.create("VideoCaptureThread", rootEglBase.eglBaseContext) | 478 | val surfaceTextureHelper = SurfaceTextureHelper.create("VideoCaptureThread", rootEglBase.eglBaseContext) |
| 479 | - val dispatchObserver = CaptureDispatchObserver() | ||
| 480 | - dispatchObserver.registerObserver(source.capturerObserver) | 479 | + |
| 480 | + // Dispatch raw frames to local renderer only if not using a VideoProcessor. | ||
| 481 | + val dispatchObserver = if (videoProcessor == null) { | ||
| 482 | + CaptureDispatchObserver().apply { | ||
| 483 | + registerObserver(source.capturerObserver) | ||
| 484 | + } | ||
| 485 | + } else { | ||
| 486 | + null | ||
| 487 | + } | ||
| 481 | 488 | ||
| 482 | capturer.initialize( | 489 | capturer.initialize( |
| 483 | surfaceTextureHelper, | 490 | surfaceTextureHelper, |
| 484 | context, | 491 | context, |
| 485 | - dispatchObserver, | 492 | + dispatchObserver ?: source.capturerObserver, |
| 486 | ) | 493 | ) |
| 487 | val rtcTrack = peerConnectionFactory.createVideoTrack(UUID.randomUUID().toString(), source) | 494 | val rtcTrack = peerConnectionFactory.createVideoTrack(UUID.randomUUID().toString(), source) |
| 488 | 495 |
livekit-android-sdk/src/main/java/io/livekit/android/room/track/video/NoDropVideoProcessor.kt
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright 2024 LiveKit, Inc. | ||
| 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 io.livekit.android.room.track.video | ||
| 18 | + | ||
| 19 | +import io.livekit.android.annotations.WebRTCSensitive | ||
| 20 | +import livekit.org.webrtc.VideoFrame | ||
| 21 | +import livekit.org.webrtc.VideoProcessor | ||
| 22 | + | ||
| 23 | +/** | ||
| 24 | + * When not connected to a room, the base [VideoProcessor] implementation will refuse | ||
| 25 | + * to process frames as they will all be dropped (i.e. not sent). | ||
| 26 | + * | ||
| 27 | + * This implementation by default forces all frames to be processed regardless of publish status. | ||
| 28 | + * | ||
| 29 | + * Change [allowDropping] to true if you want to allow dropping of frames. | ||
| 30 | + */ | ||
| 31 | +abstract class NoDropVideoProcessor : VideoProcessor { | ||
| 32 | + /** | ||
| 33 | + * If set to false, forces all frames to be processed regardless of publish status. | ||
| 34 | + * If set to true, frames will only be processed when the associated video track is published. | ||
| 35 | + * | ||
| 36 | + * By default, set to false. | ||
| 37 | + */ | ||
| 38 | + @Suppress("MemberVisibilityCanBePrivate") | ||
| 39 | + var allowDropping = false | ||
| 40 | + | ||
| 41 | + @WebRTCSensitive | ||
| 42 | + override fun onFrameCaptured(frame: VideoFrame, parameters: VideoProcessor.FrameAdaptationParameters) { | ||
| 43 | + if (allowDropping) { | ||
| 44 | + super.onFrameCaptured(frame, parameters) | ||
| 45 | + } else { | ||
| 46 | + // Altered from VideoProcessor | ||
| 47 | + val adaptedFrame = VideoProcessor.applyFrameAdaptationParameters(frame, parameters) | ||
| 48 | + if (adaptedFrame != null) { | ||
| 49 | + this.onFrameCaptured(adaptedFrame) | ||
| 50 | + adaptedFrame.release() | ||
| 51 | + } else { | ||
| 52 | + this.onFrameCaptured(frame) | ||
| 53 | + } | ||
| 54 | + } | ||
| 55 | + } | ||
| 56 | +} |
-
请 注册 或 登录 后发表评论