Android WebRTC 无法在不同网络上运行 - 无视频

Android WebRTC doesn't work on Different network - No Video

我正在尝试通过 webrtc 将视频从 Raspberry Pi 流式传输到 android 设备。我正在使用 firebase (firestore) 作为信号。我可以 运行 在连接到同一个 wifi 时进行设置,但是当使用不同的网络时它会失败。

设备 - RPI

客户端 1) Web 客户端(托管在 firebase 上) 2) Android 应用

在设备和客户端之间的同一网络 (wifi) 上,两个客户端都可以播放视频和音频。

但是当设备和客户端在不同的网络上时,Web 客户端可以显示视频,但 Android 应用程序无法显示视频。

信号正常工作,设备上的摄像头和麦克风已启动,冰候选物已成功交换。我还在 android 上添加了远程流(调用了 onAddStream)。但是没有视频和音频播放。

Android PeerConnectionClient

    class PeerConnectionClient(private val activity: MainActivity, private val fSignalling: FSignalling) {

    internal var isVideoRunning = false

    private val rootEglBase by lazy {
        EglBase.create()
    }

    private val peerConnectionFactory: PeerConnectionFactory by lazy {
        val initializationOptions = PeerConnectionFactory.InitializationOptions.builder(activity).createInitializationOptions()
        PeerConnectionFactory.initialize(initializationOptions)

        val options = PeerConnectionFactory.Options()
        val defaultVideoEncoderFactory = DefaultVideoEncoderFactory(rootEglBase.eglBaseContext, true, true)
        val defaultVideoDecoderFactory = DefaultVideoDecoderFactory(rootEglBase.eglBaseContext)
        PeerConnectionFactory.builder()
                .setOptions(options)
                .setVideoEncoderFactory(defaultVideoEncoderFactory)
                .setVideoDecoderFactory(defaultVideoDecoderFactory)
                .createPeerConnectionFactory()
    }

    private val iceServersList = mutableListOf("stun:stun.l.google.com:19302")

    private var sdpConstraints: MediaConstraints? = null
    private var localAudioTrack: AudioTrack? = null

    private var localPeer: PeerConnection? = null

    private var gotUserMedia: Boolean = false
    private var peerIceServers: MutableList<PeerConnection.IceServer> = ArrayList()

    init {
        peerIceServers.add(PeerConnection.IceServer.builder(iceServersList).createIceServer())

        // activity.surface_view.release()
        activity.surface_view.init(rootEglBase.eglBaseContext, null)
        activity.surface_view.setZOrderMediaOverlay(true)

        createPeer()
    }

    private fun createPeer() {
        sdpConstraints = MediaConstraints()


        val audioconstraints = MediaConstraints()
        val audioSource = peerConnectionFactory.createAudioSource(audioconstraints)
        localAudioTrack = peerConnectionFactory.createAudioTrack("101", audioSource)
        gotUserMedia = true

        activity.runOnUiThread {
            if (localAudioTrack != null) {
                createPeerConnection()
                // doCall()
            }
        }

    }

    /**
     * Creating the local peerconnection instance
     */
    private fun createPeerConnection() {
        val constraints = MediaConstraints()
        constraints.mandatory.add(MediaConstraints.KeyValuePair("offerToReceiveAudio", "true"))
        constraints.mandatory.add(MediaConstraints.KeyValuePair("offerToReceiveVideo", "true"))
        constraints.optional.add(MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"))

        val rtcConfig = PeerConnection.RTCConfiguration(peerIceServers)
        // TCP candidates are only useful when connecting to a server that supports
        // ICE-TCP.
        rtcConfig.enableDtlsSrtp = true
        rtcConfig.enableRtpDataChannel = true
        // rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED
        // rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE
        // rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE
        // rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY
        // Use ECDSA encryption.
        // rtcConfig.keyType = PeerConnection.KeyType.ECDSA
        localPeer = peerConnectionFactory.createPeerConnection(rtcConfig, constraints, object : PeerObserver {
            override fun onIceCandidate(p0: IceCandidate) {
                super.onIceCandidate(p0)
                onIceCandidateReceived(p0)
            }

            override fun onAddStream(p0: MediaStream) {
                activity.showToast("Received Remote stream")
                super.onAddStream(p0)
                gotRemoteStream(p0)
            }

        })

        addStreamToLocalPeer()
    }

    /**
     * Adding the stream to the localpeer
     */
    private fun addStreamToLocalPeer() {
        //creating local mediastream
        val stream = peerConnectionFactory.createLocalMediaStream("102")
        stream.addTrack(localAudioTrack)
        localPeer!!.addStream(stream)
    }

    /**
     * This method is called when the app is initiator - We generate the offer and send it over through socket
     * to remote peer
     */
    /*private fun doCall() {
        localPeer!!.createOffer(object : mySdpObserver {
            override fun onCreateSuccess(p0: SessionDescription) {
                super.onCreateSuccess(p0)
                localPeer!!.setLocalDescription(object: mySdpObserver {}, p0)
                Log.d("onCreateSuccess", "SignallingClient emit ")
            }
        }, sdpConstraints)
    }*/

    private fun onIceCandidateReceived(iceCandidate: IceCandidate) {
        //we have received ice candidate. We can set it to the other peer.
        if (localPeer == null) {
            return
        }

        val message = JSONObject()
        message.put("type", "candidate")
        message.put("label", iceCandidate.sdpMLineIndex)
        message.put("id", iceCandidate.sdpMid)
        message.put("candidate", iceCandidate.serverUrl)

        fSignalling.doSignalingSend(message.toString())
    }

    private fun gotRemoteStream(stream: MediaStream) {
        isVideoRunning = true
        //we have remote video stream. add to the renderer.
        val videoTrack = stream.videoTracks[0]
        videoTrack.setEnabled(true)
        activity.runOnUiThread {
            try {
                // val remoteRenderer = VideoRenderer(surface_view)
                activity.surface_view.visibility = View.VISIBLE
                // videoTrack.addRenderer(remoteRenderer)
                videoTrack.addSink(activity.surface_view)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    fun onReceivePeerMessage(data: JSONObject) {
        if (data.getString("type") == "offer") {
            // val sdpReturned = SdpUtils.forceChosenVideoCodec(data.getString("sdp"), "H264")
            val sdpReturned = data.getString("sdp")
            // data.remove("sdp")
            // data.put("sdp", sdpReturned)

            val sessionDescription = SessionDescription(SessionDescription.Type.OFFER, sdpReturned)

            localPeer?.setRemoteDescription(object: mySdpObserver { }, sessionDescription)

            localPeer?.createAnswer(object : mySdpObserver {
                override fun onCreateSuccess(p0: SessionDescription) {
                    super.onCreateSuccess(p0)
                    localPeer!!.setLocalDescription( object : mySdpObserver {}, p0)

                    val description = JSONObject()
                    description.put("type", p0.type.canonicalForm())
                    description.put("sdp", p0.description)

                    this@PeerConnectionClient.fSignalling.doSignalingSend(description.toString())
                }

                override fun onCreateFailure(p0: String) {
                    super.onCreateFailure(p0)
                    activity.showToast("Failed to create answer")
                }

            }, MediaConstraints())

        } else if (data.getString("type") == "candidate") {
            val iceCandidates = IceCandidate(data.getString("id"), data.getInt("label"), data.getString("candidate"))
            localPeer?.addIceCandidate(iceCandidates)
        }
    }

    internal fun close() {
        isVideoRunning = false
        localPeer?.close()
        localPeer = null
    }
 }

我的印象是,如果 Web 客户端能够在不同网络(移动热点)上显示视频,那么 android Web 客户端使用的同一互联网上的客户端也应该能够显示视频。这是错的吗? 为什么 android 不显示视频(调用 onAddStream)

是否需要使用Turn server?我的再次假设是,如果 Web 客户端有效,那么 android 也应该有效。我在 RPI 上使用的服务不支持 turn server。

补充信息: 设备在双 natted ISP 后面(我猜)(但由于 Web 客户端可以连接,所以我猜这不会成为问题)。

我找到了解决问题的方法

我正在使用

private fun onIceCandidateReceived(iceCandidate: IceCandidate) {
    //we have received ice candidate. We can set it to the other peer.
    if (localPeer == null) {
        return
    }

    val message = JSONObject()
    message.put("type", "candidate")
    message.put("label", iceCandidate.sdpMLineIndex)
    message.put("id", iceCandidate.sdpMid)
    message.put("candidate", iceCandidate.serverUrl)

    fSignalling.doSignalingSend(message.toString())
}

而是需要使用

message.put("candidate", iceCandidate.sdp) // iceCandidate.serverUrl)