From fa90a6458252e3bcaaf8aa4f923a73c16e3e5d03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com> Date: Fri, 26 Nov 2021 11:36:35 -0500 Subject: [PATCH] video: manage camera per id Change-Id: I55b7ad40de20f613e95e76823faf5b5904b96226 --- .../java/cx/ring/services/CameraService.kt | 335 +++++++++++------- .../cx/ring/services/HardwareServiceImpl.kt | 114 ++---- .../kotlin/net/jami/call/CallPresenter.kt | 2 +- .../kotlin/net/jami/services/DaemonService.kt | 6 +- .../net/jami/services/HardwareService.kt | 12 +- 5 files changed, 251 insertions(+), 218 deletions(-) diff --git a/ring-android/app/src/main/java/cx/ring/services/CameraService.kt b/ring-android/app/src/main/java/cx/ring/services/CameraService.kt index 6c5f09d6f..0a930464c 100644 --- a/ring-android/app/src/main/java/cx/ring/services/CameraService.kt +++ b/ring-android/app/src/main/java/cx/ring/services/CameraService.kt @@ -60,30 +60,38 @@ import kotlin.math.max class CameraService internal constructor(c: Context) { private val manager = c.getSystemService(Context.CAMERA_SERVICE) as CameraManager? - private val mParams = HashMap<String?, VideoParams>() + private val mParams = HashMap<String, VideoParams>() private val mNativeParams: MutableMap<String, DeviceParams> = HashMap() private val t = HandlerThread("videoHandler") private val videoLooper: Looper get() = t.apply { if (state == Thread.State.NEW) start() }.looper - val videoHandler: Handler by lazy { Handler(videoLooper) } - private var previewCamera: CameraDevice? = null - private var currentMediaProjection: MediaProjection? = null - private var virtualDisplay: VirtualDisplay? = null - private var currentCodec: MediaCodec? = null + private val videoHandler: Handler by lazy { Handler(videoLooper) } // SPS and PPS NALs (Config Data). private var codecData: ByteBuffer? = null private val maxResolutionSubject: Subject<Pair<Int?, Int?>> = BehaviorSubject.createDefault(RESOLUTION_NONE) - protected var devices: VideoDevices? = null - private var previewParams: VideoParams? = null + private var devices: VideoDevices? = null private val availabilityCallback: AvailabilityCallback = object : AvailabilityCallback() { override fun onCameraAvailable(cameraId: String) { Log.w(TAG, "onCameraAvailable $cameraId") - filterCompatibleCamera(arrayOf(cameraId), manager!!).forEach { camera: Pair<String, CameraCharacteristics> -> + filterCompatibleCamera( + arrayOf(cameraId), + manager!! + ).forEach { camera: Pair<String, CameraCharacteristics> -> + val devices = devices ?: return synchronized(addedDevices) { - if (addedDevices.add(camera.first)) { - if (!devices!!.cameras.contains(camera.first)) devices!!.cameras.add(camera.first) - JamiService.addVideoDevice(camera.first) + if (!devices.cameras.contains(camera.first)) { + when (camera.second.get(CameraCharacteristics.LENS_FACING)) { + CameraCharacteristics.LENS_FACING_FRONT -> if (devices.cameraFront == null) { + devices.addCamera(camera.first) + devices.cameraFront = camera.first + } + CameraCharacteristics.LENS_FACING_BACK -> if (devices.cameraBack == null) { + devices.addCamera(camera.first) + devices.cameraBack = camera.first + } + else -> devices.addCamera(camera.first) + } } } } @@ -92,9 +100,14 @@ class CameraService internal constructor(c: Context) { override fun onCameraUnavailable(cameraId: String) { if (devices == null || devices!!.currentId == null || devices!!.currentId != cameraId) { synchronized(addedDevices) { + val devices = devices ?: return + devices.cameras.remove(cameraId) + if (devices.cameraFront == cameraId) + devices.cameraFront = null + if (devices.cameraBack == cameraId) + devices.cameraBack = null if (addedDevices.remove(cameraId)) { - Log.w(TAG, "onCameraUnavailable $cameraId current:$previewCamera") - devices!!.cameras.remove(cameraId) + Log.w(TAG, "onCameraUnavailable $cameraId") JamiService.removeVideoDevice(cameraId) } } @@ -109,6 +122,7 @@ class CameraService internal constructor(c: Context) { var currentId: String? = null var currentIndex = 0 var cameraFront: String? = null + var cameraBack: String? = null fun switchInput(setDefaultCamera: Boolean): String? { if (setDefaultCamera && cameras.isNotEmpty()) { currentId = cameras[0] @@ -121,6 +135,13 @@ class CameraService internal constructor(c: Context) { return currentId } + fun addCamera(cameraId: String) { + cameras.add(cameraId) + if (addedDevices.add(cameraId)) { + JamiService.addVideoDevice(cameraId) + } + } + companion object { const val SCREEN_SHARING = "desktop" } @@ -134,10 +155,7 @@ class CameraService internal constructor(c: Context) { Log.w(TAG, "getParams() $camId") if (camId != null) { return mParams[camId] - } else if (previewParams != null) { - Log.w(TAG, "getParams() previewParams") - return previewParams - } else if (devices != null && devices!!.cameras.isNotEmpty()) { + } else if (!devices?.cameras.isNullOrEmpty()) { Log.w(TAG, "getParams() fallback") devices!!.currentId = devices!!.cameras[0] return mParams[devices!!.currentId] @@ -145,10 +163,6 @@ class CameraService internal constructor(c: Context) { return null } - fun setPreviewParams(params: VideoParams?) { - previewParams = params - } - fun setParameters(camId: String, format: Int, width: Int, height: Int, rate: Int, rotation: Int) { Log.w(TAG, "setParameters() $camId $format $width $height $rate $rotation") val deviceParams = mNativeParams[camId] @@ -161,8 +175,6 @@ class CameraService internal constructor(c: Context) { params = VideoParams(camId, deviceParams.size.x, deviceParams.size.y, rate) mParams[camId] = params } else { - //params.id = camId; - //params.format = format; params.width = deviceParams.size.x params.height = deviceParams.size.y params.rate = rate @@ -188,14 +200,20 @@ class CameraService internal constructor(c: Context) { JamiService.setDeviceOrientation(camId, rotation) } - fun getCameraInfo(camId: String, formats: IntVect?, sizes: UintVect, rates: UintVect, minVideoSize: Point, context: Context) { - Log.d(TAG, "getCameraInfo: $camId min. size: $minVideoSize") + fun getCameraInfo( + camId: String, + formats: IntVect?, + sizes: UintVect, + rates: UintVect, + minVideoSize: Point, + context: Context + ) { + Log.d(TAG, "getCameraInfo: $camId min size: $minVideoSize") val p = DeviceParams() rates.clear() fillCameraInfo(p, camId, formats, sizes, rates, minVideoSize, context) sizes.add(p.size.x.toLong()) sizes.add(p.size.y.toLong()) - Log.d(TAG, "getCameraInfo: " + camId + " max. size: " + p.maxSize + " size:" + p.size) mNativeParams[camId] = p } @@ -223,13 +241,20 @@ class CameraService internal constructor(c: Context) { return cameraCount > 0 } - class VideoParams(val id: String, var width: Int, var height: Int, var rate: Int) { + data class VideoParams(val id: String, var width: Int, var height: Int, var rate: Int) { val inputUri: String = "camera://$id" //public int format; // size as captured by Android var rotation = 0 var codec: String? = null + var isCapturing: Boolean = false + var camera: CameraDevice? = null + var projection: MediaProjection? = null + var display: VirtualDisplay? = null + var mediaCodec: MediaCodec? = null + var codecStarted: Boolean = false + fun getAndroidCodec(): String { return when (val codec = codec) { "H264" -> MediaFormat.MIMETYPE_VIDEO_AVC @@ -260,17 +285,20 @@ class CameraService internal constructor(c: Context) { private fun loadDevices(manager: CameraManager): Single<VideoDevices> { return Single.fromCallable { val devices = VideoDevices() - val cameras = filterCompatibleCamera(manager.cameraIdList, manager).toList() - val backCamera = filterCameraIdsFacing(cameras, CameraCharacteristics.LENS_FACING_BACK).first() - val frontCamera = filterCameraIdsFacing(cameras, CameraCharacteristics.LENS_FACING_FRONT).first() + val cameras = filterCompatibleCamera(manager.cameraIdList, manager) + val backCamera = filterCameraIdsFacing(cameras, CameraCharacteristics.LENS_FACING_BACK).firstOrNull() + val frontCamera = filterCameraIdsFacing(cameras, CameraCharacteristics.LENS_FACING_FRONT).firstOrNull() val externalCameras: List<String> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { filterCameraIdsFacing(cameras, CameraCharacteristics.LENS_FACING_EXTERNAL) } else { emptyList() } - (listOf(frontCamera, backCamera) + externalCameras).forEach { id -> devices.cameras.add(id) } + if (frontCamera != null) devices.cameras.add(frontCamera) + if (backCamera != null) devices.cameras.add(backCamera) + devices.cameras.addAll(externalCameras) if (devices.cameras.isNotEmpty()) devices.currentId = devices.cameras[0] devices.cameraFront = frontCamera + devices.cameraBack = backCamera Log.w(TAG, "Loading video devices: found " + devices.cameras.size) devices }.subscribeOn(AndroidSchedulers.from(videoLooper)) @@ -309,7 +337,7 @@ class CameraService internal constructor(c: Context) { devs } .ignoreElement() - .doOnError { e: Throwable? -> + .doOnError { e: Throwable -> Log.e(TAG, "Error initializing video device", e) maxResolutionSubject.onNext(RESOLUTION_NONE) } @@ -327,13 +355,18 @@ class CameraService internal constructor(c: Context) { fun onError() } - fun closeCamera() { - previewCamera?.let { camera -> - previewCamera = null - camera.close() - currentCodec = null + fun closeCamera(camId: String) { + mParams[camId]?.let { params -> + params.camera?.let { camera -> + camera.close() + params.camera = null + } + params.projection?.let { mediaProjection -> + mediaProjection.stop() + params.projection = null + } + params.isCapturing = false } - stopScreenSharing() } private fun openEncoder( @@ -341,11 +374,12 @@ class CameraService internal constructor(c: Context) { mimeType: String?, handler: Handler, resolution: Int, - bitrate: Int + bitrate: Int, + surface: Surface? = null ): Pair<MediaCodec?, Surface?> { Log.d(TAG, "Video with codec " + mimeType + " resolution: " + videoParams.width + "x" + videoParams.height + " Bitrate: " + bitrate) val bitrateValue: Int = if (bitrate == 0) if (resolution >= 720) 192 * 8 * 1024 else 100 * 8 * 1024 else bitrate * 8 * 1024 - val frameRate = 30 // 30 fps + val frameRate = videoParams.rate//30 // 30 fps val format = MediaFormat.createVideoFormat(mimeType!!, videoParams.width, videoParams.height).apply { setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0) setInteger(MediaFormat.KEY_BIT_RATE, bitrateValue) @@ -375,7 +409,7 @@ class CameraService internal constructor(c: Context) { params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrateValue) codec.setParameters(params) codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) - encoderInput = codec.createInputSurface() + encoderInput = /*surface?.also { codec!!.setInputSurface(it) } ?:*/ codec.createInputSurface() val callback: MediaCodec.Callback = object : MediaCodec.Callback() { override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {} override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) { @@ -383,8 +417,9 @@ class CameraService internal constructor(c: Context) { if (info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM == 0) { // Get and cache the codec data (SPS/PPS NALs) val isConfigFrame = info.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0 + val buffer = codec.getOutputBuffer(index) if (isConfigFrame) { - codec.getOutputBuffer(index)?.let { outputBuffer -> + buffer?.let { outputBuffer -> outputBuffer.position(info.offset) outputBuffer.limit(info.offset + info.size) codecData = ByteBuffer.allocateDirect(info.size).apply { @@ -393,22 +428,36 @@ class CameraService internal constructor(c: Context) { } Log.i(TAG, "Cache new codec data (SPS/PPS, ...)") } - codec.releaseOutputBuffer(index, false) } else { val isKeyFrame = info.flags and MediaCodec.BUFFER_FLAG_KEY_FRAME != 0 // If it's a key-frame, send the cached SPS/PPS NALs prior to // sending key-frame. if (isKeyFrame) { codecData?.let { data -> - JamiService.captureVideoPacket(videoParams.inputUri, data, data.capacity(), 0, false, info.presentationTimeUs, videoParams.rotation) + JamiService.captureVideoPacket( + videoParams.inputUri, + data, + data.capacity(), + 0, + false, + info.presentationTimeUs, + videoParams.rotation + ) } } // Send the encoded frame - val buffer = codec.getOutputBuffer(index) - JamiService.captureVideoPacket(videoParams.inputUri, buffer, info.size, info.offset, isKeyFrame, info.presentationTimeUs,videoParams.rotation) - codec.releaseOutputBuffer(index, false) + JamiService.captureVideoPacket( + videoParams.inputUri, + buffer, + info.size, + info.offset, + isKeyFrame, + info.presentationTimeUs, + videoParams.rotation + ) } + codec.releaseOutputBuffer(index, false) } } catch (e: IllegalStateException) { Log.e(TAG, "MediaCodec can't process buffer", e) @@ -445,45 +494,52 @@ class CameraService internal constructor(c: Context) { fun requestKeyFrame() { Log.w(TAG, "requestKeyFrame()") try { - if (currentCodec != null) { + /*if (currentCodec != null) { val params = Bundle() params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0) currentCodec!!.setParameters(params) - } + }*/ } catch (e: IllegalStateException) { Log.w(TAG, "Can't send keyframe request", e) } } - fun setBitrate(bitrate: Int) { - Log.w(TAG, "setBitrate() $bitrate") + fun setBitrate(camId: String, bitrate: Int) { + Log.w(TAG, "setBitrate() $camId $bitrate") try { - if (currentCodec != null) { - val params = Bundle() - params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrate * 1024) - currentCodec!!.setParameters(params) - } + mParams[camId]?.mediaCodec?.setParameters(Bundle().apply { + putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrate * 1024) + }) } catch (e: IllegalStateException) { Log.w(TAG, "Can't set bitrate", e) } } - private fun createVirtualDisplay(projection: MediaProjection, metrics: DisplayMetrics): Pair<MediaCodec?, VirtualDisplay>? { - var screenWidth = metrics.widthPixels - var screenHeight = metrics.heightPixels + private fun createVirtualDisplay( + params: VideoParams, + projection: MediaProjection, + surface: TextureView, + metrics: DisplayMetrics + ): Pair<MediaCodec?, VirtualDisplay>? { val screenDensity = metrics.densityDpi val handler = videoHandler var r: Pair<MediaCodec?, Surface?>? = null - while (max(screenWidth, screenHeight) > 1920) { - screenWidth /= 2 - screenHeight /= 2 - } - while (screenWidth >= 320) { - val params = VideoParams(VideoDevices.SCREEN_SHARING, screenWidth, screenHeight, 24) - r = openEncoder(params, MediaFormat.MIMETYPE_VIDEO_AVC, handler, 720, 0) + if (params.rate == 0) { + params.rate = 24 + } + while (params.width >= 320) { + Log.e(TAG, "createVirtualDisplay ${params.width}x${params.height} at ${params.rate}") + r = openEncoder( + params, + MediaFormat.MIMETYPE_VIDEO_AVC, + handler, + 720, + 0, + surface = Surface(surface.surfaceTexture) + ) if (r.first == null) { - screenWidth /= 2 - screenHeight /= 2 + params.width /= 2 + params.height /= 2 } else break } if (r == null) return null @@ -491,27 +547,30 @@ class CameraService internal constructor(c: Context) { val codec = r.first codec?.start() return try { - Pair(codec, projection.createVirtualDisplay("ScreenSharing", screenWidth, screenHeight, screenDensity, - DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface, object : VirtualDisplay.Callback() { - override fun onPaused() { - Log.w(TAG, "VirtualDisplay.onPaused") - } + Pair( + codec, projection.createVirtualDisplay( + "ScreenSharing", params.width, params.height, screenDensity, + DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, surface, object : VirtualDisplay.Callback() { + override fun onPaused() { + Log.w(TAG, "VirtualDisplay.onPaused") + } - override fun onResumed() { - Log.w(TAG, "VirtualDisplay.onResumed") - } + override fun onResumed() { + Log.w(TAG, "VirtualDisplay.onResumed") + } - override fun onStopped() { - Log.w(TAG, "VirtualDisplay.onStopped") - if (surface != null) { - surface.release() - codec?.release() - if (currentCodec == codec) currentCodec = null + override fun onStopped() { + Log.w(TAG, "VirtualDisplay.onStopped") + if (surface != null) { + surface.release() + codec?.release() + } } - } - }, handler - )) + }, handler + ) + ) } catch (e: Exception) { + Log.e(TAG, "Exception creating virtual display", e) if (codec != null) { codec.stop() codec.release() @@ -521,28 +580,37 @@ class CameraService internal constructor(c: Context) { } } - fun startScreenSharing(mediaProjection: MediaProjection, metrics: DisplayMetrics): Boolean { - val r = createVirtualDisplay(mediaProjection, metrics) + fun startScreenSharing( + params: VideoParams, + mediaProjection: MediaProjection, + surface: TextureView, + metrics: DisplayMetrics + ): Boolean { + val r = createVirtualDisplay(params, mediaProjection, surface, metrics) if (r != null) { - currentMediaProjection = mediaProjection - currentCodec = r.first - virtualDisplay = r.second + mediaProjection.registerCallback(object : MediaProjection.Callback() { + override fun onStop() { + params.mediaCodec?.let { codec -> + codec.signalEndOfInputStream() + codec.stop() + codec.release() + params.mediaCodec = null + } + params.display?.release() + params.display = null + params.codecStarted = false + } + }, videoHandler) + params.codecStarted = true + params.mediaCodec = r.first + params.projection = mediaProjection + params.display = r.second return true } + mediaProjection.stop() return false } - fun stopScreenSharing() { - if (virtualDisplay != null) { - virtualDisplay!!.release() - virtualDisplay = null - } - if (currentMediaProjection != null) { - currentMediaProjection!!.stop() - currentMediaProjection = null - } - } - fun openCamera( videoParams: VideoParams, surface: TextureView, @@ -551,7 +619,7 @@ class CameraService internal constructor(c: Context) { resolution: Int, bitrate: Int ) { - previewCamera?.close() + //previewCamera?.close() val handler = videoHandler try { val view = surface as AutoFitTextureView @@ -589,12 +657,13 @@ class CameraService internal constructor(c: Context) { targets.add(tmpReader.surface) } val reader = tmpReader - val codecStarted = booleanArrayOf(false) manager.openCamera(videoParams.id, object : CameraDevice.StateCallback() { override fun onOpened(camera: CameraDevice) { try { Log.w(TAG, "onOpened " + videoParams.id) - previewCamera = camera + //previewCamera = camera + videoParams.camera = camera + videoParams.mediaCodec = codec?.first texture!!.setDefaultBufferSize(previewSize.width, previewSize.height) val builder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) builder.addTarget(s) @@ -615,11 +684,16 @@ class CameraService internal constructor(c: Context) { try { session.setRepeatingRequest(request, if (codec?.second != null) object : CaptureCallback() { - override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long) { + override fun onCaptureStarted( + session: CameraCaptureSession, + request: CaptureRequest, + timestamp: Long, + frameNumber: Long + ) { if (frameNumber == 1L) { try { - codec.first!!.start() - codecStarted[0] = true + videoParams.mediaCodec?.start() + videoParams.codecStarted = true } catch (e: Exception) { listener.onError() } @@ -627,9 +701,6 @@ class CameraService internal constructor(c: Context) { } } else null, handler) - if (codec?.first != null) { - currentCodec = codec.first - } } catch (e: Exception) { Log.w(TAG, "onConfigured error:", e) camera.close() @@ -665,16 +736,14 @@ class CameraService internal constructor(c: Context) { override fun onClosed(camera: CameraDevice) { Log.w(TAG, "onClosed") try { - if (previewCamera === camera) previewCamera = null - if (codec != null) { - if (codec.first != null) { - if (codecStarted[0]) codec.first!!.signalEndOfInputStream() - codec.first!!.release() - if (codec.first == currentCodec) currentCodec = null - codecStarted[0] = false - } - if (codec.second != null) codec.second!!.release() + videoParams.mediaCodec?.let { mediaCodec -> + if (videoParams.codecStarted) + mediaCodec.signalEndOfInputStream() + mediaCodec.release() + videoParams.mediaCodec = null + videoParams.codecStarted = false } + codec?.second?.release() reader?.close() s.release() } catch (e: Exception) { @@ -689,8 +758,6 @@ class CameraService internal constructor(c: Context) { } } - val isOpen: Boolean - get() = previewCamera != null || currentMediaProjection != null val cameraIds: List<String> get() = devices?.cameras ?: ArrayList() val cameraCount: Int @@ -715,12 +782,14 @@ class CameraService internal constructor(c: Context) { val metrics = context.resources.displayMetrics var screenWidth = metrics.widthPixels var screenHeight = metrics.heightPixels - while (max(screenWidth, screenHeight) > 1920) { + while (max(screenWidth, screenHeight) > max(minVideoSize.x, minVideoSize.y)) { screenWidth /= 2 screenHeight /= 2 } p.size.x = screenWidth p.size.y = screenHeight + p.rate = 24 + rates.add(p.rate) return } val cc = manager.getCameraCharacteristics(camId) @@ -792,7 +861,10 @@ class CameraService internal constructor(c: Context) { return (180 - rotation + 180) % 360 } - private fun filterCompatibleCamera(cameras: Array<String>, cameraManager: CameraManager): List<Pair<String, CameraCharacteristics>> { + private fun filterCompatibleCamera( + cameras: Array<String>, + cameraManager: CameraManager + ): List<Pair<String, CameraCharacteristics>> { return cameras.map { id: String -> Pair(id, cameraManager.getCameraCharacteristics(id)) } .filter { camera: Pair<String, CameraCharacteristics> -> try { @@ -806,12 +878,14 @@ class CameraService internal constructor(c: Context) { } } - private fun filterCameraIdsFacing(cameras: List<Pair<String, CameraCharacteristics>>, facing: Int): List<String> { + private fun filterCameraIdsFacing( + cameras: List<Pair<String, CameraCharacteristics>>, + facing: Int + ): List<String> { return cameras.filter { camera -> camera.second.get(CameraCharacteristics.LENS_FACING) == facing } .map { camera -> camera.first } } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private fun listSupportedCodecs(list: MediaCodecList) { try { for (codecInfo in list.codecInfos) { @@ -862,7 +936,14 @@ class CameraService internal constructor(c: Context) { codec.setCallback(callback, handler) } - private fun chooseOptimalSize(choices: Array<Size>?, textureViewWidth: Int, textureViewHeight: Int, maxWidth: Int, maxHeight: Int, target: Size): Size { + private fun chooseOptimalSize( + choices: Array<Size>?, + textureViewWidth: Int, + textureViewHeight: Int, + maxWidth: Int, + maxHeight: Int, + target: Size + ): Size { if (choices == null) return target // Collect the supported resolutions that are at least as big as the preview Surface val bigEnough: MutableList<Size> = ArrayList() diff --git a/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.kt b/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.kt index 5ff08f52d..8bd6a922b 100644 --- a/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.kt +++ b/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.kt @@ -68,11 +68,8 @@ class HardwareServiceImpl( private val mAudioManager: AudioManager = mContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager private var mBluetoothWrapper: BluetoothWrapper? = null private var currentFocus: AudioFocusRequestCompat? = null - private var mCapturingId: String? = null - private var mIsCapturing = false - private var mIsScreenSharing = false private var pendingScreenSharingSession: MediaProjection? = null - private var mShouldCapture = false + private val shouldCapture = HashSet<String>() private var mShouldSpeakerphone = false private val mHasSpeakerPhone: Boolean = hasSpeakerphone() private var mIsChoosePlugin = false @@ -376,34 +373,6 @@ class HardwareServiceImpl( cameraService.setParameters(camId, format, width, height, rate, windowManager.defaultDisplay.rotation) } - fun startScreenShare(projection: MediaProjection, surface: TextureView): Boolean { - Log.d(TAG, "startScreenShare") - return if (!mIsScreenSharing && projection != null) { - mIsScreenSharing = true - projection.registerCallback(object : MediaProjection.Callback() { - override fun onStop() { - stopScreenShare() - } - }, cameraService.videoHandler) - if (!cameraService.startScreenSharing(projection, mContext.resources.displayMetrics)) { - mIsScreenSharing = false - projection.stop() - return false - } - true - } else { - false - } - } - - override fun stopScreenShare() { - if (mIsScreenSharing) { - cameraService.stopScreenSharing() - mIsScreenSharing = false - if (mShouldCapture) startCapture(mCapturingId) - } - } - override fun startMediaHandler(mediaHandlerId: String?) { mIsChoosePlugin = true mMediaHandlerId = mediaHandlerId @@ -419,21 +388,17 @@ class HardwareServiceImpl( } override fun startCapture(camId: String?) { - Log.w(TAG, "startCapture: $camId") - mShouldCapture = true - if (mIsCapturing && mCapturingId != null && mCapturingId == camId) { - return - } - val cam = camId ?: mCapturingId ?: cameraService.switchInput(true) - val videoParams = cameraService.getParams(cam) - if (videoParams == null) { - Log.w(TAG, "startCapture: no video parameters ") + val cam = camId ?: cameraService.switchInput(true) ?: return + Log.w(TAG, "DEBUG startCapture: $camId $cam") + shouldCapture.add(cam) + val videoParams = cameraService.getParams(cam) ?: return + if (videoParams.isCapturing) { return } val surface = mCameraPreviewSurface.get() if (surface == null) { Log.w(TAG, "Can't start capture: no surface registered.") - cameraService.setPreviewParams(videoParams) + //cameraService.setPreviewParams(videoParams) videoEvents.onNext(VideoEvent(start = true)) return } @@ -449,14 +414,17 @@ class HardwareServiceImpl( videoParams.codec = null } } - Log.w(TAG, "startCapture: cam " + cam + " " + videoParams.codec + " useHardwareCodec:" + useHardwareCodec + " bitrate:" + mPreferenceService.bitrate) - mIsCapturing = true - mCapturingId = videoParams.id - Log.d(TAG, "startCapture: openCamera " + videoParams.id + " " + videoParams.width + "x" + videoParams.height + " rot" + videoParams.rotation) + Log.w(TAG, + "startCapture: id:$cam codec:${videoParams.codec} size:${videoParams.width}x${videoParams.height} rot${videoParams.rotation} hw:$useHardwareCodec bitrate:${mPreferenceService.bitrate}" + ) + videoParams.isCapturing = true - if (mCapturingId == CameraService.VideoDevices.SCREEN_SHARING) { - startScreenShare(pendingScreenSharingSession!!, surface) + if (videoParams.id == CameraService.VideoDevices.SCREEN_SHARING) { + val projection = pendingScreenSharingSession ?: return pendingScreenSharingSession = null + if (!cameraService.startScreenSharing(videoParams, projection, surface, mContext.resources.displayMetrics)) { + projection.stop() + } return } @@ -478,7 +446,7 @@ class HardwareServiceImpl( } override fun onError() { - stopCapture(cam ?: "") + stopCapture(videoParams.id) } }, useHardwareCodec, @@ -486,7 +454,6 @@ class HardwareServiceImpl( mPreferenceService.bitrate ) } - cameraService.setPreviewParams(videoParams) videoEvents.onNext(VideoEvent( started = true, w = videoParams.width, @@ -496,27 +463,17 @@ class HardwareServiceImpl( } override fun stopCapture(camId: String) { - Log.d(TAG, "stopCapture: " + cameraService.isOpen) - mShouldCapture = false - endCapture() + shouldCapture.remove(camId) + cameraService.closeCamera(camId) + videoEvents.onNext(VideoEvent(started = false)) } override fun requestKeyFrame() { cameraService.requestKeyFrame() } - override fun setBitrate(device: String, bitrate: Int) { - cameraService.setBitrate(bitrate) - } - - override fun endCapture() { - if (cameraService.isOpen) { - //final CameraService.VideoParams params = previewParams; - cameraService.closeCamera() - videoEvents.onNext(VideoEvent(started = false)) - } - pendingScreenSharingSession = null - mIsCapturing = false + override fun setBitrate(camId: String, bitrate: Int) { + cameraService.setBitrate(camId, bitrate) } @Synchronized override fun addVideoSurface(id: String, holder: Any) { @@ -558,27 +515,25 @@ class HardwareServiceImpl( @Synchronized override fun addPreviewVideoSurface(holder: Any, conference: Conference?) { if (holder !is TextureView) return - Log.w(TAG, "addPreviewVideoSurface " + holder.hashCode() + " mCapturingId " + mCapturingId) if (mCameraPreviewSurface.get() === holder) return mCameraPreviewSurface = WeakReference(holder) mCameraPreviewCall = WeakReference(conference) - if (mShouldCapture && !mIsCapturing) { - startCapture(mCapturingId) - } + for (c in shouldCapture) + startCapture(c) } @Synchronized override fun updatePreviewVideoSurface(conference: Conference) { val old = mCameraPreviewCall.get() mCameraPreviewCall = WeakReference(conference) - if (old !== conference && mIsCapturing) { - val id = mCapturingId - stopCapture(id ?: "") - startCapture(id) + if (old !== conference) { + for (camId in shouldCapture) { + cameraService.closeCamera(camId) + startCapture(camId) + } } } @Synchronized override fun removeVideoSurface(id: String) { - Log.i(TAG, "removeVideoSurface $id") videoSurfaces.remove(id) val shm = videoInputs[id] ?: return if (shm.window != 0L) { @@ -593,20 +548,19 @@ class HardwareServiceImpl( } override fun removePreviewVideoSurface() { - Log.w(TAG, "removePreviewVideoSurface") mCameraPreviewSurface.clear() } override fun switchInput(accountId:String, callId: String, setDefaultCamera: Boolean, screenCaptureSession: Any?) { - Log.w(TAG, "switchInput $callId") - mCapturingId = if (screenCaptureSession != null) { + Log.w(TAG, "DEBUG switchInput $callId $screenCaptureSession") + val camId = if (screenCaptureSession != null) { pendingScreenSharingSession = screenCaptureSession as MediaProjection CameraService.VideoDevices.SCREEN_SHARING } else { pendingScreenSharingSession = null cameraService.switchInput(setDefaultCamera) } - switchInput(accountId, callId, "camera://$mCapturingId") + switchInput(accountId, callId, "camera://$camId") } override fun setPreviewSettings() { @@ -625,7 +579,7 @@ class HardwareServiceImpl( override fun setDeviceOrientation(rotation: Int) { cameraService.setOrientation(rotation) - mCapturingId?.let { id -> + /*mCapturingId?.let { id -> val videoParams = cameraService.getParams(id) ?: return videoEvents.onNext(VideoEvent( started = true, @@ -633,7 +587,7 @@ class HardwareServiceImpl( h = videoParams.height, rot = videoParams.rotation )) - } + }*/ } override val videoDevices: List<String> diff --git a/ring-android/libjamiclient/src/main/kotlin/net/jami/call/CallPresenter.kt b/ring-android/libjamiclient/src/main/kotlin/net/jami/call/CallPresenter.kt index 621e56991..e2882e492 100644 --- a/ring-android/libjamiclient/src/main/kotlin/net/jami/call/CallPresenter.kt +++ b/ring-android/libjamiclient/src/main/kotlin/net/jami/call/CallPresenter.kt @@ -342,7 +342,7 @@ class CallPresenter @Inject constructor( fun previewVideoSurfaceDestroyed() { mHardwareService.removePreviewVideoSurface() - mHardwareService.endCapture() + //mHardwareService.endCapture() } fun uiVisibilityChanged(displayed: Boolean) { diff --git a/ring-android/libjamiclient/src/main/kotlin/net/jami/services/DaemonService.kt b/ring-android/libjamiclient/src/main/kotlin/net/jami/services/DaemonService.kt index 0c6c18757..edc5fc7ad 100644 --- a/ring-android/libjamiclient/src/main/kotlin/net/jami/services/DaemonService.kt +++ b/ring-android/libjamiclient/src/main/kotlin/net/jami/services/DaemonService.kt @@ -302,12 +302,12 @@ class DaemonService( mHardwareService.setParameters(camId, format, width, height, rate) } - fun requestKeyFrame() { + override fun requestKeyFrame(camId: String) { mHardwareService.requestKeyFrame() } - override fun setBitrate(device: String, bitrate: Int) { - mHardwareService.setBitrate(device, bitrate) + override fun setBitrate(camId: String, bitrate: Int) { + mHardwareService.setBitrate(camId, bitrate) } override fun startCapture(camId: String) { diff --git a/ring-android/libjamiclient/src/main/kotlin/net/jami/services/HardwareService.kt b/ring-android/libjamiclient/src/main/kotlin/net/jami/services/HardwareService.kt index ad0bff282..b1d1fd291 100644 --- a/ring-android/libjamiclient/src/main/kotlin/net/jami/services/HardwareService.kt +++ b/ring-android/libjamiclient/src/main/kotlin/net/jami/services/HardwareService.kt @@ -95,10 +95,8 @@ abstract class HardwareService( abstract fun startCapture(camId: String?) abstract fun stopCapture(camId: String) abstract fun hasMicrophone(): Boolean - abstract fun endCapture() - abstract fun stopScreenShare() abstract fun requestKeyFrame() - abstract fun setBitrate(device: String, bitrate: Int) + abstract fun setBitrate(camId: String, bitrate: Int) abstract fun addVideoSurface(id: String, holder: Any) abstract fun updateVideoSurfaceId(currentId: String, newId: String) abstract fun removeVideoSurface(id: String) @@ -136,6 +134,7 @@ abstract class HardwareService( } fun startVideo(inputId: String, surface: Any, width: Int, height: Int): Long { + Log.i(TAG, "startVideo $inputId ${width}x$height") val inputWindow = JamiService.acquireNativeWindow(surface) if (inputWindow == 0L) { return inputWindow @@ -146,6 +145,7 @@ abstract class HardwareService( } fun stopVideo(inputId: String, inputWindow: Long) { + Log.i(TAG, "stopVideo $inputId $inputWindow") if (inputWindow == 0L) { return } @@ -165,11 +165,9 @@ abstract class HardwareService( @Synchronized fun startLogs(): Observable<String> { return logs ?: Observable.create(ObservableOnSubscribe { emitter: ObservableEmitter<String> -> - Log.w(TAG, "ObservableOnSubscribe JamiService.monitor(true)") logEmitter = emitter JamiService.monitor(true) emitter.setCancellable { - Log.w(TAG, "ObservableOnSubscribe CANCEL JamiService.monitor(false)") synchronized(this@HardwareService) { JamiService.monitor(false) logEmitter = null @@ -178,7 +176,7 @@ abstract class HardwareService( } } as ObservableOnSubscribe<String>) .observeOn(Schedulers.io()) - .scan(StringBuffer(1024), { sb: StringBuffer, message: String? -> sb.append(message).append('\n') }) + .scan(StringBuffer(1024)) { sb: StringBuffer, message: String -> sb.append(message).append('\n') } .throttleLatest(500, TimeUnit.MILLISECONDS) .map { obj: StringBuffer -> obj.toString() } .replay(1) @@ -203,7 +201,7 @@ abstract class HardwareService( } companion object { - private val TAG = HardwareService::class.java.simpleName + private val TAG = HardwareService::class.simpleName!! val STATE_SPEAKERS = AudioState(AudioOutput.SPEAKERS) val STATE_INTERNAL = AudioState(AudioOutput.INTERNAL) } -- GitLab