• Android WebRTC 入门教程(二) -- 模拟p2p本地视频传输


    Android WebRTC 入门教程(一) – 使用相机
    Android WebRTC 入门教程(二) – 模拟p2p本地视频传输

    源码工程: https://github.com/LillteZheng/WebRTCDemo
    今天要实现的效果:

    上一章中,我们学习了 PeerConnectionFactory ,VideoCapturer 等知识实现了相机的预览。

    这一章,学习一个比较重要的知识,Peerconnection 点对点传输,把本地摄像头的数据,采集之后,通过 Peerconnection 建立连接,把摄像头数据,传递给另一个 surface 显示,
    在这里插入图片描述

    效果如下:
    动态太大,看视频连接 https://live.csdn.net/v/242968

    device-2022-09-30-142040

    一. PeerConnection

    PeerConnection也就是Peer-to-Peer connection(P2P) ,顾名思义,这个类代表点对点之间的连接,以及音视频数据的传输。

    PeerConnection 会通过 Sdp(SessionDescription) 和 ICE(IceCandidate) 进行媒体协商,实际就是你设备支持哪些音视频编解码格式,如果两者都支持,那么就算协商成功了。
    此时通道就已经建立成功,可以从 Peerconnection 中的 MediaStream 去拿数据流了。

    这里先熟悉几个只是点:

    1.1 SDP (SessionDescription)

    会话描述符,像 RTSP 这类协议,都需要进行媒体协商,际就是你的设备所支持的音视频编解码器、使用的传输协议、SSRC等信息…通过信令服务器透传给对方。如果双方都支持,那么就算协商成功了。

    • Offer: 呼叫方发送端的 SDP 消息叫做 offer
    • Answer:被呼叫方发送端的 SDP 消息叫做 Answer

    1.2 ICE(IceCandidate)

    进行媒体协商后,通道此时还未建立,就需要交换 IceCandidate,其实是一个整合STUN和TURN的框架, 给它提供STUN和TURN服务器地址, 它会自动选择优先级高的进行NAT穿透,选择一条稳定的通道。

    二. 实现

    首先,先创建两个 SurfaceViewRender ,一个用来摄像头渲染,一个用来接收:

    
    
        

    采集摄像头数据的,我们使用 localRender ,它的具体实现,可参考上一章 :
    Android WebRTC 入门教程(一) – 使用相机

    接收端使用 remoteRender ,等待数据渲染,所以只需要初始化等待即可:

        /**
         * 配置接收端的渲染surface
         */
        fun configRemoteRender(eglBaseContext:EglBase.Context){
            remoteRender.holder.addCallback(object:SurfaceHolder.Callback{
                override fun surfaceCreated(holder: SurfaceHolder) {
                    //等待接收数据,这里只要初始化即可
                    remoteRender.init(eglBaseContext,null)
                    remoteRender.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
                    remoteRender.setEnableHardwareScaler(false)
                }
    
                override fun surfaceChanged(
                    holder: SurfaceHolder,
                    format: Int,
                    width: Int,
                    height: Int
                ) {
                }
    
                override fun surfaceDestroyed(holder: SurfaceHolder) {
                }
    
            })
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    为了方便解释,这里的发送端使用 local,接收端使用 remote,他们的流程图如下:
    在这里插入图片描述

    2.1 添加 MediaStream

    获取相机数据,上一章中,我们已经实践过了,代码不变,还未看过的可以参考上一章。
    Android WebRTC 入门教程(一) – 使用相机

    代码如下:

    createCameraCapture()?.let { camera->
                         ....
                        // 添加渲染接收端器到轨道中,画面开始呈现
                        videoTrack.addSink(localRender)
    					// 创建 mediastream
                        localMediaStream = peerConnectionFactory.createLocalMediaStream("MediaStream")
                        // 将视频轨添加到 mediastram 中,等待连接时传输 
                        localMediaStream.addTrack(videoTrack)
                    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.2 创建 Peerconnection

    要进行点对点传输,就需要创建 Peerconnection ,使用 PeerconnectionFactory 创建,如

            val iceServers: List = ArrayList()
            //内部会转成 RTCConfiguration
            localPeerConnection = peerConnectionFactory.createPeerConnection(iceServers,object:PeerObserver(){
                override fun onAddStream(strem: MediaStream?) {
                    super.onAddStream(strem)
                }
    
                override fun onIceCandidate(p0: IceCandidate?) {
                    super.onIceCandidate(p0)
                 
    
                }
            })
            remotePeerConnection = peerConnectionFactory.createPeerConnection(iceServers,object :PeerObserver(){
                override fun onAddStream(strem: MediaStream?) {
                    super.onAddStream(strem)
                   
                }
    
                override fun onIceCandidate(p0: IceCandidate?) {
                    super.onIceCandidate(p0)
                }
            })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    其中 PeerObserver 继承 PeerConnection.Observer ,减少一些方法的重写。

    2.2 Create offer

    此时,他们还没有任何联系。
    根据时序图,我们需要先创建 sdp offer,并发送给接收端。
    首先先添加媒体流

      localPeerConnection?.addStream(localMediaStream)
    
    • 1

    然后创建 offer,此时就要进行 sdp 媒体交换:
    在这里插入图片描述
    这个是双方协商的过程,因此会设计到两个比较特殊的方法,setLocalDescription 和setRemoteDescription,双方在创建sdp 之后,都需要使用 setLocalDescription 把 sdp 保存到本地,然后对方使用 setRemoteDescription,把对方的 sdp 保存到远程。
    具体方法如下:

            //客户端创建 offer
            localPeerConnection?.createOffer(object:SdpObserver("创建local offer"){
                override fun onCreateSuccess(p0: SessionDescription?) {
                    super.onCreateSuccess(p0)
                    Log.d(TAG, "创建 local offer success: ")
                    /**
                     * 此时调用setLocalDescription()方法将该Offer保存到本地Local域,
                     * 然后将Offer发送给对方
                     */
                    localPeerConnection?.setLocalDescription(SdpObserver("local 设置本地 sdp"),p0)
                    //2. 同时,接收端 setRemoteDescription 把 offer 保存到远端域
                    remotePeerConnection?.setRemoteDescription(SdpObserver("把 sdp 给到 remote"),p0)
    
                    //3. 此时remote 已经接收端了 offer ,所以创建 answer
                    remotePeerConnection?.createAnswer(object:SdpObserver("创建 remote answer"){
                        override fun onCreateSuccess(p0: SessionDescription?) {
                            super.onCreateSuccess(p0)
                            Log.d(TAG, " 创建 remote answer success: ")
                            /**
                             * 4. 此时调用setLocalDescription()方法将该 answer 保存到本地 remote 域,
                             * 然后将 answer 发送给对方
                             */
                            remotePeerConnection?.setLocalDescription(SdpObserver("remote 设置本地 sdp"),p0)
                            //5. local 把 answer 保存到它的 remote 端,此时 sdp 交换结束
                            localPeerConnection?.setRemoteDescription(SdpObserver("把 sdp 给到 local"),p0)
                        }
    
                    }, MediaConstraints())
                }
            }, MediaConstraints())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    我们以连接上的Client端为呼叫方先发起Offer请求,通过createOffer()创建一个Offer SDP。创建成功后,会在SdpObserver中收到onCreateSuccess回调,此时调用setLocalDescription()方法将该Offer保存到本地Local域,然后将Offer发送给对方。

    被呼叫方接收到Offer后,通过setRemoteDescription()方法将Offer保存到它的Remote域,并通过createAnswer()创建Answer SDP,创建成功后同样调用setLocalDescription()方法将Answer消息保存到本地的Local域,然后回复给呼叫方。

    最后,呼叫方将收到Answer消息,并通过setRemoteDescription()方法,将Answer保存到它的Remote域。至此,整个媒体协商的过程结束。

    其中 SdpObserver 继承 webrtc 的 SdpObserver ,减少一些方法的重写。

    三. 建立点对点连接

    媒体协商结束,我们的点对点并没有真正的简历。此时 PeerConnection.Observer 会回调 onIceCandidate ,我们需要将各自的 Candidate 添加给对方。
    同时,因为 local 作为视频流的一方,onAddStream 不需要理会,但 remote 作为接收方,则需要把 MediaStream 中的流拿出来,放到 SurfaceViewRender 去渲染。
    代码如下:

            localPeerConnection = peerConnectionFactory.createPeerConnection(iceServers,object:PeerObserver(){
                override fun onAddStream(strem: MediaStream?) {
                    super.onAddStream(strem)
                }
    
                override fun onIceCandidate(p0: IceCandidate?) {
                    super.onIceCandidate(p0)
                    //透传给另外一端
                    remotePeerConnection?.addIceCandidate(p0)
    
                }
            })
            remotePeerConnection = peerConnectionFactory.createPeerConnection(iceServers,object :PeerObserver(){
                override fun onAddStream(strem: MediaStream?) {
                    super.onAddStream(strem)
                    //拿到视频流,添加 sink ,SurfaceViewRender 会直接渲染
                    strem?.let {
                        runOnUiThread {
                            it.videoTracks[0].addSink(remoteRender)
                        }
                    }
                }
    
                override fun onIceCandidate(p0: IceCandidate?) {
                    super.onIceCandidate(p0)
                    //透传给另外一端
                    localPeerConnection?.addIceCandidate(p0)
    
                }
            })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    基本上学完这样,我们已经可以进行不同端传输音视频了。
    只是sdp 的协商,改成其他信令服务器。

    参考:
    https://codezjx.com/posts/webrtc-android-demo/#more

  • 相关阅读:
    电容笔和apple pencil的区别大吗?Ipad好用电容笔推荐
    跨境电商小知识之跨境电商物流定义以及方式讲解
    猿创征文|redis安装(Linux)
    weblogic CVE-2024-20931分析
    网络安全神器,资深网工必备(全都免费附安装包)
    java自定义注解
    MobTech ShareSDK 高级接口及配置
    Java百题大战
    线性dp,优化记录,273. 分级
    控制器连接Profinet转Modbus RTU网关与精密数显温控仪通讯配置案例
  • 原文地址:https://blog.csdn.net/u011418943/article/details/127117836