David Liu

more accurate dimensions for video tracks

@@ -3,7 +3,11 @@ package io.livekit.android.room.track @@ -3,7 +3,11 @@ package io.livekit.android.room.track
3 import android.Manifest 3 import android.Manifest
4 import android.content.Context 4 import android.content.Context
5 import android.content.pm.PackageManager 5 import android.content.pm.PackageManager
  6 +import android.hardware.camera2.CameraManager
6 import androidx.core.content.ContextCompat 7 import androidx.core.content.ContextCompat
  8 +import io.livekit.android.room.track.video.Camera1CapturerWithSize
  9 +import io.livekit.android.room.track.video.Camera2CapturerWithSize
  10 +import io.livekit.android.room.track.video.VideoCapturerWithSize
7 import io.livekit.android.util.LKLog 11 import io.livekit.android.util.LKLog
8 import org.webrtc.* 12 import org.webrtc.*
9 import java.util.* 13 import java.util.*
@@ -28,14 +32,17 @@ open class LocalVideoTrack( @@ -28,14 +32,17 @@ open class LocalVideoTrack(
28 override var rtcTrack: org.webrtc.VideoTrack = rtcTrack 32 override var rtcTrack: org.webrtc.VideoTrack = rtcTrack
29 internal set 33 internal set
30 34
31 - /**  
32 - * Note: these dimensions are only requested params, and may differ  
33 - * from the actual capture format used by the camera.  
34 - *  
35 - * TODO: capture actual dimensions used  
36 - */  
37 val dimensions: Dimensions 35 val dimensions: Dimensions
38 - get() = Dimensions(options.captureParams.width, options.captureParams.height) 36 + get() {
  37 + (capturer as? VideoCapturerWithSize)?.let { capturerWithSize ->
  38 + val size = capturerWithSize.findCaptureFormat(
  39 + options.captureParams.width,
  40 + options.captureParams.height
  41 + )
  42 + return Dimensions(size.width, size.height)
  43 + }
  44 + return Dimensions(options.captureParams.width, options.captureParams.height)
  45 + }
39 46
40 internal var transceiver: RtpTransceiver? = null 47 internal var transceiver: RtpTransceiver? = null
41 private val sender: RtpSender? 48 private val sender: RtpSender?
@@ -127,9 +134,9 @@ open class LocalVideoTrack( @@ -127,9 +134,9 @@ open class LocalVideoTrack(
127 134
128 private fun createVideoCapturer(context: Context, position: CameraPosition): VideoCapturer? { 135 private fun createVideoCapturer(context: Context, position: CameraPosition): VideoCapturer? {
129 val videoCapturer: VideoCapturer? = if (Camera2Enumerator.isSupported(context)) { 136 val videoCapturer: VideoCapturer? = if (Camera2Enumerator.isSupported(context)) {
130 - createCameraCapturer(Camera2Enumerator(context), position) 137 + createCameraCapturer(context, Camera2Enumerator(context), position)
131 } else { 138 } else {
132 - createCameraCapturer(Camera1Enumerator(true), position) 139 + createCameraCapturer(context, Camera1Enumerator(true), position)
133 } 140 }
134 if (videoCapturer == null) { 141 if (videoCapturer == null) {
135 LKLog.d { "Failed to open camera" } 142 LKLog.d { "Failed to open camera" }
@@ -138,26 +145,47 @@ open class LocalVideoTrack( @@ -138,26 +145,47 @@ open class LocalVideoTrack(
138 return videoCapturer 145 return videoCapturer
139 } 146 }
140 147
141 - private fun createCameraCapturer(enumerator: CameraEnumerator, position: CameraPosition): VideoCapturer? { 148 + private fun createCameraCapturer(
  149 + context: Context,
  150 + enumerator: CameraEnumerator,
  151 + position: CameraPosition
  152 + ): VideoCapturer? {
142 val deviceNames = enumerator.deviceNames 153 val deviceNames = enumerator.deviceNames
143 - 154 + var targetDeviceName: String? = null
  155 + var targetVideoCapturer: VideoCapturer? = null
144 for (deviceName in deviceNames) { 156 for (deviceName in deviceNames) {
145 if (enumerator.isFrontFacing(deviceName) && position == CameraPosition.FRONT) { 157 if (enumerator.isFrontFacing(deviceName) && position == CameraPosition.FRONT) {
146 LKLog.v { "Creating front facing camera capturer." } 158 LKLog.v { "Creating front facing camera capturer." }
147 val videoCapturer = enumerator.createCapturer(deviceName, null) 159 val videoCapturer = enumerator.createCapturer(deviceName, null)
148 if (videoCapturer != null) { 160 if (videoCapturer != null) {
149 - return videoCapturer 161 + targetDeviceName = deviceName
  162 + targetVideoCapturer = videoCapturer
  163 + break
150 } 164 }
151 } else if (enumerator.isBackFacing(deviceName) && position == CameraPosition.BACK) { 165 } else if (enumerator.isBackFacing(deviceName) && position == CameraPosition.BACK) {
152 LKLog.v { "Creating back facing camera capturer." } 166 LKLog.v { "Creating back facing camera capturer." }
153 val videoCapturer = enumerator.createCapturer(deviceName, null) 167 val videoCapturer = enumerator.createCapturer(deviceName, null)
154 if (videoCapturer != null) { 168 if (videoCapturer != null) {
155 - return videoCapturer 169 + targetDeviceName = deviceName
  170 + targetVideoCapturer = videoCapturer
  171 + break
156 } 172 }
157 } 173 }
158 } 174 }
  175 +
  176 + if (targetVideoCapturer is Camera1Capturer) {
  177 + return Camera1CapturerWithSize(targetVideoCapturer, targetDeviceName)
  178 + }
  179 +
  180 + if (targetVideoCapturer is Camera2Capturer) {
  181 + return Camera2CapturerWithSize(
  182 + targetVideoCapturer,
  183 + context.getSystemService(Context.CAMERA_SERVICE) as CameraManager,
  184 + targetDeviceName
  185 + )
  186 + }
  187 +
159 return null 188 return null
160 } 189 }
161 -  
162 } 190 }
163 -}  
  191 +}
  1 +package io.livekit.android.room.track.video
  2 +
  3 +import android.hardware.camera2.CameraManager
  4 +import org.webrtc.*
  5 +
  6 +/**
  7 + * @suppress
  8 + */
  9 +internal interface VideoCapturerWithSize : VideoCapturer {
  10 + fun findCaptureFormat(width: Int, height: Int): Size
  11 +}
  12 +
  13 +/**
  14 + * @suppress
  15 + */
  16 +internal class Camera1CapturerWithSize(
  17 + private val capturer: Camera1Capturer,
  18 + private val deviceName: String?
  19 +) : VideoCapturer by capturer, VideoCapturerWithSize {
  20 + override fun findCaptureFormat(width: Int, height: Int): Size {
  21 + val cameraId = Camera1Helper.getCameraId(deviceName)
  22 + return Camera1Helper.findClosestCaptureFormat(cameraId, width, height)
  23 + }
  24 +}
  25 +
  26 +/**
  27 + * @suppress
  28 + */
  29 +internal class Camera2CapturerWithSize(
  30 + private val capturer: Camera2Capturer,
  31 + private val cameraManager: CameraManager,
  32 + private val deviceName: String?
  33 +) : VideoCapturer by capturer, VideoCapturerWithSize {
  34 + override fun findCaptureFormat(width: Int, height: Int): Size {
  35 + return Camera2Helper.findClosestCaptureFormat(cameraManager, deviceName, width, height)
  36 + }
  37 +}
  1 +package org.webrtc
  2 +
  3 +/**
  4 + * A helper to access package-protected methods used in [Camera2Session]
  5 + * @suppress
  6 + */
  7 +internal class Camera1Helper {
  8 + companion object {
  9 + fun getCameraId(deviceName: String?) = Camera1Enumerator.getCameraIndex(deviceName)
  10 +
  11 + fun findClosestCaptureFormat(
  12 + cameraId: Int,
  13 + width: Int,
  14 + height: Int
  15 + ): Size {
  16 + return CameraEnumerationAndroid.getClosestSupportedSize(
  17 + Camera1Enumerator.getSupportedFormats(cameraId)
  18 + .map { Size(it.width, it.height) },
  19 + width,
  20 + height
  21 + )
  22 + }
  23 + }
  24 +}
  1 +package org.webrtc
  2 +
  3 +import android.hardware.camera2.CameraManager
  4 +
  5 +/**
  6 + * A helper to access package-protected methods used in [Camera2Session]
  7 + * @suppress
  8 + */
  9 +internal class Camera2Helper {
  10 + companion object {
  11 + fun findClosestCaptureFormat(
  12 + cameraManager: CameraManager,
  13 + cameraId: String?,
  14 + width: Int,
  15 + height: Int
  16 + ): Size {
  17 + val sizes = Camera2Enumerator.getSupportedFormats(cameraManager, cameraId)
  18 + ?.map { Size(it.width, it.height) }
  19 + ?: emptyList()
  20 + return CameraEnumerationAndroid.getClosestSupportedSize(sizes, width, height)
  21 + }
  22 + }
  23 +}