• android WebRtc 视频通话(P2P)


    概述

    在这里插入图片描述

         WebRTC名称源自网页实时通信(Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的技术,是谷歌2010年以6820万美元收购Global IP Solutions公司而获得的一项技术。Google于2011年6月3日开源的即时通讯项目,旨在使其成为客户端视频通话的标准。其实在Google将WebRTC开源之前,微软和苹果各自的通讯产品已占用很大市场份额(如Skype),Google也是为了快速扩大市场,所以将他给开源。在行业内得到了广泛的支持和应用,成为下一代视频通话的标准。更多介绍可以去官网上看。

         WebRTC被誉为是web长期开源开发的一个新启元,是近年来Web开发的最重要创新。WebRTC允许Web开发者在其web应用中添加视频聊天或者点对点数据传输,不需要复杂的代码或者昂贵的配置。目前支持Chrome、Firefox和Opera,后续会支持更多的浏览器,它有能力达到数十亿的设备。

         目前,WebRTC的应用已经不局限在浏览器与浏览器之间,通过官方提供的SDK,我们可以很容易的实现本地应用间的音视频传输。在Android平台上,我们也非常容易的集成WebRTC框架,用非常简洁的代码就能实现强大、可靠的音视频传输功能。
     

    实现

    说明

    本文代码修改自meshenger-android

    True P2P Voice- and video phone calls without the need for accounts or access to the Internet. There is no discovery mechanism, no meshing and no servers. Just scan each others QR-Code that will contain the contacts IP address. This works in many local networks such as community mesh networks, company networks or at home.

    翻译如下:

    真正的 P2P 语音和视频电话呼叫,无需帐户或访问互联网。 没有发现机制,没有网格化,也没有服务器。 只需相互扫描包含联系人 IP 地址的二维码即可。 这适用于许多本地网络,例如社区网状网络、公司网络或家庭网络。

    版本: meshenger-android-3.0.3 最后的JAVA版本, 最新版本已经改用kotlin
    基本情况:
       功能满足一个局域网P2P的视频通话功能

    • 二维码身份生成-----正常
    • 二维码扫码添加通讯录-----正常
    • 连接发起通话------异常
      主要问题在于无法创建两端之间的Socket连接, 与WebRTC本身无关
    • 通讯加解密 ----- 异常
    • 视频通话 ---- 只显示对方视频不显示自身

    感谢作者


    1. 增加WebRtc支持

    build.gradle

    dependencies {
        implementation 'org.webrtc:google-webrtc:1.0.32006'
    }
    
    • 1
    • 2
    • 3
    1. 关键代码及应用

    创建 PeerConnectionFactory

        private void initRTC() {
            log("initRTC");
            eglCtxRemote = EglBase.create().getEglBaseContext();
            eglCtxLocal = EglBase.create().getEglBaseContext();
    
            //创建 PeerConnectionFactory
            //这种方法存在兼容性问题, 在一些平台上, 会导致后续流程不能正常执行.
            /*PeerConnectionFactory.initialize(PeerConnectionFactory
                    .InitializationOptions.builder(context)
                    .createInitializationOptions());
            factory = PeerConnectionFactory.builder().createPeerConnectionFactory();*/
    
            constraints = new MediaConstraints();
            constraints.optional.add(new MediaConstraints.KeyValuePair("offerToReceiveAudio", "true"));
            constraints.optional.add(new MediaConstraints.KeyValuePair("offerToReceiveVideo", "true"));
            constraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
    
            //initVideoTrack();
    
            PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(context).createInitializationOptions());
            PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
            //https://yuriyshea.com/archives/androidwebrtc%E8%B8%A9%E5%9D%91%E6%8C%87%E5%8D%97
            DefaultVideoEncoderFactory enVdf = new DefaultVideoEncoderFactory(eglCtxRemote, true, true);
            VideoDecoderFactory deVdf = new DefaultVideoDecoderFactory(eglCtxRemote);
    
            //创建 PeerConnectionFactory
            factory = PeerConnectionFactory.builder()
                    .setOptions(options)
                    .setVideoEncoderFactory(enVdf)
                    .setVideoDecoderFactory(deVdf)
                    .createPeerConnectionFactory();
            log("initRTC done");
        }
    
    • 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
    • 31
    • 32
    • 33

    创建PeerConnection 发起呼叫

    connection = factory.createPeerConnection(Collections.emptyList(), new DefaultObserver() {
                    @Override
                    public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
                        super.onIceGatheringChange(iceGatheringState);
                        log("Outgoing.onIceGatheringChange " + iceGatheringState.name());
    
                        if (iceGatheringState == PeerConnection.IceGatheringState.COMPLETE) {
                                        log("Outgoing.connect call from remote address: " + contact.getAddresses());
                                        reportStateChange(CallState.CONNECTING);
                                        //发送信息给接收方,告知发起通话.
                                        getPublisher().sendCall(contact.getAddresses(), connection.getLocalDescription().description);
                    }
    
                    @Override
                    public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
                        log("Outgoing.onIceConnectionChange " + iceConnectionState.name());
                        super.onIceConnectionChange(iceConnectionState);
                        if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED) {
                            reportStateChange(CallState.DISCONNECTED);
                        }else if (iceConnectionState == PeerConnection.IceConnectionState.CLOSED) {
                            hangUp("Outgoing.onIceConnectionChange CLOSED");
                        }
                    }
    
                    @Override
                    public void onAddStream(MediaStream mediaStream) {
                        log("Outgoing.onAddStream");
                        super.onAddStream(mediaStream);
                        handleMediaStream(mediaStream);
                    }
    
                    @Override
                    public void onDataChannel(DataChannel dataChannel) {
                        log("Outgoing.onDataChannel");
                        super.onDataChannel(dataChannel);
                        RTCCall.this.dataChannel = dataChannel;
                        dataChannel.registerObserver(RTCCall.this);
                    }
                });
    			//初始化音视频通道
                connection.addStream(createStream());
                //创建数据通道, 可用于收发消息.
                this.dataChannel = connection.createDataChannel("data", new DataChannel.Init());
                this.dataChannel.registerObserver(this);
                log("Outgoing.createOffer");
                connection.createOffer(new DefaultSdpObserver() {
                    @Override
                    public void onCreateSuccess(SessionDescription sessionDescription) {
                        log("Outgoing.onCreateSuccess");
                        super.onCreateSuccess(sessionDescription);
                        connection.setLocalDescription(new DefaultSdpObserver(), sessionDescription);
                    }
                }, constraints);
    
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    呼叫方收到后同样初始化, 并在点击接听后创建PeerConnection

        public void accept(OnStateChangeListener listener) {
            log("accept");
            this.listener = listener;
            new Thread(() -> {
                connection = factory.createPeerConnection(this.iceServers, new DefaultObserver() {
                    @Override
                    public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
                        super.onIceGatheringChange(iceGatheringState);
                        if (iceGatheringState == PeerConnection.IceGatheringState.COMPLETE) {
                            log("Incoming.onIceGatheringChange");
                            //通知已接听
                            getPublisher().sendAnswer(contact.getAddresses(),
                                    connection.getLocalDescription().description);
                            reportStateChange(CallState.CONNECTED);
                        }
                    }
    
                    @Override
                    public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
                        log("Incoming.onIceConnectionChange " + iceConnectionState.name());
                        super.onIceConnectionChange(iceConnectionState);
                        if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED) {
                            reportStateChange(CallState.DISCONNECTED);
                        }else if (iceConnectionState == PeerConnection.IceConnectionState.CLOSED) {
                            hangUp("Incoming.onIceConnectionChange CLOSED");
                        }
                    }
    
                    @Override
                    public void onAddStream(MediaStream mediaStream) {
                        log("Incoming.onAddStream");
                        super.onAddStream(mediaStream);
                        handleMediaStream(mediaStream);
                    }
    
                    @Override
                    public void onDataChannel(DataChannel dataChannel) {
                        super.onDataChannel(dataChannel);
                        RTCCall.this.dataChannel = dataChannel;
                        dataChannel.registerObserver(RTCCall.this);
                    }
    
                });
                connection.addStream(createStream());
                //this.dataChannel = connection.createDataChannel("data", new DataChannel.Init());
    
                log("Incoming.setting remote description");
                //设置会话, 创建响应应答
                connection.setRemoteDescription(new DefaultSdpObserver() {
                    @Override
                    public void onSetSuccess() {
                        super.onSetSuccess();
                        log("creating answer...");
                        connection.createAnswer(new DefaultSdpObserver() {
                            @Override
                            public void onCreateSuccess(SessionDescription sessionDescription) {
                                log("Incoming.onCreateSuccess");
                                super.onCreateSuccess(sessionDescription);
                                connection.setLocalDescription(new DefaultSdpObserver(), sessionDescription);
                            }
    
                            @Override
                            public void onCreateFailure(String s) {
                                super.onCreateFailure(s);
                                log("Incoming.onCreateFailure: " + s);
                            }
                        }, constraints);
                    }
                }, new SessionDescription(SessionDescription.Type.OFFER, offer));
            }).start();
        }
    
    
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    呼叫方处理应答

        private void handleAnswer(String remoteDesc) {
            log("handleAnswer");
            connection.setRemoteDescription(new DefaultSdpObserver() {
                @Override
                public void onSetSuccess() {
                    super.onSetSuccess();
                }
    
                @Override
                public void onSetFailure(String s) {
                    super.onSetFailure(s);
                }
            }, new SessionDescription(SessionDescription.Type.ANSWER, remoteDesc));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    启动摄像头

        public void setVideoEnabled(boolean enabled) {
            log("setVideoEnabled enabled=" + enabled);
            this.videoEnabled = enabled;
            try {
                if (enabled) {
                    this.capturer.startCapture(640, 480, 30);
                } else {
                    this.capturer.stopCapture();
                }
                JSONObject object = new JSONObject();
                object.put(StateChangeMessage, enabled ? CameraEnabledMessage : CameraDisabledMessage);
                log("setVideoEnabled: " + object);
                dataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(object.toString().getBytes()), false));
            } catch (JSONException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    挂断

        public void hangUp(String res) {
            if(state == CallState.ENDED){
                log("hangUp Ignored already ENDED:" + res);
                return;
            }
    
            log("hangUp:" + res);
            reportStateChange(CallState.ENDED);
            closePeerConnection();
    		//通知挂断.
            new Thread(() -> {
                getPublisher().sendHangup(contact.getAddresses());
            }).start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    //Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 in tid 13128 (signaling_threa), pid 13067 (d.d.meshenger)
        //type=1400 audit(0.0:28585): avc: granted { nlmsg_readpriv } for scontext=u:r:untrusted_app_29:s0:c5,c257,c512,c768 tcontext=u:r:untrusted_app_29:s0:c5,c257,c512,c768 tclass=netlink_route_socket app=d.d.meshenger
        //pid: 13067, tid: 13128, name: signaling_threa  >>> d.d.meshenger <<<
        private void closePeerConnection() {
            log("closePeerConnection");
            if(localRenderer != null){
                localRenderer.release();
                localRenderer = null;
            }
            if(remoteRenderer != null){
                remoteRenderer.release();
                remoteRenderer = null;
            }
            if(capturer != null){
                try {
                    capturer.stopCapture();
                    capturer.dispose();
                    capturer = null;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }
            if (connection != null) {
                try {
                    PeerConnection conn = connection;
                    connection = null;
                    conn.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                //connection = null;
                log("closePeerConnection done");
            }
        }
    
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35

    完整呼叫流程LOG如下

    [呼叫方]

    ## 开始呼出
    2022-12-01 14:50:09.016 23145-23717 RTCCall  D  RTCCall created
    2022-12-01 14:50:09.016 23145-23717 RTCCall  D  initRTC
    2022-12-01 14:50:09.041 23145-23717 RTCCall  D  initRTC done
    2022-12-01 14:50:09.042 23145-23728 RTCCall  D  createPeerConnection
    2022-12-01 14:50:09.052 23145-23728 RTCCall  D  createStream
    2022-12-01 14:50:09.058 23145-23728 RTCCall  D  createCapturer
    2022-12-01 14:50:09.139 23145-23728 RTCCall  D  Outgoing.createOffer
    2022-12-01 14:50:09.144 23145-23725 RTCCall  D  Outgoing.onCreateSuccess
    2022-12-01 14:50:09.212 23145-23725 RTCCall  D  Outgoing.onIceGatheringChange GATHERING
    2022-12-01 14:50:09.327 23145-23725 RTCCall  D  Outgoing.onIceGatheringChange COMPLETE
    2022-12-01 14:50:09.327 23145-23725 RTCCall  D  transferring offer...
    2022-12-01 14:50:09.328 23145-23735 RTCCall  D  Outgoing.connect call from remote address: 192.168.7.239
    2022-12-01 14:50:09.328 23145-23735 RTCCall  D  reportStateChange CONNECTING
    
    ## 对方接听
    2022-12-01 14:50:15.331 23145-23168 RTCCall  D  reportStateChange CONNECTED
    2022-12-01 14:50:15.331 23145-23168 RTCCall  D  handleAnswer
    2022-12-01 14:50:15.409 23145-23725 RTCCall  D  Outgoing.onIceConnectionChange CHECKING
    2022-12-01 14:50:15.415 23145-23725 RTCCall  D  Outgoing.onAddStream
    2022-12-01 14:50:15.415 23145-23725 RTCCall  D  handleMediaStream ava=false
    2022-12-01 14:50:15.416 23145-23725 RTCCall  D  handleAnswer.onSetSuccess
    2022-12-01 14:50:15.512 23145-23725 RTCCall  D  Outgoing.onIceConnectionChange CONNECTED
    2022-12-01 14:50:15.528 23145-23725 RTCCall  D  onStateChange
    
    ## 对方开启摄像头, 并推送
    2022-12-01 14:50:24.939 23145-23725 RTCCall  D  onMessage: {"StateChange":"CameraEnabled"}
    2022-12-01 14:50:30.932 23145-23725 RTCCall  D  Outgoing.onIceConnectionChange COMPLETED
    
    ## 开启摄像头并推送
    2022-12-01 14:50:39.122 23145-23145 RTCCall  D  setVideoEnabled enabled=true
    2022-12-01 14:50:39.122 23145-23145 RTCCall  D  setVideoEnabled: {"StateChange":"CameraEnabled"}
    2022-12-01 14:50:39.124 23145-23725 RTCCall  D  onBufferedAmountChange l=31
    
    ## 挂断
    2022-12-01 14:51:03.363 23145-23145 RTCCall  D  hangUp:UI.callDecline click
    2022-12-01 14:51:03.363 23145-23145 RTCCall  D  reportStateChange ENDED
    2022-12-01 14:51:03.363 23145-23145 RTCCall  D  closePeerConnection
    2022-12-01 14:51:03.370 23145-23145 RTCCall  D  closePeerConnection state=CONNECTED
    2022-12-01 14:51:03.372 23145-23725 RTCCall  D  Outgoing.onIceConnectionChange CLOSED
    2022-12-01 14:51:03.372 23145-23725 RTCCall  D  hangUp Ignored already ENDED:Outgoing.onIceConnectionChange CLOSED
    2022-12-01 14:51:03.547 23145-23725 RTCCall  D  onStateChange
    2022-12-01 14:51:03.547 23145-23725 RTCCall  D  onStateChange
    2022-12-01 14:51:03.580 23145-23145 RTCCall  D  closePeerConnection done
    2022-12-01 14:51:04.059 23145-23145 RTCCall  D  releaseCamera
    2022-12-01 14:51:04.149 23145-23168 RTCCall  D  hangUp Ignored already ENDED:publisher msg
    
    
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    [被叫方]

    ## 来电并响铃
    2022-12-01 14:22:30.319  8916-8943  RTCCall  D  initRTC
    2022-12-01 14:22:30.363  8916-8943  RTCCall  D  initRTC done
    2022-12-01 14:22:30.364  8916-8943  RTCCall  D  reportStateChange RINGING
    
    ## 接听
    2022-12-01 14:22:35.625  8916-8916  RTCCall  D  setRenderer
    2022-12-01 14:22:35.625  8916-8916  RTCCall  D  accept
    2022-12-01 14:22:35.661  8916-14310 RTCCall  D  createStream
    2022-12-01 14:22:35.662  8916-14310 RTCCall  D  createCapturer
    2022-12-01 14:22:35.677  8916-14310 RTCCall  D  Incoming.setting remote description
    2022-12-01 14:22:35.768  8916-14256 RTCCall  D  Incoming.onAddStream
    2022-12-01 14:22:35.768  8916-14256 RTCCall  D  handleMediaStream ava=false
    2022-12-01 14:22:35.769  8916-14256 RTCCall  D  creating answer...
    2022-12-01 14:22:35.773  8916-14256 RTCCall  D  Incoming.onCreateSuccess
    2022-12-01 14:22:35.873  8916-14256 RTCCall  D  Incoming.onIceConnectionChange CHECKING
    2022-12-01 14:22:36.035  8916-14256 RTCCall  D  Incoming.onIceGatheringChange
    
    ## 连接已建立
    2022-12-01 14:22:36.048  8916-14256 RTCCall  D  reportStateChange CONNECTED
    2022-12-01 14:22:36.256  8916-14256 RTCCall  D  Incoming.onIceConnectionChange CONNECTED
    2022-12-01 14:22:36.278  8916-14256 RTCCall  D  onStateChange
    
    ## 开启摄像头并推送
    2022-12-01 14:22:45.635  8916-8916  RTCCall  D  setVideoEnabled enabled=true
    2022-12-01 14:22:45.636  8916-8916  RTCCall  D  setVideoEnabled: {"StateChange":"CameraEnabled"}
    2022-12-01 14:22:45.638  8916-14256 RTCCall  D  onBufferedAmountChange l=31
    
    ## 对方开启摄像头
    2022-12-01 14:23:02.916  8916-14256 RTCCall  D  onMessage: {"StateChange":"CameraEnabled"}
    
    ## 对方已挂断
    2022-12-01 14:23:24.318  8916-14256 RTCCall  D  Incoming.onIceConnectionChange DISCONNECTED
    2022-12-01 14:23:24.318  8916-14256 RTCCall  D  reportStateChange DISCONNECTED
    2022-12-01 14:23:24.320  8916-14256 RTCCall  D  onStateChange
    2022-12-01 14:23:24.320  8916-14256 RTCCall  D  onStateChange
    2022-12-01 14:23:24.430  8916-8943  RTCCall  D  hangUp:publisher msg
    2022-12-01 14:23:24.430  8916-8943  RTCCall  D  reportStateChange ENDED
    2022-12-01 14:23:24.431  8916-8943  RTCCall  D  closePeerConnection
    2022-12-01 14:23:24.445  8916-8943  RTCCall  D  closePeerConnection state=CONNECTED
    2022-12-01 14:23:24.449  8916-14256 RTCCall  D  Incoming.onIceConnectionChange CLOSED
    2022-12-01 14:23:24.449  8916-14256 RTCCall  D  hangUp Ignored already ENDED:Incoming.onIceConnectionChange CLOSED
    2022-12-01 14:23:24.705  8916-8943  RTCCall  D  closePeerConnection done
    2022-12-01 14:23:24.924  8916-8916  RTCCall  D  releaseCamera
    
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    一些问题

    首先是源码中Socket连接的问题

    Utils.java 与IPV6有关, 这里改成了IPV4.
    并在后续的网络相关部分改为使用IP地址.

        public static List<InetSocketAddress> getAddressPermutations(String contact_mac, int port) {
            byte[] contact_mac_bytes = Utils.macAddressToBytes(contact_mac);
            ArrayList<InetSocketAddress> addrs = new ArrayList<InetSocketAddress>();
            try {
                List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
                for (NetworkInterface nif : all) {
                    if (nif.isLoopback()) {
                        continue;
                    }
    
                    for (InterfaceAddress ia : nif.getInterfaceAddresses()) {
                        InetAddress addr = ia.getAddress();
                        if (addr.isLoopbackAddress()) {
                            continue;
                        }
    
                        android.util.Log.d("Utils", "getAddressPermutations " + addr.getHostName() + "," + addr.getHostAddress() + ",");
    
                        if (addr instanceof Inet4Address) {
                            addrs.add(new InetSocketAddress(addr, port));
                            /*Inet6Address addr6 = (Inet6Address) addr;
                            byte[] extracted_mac = getEUI64MAC(addr6);
                            if (extracted_mac != null && Arrays.equals(extracted_mac, nif.getHardwareAddress())) {
                                // We found the interface MAC address in the IPv6 assigned to that interface in the EUI-64 scheme.
                                // Now assume that the contact has an address with the same scheme.
                                InetAddress new_addr = createEUI64Address(addr6, contact_mac_bytes);
                                if (new_addr != null) {
                                    addrs.add(new InetSocketAddress(new_addr, port));
                                }
                            }*/
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
    
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    增加本地摄像头预览显示
    方法是传入本地的SurfaceViewRenderer并修改getVideoTrack

        private boolean enablePreview = true;
        private VideoTrack getVideoTrack() {
            this.capturer = createCapturer();
            if(!enablePreview) {
                return factory.createVideoTrack("video1", factory.createVideoSource(false));
            }else {
                VideoSource videoSource = factory.createVideoSource(false);
                //EglBase.Context eglBaseContext = EglBase.create().getEglBaseContext();
                SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglCtxLocal);
                capturer.initialize(surfaceTextureHelper, App.getApp(), videoSource.getCapturerObserver());
                //capturer.startCapture(480, 640, 30);
                VideoTrack track = factory.createVideoTrack("video1", videoSource);
                track.addSink(localRenderer);
                return track;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    初始化方式问题导致无正常回调

            constraints = new MediaConstraints();
            constraints.optional.add(new MediaConstraints.KeyValuePair("offerToReceiveAudio", "true"));
            constraints.optional.add(new MediaConstraints.KeyValuePair("offerToReceiveVideo", "true"));
            constraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
    //方式1:
    	PeerConnectionFactory.initialize(PeerConnectionFactory
                    .InitializationOptions.builder(context)
                    .createInitializationOptions());
            factory = PeerConnectionFactory.builder().createPeerConnectionFactory();
    
    //方式2:        
            PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(context).createInitializationOptions());
            PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
            DefaultVideoEncoderFactory enVdf = new DefaultVideoEncoderFactory(eglCtxRemote, true, true);
            VideoDecoderFactory deVdf = new DefaultVideoDecoderFactory(eglCtxRemote);
    
            factory = PeerConnectionFactory.builder()
                    .setOptions(options)
                    .setVideoEncoderFactory(enVdf)
                    .setVideoDecoderFactory(deVdf)
                    .createPeerConnectionFactory();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    方式1 会导致呼出时的错误如下

    connection.createOffer(new DefaultSdpObserver() {
                    @Override
                    public void onCreateSuccess(SessionDescription sessionDescription) {
                        log("Outgoing.onCreateSuccess");
                        super.onCreateSuccess(sessionDescription);
                        connection.setLocalDescription(new DefaultSdpObserver(){
                            @Override
                            public void onSetFailure(String s) {
                                super.onSetFailure(s);
                                //出错时的LOG:
                                //onSetFailure s=
                                //Failed to set local offer sdp: 
                                //Failed to set local video description recv parameters for m-section with mid='video'.
                                log("Outgoing.onSetFailure s=" + s);
                            }
                        }, sessionDescription);
                    }
                }, constraints);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    多次调用PeerConnection.close()会导致崩溃

    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A  Build fingerprint: 'google/blueline/blueline:12/SP1A.210812.016.C1/8029091:user/release-keys'
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A  Revision: 'MP1.0'
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A  ABI: 'arm64'
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A  Timestamp: 2022-12-01 14:31:57.642912791+0800
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A  Process uptime: 0s
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A  Cmdline: d.d.meshenger
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A  pid: 22778, tid: 22851, name: signaling_threa  >>> d.d.meshenger <<<
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A  uid: 10261
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A  signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A  Cause: null pointer dereference
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      x0  0000000000000000  x1  0000000000000005  x2  0000000000000000  x3  0000007c6f2be89d
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      x4  0000007c584fd818  x5  0000007f12e7a555  x6  73656d2f642f644c  x7  632f7265676e6568
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      x8  0000007be2f5a000  x9  81248e9b13f44bd4  x10 0000000000430000  x11 0000000000000001
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      x12 0000000000000004  x13 0000000000000004  x14 7ffbffff00000000  x15 0000000000000000
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      x16 0000007c584fcf00  x17 0000007f23435688  x18 0000007be1bc6000  x19 0000007d918a0910
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      x20 0000000000000005  x21 0000007c584fe000  x22 0000007c584fd960  x23 0000007c584fe000
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      x24 0000007be2f16ab8  x25 00000000ffffffff  x26 0000007c584fdff8  x27 00000000000fc000
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      x28 0000007c58405000  x29 0000007c584fda10
    2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      lr  0000007be34be6e0  sp  0000007c584fd950  pc  0000007be34be6f4  pst 0000000060000000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    资源

    源码及资料下载
    Android WebRTC 的一些资料

    参考

    WebRTC
    googlesource webrtc / src
       git clone https://webrtc.googlesource.com/src (需要连接外网)
    meshenger-android
    在这里插入图片描述
    WebRTC-Android 探索 - 创建音视频通话程序的基本姿势
    WebRTC实现Android传屏demo
    Android WebRTC踩坑指南

    Google WebRtc Android 使用详解(包括客户端和服务端代码)
    owt-client-android
    android webrtc学习 一(源码下载和编译)
    编译webrtc android源码
    libwebrtc.aar

  • 相关阅读:
    unity3d与vs 附加到unity debug报错
    社群运营怎么做?
    代码随想录二刷 Day 44
    T-SQL——将字符串转为单列
    Java-数据库基本概念
    2022/09/02 day02:连接远程仓库,推送、克隆
    【HTML+CSS+JS】模仿QQ登录界面
    模型分类model
    圆通快递订单创建接口asp版,面单打印接口asp版,asp圆通快递物流轨迹查询接口
    [实践篇]13.4 QNX侧如何查看进程信息?
  • 原文地址:https://blog.csdn.net/ansondroider/article/details/128126250