David Zhao

Audio noise cancellation optionns, video capture resolution control

@@ -11,7 +11,7 @@ buildscript { @@ -11,7 +11,7 @@ buildscript {
11 11
12 } 12 }
13 dependencies { 13 dependencies {
14 - classpath 'com.android.tools.build:gradle:7.0.0-beta02' 14 + classpath 'com.android.tools.build:gradle:7.0.0-beta03'
15 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 15 classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
16 classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" 16 classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
17 classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version" 17 classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
@@ -22,7 +22,6 @@ constructor( @@ -22,7 +22,6 @@ constructor(
22 ) { 22 ) {
23 val peerConnection: PeerConnection = connectionFactory.createPeerConnection( 23 val peerConnection: PeerConnection = connectionFactory.createPeerConnection(
24 config, 24 config,
25 - RTCEngine.CONN_CONSTRAINTS,  
26 listener 25 listener
27 ) ?: throw IllegalStateException("peer connection creation failed?") 26 ) ?: throw IllegalStateException("peer connection creation failed?")
28 val pendingCandidates = mutableListOf<IceCandidate>() 27 val pendingCandidates = mutableListOf<IceCandidate>()
@@ -225,6 +225,7 @@ constructor( @@ -225,6 +225,7 @@ constructor(
225 val rtcConfig = PeerConnection.RTCConfiguration(iceServers).apply { 225 val rtcConfig = PeerConnection.RTCConfiguration(iceServers).apply {
226 sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN 226 sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN
227 continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY 227 continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY
  228 + enableDtlsSrtp = true
228 } 229 }
229 230
230 publisher = pctFactory.create(rtcConfig, publisherObserver) 231 publisher = pctFactory.create(rtcConfig, publisherObserver)
@@ -33,19 +33,30 @@ internal constructor( @@ -33,19 +33,30 @@ internal constructor(
33 get() = tracks.values.toList() 33 get() = tracks.values.toList()
34 34
35 fun createAudioTrack( 35 fun createAudioTrack(
36 - audioConstraints: MediaConstraints = MediaConstraints(),  
37 - name: String = ""  
38 - ) = LocalAudioTrack.createTrack(peerConnectionFactory, audioConstraints, name) 36 + name: String = "",
  37 + options: LocalAudioTrackOptions = LocalAudioTrackOptions(),
  38 + ): LocalAudioTrack {
  39 + val audioConstraints = MediaConstraints()
  40 + val items = listOf(
  41 + MediaConstraints.KeyValuePair("googEchoCancellation", options.echoCancellation.toString()),
  42 + MediaConstraints.KeyValuePair("googAutoGainControl", options.autoGainControl.toString()),
  43 + MediaConstraints.KeyValuePair("googHighpassFilter", options.highPassFilter.toString()),
  44 + MediaConstraints.KeyValuePair("googNoiseSuppression", options.noiseSuppression.toString()),
  45 + MediaConstraints.KeyValuePair("googTypingNoiseDetection", options.typingNoiseDetection.toString()),
  46 + )
  47 +// audioConstraints.optional.addAll(items)
  48 + return LocalAudioTrack.createTrack(peerConnectionFactory, audioConstraints, name)
  49 + }
39 50
40 fun createVideoTrack( 51 fun createVideoTrack(
41 - isScreencast: Boolean = false,  
42 name: String = "", 52 name: String = "",
  53 + options: LocalVideoTrackOptions = LocalVideoTrackOptions(),
43 ): LocalVideoTrack { 54 ): LocalVideoTrack {
44 return LocalVideoTrack.createTrack( 55 return LocalVideoTrack.createTrack(
45 peerConnectionFactory, 56 peerConnectionFactory,
46 context, 57 context,
47 - isScreencast,  
48 name, 58 name,
  59 + options,
49 eglBase 60 eglBase
50 ) 61 )
51 } 62 }
1 -package io.livekit.android.room.track  
2 -  
3 -class AudioOptions {  
4 -}  
@@ -5,5 +5,4 @@ import org.webrtc.AudioTrack @@ -5,5 +5,4 @@ import org.webrtc.AudioTrack
5 5
6 open class AudioTrack(name: String, override val rtcTrack: AudioTrack) : 6 open class AudioTrack(name: String, override val rtcTrack: AudioTrack) :
7 Track(name, Kind.AUDIO, rtcTrack) { 7 Track(name, Kind.AUDIO, rtcTrack) {
8 -  
9 } 8 }
@@ -11,7 +11,6 @@ import java.util.* @@ -11,7 +11,6 @@ import java.util.*
11 */ 11 */
12 class LocalAudioTrack( 12 class LocalAudioTrack(
13 name: String, 13 name: String,
14 - audioOptions: AudioOptions? = null,  
15 mediaTrack: org.webrtc.AudioTrack 14 mediaTrack: org.webrtc.AudioTrack
16 ) : AudioTrack(name, mediaTrack) { 15 ) : AudioTrack(name, mediaTrack) {
17 var enabled: Boolean 16 var enabled: Boolean
@@ -20,16 +19,12 @@ class LocalAudioTrack( @@ -20,16 +19,12 @@ class LocalAudioTrack(
20 rtcTrack.setEnabled(value) 19 rtcTrack.setEnabled(value)
21 } 20 }
22 21
23 - var audioOptions = audioOptions  
24 - private set  
25 -  
26 companion object { 22 companion object {
27 internal fun createTrack( 23 internal fun createTrack(
28 factory: PeerConnectionFactory, 24 factory: PeerConnectionFactory,
29 audioConstraints: MediaConstraints = MediaConstraints(), 25 audioConstraints: MediaConstraints = MediaConstraints(),
30 name: String = "" 26 name: String = ""
31 ): LocalAudioTrack { 27 ): LocalAudioTrack {
32 -  
33 val audioSource = factory.createAudioSource(audioConstraints) 28 val audioSource = factory.createAudioSource(audioConstraints)
34 val rtcAudioTrack = 29 val rtcAudioTrack =
35 factory.createAudioTrack(UUID.randomUUID().toString(), audioSource) 30 factory.createAudioTrack(UUID.randomUUID().toString(), audioSource)
  1 +package io.livekit.android.room.track
  2 +
  3 +class LocalAudioTrackOptions(
  4 + var noiseSuppression: Boolean = true,
  5 + var echoCancellation: Boolean = true,
  6 + var autoGainControl: Boolean = true,
  7 + var highPassFilter: Boolean = true,
  8 + var typingNoiseDetection: Boolean = true,
  9 +)
@@ -14,10 +14,11 @@ class LocalVideoTrack( @@ -14,10 +14,11 @@ class LocalVideoTrack(
14 private val capturer: VideoCapturer, 14 private val capturer: VideoCapturer,
15 private val source: VideoSource, 15 private val source: VideoSource,
16 name: String, 16 name: String,
  17 + private val options: LocalVideoTrackOptions,
17 rtcTrack: org.webrtc.VideoTrack 18 rtcTrack: org.webrtc.VideoTrack
18 ) : VideoTrack(name, rtcTrack) { 19 ) : VideoTrack(name, rtcTrack) {
19 fun startCapture() { 20 fun startCapture() {
20 - capturer.startCapture(400, 400, 30) 21 + capturer.startCapture(options.captureParams.width, options.captureParams.height, options.captureParams.maxFps)
21 } 22 }
22 23
23 override fun stop() { 24 override fun stop() {
@@ -29,12 +30,12 @@ class LocalVideoTrack( @@ -29,12 +30,12 @@ class LocalVideoTrack(
29 internal fun createTrack( 30 internal fun createTrack(
30 peerConnectionFactory: PeerConnectionFactory, 31 peerConnectionFactory: PeerConnectionFactory,
31 context: Context, 32 context: Context,
32 - isScreencast: Boolean,  
33 name: String, 33 name: String,
  34 + options: LocalVideoTrackOptions,
34 rootEglBase: EglBase, 35 rootEglBase: EglBase,
35 ): LocalVideoTrack { 36 ): LocalVideoTrack {
36 - val source = peerConnectionFactory.createVideoSource(isScreencast)  
37 - val capturer = createVideoCapturer(context) ?: TODO() 37 + val source = peerConnectionFactory.createVideoSource(options.isScreencast)
  38 + val capturer = createVideoCapturer(context, options.position) ?: TODO()
38 capturer.initialize( 39 capturer.initialize(
39 SurfaceTextureHelper.create("VideoCaptureThread", rootEglBase.eglBaseContext), 40 SurfaceTextureHelper.create("VideoCaptureThread", rootEglBase.eglBaseContext),
40 context, 41 context,
@@ -45,16 +46,17 @@ class LocalVideoTrack( @@ -45,16 +46,17 @@ class LocalVideoTrack(
45 return LocalVideoTrack( 46 return LocalVideoTrack(
46 capturer = capturer, 47 capturer = capturer,
47 source = source, 48 source = source,
  49 + options = options,
48 name = name, 50 name = name,
49 rtcTrack = track, 51 rtcTrack = track,
50 ) 52 )
51 } 53 }
52 54
53 - private fun createVideoCapturer(context: Context): VideoCapturer? { 55 + private fun createVideoCapturer(context: Context, position: CameraPosition): VideoCapturer? {
54 val videoCapturer: VideoCapturer? = if (Camera2Enumerator.isSupported(context)) { 56 val videoCapturer: VideoCapturer? = if (Camera2Enumerator.isSupported(context)) {
55 - createCameraCapturer(Camera2Enumerator(context)) 57 + createCameraCapturer(Camera2Enumerator(context), position)
56 } else { 58 } else {
57 - createCameraCapturer(Camera1Enumerator(true)) 59 + createCameraCapturer(Camera1Enumerator(true), position)
58 } 60 }
59 if (videoCapturer == null) { 61 if (videoCapturer == null) {
60 Timber.d { "Failed to open camera" } 62 Timber.d { "Failed to open camera" }
@@ -63,24 +65,18 @@ class LocalVideoTrack( @@ -63,24 +65,18 @@ class LocalVideoTrack(
63 return videoCapturer 65 return videoCapturer
64 } 66 }
65 67
66 - private fun createCameraCapturer(enumerator: CameraEnumerator): VideoCapturer? { 68 + private fun createCameraCapturer(enumerator: CameraEnumerator, position: CameraPosition): VideoCapturer? {
67 val deviceNames = enumerator.deviceNames 69 val deviceNames = enumerator.deviceNames
68 70
69 - // First, try to find front facing camera  
70 for (deviceName in deviceNames) { 71 for (deviceName in deviceNames) {
71 - if (enumerator.isFrontFacing(deviceName)) { 72 + if (enumerator.isFrontFacing(deviceName) && position == CameraPosition.FRONT) {
72 Timber.v { "Creating front facing camera capturer." } 73 Timber.v { "Creating front facing camera capturer." }
73 val videoCapturer = enumerator.createCapturer(deviceName, null) 74 val videoCapturer = enumerator.createCapturer(deviceName, null)
74 if (videoCapturer != null) { 75 if (videoCapturer != null) {
75 return videoCapturer 76 return videoCapturer
76 } 77 }
77 - }  
78 - }  
79 -  
80 - // Front facing camera not found, try something else  
81 - for (deviceName in deviceNames) {  
82 - if (!enumerator.isFrontFacing(deviceName)) {  
83 - Timber.v { "Creating other camera capturer." } 78 + } else if (enumerator.isBackFacing(deviceName) && position == CameraPosition.BACK) {
  79 + Timber.v { "Creating back facing camera capturer." }
84 val videoCapturer = enumerator.createCapturer(deviceName, null) 80 val videoCapturer = enumerator.createCapturer(deviceName, null)
85 if (videoCapturer != null) { 81 if (videoCapturer != null) {
86 return videoCapturer 82 return videoCapturer
  1 +package io.livekit.android.room.track
  2 +
  3 +class LocalVideoTrackOptions(
  4 + var isScreencast: Boolean = false,
  5 + var position: CameraPosition = CameraPosition.FRONT,
  6 + var captureParams: VideoCaptureParameter = VideoPreset.QHD.capture
  7 +)
  8 +
  9 +class VideoCaptureParameter(
  10 + val width: Int,
  11 + val height: Int,
  12 + val maxFps: Int,
  13 +)
  14 +
  15 +class VideoEncoding(
  16 + val maxBitrate: Int,
  17 + val maxFps: Int,
  18 +)
  19 +
  20 +enum class CameraPosition {
  21 + FRONT,
  22 + BACK
  23 +}
  24 +
  25 +/**
  26 + * Video presets along with suggested bitrates
  27 + */
  28 +enum class VideoPreset(
  29 + val capture: VideoCaptureParameter,
  30 + val encoding: VideoEncoding,
  31 +) {
  32 + QVGA(
  33 + VideoCaptureParameter(320, 240, 15),
  34 + VideoEncoding(100_000, 15),
  35 + ),
  36 + VGA(
  37 + VideoCaptureParameter(640, 360, 30),
  38 + VideoEncoding(400_000, 30),
  39 + ),
  40 + QHD(
  41 + VideoCaptureParameter(960, 540, 30),
  42 + VideoEncoding(700_000, 30),
  43 + ),
  44 + HD(
  45 + VideoCaptureParameter(1280, 720, 30),
  46 + VideoEncoding(2_000_000, 30),
  47 + ),
  48 + FHD(
  49 + VideoCaptureParameter(1920, 1080, 30),
  50 + VideoEncoding(4_000_000, 30),
  51 + )
  52 +
  53 +}