Remote Video Is White Screen In Android WebRTC

492 views Asked by At

I am using webRTC for peer to peer video communication in a native Android project. I exchanged both sdp and candidates successfully. My problem is; I can connect with audio but remote video is white screen.

Screenshot

Here my codes:

Creating PeerConnection

private val peerConnection by lazy { buildPeerConnection(observer) }

private fun initPeerConnectionFactory(context: Application) {
    val options = PeerConnectionFactory.InitializationOptions.builder(context)
        .setEnableInternalTracer(true)
        .setFieldTrials("WebRTC-H264HighProfile/Enabled/")
        .createInitializationOptions()
    PeerConnectionFactory.initialize(options)
}

private fun buildPeerConnectionFactory(): PeerConnectionFactory {
    return PeerConnectionFactory
        .builder()
        .setVideoDecoderFactory(DefaultVideoDecoderFactory(rootEglBase.eglBaseContext))
        .setVideoEncoderFactory(
            DefaultVideoEncoderFactory(
                rootEglBase.eglBaseContext,
                true,
                true
            )
        )
        .setOptions(PeerConnectionFactory.Options().apply {
            disableEncryption = true
            disableNetworkMonitor = true
        })
        .createPeerConnectionFactory()
}

private fun buildPeerConnection(observer: PeerConnection.Observer) :PeerConnection  {

    val iceServers: ArrayList<PeerConnection.IceServer> = ArrayList()

    val iceServerBuilder: PeerConnection.IceServer.Builder = PeerConnection.IceServer.builder(STUNList)
    iceServerBuilder.setTlsCertPolicy(PeerConnection.TlsCertPolicy.TLS_CERT_POLICY_INSECURE_NO_CHECK)

    val iceServer = iceServerBuilder.createIceServer()
    iceServers.add(iceServer)

    val rtcConfig = RTCConfiguration(iceServers)

    rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED
    rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY
    rtcConfig.enableDtlsSrtp = true

    rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.MAXCOMPAT

    return peerConnectionFactory.createPeerConnection(rtcConfig, observer)!!
}

Create Offer and Answer

private fun PeerConnection.call(sdpObserver: SdpObserver, meetingID: String, callSdpUUID: String) {
    val constraints = MediaConstraints().apply {
        mandatory.add(MediaConstraints.KeyValuePair("offerToReceiveVideo", "true"))
        mandatory.add(MediaConstraints.KeyValuePair("offerToReceiveAudio", "true"))
    }

    createOffer(object : SdpObserver by sdpObserver {
        override fun onCreateSuccess(desc: SessionDescription?) {
            setLocalDescription(object : SdpObserver {
                override fun onSetFailure(p0: String?) {
                    Log.e(TAG, "onCreateOfferSetFailure: $p0")
                }

                override fun onSetSuccess() {

                    val offerSdp = CallsSdp()
                    offerSdp.uuid = callSdpUUID
                    offerSdp.meetingID = meetingID
                    offerSdp.sdp = Text(desc?.description)
                    offerSdp.callType = desc?.type.toString()

                    val upsertTask = cloudDBZone?.executeUpsert(offerSdp)
                    upsertTask?.addOnSuccessListener { cloudDBZoneResult ->
                        Log.i(TAG, "Calls Sdp Upsert success: $cloudDBZoneResult")
                    }?.addOnFailureListener {
                        Log.i(TAG, "Calls Sdp Upsert failed: ${it.message}")
                    }
                    Log.e(TAG, "onLocalOfferSdpSetSuccess")
                }

                override fun onCreateSuccess(p0: SessionDescription?) {
                    Log.e(TAG, "onCreateSuccess: Description $p0")
                }

                override fun onCreateFailure(p0: String?) {
                    Log.e(TAG, "onCreateFailure: $p0")
                }
            }, desc)
            sdpObserver.onCreateSuccess(desc)
        }

        override fun onSetFailure(p0: String?) {
            Log.e(TAG, "onSetCreateOfferFailure: $p0")
        }

        override fun onCreateFailure(p0: String?) {
            Log.e(TAG, "onCreateFailure: $p0")
        }
    }, constraints)
}

private fun PeerConnection.answer(sdpObserver: SdpObserver, meetingID: String, callSdpUUID: String) {
    val constraints = MediaConstraints().apply {
        mandatory.add(MediaConstraints.KeyValuePair("offerToReceiveVideo", "true"))
        mandatory.add(MediaConstraints.KeyValuePair("offerToReceiveAudio", "true"))
    }
    createAnswer(object : SdpObserver by sdpObserver {
        override fun onCreateSuccess(desc: SessionDescription?) {

            val answerSdp = CallsSdp()
            answerSdp.uuid = callSdpUUID
            answerSdp.meetingID = meetingID
            answerSdp.sdp = Text(desc?.description)
            answerSdp.callType = desc?.type.toString()

            val upsertTask = cloudDBZone?.executeUpsert(answerSdp)
            upsertTask?.addOnSuccessListener { cloudDBZoneResult ->
                setLocalDescription(object : SdpObserver {
                    override fun onSetFailure(p0: String?) {
                        Log.e(TAG, "onSetAnswerSdpFailure: $p0")
                    }

                    override fun onSetSuccess() {
                        Log.e(TAG, "onSetAnswerLocalSdpSuccess")
                    }

                    override fun onCreateSuccess(p0: SessionDescription?) {
                        Log.e(TAG, "onCreateSuccess: Description $p0")
                    }

                    override fun onCreateFailure(p0: String?) {
                        Log.e(TAG, "onCreateFailureLocal: $p0")
                    }
                }, desc)
                sdpObserver.onCreateSuccess(desc)
                Log.i(TAG, "Calls Sdp Upsert success: $cloudDBZoneResult")
            }?.addOnFailureListener {
                Log.i(TAG, "Calls Sdp Upsert failed: ${it.message}")
            }
        }

        override fun onCreateFailure(p0: String?) {
            Log.e(TAG, "onCreateFailureRemote: $p0")
        }
    }, constraints)
}

Create and Send Candidates

rtcClient = RTCClient(
        application,
        object : PeerConnectionObserver() {
            override fun onIceCandidate(p0: IceCandidate?) {
                super.onIceCandidate(p0)
                Log.e(TAG, "onIceCandidate: ${p0?.sdpMLineIndex}")
                iceCandidateList.add(p0)
                rtcClient.addIceCandidate(p0)
            }

            override fun onAddStream(p0: MediaStream?) {
                super.onAddStream(p0)
                Log.e(TAG, "onAddStream: $p0")
                p0!!.videoTracks[0].setEnabled(true)
                p0.audioTracks[0].setEnabled(true)


                p0.videoTracks.forEach {
                    it.setEnabled(true)
                    it.addSink(binding.vRemote)
                }

            }

            override fun onIceConnectionChange(p0: PeerConnection.IceConnectionState?) {
                Log.e(TAG, "onIceConnectionChange: $p0")
            }

            override fun onIceConnectionReceivingChange(p0: Boolean) {
                Log.e(TAG, "onIceConnectionReceivingChange: $p0")
            }

            override fun onConnectionChange(newState: PeerConnection.PeerConnectionState?) {
                Log.e(TAG, "onConnectionChange: $newState")
            }

            override fun onDataChannel(p0: DataChannel?) {
                Log.e(TAG, "onDataChannel: $p0")
            }

            override fun onRenegotiationNeeded() {
                super.onRenegotiationNeeded()
            }

            override fun onStandardizedIceConnectionChange(newState: PeerConnection.IceConnectionState?) {
                Log.e(TAG, "onStandardizedIceConnectionChange: $newState")
            }

            override fun onAddTrack(p0: RtpReceiver?, p1: Array<out MediaStream>?) {
                Log.e(TAG, "onAddTrack: $p0 \n $p1")
                p1?.get(0)?.videoTracks?.forEach {
                    it.addSink(binding.vRemote)
                }
            }

            override fun onTrack(transceiver: RtpTransceiver?) {
                val videoTrack = transceiver?.receiver?.track() as VideoTrack
                videoTrack.addSink(binding.vRemote)
                Log.e(TAG, "onTrack: $transceiver")
            }

            override fun onIceGatheringChange(p0: PeerConnection.IceGatheringState?) {
                super.onIceGatheringChange(p0)
                Log.e(TAG, "onIceGathering: $p0")
            }


        }
    )

I am using Huawei CloudDB to exchange both SDPs and Ice Candidates. I can mace audio call but can not see remote pair's video.

v=0
o=- 8705247041455250357 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS local_track
m=audio 9 RTP/AVPF 111 103 104 9 102 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:3eMh
a=ice-pwd:130+TZSM2chFRq2B2KqnwyB8
a=ice-options:trickle renomination
a=mid:audio
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=sendrecv
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:102 ILBC/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:112 telephone-event/32000
a=rtpmap:113 telephone-event/16000
a=rtpmap:126 telephone-event/8000
a=ssrc:241496046 cname:BFs8flhlxmCX8xYM
a=ssrc:241496046 msid:local_track local_track_audio
a=ssrc:241496046 mslabel:local_track
a=ssrc:241496046 label:local_track_audio
m=video 9 RTP/AVPF 96 97 98 99 100 101 127
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:3eMh
a=ice-pwd:130+TZSM2chFRq2B2KqnwyB8
a=ice-options:trickle renomination
a=mid:video
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:13 urn:3gpp:video-orientation
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=sendrecv
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 red/90000
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:127 ulpfec/90000
a=ssrc-group:FID 2826328365 696799361
a=ssrc:2826328365 cname:BFs8flhlxmCX8xYM
a=ssrc:2826328365 msid:local_track local_track
a=ssrc:2826328365 mslabel:local_track
a=ssrc:2826328365 label:local_track
a=ssrc:696799361 cname:BFs8flhlxmCX8xYM
a=ssrc:696799361 msid:local_track local_track
a=ssrc:696799361 mslabel:local_track
a=ssrc:696799361 label:local_track

Here my candidate:

candidate:2158047068 1 udp 1686052607 213.74.85.100 36490 typ srflx raddr 192.168.1.3 rport 55376 generation 0 ufrag 3eMh network-id 3 network-cost 10
0

There are 0 answers