android WebRtc 视频通话(P2P)

news2024/11/27 8:27:33

概述

在这里插入图片描述

     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. 关键代码及应用

创建 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");
    }

创建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);

呼叫方收到后同样初始化, 并在点击接听后创建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();
    }

呼叫方处理应答

    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));
    }

启动摄像头

    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();
        }
    }

挂断

    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();
    }
//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");
        }
    }

完整呼叫流程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

[被叫方]

## 来电并响铃
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

一些问题

首先是源码中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();
        }

增加本地摄像头预览显示
方法是传入本地的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;
        }
    }

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

        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 会导致呼出时的错误如下

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);

多次调用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

资源

源码及资料下载
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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/51699.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

centos7中mysql5.7.32服务离线升级到5.7.39教程

目录 一、导入新的离线安装包 二、备份原有mysql数据库 1、停止tomcat服务 2、查看mysql服务 3、备份数据库 三、停止mysql服务并打包备份旧版本 1、停止mysql 2、打包旧的mysql文件夹 3、删除旧的mysql文件夹 4、删除/etc/init.d/下跟mysql有关的全部文件&#xff0…

MongoDB数据迁移之迁移工具Kettle

MongoDB数据迁移之迁移工具Kettle ETL:简介 ETL&#xff08;Extract-Transform-Load的缩写&#xff0c;即数据抽取、转换、装载的过程&#xff09;&#xff0c;对于企业或行业应用来说&#xff0c;我们经常会遇到各种数据的处理&#xff0c;转换&#xff0c;迁移&#xff0c;所…

Java+JSP+MySQL基于SSM的医院挂号就诊系统-计算机毕业设计

项目介绍 随着计算机科技的快速发展&#xff0c;很多地方都实现了自动化管理&#xff0c;医院也不例外。在大多数医院&#xff0c;无论是挂号处&#xff0c;还是取药的窗口&#xff0c;都会看到有很长的队伍&#xff0c;很显然这样会让患者就医的过程中浪费太多的时间。其次&a…

【读论文】GANMcC

GANMcC简单介绍网络结构生成器辨别器损失函数生成器损失函数辨别器tips总结参考论文&#xff1a;https://ieeexplore.ieee.org/document/9274337 如有侵权请联系博主 这几天又读了一篇关于GAN实现红外融合的论文&#xff0c;不出意外&#xff0c;还是FusionGAN作者团队的人写…

Python语音合成小工具(PyQt5 + pyttsx3)

TTS简介 TTS&#xff08;Text To Speech&#xff09;是一种语音合成技术&#xff0c;可以让机器将输入文本以语音的方式播放出来&#xff0c;实现机器说话的效果。 TTS分成语音处理及语音合成&#xff0c;先由机器识别输入的文字&#xff0c;再根据语音库进行语音合成。现在有…

JavaScript -- 三种循环语句的介绍及示例代码

文章目录循环语句1 While循环2 do-while循环3 for循环4 嵌套循环循环语句 通过循环语句可以使指定的代码反复执行 JS中一共有三种循环语句 while语句do-while语句for语句 通常编写一个循环&#xff0c;要有三个要件 初始化表达式&#xff08;初始化变量&#xff09;条件表…

风云气象卫星系列介绍

风云气象卫星系列是中国于1977年开始研制的气象卫星系列&#xff0c;目前发射了风云一号、风云二号、风云三号、风云四号等卫星。 风云一号 FY-1卫星分为两个批次&#xff0c;各两颗星。01批的FY-1A星于1988年7月9日发射&#xff0c;FY-1B星于1990年9月3日发射。02批卫星在01批…

Word处理控件Aspose.Words功能演示:在 Java 中将 Word 文档转换为 EPUB

大多数智能设备&#xff0c;如智能手机、平板电脑、笔记本电脑等&#xff0c;都支持EPUB格式来查看或阅读文档。它是电子书或电子出版物的常用格式。另一方面&#xff0c;MS Word 格式&#xff0c;如DOCX、DOC等&#xff0c;是数字世界中广泛使用的文档格式之一。在本文中&…

Web3中文|NFT无法保障数字所有权?

来源 | nftnow 编译 | DaliiNFTnews.com 2021年&#xff0c;有这样一个头条新闻&#xff1a;一家投资公司以大约400万美元的价格在The Sandbox上买下了2000英亩的虚拟地产。 通过在以太坊区块链上购买792个NFT&#xff0c;该公司得到了元宇宙平台上的1200个城市街区。 但是…

家用宽带如何叠加多条宽带,提高局域网速度

前言 关于多条宽带如何合并&#xff0c;使局域网内带宽更快&#xff1f;通常我们在企业网络或实际项目中&#xff0c;随着用户的增加&#xff0c;一条或者几条带宽不能满足正常使用&#xff0c;便可以对带宽进行叠加&#xff0c;便于网络带度更快&#xff1b; 一、为什么要用…

web基础阶段的小兔鲜儿项目学习

小兔鲜儿1. 所用素材2. 项目文件介绍3. index页面的基本骨架4. 思路&#xff1a;先写外面大盒子和版心&#xff0c;由外往内写5. 源码&#xff1a;1. 所用素材 素材链接&#xff0c;点我跳转&#xff1a;https://download.csdn.net/download/angrynouse/87228151 2. 项目文件…

全国产!全志T3+Logos FPGA核心板(4核ARM Cortex-A7)规格书

核心板简介 创龙科技SOM-TLT3F是一款基于全志科技T3四核ARM Cortex-A7处理器 + 紫光同创Logos PGL25G/PGL50G FPGA设计的异构多核全国产工业核心板,ARM Cortex-A7处理单元主频高达1.2GHz。核心板CPU、FPGA、ROM、RAM、电源、晶振、连接器等所有器件均采用国产工业级方案,国产…

【HDU No. 4902】 数据结构难题 Nice boat

【HDU No. 4902】 数据结构难题 Nice boat 杭电OJ 题目地址 【题意】 有n 个数字a 1 , a 2 , …, an &#xff0c;每次都可以将[l , r ]区间的每个数字都更改为数字x &#xff08;类型1&#xff09;&#xff0c;或将[l ,r ]区间每个大于x 的ai 都更改为最大公约数gcd(ai , x …

云服务连续三年增长150%,网宿科技开拓新赛道

摘要&#xff1a;开拓云服务市场&#xff0c;网宿科技的打法。 提到网宿科技&#xff0c;很多人还停留在传统IT服务商的印象中。其实&#xff0c;网宿科技已经在一条新赛道加速前行&#xff0c;这就是云服务。 “借助亚马逊云科技的持续赋能&#xff0c;网宿科技积累了丰富的云…

swiper轮播图片+视频播放,预览及页面跳转功能

1.效果 2.上代码 <template> <swiper :circulartrue indicator-dots"true" change"changeSwiper" :autoplay"true" interval5000 classswiper-view><swiper-item class"swiper-img" v-for"(item,index) in swipe…

2022 CMU15-445 Project0 Trie

通过截图 在线测试 本地测试 总览 代码风格 我们的代码必须遵循 Google C Style Guide。在线检测网站使用 Clang 自动检查源代码的质量。如果我们的提交未通过任何这些检查&#xff0c;您的项目成绩将为零。 对于 Google C Style Guide &#xff0c;我们可以看这里 google-s…

Spring Boot Logback启动流程

Spring Boot 默认使用的是 Logback 的日志框架、Logback 的组件主要通过 Spring Boot ApplicationListener 启动的 // LoggingApplicationListener Override public void onApplicationEvent(ApplicationEvent event) {if (event instanceof ApplicationStartingEvent) {onApp…

测试工作中的测试用例设计

测试工作中的测试用例设计 测试工作的最核心的模块&#xff0c;在执行任何测试之前&#xff0c;首先必须完成测试用例的编写。测试用例是指导你执行测试&#xff0c;帮助证明软件功能或发现软件缺陷的一种说明。 进行用例设计&#xff0c;需要对项目的需求有清晰的了解&#xf…

Excel找回打开密码过程

Excel文件设置了打开密码&#xff0c;但是忘记了打开密码或者不知道这份文件的打开密码都没办法打开excel文件了。可是文件的打开密码&#xff0c;一旦忘记了&#xff0c;想要再打开文件&#xff0c;都是需要找回密码的。网上的一些绕过密码、直接删除密码都是无效的解决方法。…

C# 文件压缩解压与sqlite存储文件数据

文章目录环境压缩nugetUI代码资源链接&#xff08;下载地址&#xff09;ZipFile 类方法环境 .netframerwork4.8sqlite3 压缩 nuget <package id"System.IO.Compression" version"4.3.0" targetFramework"net48" /><package id"…