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.
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