twilio / video-quickstart-android

Twilio Video Quickstart for Android
MIT License
212 stars 159 forks source link

Crash on connecting without microphone enabled #727

Closed KamilSucharski closed 1 year ago

KamilSucharski commented 1 year ago

Description

When connecting to the room without microphone enabled we get a crash. This only happened on our Google Pixel devices. The stacktrace is not very helpful, as the relevant part looks to be

Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0

but there are no further details.

Steps to Reproduce

  1. Create localAudioTrack and localVideoTrack for preview.
  2. Disable localAudioTrack.
  3. Join room.

Code

private fun connectToRoom(accessToken: String, roomName: String) {
    val connectOptions = ConnectOptions.Builder(accessToken)
        .roomName(roomName)
        .audioTracks(listOf(localAudioTrack))
        .videoTracks(listOf(localVideoTrack))
        .build()
    room = Video.connect(context, connectOptions, roomListener())
}

Expected Behavior

Successful connection, like on other brand devices.

Actual Behavior

Crash.

Reproduces how Often

Happens 100% of the time

Logs

2022-09-23 12:01:10.000 20949-21087/com.test.app I/tvi.webrtc.Logging: CameraStatistics: Camera fps: 29.
2022-09-23 12:01:10.827 20949-21218/com.test.app I/tvi.webrtc.Logging: EglBase14Impl: Using OpenGL ES version 2
2022-09-23 12:01:10.841 20949-20949/com.test.app I/tvi.webrtc.Logging: CameraCapturer: startCapture: 640x480@30
2022-09-23 12:01:10.841 20949-21218/com.test.app I/tvi.webrtc.Logging: Camera2Session: Create new camera2 session on camera 1
2022-09-23 12:01:10.841 20949-21218/com.test.app I/tvi.webrtc.Logging: Camera2Session: start
2022-09-23 12:01:10.844 20949-21218/com.test.app I/tvi.webrtc.Logging: Camera2Session: Available preview sizes: [3264x2448, 3264x1836, 2560x1920, 2688x1512, 1920x1920, 2560x1280, 2048x1536, 1920x1440, 1920x1080, 1600x1200, 1920x960, 1440x1080, 1280x960, 1080x1080, 1280x720, 1024x768, 800x600, 720x480, 640x480, 640x360, 352x288, 320x240, 176x144, 3280x2464]
2022-09-23 12:01:10.845 20949-21218/com.test.app I/tvi.webrtc.Logging: Camera2Session: Available fps ranges: [[15.0:15.0], [15.0:30.0], [30.0:30.0]]
2022-09-23 12:01:10.845 20949-21218/com.test.app I/tvi.webrtc.Logging: Camera2Session: Using capture format: 640x480@[15.0:30.0]
2022-09-23 12:01:10.845 20949-21218/com.test.app I/tvi.webrtc.Logging: Camera2Session: Opening camera 1
2022-09-23 12:01:10.862 20949-20949/com.test.app I/tvi.webrtc.Logging: EglRenderer: videoCallRemoteView: Initializing EglRenderer
2022-09-23 12:01:10.863 20949-21225/com.test.app I/tvi.webrtc.Logging: EglRenderer: videoCallRemoteView: EglBase.create shared context
2022-09-23 12:01:10.864 20949-21225/com.test.app I/tvi.webrtc.Logging: EglBase14Impl: Using OpenGL ES version 2
2022-09-23 12:01:10.874 20949-20949/com.test.app I/tvi.webrtc.Logging: EglRenderer: videoCallLocalView: Initializing EglRenderer
2022-09-23 12:01:10.875 20949-21233/com.test.app I/tvi.webrtc.Logging: EglRenderer: videoCallLocalView: EglBase.create shared context
2022-09-23 12:01:10.875 20949-21233/com.test.app I/tvi.webrtc.Logging: EglBase14Impl: Using OpenGL ES version 2
2022-09-23 12:01:10.882 20949-20949/com.test.app I/tvi.webrtc.Logging: EglRenderer: videoCallLocalView: setMirrorHorizontally: true
2022-09-23 12:01:10.884 20949-20949/com.test.app I/tvi.webrtc.Logging: EglRenderer: video_view: Releasing.
2022-09-23 12:01:10.884 20949-21187/com.test.app I/tvi.webrtc.Logging: GlShader: Deleting shader.
2022-09-23 12:01:10.884 20949-21187/com.test.app I/tvi.webrtc.Logging: EglRenderer: video_view: eglBase detach and release.
2022-09-23 12:01:10.887 20949-20949/com.test.app I/tvi.webrtc.Logging: EglRenderer: video_view: Releasing done.
2022-09-23 12:01:10.887 20949-21187/com.test.app I/tvi.webrtc.Logging: EglRenderer: video_view: Quitting render thread.
2022-09-23 12:01:10.888 20949-21223/com.test.app D/TrafficStats: tagSocket(189) with statsTag=0xffffffff, statsUid=-1
2022-09-23 12:01:11.008 20949-21087/com.test.app E/CameraCaptureSession: Session 0: Exception while stopping repeating: 
    android.hardware.camera2.CameraAccessException: CAMERA_ERROR (3): cancelRequest:601: Camera 1: Error clearing streaming request: Function not implemented (-38)
        at android.hardware.camera2.CameraManager.throwAsPublicException(CameraManager.java:1390)
        at android.hardware.camera2.impl.ICameraDeviceUserWrapper.cancelRequest(ICameraDeviceUserWrapper.java:99)
        at android.hardware.camera2.impl.CameraDeviceImpl.stopRepeating(CameraDeviceImpl.java:1341)
        at android.hardware.camera2.impl.CameraCaptureSessionImpl.close(CameraCaptureSessionImpl.java:579)
        at android.hardware.camera2.impl.CameraCaptureSessionImpl$2.onDisconnected(CameraCaptureSessionImpl.java:805)
        at android.hardware.camera2.impl.CameraDeviceImpl$7.run(CameraDeviceImpl.java:267)
        at android.os.Handler.handleCallback(Handler.java:942)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.os.HandlerThread.run(HandlerThread.java:67)
     Caused by: android.os.ServiceSpecificException: cancelRequest:601: Camera 1: Error clearing streaming request: Function not implemented (-38) (code 10)
        at android.os.Parcel.createExceptionOrNull(Parcel.java:3025)
        at android.os.Parcel.createException(Parcel.java:2995)
        at android.os.Parcel.readException(Parcel.java:2978)
        at android.os.Parcel.readException(Parcel.java:2920)
        at android.hardware.camera2.ICameraDeviceUser$Stub$Proxy.cancelRequest(ICameraDeviceUser.java:648)
        at android.hardware.camera2.impl.ICameraDeviceUserWrapper.cancelRequest(ICameraDeviceUserWrapper.java:97)
        at android.hardware.camera2.impl.CameraDeviceImpl.stopRepeating(CameraDeviceImpl.java:1341) 
        at android.hardware.camera2.impl.CameraCaptureSessionImpl.close(CameraCaptureSessionImpl.java:579) 
        at android.hardware.camera2.impl.CameraCaptureSessionImpl$2.onDisconnected(CameraCaptureSessionImpl.java:805) 
        at android.hardware.camera2.impl.CameraDeviceImpl$7.run(CameraDeviceImpl.java:267) 
        at android.os.Handler.handleCallback(Handler.java:942) 
        at android.os.Handler.dispatchMessage(Handler.java:99) 
        at android.os.Looper.loopOnce(Looper.java:201) 
        at android.os.Looper.loop(Looper.java:288) 
        at android.os.HandlerThread.run(HandlerThread.java:67) 
2022-09-23 12:01:11.009 20949-21087/com.test.app I/tvi.webrtc.Logging: Camera2Session: Stop internal
2022-09-23 12:01:11.009 20949-21087/com.test.app I/tvi.webrtc.Logging: SurfaceTextureHelper: stopListening()
2022-09-23 12:01:11.009 20949-21087/com.test.app I/tvi.webrtc.Logging: Camera2Session: Stop done
2022-09-23 12:01:11.009 20949-21087/com.test.app I/tvi.webrtc.Logging: CameraCapturer: Stop capture
2022-09-23 12:01:11.009 20949-21087/com.test.app I/tvi.webrtc.Logging: CameraCapturer: Stop capture: Nulling session
2022-09-23 12:01:11.009 20949-21087/com.test.app I/tvi.webrtc.Logging: CameraCapturer: Stop capture done
2022-09-23 12:01:11.009 20949-21087/com.test.app I/tvi.webrtc.Logging: Camera2Session: Camera device closed.
2022-09-23 12:01:11.009 20949-21087/com.test.app I/tvi.webrtc.Logging: Camera2Session: Stop camera2 session on camera 1
2022-09-23 12:01:11.023 20949-21218/com.test.app I/tvi.webrtc.Logging: Camera2Session: Camera opened.
2022-09-23 12:01:11.035 20949-20949/com.test.app D/LeakCanary: Watching instance of android.widget.ScrollView (com.test.app.ui.call.waitingRoom.WaitingRoomFragment received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks)) with key d83cf051-2873-46a5-a0cf-e1c564f4b3de
2022-09-23 12:01:11.046 20949-21218/com.test.app I/tvi.webrtc.Logging: Camera2Session: Camera capture session configured.
2022-09-23 12:01:11.046 20949-21218/com.test.app I/tvi.webrtc.Logging: Camera2Session: Using video stabilization.
2022-09-23 12:01:11.046 20949-21218/com.test.app I/tvi.webrtc.Logging: Camera2Session: Auto-focus is not available.
2022-09-23 12:01:11.048 20949-21218/com.test.app I/tvi.webrtc.Logging: Camera2Session: Camera device successfully started.
2022-09-23 12:01:11.048 20949-21218/com.test.app I/tvi.webrtc.Logging: CameraCapturer: Create session done. Switch state: IDLE
2022-09-23 12:01:11.048 20949-21218/com.test.app I/tvi.webrtc.Logging: SurfaceTextureHelper: Setting listener to tvi.webrtc.Camera2Session$CaptureSessionCallback$$ExternalSyntheticLambda0@669c936
2022-09-23 12:01:11.050 20949-21224/com.test.app D/TrafficStats: tagSocket(81) with statsTag=0xffffffff, statsUid=-1
2022-09-23 12:01:11.089 20949-20949/com.test.app I/tvi.webrtc.Logging: EglRenderer: videoCallLocalView: setLayoutAspectRatio: 0.50095236
2022-09-23 12:01:11.124 20949-20949/com.test.app W/CaptureThread: type=1400 audit(0.0:11175): avc: denied { getattr } for name="/" dev="dmabuf" ino=1 scontext=u:r:untrusted_app_30:s0:c14,c257,c512,c768 tcontext=u:object_r:unlabeled:s0 tclass=filesystem permissive=0 app=com.test.app
2022-09-23 12:01:11.150 20949-20949/com.test.app I/tvi.webrtc.Logging: EglRenderer: videoCallLocalView: setLayoutAspectRatio: 0.50095236
2022-09-23 12:01:11.192 20949-20949/com.test.app W/CaptureThread: type=1400 audit(0.0:11176): avc: denied { getattr } for name="/" dev="dmabuf" ino=1 scontext=u:r:untrusted_app_30:s0:c14,c257,c512,c768 tcontext=u:object_r:unlabeled:s0 tclass=filesystem permissive=0 app=com.test.app
2022-09-23 12:01:11.224 20949-20949/com.test.app W/CaptureThread: type=1400 audit(0.0:11177): avc: denied { getattr } for name="/" dev="dmabuf" ino=1 scontext=u:r:untrusted_app_30:s0:c14,c257,c512,c768 tcontext=u:object_r:unlabeled:s0 tclass=filesystem permissive=0 app=com.test.app
2022-09-23 12:01:11.777 20949-20949/com.test.app E/RoomManagerImpl$roomListener: Connected to 3f5feae3-bb1e-4e9d-aa31-272f0b192611
2022-09-23 12:01:11.780 20949-20962/com.test.app I/com.test.app: Background young concurrent copying GC freed 1826826(43MB) AllocSpace objects, 0(0B) LOS objects, 17% free, 102MB/123MB, paused 86us,443us total 560.529ms
2022-09-23 12:01:11.789 20949-20949/com.test.app I/tvi.webrtc.Logging: EglRenderer: videoCallRemoteView: setLayoutAspectRatio: 0.5009276
2022-09-23 12:01:12.062 20949-21080/com.test.app D/OpenSLESPlayer: InitPlayout[tid=21080]
2022-09-23 12:01:12.062 20949-21080/com.test.app D/OpenSLESPlayer: ObtainEngineInterface
2022-09-23 12:01:12.063 20949-21080/com.test.app D/OpenSLESPlayer: CreateMix
2022-09-23 12:01:12.063 20949-21080/com.test.app D/OpenSLESPlayer: StartPlayout[tid=21080]
2022-09-23 12:01:12.063 20949-21080/com.test.app D/OpenSLESPlayer: CreateAudioPlayer
2022-09-23 12:01:12.063 20949-21080/com.test.app D/com.test.app: PlayerBase::PlayerBase()
2022-09-23 12:01:12.064 20949-21080/com.test.app D/com.test.app: TrackPlayerBase::TrackPlayerBase()
2022-09-23 12:01:12.064 20949-21080/com.test.app I/libOpenSLES: Emulating old channel mask behavior (ignoring positional mask 0x4, using default mask 0x1 based on channel count of 1)
2022-09-23 12:01:12.069 20949-21080/com.test.app I/AudioTrack: createTrack_l(0): AUDIO_OUTPUT_FLAG_FAST successful; frameCount 0 -> 960
2022-09-23 12:01:13.359 21365-21365/com.test.app D/nativeloader: Configuring classloader-namespace for other apk /system/framework/org.apache.http.legacy.jar. target_sdk_version=31, uses_libraries=ALL, library_path=/data/app/~~ByRI6AykBrCb_9ZLqNQ2QA==/com.test.app-Q_nNKVEJPr_PVF4lmhG5Tg==/lib/arm64:/data/app/~~ByRI6AykBrCb_9ZLqNQ2QA==/com.test.app-Q_nNKVEJPr_PVF4lmhG5Tg==/base.apk!/lib/arm64-v8a, permitted_path=/data:/mnt/expand:/data/user/0/com.test.app
2022-09-23 12:01:12.077 20949-21080/com.test.app I/tvi.webrtc.Logging: AndroidVideoDecoder: ctor name: c2.exynos.vp8.decoder type: VP8 color format: 19 context: tvi.webrtc.EglBase14Impl$Context@7a0a803
2022-09-23 12:01:12.078 20949-21080/com.test.app I/tvi.webrtc.Logging: AndroidVideoDecoder: ctor name: c2.exynos.vp9.decoder type: VP9 color format: 19 context: tvi.webrtc.EglBase14Impl$Context@7a0a803
2022-09-23 12:01:13.360 21365-21365/com.test.app W/ziparchive: Unable to open '/data/app/~~ByRI6AykBrCb_9ZLqNQ2QA==/com.test.app-Q_nNKVEJPr_PVF4lmhG5Tg==/base.dm': No such file or directory
2022-09-23 12:01:13.360 21365-21365/com.test.app W/ziparchive: Unable to open '/data/app/~~ByRI6AykBrCb_9ZLqNQ2QA==/com.test.app-Q_nNKVEJPr_PVF4lmhG5Tg==/base.dm': No such file or directory
2022-09-23 12:01:12.083 20949-21322/com.test.app I/DefaultVideoEncoderFactory: enableHardwareEncoding:true, isHardwareEnabledForMimetype(): true
2022-09-23 12:01:12.084 20949-21322/com.test.app I/DefaultVideoEncoderFactory: Software encoder used (video/x-vnd.on2.vp8)
2022-09-23 12:01:12.084 20949-21233/com.test.app I/tvi.webrtc.Logging: GlShader: Deleting shader.
2022-09-23 12:01:12.085 20949-21080/com.test.app I/tvi.webrtc.Logging: AndroidVideoDecoder: ctor name: c2.exynos.vp8.decoder type: VP8 color format: 19 context: tvi.webrtc.EglBase14Impl$Context@7a0a803
2022-09-23 12:01:12.085 20949-21080/com.test.app I/tvi.webrtc.Logging: AndroidVideoDecoder: ctor name: c2.exynos.vp9.decoder type: VP9 color format: 19 context: tvi.webrtc.EglBase14Impl$Context@7a0a803
2022-09-23 12:01:12.085 20949-21080/com.test.app I/tvi.webrtc.Logging: AndroidVideoDecoder: ctor name: c2.exynos.h264.decoder type: H264 color format: 19 context: tvi.webrtc.EglBase14Impl$Context@7a0a803
2022-09-23 12:01:12.085 20949-21080/com.test.app I/tvi.webrtc.Logging: AndroidVideoDecoder: ctor name: c2.android.avc.decoder type: H264 color format: 19 context: tvi.webrtc.EglBase14Impl$Context@7a0a803
2022-09-23 12:01:12.088 20949-21080/com.test.app D/OpenSLESPlayer: StopPlayout[tid=21080]
2022-09-23 12:01:12.088 20949-21080/com.test.app D/com.test.app: PlayerBase::stop() from IPlayer
2022-09-23 12:01:12.088 20949-21080/com.test.app D/AudioTrack: stop(624): called with 960 frames delivered
2022-09-23 12:01:12.089 20949-21080/com.test.app D/OpenSLESPlayer: DestroyAudioPlayer
2022-09-23 12:01:12.089 20949-21080/com.test.app D/OpenSLESPlayer: InitPlayout[tid=21080]
2022-09-23 12:01:12.089 20949-21080/com.test.app D/OpenSLESPlayer: ObtainEngineInterface
2022-09-23 12:01:12.089 20949-21080/com.test.app D/OpenSLESPlayer: CreateMix
2022-09-23 12:01:12.089 20949-21080/com.test.app D/OpenSLESPlayer: StartPlayout[tid=21080]
2022-09-23 12:01:12.089 20949-21080/com.test.app D/OpenSLESPlayer: CreateAudioPlayer
2022-09-23 12:01:12.089 20949-21080/com.test.app D/com.test.app: PlayerBase::PlayerBase()
2022-09-23 12:01:12.090 20949-21080/com.test.app D/com.test.app: TrackPlayerBase::TrackPlayerBase()
2022-09-23 12:01:12.090 20949-21080/com.test.app I/libOpenSLES: Emulating old channel mask behavior (ignoring positional mask 0x4, using default mask 0x1 based on channel count of 1)
2022-09-23 12:01:12.098 20949-20949/com.test.app I/tvi.webrtc.Logging: EglRenderer: videoCallLocalView: setLayoutAspectRatio: 0.50095236
2022-09-23 12:01:12.104 20949-21080/com.test.app I/AudioTrack: createTrack_l(624): AUDIO_OUTPUT_FLAG_FAST successful; frameCount 0 -> 960
2022-09-23 12:01:12.105 20949-21080/com.test.app D/OpenSLESPlayer: StopPlayout[tid=21080]
2022-09-23 12:01:12.105 20949-21080/com.test.app D/com.test.app: PlayerBase::stop() from IPlayer
2022-09-23 12:01:12.105 20949-21080/com.test.app D/AudioTrack: stop(625): called with 0 frames delivered
2022-09-23 12:01:12.105 20949-21080/com.test.app D/OpenSLESPlayer: DestroyAudioPlayer
2022-09-23 12:01:12.105 20949-21080/com.test.app D/OpenSLESPlayer: InitPlayout[tid=21080]
2022-09-23 12:01:12.105 20949-21080/com.test.app D/OpenSLESPlayer: ObtainEngineInterface
2022-09-23 12:01:12.106 20949-21080/com.test.app D/OpenSLESPlayer: CreateMix
2022-09-23 12:01:12.106 20949-21080/com.test.app D/OpenSLESPlayer: StartPlayout[tid=21080]
2022-09-23 12:01:12.106 20949-21080/com.test.app D/OpenSLESPlayer: CreateAudioPlayer
2022-09-23 12:01:12.106 20949-21080/com.test.app D/com.test.app: PlayerBase::PlayerBase()
2022-09-23 12:01:12.107 20949-21080/com.test.app D/com.test.app: TrackPlayerBase::TrackPlayerBase()
2022-09-23 12:01:12.107 20949-21080/com.test.app I/libOpenSLES: Emulating old channel mask behavior (ignoring positional mask 0x4, using default mask 0x1 based on channel count of 1)
2022-09-23 12:01:12.111 20949-21080/com.test.app I/AudioTrack: createTrack_l(625): AUDIO_OUTPUT_FLAG_FAST successful; frameCount 0 -> 960
2022-09-23 12:01:12.113 20949-21080/com.test.app D/OpenSLESRecorder: InitRecording[tid=21080]
2022-09-23 12:01:12.113 20949-21080/com.test.app D/OpenSLESRecorder: ObtainEngineInterface
2022-09-23 12:01:12.113 20949-21080/com.test.app D/OpenSLESRecorder: CreateAudioRecorder
2022-09-23 12:01:12.113 20949-21080/com.test.app W/libOpenSLES: Conversion from OpenSL ES positional channel mask 0x4 to Android mask 0 loses channels
2022-09-23 12:01:12.113 20949-21080/com.test.app I/libOpenSLES: Emulating old channel mask behavior (ignoring positional mask 0x4, using default mask 0x10 based on channel count of 1)
2022-09-23 12:01:12.120 20949-21064/com.test.app W/AudioRecord: restoreRecord_l(622): dead IAudioRecord, creating a new one from obtainBuffer()
2022-09-23 12:01:12.410 20949-21064/com.test.app W/AudioRecord: restoreRecord_l(630): dead IAudioRecord, creating a new one from obtainBuffer()
2022-09-23 12:01:12.732 20949-21064/com.test.app W/AudioRecord: restoreRecord_l(634): dead IAudioRecord, creating a new one from obtainBuffer()
2022-09-23 12:01:12.766 20949-21080/com.test.app E/AudioRecord: createRecord_l(6645601): AudioFlinger could not create record track, status: -2147483646
2022-09-23 12:01:12.766 20949-21080/com.test.app E/libOpenSLES: android_audioRecorder_realize(0xb4000079091e4790) error creating AudioRecord object; status -2147483646
2022-09-23 12:01:12.766 20949-21080/com.test.app W/libOpenSLES: Leaving Object::Realize (SL_RESULT_CONTENT_UNSUPPORTED)
2022-09-23 12:01:12.766 20949-21080/com.test.app E/OpenSLESRecorder: ../../../src/modules/audio_device/android/opensles_recorder.cc:282 (recorder_object_->Realize(recorder_object_.Get(), SL_BOOLEAN_FALSE)) failed: SL_RESULT_CONTENT_UNSUPPORTED
2022-09-23 12:01:12.766 20949-21080/com.test.app D/OpenSLESRecorder: StartRecording[tid=21080]

    --------- beginning of crash
2022-09-23 12:01:12.766 20949-21080/com.test.app A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 in tid 21080 (Thread-11), pid 20949 (com.test.app)
2022-09-23 12:01:12.920 20949-20949/com.test.app W/CaptureThread: type=1400 audit(0.0:11178): avc: denied { getattr } for name="/" dev="dmabuf" ino=1 scontext=u:r:untrusted_app_30:s0:c14,c257,c512,c768 tcontext=u:object_r:unlabeled:s0 tclass=filesystem permissive=0 app=com.test.app
2022-09-23 12:01:13.048 20949-21218/com.test.app I/tvi.webrtc.Logging: CameraStatistics: Camera fps: 28.
2022-09-23 12:01:13.569 21365-21365/com.test.app D/nativeloader: Configuring classloader-namespace for other apk /data/app/~~ByRI6AykBrCb_9ZLqNQ2QA==/com.test.app-Q_nNKVEJPr_PVF4lmhG5Tg==/base.apk. target_sdk_version=31, uses_libraries=, library_path=/data/app/~~ByRI6AykBrCb_9ZLqNQ2QA==/com.test.app-Q_nNKVEJPr_PVF4lmhG5Tg==/lib/arm64:/data/app/~~ByRI6AykBrCb_9ZLqNQ2QA==/com.test.app-Q_nNKVEJPr_PVF4lmhG5Tg==/base.apk!/lib/arm64-v8a, permitted_path=/data:/mnt/expand:/data/user/0/com.test.app

Versions

All relevant version information for issue.

Video Android SDK

7.1.0, but tried the newest too and it did not help.

Android API

SDK 33 (Android 13)

Android Device

Google Pixel 4a, Google Pixel 6a

afalls-twilio commented 1 year ago

@KamilSucharski Thank you for the bug report, have you tried with the camera2 capturer?

KamilSucharski commented 1 year ago

@afalls-twilio Yes we use the Camera2Capturer. Here is the full class that relates to the video call:

import android.content.Context
import androidx.lifecycle.MutableLiveData
import com.twilio.audioswitch.AudioDevice
import com.twilio.audioswitch.AudioSwitch
import com.twilio.video.*
import io.reactivex.rxjava3.subjects.PublishSubject
import timber.log.Timber
import tvi.webrtc.Camera2Enumerator

class RoomManagerImpl(
    private val context: Context,
    waitingRoomSettings: WaitingRoomSettings
) : RoomManager {

    private var localVideoTrack: LocalVideoTrack? = null
    private var localAudioTrack: LocalAudioTrack? = null
    private var audioSwitch: AudioSwitch? = null

    private var currentCameraId: String? = null
    private var frontCameraId: String? = null
    private var backCameraId: String? = null
    private var cameraCapturer: Camera2Capturer? = null

    override val isMicrophoneEnabled = waitingRoomSettings.isMicrophoneEnabled
    override val isCameraEnabled = waitingRoomSettings.isCameraEnabled
    override val isDoctorCameraEnabled = MutableLiveData(false)
    override val doctorVideoTrack = MutableLiveData<RemoteVideoTrack>()
    override val patientVideoTrack = MutableLiveData<LocalVideoTrack>()
    override val isPatientVideoMirrored = MutableLiveData(false)
    override val doctorJoinedEventTrigger = waitingRoomSettings.doctorJoinedEventTrigger
    override val doctorConnectionChangedTrigger: PublishSubject<Boolean> = PublishSubject.create()
    private var room: Room? = null

    override fun setVideoCapture() {
        val camera2Enumerator = Camera2Enumerator(context)

        localAudioTrack = LocalAudioTrack.create(context, isMicrophoneEnabled.value ?: true)

        for (cameraId in camera2Enumerator.deviceNames) {
            if (camera2Enumerator.isFrontFacing(cameraId)) {
                frontCameraId = cameraId
            }
            if (camera2Enumerator.isBackFacing(cameraId)) {
                backCameraId = cameraId
            }
        }

        val frontId = frontCameraId
        val backId = backCameraId

        when {
            frontId != null -> {
                cameraCapturer = Camera2Capturer(context, frontId)
                localVideoTrack = LocalVideoTrack.create(
                    context,
                    isCameraEnabled.value ?: true,
                    cameraCapturer!!
                )
                currentCameraId = frontId
                setAudio()
                patientVideoTrack.value = localVideoTrack!!
                isPatientVideoMirrored.value = true
            }
            backId != null -> {
                cameraCapturer = Camera2Capturer(context, backId)
                localVideoTrack = LocalVideoTrack.create(
                    context,
                    isCameraEnabled.value ?: true,
                    cameraCapturer!!
                )
                currentCameraId = backId
                setAudio()
                patientVideoTrack.value = localVideoTrack!!
                isPatientVideoMirrored.value = false
            }
            else -> return
        }
    }

    override fun connect(accessString: String, roomName: String) {
        connectToRoom(accessString, roomName)
        patientVideoTrack.value = localVideoTrack!!
    }

    override fun disconnect() {
        room?.disconnect()
    }

    override fun stop() {
        localAudioTrack?.release()
        localVideoTrack?.release()
        audioSwitch?.stop()

        localAudioTrack = null
        localVideoTrack = null
        audioSwitch = null
        currentCameraId = null
        frontCameraId = null
        backCameraId = null
        cameraCapturer = null
    }

    override fun switchCamera() {
        if (currentCameraId == frontCameraId) {
            backCameraId?.let { cameraCapturer?.switchCamera(it) }
            currentCameraId = backCameraId
            isPatientVideoMirrored.value = false
        } else {
            frontCameraId?.let { cameraCapturer?.switchCamera(it) }
            currentCameraId = frontCameraId
            isPatientVideoMirrored.value = true
        }
    }

    override fun enableCamera(isEnabled: Boolean) {
        localVideoTrack?.enable(isEnabled)
    }

    override fun enableMicrophone(isEnabled: Boolean) {
        if (isEnabled) {
            audioSwitch?.activate()
        } else {
            audioSwitch?.deactivate()
        }
        localAudioTrack?.enable(isEnabled)
    }

    private fun setAudio() {
        audioSwitch = AudioSwitch(context)
        startAudio()
    }

    private fun startAudio() {
        audioSwitch?.start { audioDevices, _ ->
            audioDevices
                .firstOrNull { it is AudioDevice.Speakerphone }
                ?.let { audioSwitch?.selectDevice(it) }
        }
        audioSwitch?.activate()
    }

    private fun connectToRoom(accessToken: String, roomName: String) {
        val connectOptions = ConnectOptions.Builder(accessToken)
            .roomName(roomName)
            .audioTracks(listOf(localAudioTrack))
            .videoTracks(listOf(localVideoTrack))
            .build()
        room = Video.connect(context, connectOptions, roomListener())
    }

    private fun remoteParticipantListener(): RemoteParticipant.Listener {
        return object : RemoteParticipant.Listener {
            override fun onAudioTrackPublished(
                remoteParticipant: RemoteParticipant,
                remoteAudioTrackPublication: RemoteAudioTrackPublication
            ) = Unit

            override fun onAudioTrackUnpublished(
                remoteParticipant: RemoteParticipant,
                remoteAudioTrackPublication: RemoteAudioTrackPublication
            ) = Unit

            override fun onAudioTrackSubscribed(
                remoteParticipant: RemoteParticipant,
                remoteAudioTrackPublication: RemoteAudioTrackPublication,
                remoteAudioTrack: RemoteAudioTrack
            ) = Unit

            override fun onAudioTrackSubscriptionFailed(
                remoteParticipant: RemoteParticipant,
                remoteAudioTrackPublication: RemoteAudioTrackPublication,
                twilioException: TwilioException
            ) = Unit

            override fun onAudioTrackUnsubscribed(
                remoteParticipant: RemoteParticipant,
                remoteAudioTrackPublication: RemoteAudioTrackPublication,
                remoteAudioTrack: RemoteAudioTrack
            ) = Unit

            override fun onVideoTrackPublished(
                remoteParticipant: RemoteParticipant,
                remoteVideoTrackPublication: RemoteVideoTrackPublication
            ) {
                isDoctorCameraEnabled.value = true
            }

            override fun onVideoTrackUnpublished(
                remoteParticipant: RemoteParticipant,
                remoteVideoTrackPublication: RemoteVideoTrackPublication
            ) {
                isDoctorCameraEnabled.value = false
            }

            override fun onVideoTrackSubscriptionFailed(
                remoteParticipant: RemoteParticipant,
                remoteVideoTrackPublication: RemoteVideoTrackPublication,
                twilioException: TwilioException
            ) {
                isDoctorCameraEnabled.value = false
            }

            override fun onVideoTrackUnsubscribed(
                remoteParticipant: RemoteParticipant,
                remoteVideoTrackPublication: RemoteVideoTrackPublication,
                remoteVideoTrack: RemoteVideoTrack
            ) {
                isDoctorCameraEnabled.value = false
            }

            override fun onDataTrackPublished(
                remoteParticipant: RemoteParticipant,
                remoteDataTrackPublication: RemoteDataTrackPublication
            ) = Unit

            override fun onDataTrackUnpublished(
                remoteParticipant: RemoteParticipant,
                remoteDataTrackPublication: RemoteDataTrackPublication
            ) = Unit

            override fun onDataTrackSubscribed(
                remoteParticipant: RemoteParticipant,
                remoteDataTrackPublication: RemoteDataTrackPublication,
                remoteDataTrack: RemoteDataTrack
            ) = Unit

            override fun onDataTrackSubscriptionFailed(
                remoteParticipant: RemoteParticipant,
                remoteDataTrackPublication: RemoteDataTrackPublication,
                twilioException: TwilioException
            ) = Unit

            override fun onDataTrackUnsubscribed(
                remoteParticipant: RemoteParticipant,
                remoteDataTrackPublication: RemoteDataTrackPublication,
                remoteDataTrack: RemoteDataTrack
            ) = Unit

            override fun onAudioTrackEnabled(
                remoteParticipant: RemoteParticipant,
                remoteAudioTrackPublication: RemoteAudioTrackPublication
            ) = Unit

            override fun onAudioTrackDisabled(
                remoteParticipant: RemoteParticipant,
                remoteAudioTrackPublication: RemoteAudioTrackPublication
            ) = Unit

            override fun onVideoTrackEnabled(
                remoteParticipant: RemoteParticipant,
                remoteVideoTrackPublication: RemoteVideoTrackPublication
            ) {
                isDoctorCameraEnabled.value = true
            }

            override fun onVideoTrackDisabled(
                remoteParticipant: RemoteParticipant,
                remoteVideoTrackPublication: RemoteVideoTrackPublication
            ) {
                isDoctorCameraEnabled.value = false
            }

            override fun onVideoTrackSubscribed(
                participant: RemoteParticipant,
                remoteVideoTrackPublication: RemoteVideoTrackPublication,
                remoteVideoTrack: RemoteVideoTrack
            ) {
                doctorVideoTrack.value = remoteVideoTrack
            }
        }
    }

    private fun roomListener(): Room.Listener {
        return object : Room.Listener {
            override fun onConnected(room: Room) {
                Timber.e("Connected to ${room.name}")
                if (room.remoteParticipants.isNotEmpty()) {
                    val remoteParticipant: RemoteParticipant = room.remoteParticipants[0]
                    remoteParticipant.setListener(remoteParticipantListener())

                    if (remoteParticipant.remoteVideoTracks.any()) {
                        isDoctorCameraEnabled.value = true
                    }
                }

            }

            override fun onConnectFailure(room: Room, twilioException: TwilioException) {
                Timber.e("connect failure: $twilioException")
            }

            override fun onReconnecting(room: Room, twilioException: TwilioException) {
                Timber.e("connect onReconnecting: ${room.state}")

            }

            override fun onReconnected(room: Room) {
                Timber.e("connect onReconnected")
            }

            override fun onDisconnected(room: Room, twilioException: TwilioException?) {
                Timber.e("connect disconnected")
            }

            override fun onParticipantConnected(room: Room, remoteParticipant: RemoteParticipant) {
                doctorConnectionChangedTrigger.onNext(true)
                remoteParticipant.setListener(remoteParticipantListener())

                if (remoteParticipant.remoteVideoTracks.any()) {
                    isDoctorCameraEnabled.value = true
                }
            }

            override fun onParticipantDisconnected(
                room: Room,
                remoteParticipant: RemoteParticipant
            ) {
                Timber.e("connect onParticipantDisconnected")
                doctorConnectionChangedTrigger.onNext(false)
            }

            override fun onRecordingStarted(room: Room) = Unit

            override fun onRecordingStopped(room: Room) = Unit
        }
    }
}
afalls-twilio commented 1 year ago

@KamilSucharski Thank you for reporting the issue. Unfortunately, we were not able to repo it. We have included a test application attempting to recreate the issue. If you can please modify it so that we can reproduce the issue using the test app, we can more easily fix the issue.

link to the test app is here

Also, just looking at what was posted, AudioSwitch doesn't need to be called to enable/disable an audiotrack. In fact, see what happens if AudioSwitch is enabled at app start up and disabled at app shutdown.

KamilSucharski commented 1 year ago

@afalls-twilio Here are the results:

  1. I tried your suggestion, by modifying AudioSwitch in our code - didn't help.
  2. I tried "breaking" your mini-app so it has the same issue as our code - but it still worked...
  3. I looked for differences between our project and your mini-app, and found that we have a volume indicator in the waiting room before connecting. When I commented it out, the app stopped crashing. Eventually I just reworked it as shown on the screenshot and it (hopefully) works without crashes.

obraz

I'm not 100% sure what was the cause here, but I have a suspicion. The ugly while (true) in old line 193 was probably too agressive on the Schedulers.io() thread, which i imagine Twilio also uses and maybe it created some friction? I had issues with some network calls not going out of the app until I changed them to use Schedulers.newThread() instead of Schedulers.io() - at the time I suspected Twilio of hogging the thread to itself, but now I suspect both it and that volume indicator that was here.

afalls-twilio commented 1 year ago

@KamilSucharski What are you attempting to do? It looks like the code you posted is creating an audio track just to calculate what the audio level in decibels. Would it be easier to make a custom audio device that calculated the decibels as it recorded the audio that is being sent to webrtc?

KamilSucharski commented 1 year ago

@afalls-twilio It shows the users their mic volume before they join the call.