Committed by
GitHub
Dl/lk 356 android send explicit video layers (#30)
* send video layers when adding track * handle portrait aspect ratio
正在显示
1 个修改的文件
包含
116 行增加
和
70 行删除
| @@ -22,6 +22,8 @@ import org.webrtc.RtpParameters | @@ -22,6 +22,8 @@ import org.webrtc.RtpParameters | ||
| 22 | import org.webrtc.RtpTransceiver | 22 | import org.webrtc.RtpTransceiver |
| 23 | import javax.inject.Named | 23 | import javax.inject.Named |
| 24 | import kotlin.math.abs | 24 | import kotlin.math.abs |
| 25 | +import kotlin.math.max | ||
| 26 | +import kotlin.math.min | ||
| 25 | import kotlin.math.roundToInt | 27 | import kotlin.math.roundToInt |
| 26 | 28 | ||
| 27 | class LocalParticipant | 29 | class LocalParticipant |
| @@ -185,40 +187,14 @@ internal constructor( | @@ -185,40 +187,14 @@ internal constructor( | ||
| 185 | ), | 187 | ), |
| 186 | publishListener: PublishListener? = null | 188 | publishListener: PublishListener? = null |
| 187 | ) { | 189 | ) { |
| 188 | - if (localTrackPublications.any { it.track == track }) { | ||
| 189 | - publishListener?.onPublishFailure(TrackException.PublishException("Track has already been published")) | ||
| 190 | - return | ||
| 191 | - } | ||
| 192 | - | ||
| 193 | - val cid = track.rtcTrack.id() | ||
| 194 | - val builder = LivekitRtc.AddTrackRequest.newBuilder().apply { | ||
| 195 | - disableDtx = !options.dtx | ||
| 196 | - source = LivekitModels.TrackSource.MICROPHONE | ||
| 197 | - } | ||
| 198 | - val trackInfo = engine.addTrack( | ||
| 199 | - cid = cid, | ||
| 200 | - name = track.name, | ||
| 201 | - kind = track.kind.toProto(), | ||
| 202 | - builder = builder | ||
| 203 | - ) | ||
| 204 | - val transInit = RtpTransceiver.RtpTransceiverInit( | ||
| 205 | - RtpTransceiver.RtpTransceiverDirection.SEND_ONLY, | ||
| 206 | - listOf(this.sid) | 190 | + publishTrackImpl( |
| 191 | + track, | ||
| 192 | + requestConfig = { | ||
| 193 | + disableDtx = !options.dtx | ||
| 194 | + source = LivekitModels.TrackSource.MICROPHONE | ||
| 195 | + }, | ||
| 196 | + publishListener = publishListener, | ||
| 207 | ) | 197 | ) |
| 208 | - // TODO: sendEncodings to customize | ||
| 209 | - val transceiver = engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit) | ||
| 210 | - track.transceiver = transceiver | ||
| 211 | - | ||
| 212 | - if (transceiver == null) { | ||
| 213 | - publishListener?.onPublishFailure(TrackException.PublishException("null sender returned from peer connection")) | ||
| 214 | - return | ||
| 215 | - } | ||
| 216 | - | ||
| 217 | - val publication = LocalTrackPublication(trackInfo, track, this) | ||
| 218 | - addTrackPublication(publication) | ||
| 219 | - publishListener?.onPublishSuccess(publication) | ||
| 220 | - internalListener?.onTrackPublished(publication, this) | ||
| 221 | - eventBus.postEvent(ParticipantEvent.LocalTrackPublished(this, publication), scope) | ||
| 222 | } | 198 | } |
| 223 | 199 | ||
| 224 | suspend fun publishVideoTrack( | 200 | suspend fun publishVideoTrack( |
| @@ -226,6 +202,34 @@ internal constructor( | @@ -226,6 +202,34 @@ internal constructor( | ||
| 226 | options: VideoTrackPublishOptions = VideoTrackPublishOptions(null, videoTrackPublishDefaults), | 202 | options: VideoTrackPublishOptions = VideoTrackPublishOptions(null, videoTrackPublishDefaults), |
| 227 | publishListener: PublishListener? = null | 203 | publishListener: PublishListener? = null |
| 228 | ) { | 204 | ) { |
| 205 | + | ||
| 206 | + val encodings = computeVideoEncodings(track.dimensions, options) | ||
| 207 | + val videoLayers = videoLayersFromEncodings(track.dimensions.width, track.dimensions.height, encodings) | ||
| 208 | + | ||
| 209 | + publishTrackImpl( | ||
| 210 | + track, | ||
| 211 | + requestConfig = { | ||
| 212 | + width = track.dimensions.width | ||
| 213 | + height = track.dimensions.height | ||
| 214 | + source = if (track.options.isScreencast) { | ||
| 215 | + LivekitModels.TrackSource.SCREEN_SHARE | ||
| 216 | + } else { | ||
| 217 | + LivekitModels.TrackSource.CAMERA | ||
| 218 | + } | ||
| 219 | + addAllLayers(videoLayers) | ||
| 220 | + }, | ||
| 221 | + encodings = encodings, | ||
| 222 | + publishListener = publishListener | ||
| 223 | + ) | ||
| 224 | + } | ||
| 225 | + | ||
| 226 | + | ||
| 227 | + private suspend fun publishTrackImpl( | ||
| 228 | + track: Track, | ||
| 229 | + requestConfig: LivekitRtc.AddTrackRequest.Builder.() -> Unit, | ||
| 230 | + encodings: List<RtpParameters.Encoding> = emptyList(), | ||
| 231 | + publishListener: PublishListener? = null | ||
| 232 | + ) { | ||
| 229 | if (localTrackPublications.any { it.track == track }) { | 233 | if (localTrackPublications.any { it.track == track }) { |
| 230 | publishListener?.onPublishFailure(TrackException.PublishException("Track has already been published")) | 234 | publishListener?.onPublishFailure(TrackException.PublishException("Track has already been published")) |
| 231 | return | 235 | return |
| @@ -233,28 +237,28 @@ internal constructor( | @@ -233,28 +237,28 @@ internal constructor( | ||
| 233 | 237 | ||
| 234 | val cid = track.rtcTrack.id() | 238 | val cid = track.rtcTrack.id() |
| 235 | val builder = LivekitRtc.AddTrackRequest.newBuilder().apply { | 239 | val builder = LivekitRtc.AddTrackRequest.newBuilder().apply { |
| 236 | - width = track.dimensions.width | ||
| 237 | - height = track.dimensions.height | ||
| 238 | - source = if(track.options.isScreencast){ | ||
| 239 | - LivekitModels.TrackSource.SCREEN_SHARE | ||
| 240 | - } else { | ||
| 241 | - LivekitModels.TrackSource.CAMERA | ||
| 242 | - } | 240 | + this.requestConfig() |
| 243 | } | 241 | } |
| 244 | val trackInfo = engine.addTrack( | 242 | val trackInfo = engine.addTrack( |
| 245 | cid = cid, | 243 | cid = cid, |
| 246 | name = track.name, | 244 | name = track.name, |
| 247 | - kind = LivekitModels.TrackType.VIDEO, | 245 | + kind = track.kind.toProto(), |
| 248 | builder = builder | 246 | builder = builder |
| 249 | ) | 247 | ) |
| 250 | - val encodings = computeVideoEncodings(track.dimensions, options) | ||
| 251 | val transInit = RtpTransceiver.RtpTransceiverInit( | 248 | val transInit = RtpTransceiver.RtpTransceiverInit( |
| 252 | RtpTransceiver.RtpTransceiverDirection.SEND_ONLY, | 249 | RtpTransceiver.RtpTransceiverDirection.SEND_ONLY, |
| 253 | listOf(this.sid), | 250 | listOf(this.sid), |
| 254 | encodings | 251 | encodings |
| 255 | ) | 252 | ) |
| 256 | val transceiver = engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit) | 253 | val transceiver = engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit) |
| 257 | - track.transceiver = transceiver | 254 | + |
| 255 | + when (track) { | ||
| 256 | + is LocalVideoTrack -> track.transceiver | ||
| 257 | + is LocalAudioTrack -> track.transceiver | ||
| 258 | + else -> { | ||
| 259 | + throw IllegalArgumentException("Trying to publish a non local track of type ${track.javaClass}") | ||
| 260 | + } | ||
| 261 | + } | ||
| 258 | 262 | ||
| 259 | if (transceiver == null) { | 263 | if (transceiver == null) { |
| 260 | publishListener?.onPublishFailure(TrackException.PublishException("null sender returned from peer connection")) | 264 | publishListener?.onPublishFailure(TrackException.PublishException("null sender returned from peer connection")) |
| @@ -289,14 +293,14 @@ internal constructor( | @@ -289,14 +293,14 @@ internal constructor( | ||
| 289 | 293 | ||
| 290 | val encodings = mutableListOf<RtpParameters.Encoding>() | 294 | val encodings = mutableListOf<RtpParameters.Encoding>() |
| 291 | if (simulcast) { | 295 | if (simulcast) { |
| 292 | - encodings.add(encoding.toRtpEncoding("f")) | ||
| 293 | 296 | ||
| 294 | val presets = presetsForResolution(width, height) | 297 | val presets = presetsForResolution(width, height) |
| 295 | val midPreset = presets[1] | 298 | val midPreset = presets[1] |
| 296 | val lowPreset = presets[0] | 299 | val lowPreset = presets[0] |
| 297 | 300 | ||
| 298 | fun calculateScale(parameter: VideoCaptureParameter): Double { | 301 | fun calculateScale(parameter: VideoCaptureParameter): Double { |
| 299 | - return height / parameter.height.toDouble() | 302 | + val longestSize = max(width, height) |
| 303 | + return longestSize / parameter.width.toDouble() | ||
| 300 | } | 304 | } |
| 301 | 305 | ||
| 302 | fun checkEvenDimensions(parameter: VideoCaptureParameter): Boolean { | 306 | fun checkEvenDimensions(parameter: VideoCaptureParameter): Boolean { |
| @@ -306,36 +310,32 @@ internal constructor( | @@ -306,36 +310,32 @@ internal constructor( | ||
| 306 | return isEven(parameter.height * scale) && isEven(parameter.width * scale) | 310 | return isEven(parameter.height * scale) && isEven(parameter.width * scale) |
| 307 | } | 311 | } |
| 308 | 312 | ||
| 313 | + fun addEncoding(videoEncoding: VideoEncoding, scale: Double) { | ||
| 314 | + if(encodings.size >= VIDEO_RIDS.size) { | ||
| 315 | + throw IllegalStateException("Attempting to add more encodings than we have rids for!") | ||
| 316 | + } | ||
| 317 | + val rid = VIDEO_RIDS[encodings.size] | ||
| 318 | + encodings.add(videoEncoding.toRtpEncoding(rid, scale)) | ||
| 319 | + } | ||
| 320 | + | ||
| 309 | // if resolution is high enough, we send both h and q res. | 321 | // if resolution is high enough, we send both h and q res. |
| 310 | // otherwise only send h | 322 | // otherwise only send h |
| 311 | - if (width >= 960) { | 323 | + val size = max(width, height) |
| 324 | + if (size >= 960) { | ||
| 312 | val hasEvenDimensions = | 325 | val hasEvenDimensions = |
| 313 | checkEvenDimensions(midPreset.capture) && checkEvenDimensions(lowPreset.capture) | 326 | checkEvenDimensions(midPreset.capture) && checkEvenDimensions(lowPreset.capture) |
| 314 | val midScale = if (hasEvenDimensions) calculateScale(midPreset.capture) else 2.0 | 327 | val midScale = if (hasEvenDimensions) calculateScale(midPreset.capture) else 2.0 |
| 315 | val lowScale = if (hasEvenDimensions) calculateScale(lowPreset.capture) else 4.0 | 328 | val lowScale = if (hasEvenDimensions) calculateScale(lowPreset.capture) else 4.0 |
| 316 | 329 | ||
| 317 | - encodings.add( | ||
| 318 | - midPreset.encoding.toRtpEncoding( | ||
| 319 | - "h", | ||
| 320 | - midScale | ||
| 321 | - ) | ||
| 322 | - ) | ||
| 323 | - encodings.add( | ||
| 324 | - lowPreset.encoding.toRtpEncoding( | ||
| 325 | - "q", | ||
| 326 | - lowScale | ||
| 327 | - ) | ||
| 328 | - ) | 330 | + addEncoding(lowPreset.encoding, lowScale) |
| 331 | + addEncoding(midPreset.encoding, midScale) | ||
| 329 | } else { | 332 | } else { |
| 330 | val hasEvenDimensions = checkEvenDimensions(lowPreset.capture) | 333 | val hasEvenDimensions = checkEvenDimensions(lowPreset.capture) |
| 331 | val lowScale = if (hasEvenDimensions) calculateScale(lowPreset.capture) else 2.0 | 334 | val lowScale = if (hasEvenDimensions) calculateScale(lowPreset.capture) else 2.0 |
| 332 | - encodings.add( | ||
| 333 | - lowPreset.encoding.toRtpEncoding( | ||
| 334 | - "h", | ||
| 335 | - lowScale | ||
| 336 | - ) | ||
| 337 | - ) | 335 | + addEncoding(lowPreset.encoding, lowScale) |
| 338 | } | 336 | } |
| 337 | + | ||
| 338 | + addEncoding(encoding, 1.0) | ||
| 339 | } else { | 339 | } else { |
| 340 | encodings.add(encoding.toRtpEncoding()) | 340 | encodings.add(encoding.toRtpEncoding()) |
| 341 | } | 341 | } |
| @@ -345,19 +345,63 @@ internal constructor( | @@ -345,19 +345,63 @@ internal constructor( | ||
| 345 | private fun determineAppropriateEncoding(width: Int, height: Int): VideoEncoding { | 345 | private fun determineAppropriateEncoding(width: Int, height: Int): VideoEncoding { |
| 346 | val presets = presetsForResolution(width, height) | 346 | val presets = presetsForResolution(width, height) |
| 347 | 347 | ||
| 348 | + // presets assume width is longest size | ||
| 349 | + val longestSize = max(width, height) | ||
| 348 | val preset = presets | 350 | val preset = presets |
| 349 | - .lastOrNull { width >= it.capture.width && height >= it.capture.height } | ||
| 350 | - ?: presets.first() | 351 | + .firstOrNull { it.capture.width >= longestSize} |
| 352 | + ?: presets.last() | ||
| 351 | 353 | ||
| 352 | return preset.encoding | 354 | return preset.encoding |
| 353 | } | 355 | } |
| 354 | 356 | ||
| 355 | private fun presetsForResolution(width: Int, height: Int): List<VideoPreset> { | 357 | private fun presetsForResolution(width: Int, height: Int): List<VideoPreset> { |
| 356 | - val aspectRatio = width.toFloat() / height | ||
| 357 | - if (abs(aspectRatio - 16f / 9f) < abs(aspectRatio - 4f / 3f)) { | ||
| 358 | - return PRESETS_16_9 | 358 | + val longestSize = max(width, height) |
| 359 | + val shortestSize = min(width, height) | ||
| 360 | + val aspectRatio = longestSize.toFloat() / shortestSize | ||
| 361 | + return if (abs(aspectRatio - 16f / 9f) < abs(aspectRatio - 4f / 3f)) { | ||
| 362 | + PRESETS_16_9 | ||
| 363 | + } else { | ||
| 364 | + PRESETS_4_3 | ||
| 365 | + } | ||
| 366 | + } | ||
| 367 | + | ||
| 368 | + private fun videoLayersFromEncodings( | ||
| 369 | + trackWidth: Int, | ||
| 370 | + trackHeight: Int, | ||
| 371 | + encodings: List<RtpParameters.Encoding> | ||
| 372 | + ): List<LivekitModels.VideoLayer> { | ||
| 373 | + return if (encodings.isEmpty()) { | ||
| 374 | + listOf( | ||
| 375 | + LivekitModels.VideoLayer.newBuilder().apply { | ||
| 376 | + width = trackWidth | ||
| 377 | + height = trackHeight | ||
| 378 | + quality = LivekitModels.VideoQuality.HIGH | ||
| 379 | + bitrate = 0 | ||
| 380 | + }.build() | ||
| 381 | + ) | ||
| 359 | } else { | 382 | } else { |
| 360 | - return PRESETS_4_3 | 383 | + encodings.map { |
| 384 | + val scale = it.scaleResolutionDownBy ?: 1.0 | ||
| 385 | + var videoQuality = videoQualityForRid(it.rid ?: "") | ||
| 386 | + if (videoQuality == LivekitModels.VideoQuality.UNRECOGNIZED && encodings.size == 1) { | ||
| 387 | + videoQuality = LivekitModels.VideoQuality.HIGH | ||
| 388 | + } | ||
| 389 | + LivekitModels.VideoLayer.newBuilder().apply { | ||
| 390 | + width = (trackWidth * scale).roundToInt() | ||
| 391 | + height = (trackHeight * scale).roundToInt() | ||
| 392 | + quality = videoQuality | ||
| 393 | + bitrate = it.maxBitrateBps ?: 0 | ||
| 394 | + }.build() | ||
| 395 | + } | ||
| 396 | + } | ||
| 397 | + } | ||
| 398 | + | ||
| 399 | + private fun videoQualityForRid(rid: String): LivekitModels.VideoQuality { | ||
| 400 | + return when (rid) { | ||
| 401 | + "f" -> LivekitModels.VideoQuality.HIGH | ||
| 402 | + "h" -> LivekitModels.VideoQuality.MEDIUM | ||
| 403 | + "q" -> LivekitModels.VideoQuality.LOW | ||
| 404 | + else -> LivekitModels.VideoQuality.UNRECOGNIZED | ||
| 361 | } | 405 | } |
| 362 | } | 406 | } |
| 363 | 407 | ||
| @@ -446,6 +490,8 @@ internal constructor( | @@ -446,6 +490,8 @@ internal constructor( | ||
| 446 | } | 490 | } |
| 447 | 491 | ||
| 448 | companion object { | 492 | companion object { |
| 493 | + private val VIDEO_RIDS = arrayOf("q", "h", "f") | ||
| 494 | + | ||
| 449 | // Note: maintain order from smallest to biggest. | 495 | // Note: maintain order from smallest to biggest. |
| 450 | private val PRESETS_16_9 = listOf( | 496 | private val PRESETS_16_9 = listOf( |
| 451 | VideoPreset169.QVGA, | 497 | VideoPreset169.QVGA, |
-
请 注册 或 登录 后发表评论