Committed by
GitHub
Render video directly from capturer for local video tracks (#419)
* Render video directly from capturer for local video tracks * spotless
正在显示
3 个修改的文件
包含
123 行增加
和
36 行删除
| @@ -121,7 +121,7 @@ internal constructor( | @@ -121,7 +121,7 @@ internal constructor( | ||
| 121 | options: LocalVideoTrackOptions = videoTrackCaptureDefaults.copy(), | 121 | options: LocalVideoTrackOptions = videoTrackCaptureDefaults.copy(), |
| 122 | videoProcessor: VideoProcessor? = null, | 122 | videoProcessor: VideoProcessor? = null, |
| 123 | ): LocalVideoTrack { | 123 | ): LocalVideoTrack { |
| 124 | - return LocalVideoTrack.createTrack( | 124 | + return LocalVideoTrack.createCameraTrack( |
| 125 | peerConnectionFactory, | 125 | peerConnectionFactory, |
| 126 | context, | 126 | context, |
| 127 | name, | 127 | name, |
| @@ -31,6 +31,7 @@ import io.livekit.android.room.track.video.CameraCapturerUtils.createCameraEnume | @@ -31,6 +31,7 @@ import io.livekit.android.room.track.video.CameraCapturerUtils.createCameraEnume | ||
| 31 | import io.livekit.android.room.track.video.CameraCapturerUtils.findCamera | 31 | import io.livekit.android.room.track.video.CameraCapturerUtils.findCamera |
| 32 | import io.livekit.android.room.track.video.CameraCapturerUtils.getCameraPosition | 32 | import io.livekit.android.room.track.video.CameraCapturerUtils.getCameraPosition |
| 33 | import io.livekit.android.room.track.video.CameraCapturerWithSize | 33 | import io.livekit.android.room.track.video.CameraCapturerWithSize |
| 34 | +import io.livekit.android.room.track.video.CaptureDispatchObserver | ||
| 34 | import io.livekit.android.room.track.video.VideoCapturerWithSize | 35 | import io.livekit.android.room.track.video.VideoCapturerWithSize |
| 35 | import io.livekit.android.room.util.EncodingUtils | 36 | import io.livekit.android.room.util.EncodingUtils |
| 36 | import io.livekit.android.util.FlowObservable | 37 | import io.livekit.android.util.FlowObservable |
| @@ -50,6 +51,7 @@ import livekit.org.webrtc.RtpTransceiver | @@ -50,6 +51,7 @@ import livekit.org.webrtc.RtpTransceiver | ||
| 50 | import livekit.org.webrtc.SurfaceTextureHelper | 51 | import livekit.org.webrtc.SurfaceTextureHelper |
| 51 | import livekit.org.webrtc.VideoCapturer | 52 | import livekit.org.webrtc.VideoCapturer |
| 52 | import livekit.org.webrtc.VideoProcessor | 53 | import livekit.org.webrtc.VideoProcessor |
| 54 | +import livekit.org.webrtc.VideoSink | ||
| 53 | import livekit.org.webrtc.VideoSource | 55 | import livekit.org.webrtc.VideoSource |
| 54 | import java.util.UUID | 56 | import java.util.UUID |
| 55 | import livekit.LivekitModels.VideoQuality as ProtoVideoQuality | 57 | import livekit.LivekitModels.VideoQuality as ProtoVideoQuality |
| @@ -72,6 +74,11 @@ constructor( | @@ -72,6 +74,11 @@ constructor( | ||
| 72 | private val eglBase: EglBase, | 74 | private val eglBase: EglBase, |
| 73 | private val defaultsManager: DefaultsManager, | 75 | private val defaultsManager: DefaultsManager, |
| 74 | private val trackFactory: Factory, | 76 | private val trackFactory: Factory, |
| 77 | + /** | ||
| 78 | + * If this is assigned, you must ensure that this observer is associated with the [capturer], | ||
| 79 | + * as this will be used to receive frames in [addRenderer]. | ||
| 80 | + **/ | ||
| 81 | + @Assisted private var dispatchObserver: CaptureDispatchObserver? = null, | ||
| 75 | ) : VideoTrack(name, rtcTrack) { | 82 | ) : VideoTrack(name, rtcTrack) { |
| 76 | 83 | ||
| 77 | override var rtcTrack: livekit.org.webrtc.VideoTrack = rtcTrack | 84 | override var rtcTrack: livekit.org.webrtc.VideoTrack = rtcTrack |
| @@ -126,6 +133,22 @@ constructor( | @@ -126,6 +133,22 @@ constructor( | ||
| 126 | closeableManager.close() | 133 | closeableManager.close() |
| 127 | } | 134 | } |
| 128 | 135 | ||
| 136 | + override fun addRenderer(renderer: VideoSink) { | ||
| 137 | + if (dispatchObserver != null) { | ||
| 138 | + dispatchObserver?.registerSink(renderer) | ||
| 139 | + } else { | ||
| 140 | + super.addRenderer(renderer) | ||
| 141 | + } | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + override fun removeRenderer(renderer: VideoSink) { | ||
| 145 | + if (dispatchObserver != null) { | ||
| 146 | + dispatchObserver?.unregisterSink(renderer) | ||
| 147 | + } else { | ||
| 148 | + super.removeRenderer(renderer) | ||
| 149 | + } | ||
| 150 | + } | ||
| 151 | + | ||
| 129 | fun setDeviceId(deviceId: String) { | 152 | fun setDeviceId(deviceId: String) { |
| 130 | restartTrack(options.copy(deviceId = deviceId)) | 153 | restartTrack(options.copy(deviceId = deviceId)) |
| 131 | } | 154 | } |
| @@ -239,7 +262,7 @@ constructor( | @@ -239,7 +262,7 @@ constructor( | ||
| 239 | val oldCloseable = closeableManager.unregisterResource(oldRtcTrack) | 262 | val oldCloseable = closeableManager.unregisterResource(oldRtcTrack) |
| 240 | oldCloseable?.close() | 263 | oldCloseable?.close() |
| 241 | 264 | ||
| 242 | - val newTrack = createTrack( | 265 | + val newTrack = createCameraTrack( |
| 243 | peerConnectionFactory, | 266 | peerConnectionFactory, |
| 244 | context, | 267 | context, |
| 245 | name, | 268 | name, |
| @@ -386,85 +409,78 @@ constructor( | @@ -386,85 +409,78 @@ constructor( | ||
| 386 | name: String, | 409 | name: String, |
| 387 | options: LocalVideoTrackOptions, | 410 | options: LocalVideoTrackOptions, |
| 388 | rtcTrack: livekit.org.webrtc.VideoTrack, | 411 | rtcTrack: livekit.org.webrtc.VideoTrack, |
| 412 | + dispatchObserver: CaptureDispatchObserver?, | ||
| 389 | ): LocalVideoTrack | 413 | ): LocalVideoTrack |
| 390 | } | 414 | } |
| 391 | 415 | ||
| 392 | companion object { | 416 | companion object { |
| 393 | 417 | ||
| 394 | - internal fun createTrack( | 418 | + internal fun createCameraTrack( |
| 395 | peerConnectionFactory: PeerConnectionFactory, | 419 | peerConnectionFactory: PeerConnectionFactory, |
| 396 | context: Context, | 420 | context: Context, |
| 397 | name: String, | 421 | name: String, |
| 398 | - capturer: VideoCapturer, | ||
| 399 | - options: LocalVideoTrackOptions = LocalVideoTrackOptions(), | 422 | + options: LocalVideoTrackOptions, |
| 400 | rootEglBase: EglBase, | 423 | rootEglBase: EglBase, |
| 401 | trackFactory: Factory, | 424 | trackFactory: Factory, |
| 402 | videoProcessor: VideoProcessor? = null, | 425 | videoProcessor: VideoProcessor? = null, |
| 403 | ): LocalVideoTrack { | 426 | ): LocalVideoTrack { |
| 404 | - val source = peerConnectionFactory.createVideoSource(false) | ||
| 405 | - source.setVideoProcessor(videoProcessor) | ||
| 406 | - val surfaceTextureHelper = SurfaceTextureHelper.create("VideoCaptureThread", rootEglBase.eglBaseContext) | ||
| 407 | - capturer.initialize( | ||
| 408 | - surfaceTextureHelper, | ||
| 409 | - context, | ||
| 410 | - source.capturerObserver, | ||
| 411 | - ) | ||
| 412 | - val rtcTrack = peerConnectionFactory.createVideoTrack(UUID.randomUUID().toString(), source) | 427 | + if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != |
| 428 | + PackageManager.PERMISSION_GRANTED | ||
| 429 | + ) { | ||
| 430 | + throw SecurityException("Camera permissions are required to create a camera video track.") | ||
| 431 | + } | ||
| 413 | 432 | ||
| 414 | - val track = trackFactory.create( | ||
| 415 | - capturer = capturer, | ||
| 416 | - source = source, | ||
| 417 | - options = options, | ||
| 418 | - name = name, | ||
| 419 | - rtcTrack = rtcTrack, | ||
| 420 | - ) | 433 | + val (capturer, newOptions) = CameraCapturerUtils.createCameraCapturer(context, options) ?: TODO() |
| 421 | 434 | ||
| 422 | - track.closeableManager.registerResource( | ||
| 423 | - rtcTrack, | ||
| 424 | - SurfaceTextureHelperCloser(surfaceTextureHelper), | 435 | + return createTrack( |
| 436 | + peerConnectionFactory = peerConnectionFactory, | ||
| 437 | + context = context, | ||
| 438 | + name = name, | ||
| 439 | + capturer = capturer, | ||
| 440 | + options = newOptions, | ||
| 441 | + rootEglBase = rootEglBase, | ||
| 442 | + trackFactory = trackFactory, | ||
| 443 | + videoProcessor = videoProcessor, | ||
| 425 | ) | 444 | ) |
| 426 | - return track | ||
| 427 | } | 445 | } |
| 428 | 446 | ||
| 429 | internal fun createTrack( | 447 | internal fun createTrack( |
| 430 | peerConnectionFactory: PeerConnectionFactory, | 448 | peerConnectionFactory: PeerConnectionFactory, |
| 431 | context: Context, | 449 | context: Context, |
| 432 | name: String, | 450 | name: String, |
| 433 | - options: LocalVideoTrackOptions, | 451 | + capturer: VideoCapturer, |
| 452 | + options: LocalVideoTrackOptions = LocalVideoTrackOptions(), | ||
| 434 | rootEglBase: EglBase, | 453 | rootEglBase: EglBase, |
| 435 | trackFactory: Factory, | 454 | trackFactory: Factory, |
| 436 | videoProcessor: VideoProcessor? = null, | 455 | videoProcessor: VideoProcessor? = null, |
| 437 | ): LocalVideoTrack { | 456 | ): LocalVideoTrack { |
| 438 | - if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != | ||
| 439 | - PackageManager.PERMISSION_GRANTED | ||
| 440 | - ) { | ||
| 441 | - throw SecurityException("Camera permissions are required to create a camera video track.") | ||
| 442 | - } | ||
| 443 | - | ||
| 444 | val source = peerConnectionFactory.createVideoSource(options.isScreencast) | 457 | val source = peerConnectionFactory.createVideoSource(options.isScreencast) |
| 445 | source.setVideoProcessor(videoProcessor) | 458 | source.setVideoProcessor(videoProcessor) |
| 446 | - val (capturer, newOptions) = CameraCapturerUtils.createCameraCapturer(context, options) ?: TODO() | 459 | + |
| 447 | val surfaceTextureHelper = SurfaceTextureHelper.create("VideoCaptureThread", rootEglBase.eglBaseContext) | 460 | val surfaceTextureHelper = SurfaceTextureHelper.create("VideoCaptureThread", rootEglBase.eglBaseContext) |
| 461 | + val dispatchObserver = CaptureDispatchObserver() | ||
| 462 | + dispatchObserver.registerObserver(source.capturerObserver) | ||
| 463 | + | ||
| 448 | capturer.initialize( | 464 | capturer.initialize( |
| 449 | surfaceTextureHelper, | 465 | surfaceTextureHelper, |
| 450 | context, | 466 | context, |
| 451 | - source.capturerObserver, | 467 | + dispatchObserver, |
| 452 | ) | 468 | ) |
| 453 | val rtcTrack = peerConnectionFactory.createVideoTrack(UUID.randomUUID().toString(), source) | 469 | val rtcTrack = peerConnectionFactory.createVideoTrack(UUID.randomUUID().toString(), source) |
| 454 | 470 | ||
| 455 | val track = trackFactory.create( | 471 | val track = trackFactory.create( |
| 456 | capturer = capturer, | 472 | capturer = capturer, |
| 457 | source = source, | 473 | source = source, |
| 458 | - options = newOptions, | 474 | + options = options, |
| 459 | name = name, | 475 | name = name, |
| 460 | rtcTrack = rtcTrack, | 476 | rtcTrack = rtcTrack, |
| 477 | + dispatchObserver = dispatchObserver, | ||
| 461 | ) | 478 | ) |
| 462 | 479 | ||
| 463 | track.closeableManager.registerResource( | 480 | track.closeableManager.registerResource( |
| 464 | rtcTrack, | 481 | rtcTrack, |
| 465 | SurfaceTextureHelperCloser(surfaceTextureHelper), | 482 | SurfaceTextureHelperCloser(surfaceTextureHelper), |
| 466 | ) | 483 | ) |
| 467 | - | ||
| 468 | return track | 484 | return track |
| 469 | } | 485 | } |
| 470 | } | 486 | } |
livekit-android-sdk/src/main/java/io/livekit/android/room/track/video/CaptureDispatchObserver.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 livekit.org.webrtc.CapturerObserver | ||
| 20 | +import livekit.org.webrtc.VideoFrame | ||
| 21 | +import livekit.org.webrtc.VideoSink | ||
| 22 | + | ||
| 23 | +class CaptureDispatchObserver : CapturerObserver { | ||
| 24 | + private val observers = linkedSetOf<CapturerObserver>() | ||
| 25 | + private val sinks = linkedSetOf<VideoSink>() | ||
| 26 | + | ||
| 27 | + @Synchronized | ||
| 28 | + fun registerObserver(observer: CapturerObserver) { | ||
| 29 | + observers.add(observer) | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + @Synchronized | ||
| 33 | + fun unregisterObserver(observer: CapturerObserver) { | ||
| 34 | + observers.remove(observer) | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + @Synchronized | ||
| 38 | + fun registerSink(sink: VideoSink) { | ||
| 39 | + sinks.add(sink) | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + @Synchronized | ||
| 43 | + fun unregisterSink(sink: VideoSink) { | ||
| 44 | + sinks.remove(sink) | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + @Synchronized | ||
| 48 | + override fun onCapturerStarted(success: Boolean) { | ||
| 49 | + for (observer in observers) { | ||
| 50 | + observer.onCapturerStarted(success) | ||
| 51 | + } | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + @Synchronized | ||
| 55 | + override fun onCapturerStopped() { | ||
| 56 | + for (observer in observers) { | ||
| 57 | + observer.onCapturerStopped() | ||
| 58 | + } | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + @Synchronized | ||
| 62 | + override fun onFrameCaptured(frame: VideoFrame) { | ||
| 63 | + for (observer in observers) { | ||
| 64 | + observer.onFrameCaptured(frame) | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + for (sink in sinks) { | ||
| 68 | + sink.onFrame(frame) | ||
| 69 | + } | ||
| 70 | + } | ||
| 71 | +} |
-
请 注册 或 登录 后发表评论