davidliu
Committed by GitHub

android dtx support (#15)

* update protocol submodule commit

* Add option to control DTX (Discontinuous Transmission)

* Network monitoring tool to measure tx stats

* Move network monitor into sdk for now

* Use LKLog instead of timber
@@ -208,11 +208,14 @@ internal constructor( @@ -208,11 +208,14 @@ internal constructor(
208 lossyDataChannel!!.registerObserver(this) 208 lossyDataChannel!!.registerObserver(this)
209 } 209 }
210 210
  211 + /**
  212 + * @param builder an optional builder to include other parameters related to the track
  213 + */
211 suspend fun addTrack( 214 suspend fun addTrack(
212 cid: String, 215 cid: String,
213 name: String, 216 name: String,
214 kind: LivekitModels.TrackType, 217 kind: LivekitModels.TrackType,
215 - dimensions: Track.Dimensions? = null 218 + builder: LivekitRtc.AddTrackRequest.Builder = LivekitRtc.AddTrackRequest.newBuilder()
216 ): LivekitModels.TrackInfo { 219 ): LivekitModels.TrackInfo {
217 if (pendingTrackResolvers[cid] != null) { 220 if (pendingTrackResolvers[cid] != null) {
218 throw TrackException.DuplicateTrackException("Track with same ID $cid has already been published!") 221 throw TrackException.DuplicateTrackException("Track with same ID $cid has already been published!")
@@ -220,7 +223,7 @@ internal constructor( @@ -220,7 +223,7 @@ internal constructor(
220 223
221 return suspendCoroutine { cont -> 224 return suspendCoroutine { cont ->
222 pendingTrackResolvers[cid] = cont 225 pendingTrackResolvers[cid] = cont
223 - client.sendAddTrack(cid, name, kind, dimensions) 226 + client.sendAddTrack(cid, name, kind, builder)
224 } 227 }
225 } 228 }
226 229
@@ -252,16 +252,19 @@ constructor( @@ -252,16 +252,19 @@ constructor(
252 sendRequest(request) 252 sendRequest(request)
253 } 253 }
254 254
255 - fun sendAddTrack(cid: String, name: String, type: LivekitModels.TrackType, dimensions: Track.Dimensions? = null) {  
256 - val addTrackRequest = LivekitRtc.AddTrackRequest.newBuilder() 255 + /**
  256 + * @param builder an optional builder to include other parameters related to the track
  257 + */
  258 + fun sendAddTrack(
  259 + cid: String,
  260 + name: String,
  261 + type: LivekitModels.TrackType,
  262 + builder: LivekitRtc.AddTrackRequest.Builder = LivekitRtc.AddTrackRequest.newBuilder()
  263 + ) {
  264 + val addTrackRequest = builder
257 .setCid(cid) 265 .setCid(cid)
258 .setName(name) 266 .setName(name)
259 .setType(type) 267 .setType(type)
260 - if (dimensions != null) {  
261 - addTrackRequest.width = dimensions.width  
262 - addTrackRequest.height = dimensions.height  
263 - }  
264 -  
265 val request = LivekitRtc.SignalRequest.newBuilder() 268 val request = LivekitRtc.SignalRequest.newBuilder()
266 .setAddTrack(addTrackRequest) 269 .setAddTrack(addTrackRequest)
267 .build() 270 .build()
@@ -2,8 +2,6 @@ package io.livekit.android.room.participant @@ -2,8 +2,6 @@ package io.livekit.android.room.participant
2 2
3 import android.Manifest 3 import android.Manifest
4 import android.content.Context 4 import android.content.Context
5 -import android.media.MediaCodecInfo  
6 -import androidx.annotation.RequiresPermission  
7 import com.google.protobuf.ByteString 5 import com.google.protobuf.ByteString
8 import dagger.assisted.Assisted 6 import dagger.assisted.Assisted
9 import dagger.assisted.AssistedFactory 7 import dagger.assisted.AssistedFactory
@@ -12,6 +10,7 @@ import io.livekit.android.room.RTCEngine @@ -12,6 +10,7 @@ import io.livekit.android.room.RTCEngine
12 import io.livekit.android.room.track.* 10 import io.livekit.android.room.track.*
13 import io.livekit.android.util.LKLog 11 import io.livekit.android.util.LKLog
14 import livekit.LivekitModels 12 import livekit.LivekitModels
  13 +import livekit.LivekitRtc
15 import org.webrtc.* 14 import org.webrtc.*
16 import kotlin.math.abs 15 import kotlin.math.abs
17 16
@@ -75,6 +74,7 @@ internal constructor( @@ -75,6 +74,7 @@ internal constructor(
75 74
76 suspend fun publishAudioTrack( 75 suspend fun publishAudioTrack(
77 track: LocalAudioTrack, 76 track: LocalAudioTrack,
  77 + options: AudioTrackPublishOptions = AudioTrackPublishOptions(),
78 publishListener: PublishListener? = null 78 publishListener: PublishListener? = null
79 ) { 79 ) {
80 if (localTrackPublications.any { it.track == track }) { 80 if (localTrackPublications.any { it.track == track }) {
@@ -83,8 +83,15 @@ internal constructor( @@ -83,8 +83,15 @@ internal constructor(
83 } 83 }
84 84
85 val cid = track.rtcTrack.id() 85 val cid = track.rtcTrack.id()
86 - val trackInfo =  
87 - engine.addTrack(cid = cid, name = track.name, kind = track.kind.toProto()) 86 + val builder = LivekitRtc.AddTrackRequest.newBuilder().apply {
  87 + disableDtx = !options.dtx
  88 + }
  89 + val trackInfo = engine.addTrack(
  90 + cid = cid,
  91 + name = track.name,
  92 + kind = track.kind.toProto(),
  93 + builder = builder
  94 + )
88 val transInit = RtpTransceiver.RtpTransceiverInit( 95 val transInit = RtpTransceiver.RtpTransceiverInit(
89 RtpTransceiver.RtpTransceiverDirection.SEND_ONLY, 96 RtpTransceiver.RtpTransceiverDirection.SEND_ONLY,
90 listOf(this.sid) 97 listOf(this.sid)
@@ -114,11 +121,15 @@ internal constructor( @@ -114,11 +121,15 @@ internal constructor(
114 } 121 }
115 122
116 val cid = track.rtcTrack.id() 123 val cid = track.rtcTrack.id()
  124 + val builder = LivekitRtc.AddTrackRequest.newBuilder().apply {
  125 + width = track.dimensions.width
  126 + height = track.dimensions.height
  127 + }
117 val trackInfo = engine.addTrack( 128 val trackInfo = engine.addTrack(
118 cid = cid, 129 cid = cid,
119 name = track.name, 130 name = track.name,
120 kind = LivekitModels.TrackType.VIDEO, 131 kind = LivekitModels.TrackType.VIDEO,
121 - dimensions = track.dimensions 132 + builder = builder
122 ) 133 )
123 val encodings = computeVideoEncodings(track.dimensions, options) 134 val encodings = computeVideoEncodings(track.dimensions, options)
124 val transInit = RtpTransceiver.RtpTransceiverInit( 135 val transInit = RtpTransceiver.RtpTransceiverInit(
@@ -312,4 +323,5 @@ data class VideoTrackPublishOptions( @@ -312,4 +323,5 @@ data class VideoTrackPublishOptions(
312 data class AudioTrackPublishOptions( 323 data class AudioTrackPublishOptions(
313 override val name: String? = null, 324 override val name: String? = null,
314 val audioBitrate: Int? = null, 325 val audioBitrate: Int? = null,
  326 + val dtx: Boolean = true
315 ) : TrackPublishOptions 327 ) : TrackPublishOptions
@@ -30,6 +30,8 @@ class LocalVideoTrack( @@ -30,6 +30,8 @@ class LocalVideoTrack(
30 /** 30 /**
31 * Note: these dimensions are only requested params, and may differ 31 * Note: these dimensions are only requested params, and may differ
32 * from the actual capture format used by the camera. 32 * from the actual capture format used by the camera.
  33 + *
  34 + * TODO: capture actual dimensions used
33 */ 35 */
34 val dimensions: Dimensions 36 val dimensions: Dimensions
35 get() = Dimensions(options.captureParams.width, options.captureParams.height) 37 get() = Dimensions(options.captureParams.width, options.captureParams.height)
  1 +package io.livekit.android.stats
  2 +
  3 +import android.content.Context
  4 +import android.net.TrafficStats
  5 +import io.livekit.android.util.LKLog
  6 +import kotlinx.coroutines.*
  7 +import kotlin.coroutines.CoroutineContext
  8 +
  9 +internal class NetworkMonitor(private val context: Context) {
  10 +
  11 + private lateinit var coroutineContext: CoroutineContext
  12 + private lateinit var scope: CoroutineScope
  13 + fun start() {
  14 + coroutineContext = SupervisorJob() + Dispatchers.IO
  15 + scope = CoroutineScope(coroutineContext)
  16 + scope.launch {
  17 +
  18 + val uid = context.packageManager.getApplicationInfo(context.packageName, 0).uid
  19 +
  20 + var prevTxBytes = TrafficStats.getUidTxBytes(uid)
  21 + var emaTxBytes = 0L
  22 + while (this.isActive) {
  23 + val totalTxBytes = TrafficStats.getUidTxBytes(uid)
  24 + val intervalTxBytes = totalTxBytes - prevTxBytes
  25 + prevTxBytes = totalTxBytes
  26 + emaTxBytes = emaTxBytes / 2 + intervalTxBytes / 2
  27 +
  28 + LKLog.v { "send rate: ${convertBytesToReadableString(emaTxBytes)}" }
  29 +
  30 + delay(1000)
  31 + }
  32 + }
  33 + }
  34 +
  35 + private fun convertBytesToReadableString(bytes: Long): String {
  36 + var num = bytes.toFloat()
  37 + var level = 0
  38 + while (num >= 1024 && level < 2) {
  39 + num /= 1024
  40 + level++
  41 + }
  42 +
  43 + // MBps should be way more than enough.
  44 + val suffix = when (level) {
  45 + 0 -> "Bps"
  46 + 1 -> "kBps"
  47 + 2 -> "MBps"
  48 + else -> throw IllegalStateException("this shouldn't happen. level = $level")
  49 + }
  50 +
  51 + return "$num $suffix"
  52 + }
  53 +
  54 + fun stop() {
  55 + coroutineContext.cancel()
  56 + }
  57 +}
1 -Subproject commit 1dadf893095928a27ae47b63cdce74607c38f229 1 +Subproject commit 4d580badfde3d8b794ea54fcf417747aa60af20a