返回顶部
首页 > 资讯 > 精选 >android WebRtc 视频通话(P2P)
  • 878
分享到

android WebRtc 视频通话(P2P)

p2pandroidwebrtc 2023-08-25 08:08:00 878人浏览 泡泡鱼
摘要

概述      WebRTC名称源自网页实时通信(WEB Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的技术,是谷歌2010年以6820万美元收购Global IP Solutio

概述

在这里插入图片描述

     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        //这种方法存在兼容性问题, 在一些平台上, 会导致后续流程不能正常执行.                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踩坑指南        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 created2022-12-01 14:50:09.016 23145-23717 RTCCall  D  initRTC2022-12-01 14:50:09.041 23145-23717 RTCCall  D  initRTC done2022-12-01 14:50:09.042 23145-23728 RTCCall  D  createPeerConnection2022-12-01 14:50:09.052 23145-23728 RTCCall  D  createStream2022-12-01 14:50:09.058 23145-23728 RTCCall  D  createCapturer2022-12-01 14:50:09.139 23145-23728 RTCCall  D  Outgoing.createOffer2022-12-01 14:50:09.144 23145-23725 RTCCall  D  Outgoing.onCreateSuccess2022-12-01 14:50:09.212 23145-23725 RTCCall  D  Outgoing.onIceGatheringChange GATHERING2022-12-01 14:50:09.327 23145-23725 RTCCall  D  Outgoing.onIceGatheringChange COMPLETE2022-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.2392022-12-01 14:50:09.328 23145-23735 RTCCall  D  reportStateChange CONNECTING## 对方接听2022-12-01 14:50:15.331 23145-23168 RTCCall  D  reportStateChange CONNECTED2022-12-01 14:50:15.331 23145-23168 RTCCall  D  handleAnswer2022-12-01 14:50:15.409 23145-23725 RTCCall  D  Outgoing.onIceConnectionChange CHECKING2022-12-01 14:50:15.415 23145-23725 RTCCall  D  Outgoing.onAddStream2022-12-01 14:50:15.415 23145-23725 RTCCall  D  handleMediaStream ava=false2022-12-01 14:50:15.416 23145-23725 RTCCall  D  handleAnswer.onSetSuccess2022-12-01 14:50:15.512 23145-23725 RTCCall  D  Outgoing.onIceConnectionChange CONNECTED2022-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=true2022-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 click2022-12-01 14:51:03.363 23145-23145 RTCCall  D  reportStateChange ENDED2022-12-01 14:51:03.363 23145-23145 RTCCall  D  closePeerConnection2022-12-01 14:51:03.370 23145-23145 RTCCall  D  closePeerConnection state=CONNECTED2022-12-01 14:51:03.372 23145-23725 RTCCall  D  Outgoing.onIceConnectionChange CLOSED2022-12-01 14:51:03.372 23145-23725 RTCCall  D  hangUp Ignored already ENDED:Outgoing.onIceConnectionChange CLOSED2022-12-01 14:51:03.547 23145-23725 RTCCall  D  onStateChange2022-12-01 14:51:03.547 23145-23725 RTCCall  D  onStateChange2022-12-01 14:51:03.580 23145-23145 RTCCall  D  closePeerConnection done2022-12-01 14:51:04.059 23145-23145 RTCCall  D  releaseCamera2022-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  initRTC2022-12-01 14:22:30.363  8916-8943  RTCCall  D  initRTC done2022-12-01 14:22:30.364  8916-8943  RTCCall  D  reportStateChange RINGING## 接听2022-12-01 14:22:35.625  8916-8916  RTCCall  D  setRenderer2022-12-01 14:22:35.625  8916-8916  RTCCall  D  accept2022-12-01 14:22:35.661  8916-14310 RTCCall  D  createStream2022-12-01 14:22:35.662  8916-14310 RTCCall  D  createCapturer2022-12-01 14:22:35.677  8916-14310 RTCCall  D  Incoming.setting remote description2022-12-01 14:22:35.768  8916-14256 RTCCall  D  Incoming.onAddStream2022-12-01 14:22:35.768  8916-14256 RTCCall  D  handleMediaStream ava=false2022-12-01 14:22:35.769  8916-14256 RTCCall  D  creating answer...2022-12-01 14:22:35.773  8916-14256 RTCCall  D  Incoming.onCreateSuccess2022-12-01 14:22:35.873  8916-14256 RTCCall  D  Incoming.onIceConnectionChange CHECKING2022-12-01 14:22:36.035  8916-14256 RTCCall  D  Incoming.onIceGatheringChange## 连接已建立2022-12-01 14:22:36.048  8916-14256 RTCCall  D  reportStateChange CONNECTED2022-12-01 14:22:36.256  8916-14256 RTCCall  D  Incoming.onIceConnectionChange CONNECTED2022-12-01 14:22:36.278  8916-14256 RTCCall  D  onStateChange## 开启摄像头并推送2022-12-01 14:22:45.635  8916-8916  RTCCall  D  setVideoEnabled enabled=true2022-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 DISCONNECTED2022-12-01 14:23:24.318  8916-14256 RTCCall  D  reportStateChange DISCONNECTED2022-12-01 14:23:24.320  8916-14256 RTCCall  D  onStateChange2022-12-01 14:23:24.320  8916-14256 RTCCall  D  onStateChange2022-12-01 14:23:24.430  8916-8943  RTCCall  D  hangUp:publisher msg2022-12-01 14:23:24.430  8916-8943  RTCCall  D  reportStateChange ENDED2022-12-01 14:23:24.431  8916-8943  RTCCall  D  closePeerConnection2022-12-01 14:23:24.445  8916-8943  RTCCall  D  closePeerConnection state=CONNECTED2022-12-01 14:23:24.449  8916-14256 RTCCall  D  Incoming.onIceConnectionChange CLOSED2022-12-01 14:23:24.449  8916-14256 RTCCall  D  hangUp Ignored already ENDED:Incoming.onIceConnectionChange CLOSED2022-12-01 14:23:24.705  8916-8943  RTCCall  D  closePeerConnection done2022-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));                                            }                }            }        } 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+08002022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A  Process uptime: 0s2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A  Cmdline: d.d.meshenger2022-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: 102612022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A  signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x02022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A  Cause: null pointer dereference2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      x0  0000000000000000  x1  0000000000000005  x2  0000000000000000  x3  0000007c6f2be89d2022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      x4  0000007c584fd818  x5  0000007f12e7a555  x6  73656d2f642f644c  x7  632f7265676e65682022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      x8  0000007be2f5a000  x9  81248e9b13f44bd4  x10 0000000000430000  x11 00000000000000012022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      x12 0000000000000004  x13 0000000000000004  x14 7ffbffff00000000  x15 00000000000000002022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      x16 0000007c584fcf00  x17 0000007f23435688  x18 0000007be1bc6000  x19 0000007d918a09102022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      x20 0000000000000005  x21 0000007c584fe000  x22 0000007c584fd960  x23 0000007c584fe0002022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      x24 0000007be2f16ab8  x25 00000000ffffffff  x26 0000007c584fdff8  x27 00000000000fc0002022-12-01 14:31:57.847 22887-22887 DEBUG              pid-22887             A      x28 0000007c58405000  x29 0000007c584fda102022-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

来源地址:https://blog.csdn.net/ansondroider/article/details/128126250

--结束END--

本文标题: android WebRtc 视频通话(P2P)

本文链接: https://lsjlt.com/news/380031.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作