• Google WebRtc Android 使用详解(包括客户端和服务端代码)


    转自:https://zhuanlan.zhihu.com/p/82446482

    1、Google Webrtc介绍

    WebRTC(Web Real-Time Communication)实现了基于网页的视频会议,标准是WHATWG 协议,目的是通过浏览器提供简单的javascript就可以达到实时通讯(Real-Time Communications (RTC))能力。

    提供了视频会议的核心技术,包括音视频的采集、编解码、网络传输、显示等功能,并且还支持跨平台:windows,linux,mac,android,音视频通讯技术是基于p2p实现的

    2、Google Webrtc服务器配置

    信令服务器(signaling),转发服务器(TURN),穿透服务器(STUN)(自行百度,配置),更详细的介绍请看书籍 WebRtc权威指南

    2.1 信令服务器(signaling)

    WebRTC 之间使用PeerConnection交流数据,但还需要一种机制来协调沟通和发送控制消息,这一过程称之为信令传输

    2.2 转发服务器(TURN) & 穿透服务器(STUN)

    WebRTC 被设计为点对点工作模式,所以用户之间是尽可能地通过最短路线进行连接,然而在现实世界当中:客户端应用需要穿透 NAT 网关 和防火墙,并且点对点网络需要握手来防止直接连接失败,在这一过程中,WebRTC使用STUN服务器和TURN服务器来获取计算机IP地址来保证点对点连接成功

    3、ICE框架

    基于信令协议的多媒体传输是一个两段式传输,首先通过信令协议(如WebSocket)建立一个连接,通过该连接,双方交换传输媒体时所必须的信息。基于传输效率的考虑,在完成第一阶段的交互之后,通信双方会建立一条通道来实现媒体传输,以减少传输时延、降低丢包率并减少开销。由于使用新的链路,当传输双方有任意一方位于NAT之后,新的传输链路就需要考虑NAT穿越的问题了通常有四种形式的NAT,每一种NAT都有相应的解决方案,然而在复杂的网络环境中,由于每一种NAT穿越方案都局限于对应的NAT方式,这些方案就给整个系统带来了一定的复杂性。在这种背景下,交互式连通建立方式(Interactive Connectivity Establishment)即ICE解决方案应运而生,ICE能够在不增加整个系统的复杂性和脆弱性的情况下,实现对各种形式的NAT的穿越。

    官方提供的实现原理 基于下图

     

    4、Android Webrtc的配置

    客户端和服务端通信 采用WebSocket
    摄像头,录音,SD卡读取等其他权限加入,有些需要在运行时调用

    4.1 在build.gradle 引入websocket 和 webrtc

        dependencies {
                implementation 'org.webrtc:google-webrtc:1.0.28513'
                implementation 'org.java-websocket:Java-WebSocket:1.4.0'
                implementation 'com.google.code.gson:gson:2.8.5'
            }

    5、Android Webrtc的使用

    5.1 创建websocket

         private void connectionWebsocket() {
                try {
                    webSocketClient = new WebSocketClient(URI.create(Constant.URL)) {
                        @Override
                        public void onOpen(ServerHandshake handshakedata) {
                            setText("已连接");
                            Log.e(TAG, "onOpen == Status == " + handshakedata.getHttpStatus() + " StatusMessage == " + handshakedata.getHttpStatusMessage());
                            Model model = new Model(Constant.REGISTER, getFromName(), getFrom(), getToName(), getTo());
                            webSocketClient.send(new Gson().toJson(model));
                        }
    
                        @Override
                        public void onMessage(String message) {
                            Log.e(TAG, "onMessage == " + message);
                            if (!TextUtils.isEmpty(message)) {
                                Model model = new Gson().fromJson(message, Model.class);
                                if (model != null) {
                                    String id = model.getId();
                                    if (!TextUtils.isEmpty(id)) {
                                        int isSucceed = model.getIsSucceed();
                                        switch (id) {
                                            case Constant.REGISTER_RESPONSE:
                                                if (isSucceed == Constant.RESPONSE_SUCCEED) {
                                                    Message msg = new Message();
                                                    msg.obj = Constant.OPEN;
                                                    handler.sendMessage(msg);
                                                    Log.e(TAG, "连接成功");
                                                } else if (isSucceed == Constant.RESPONSE_FAILURE) {
                                                    Log.e(TAG, "注册失败,已经注册");
                                                }
                                                break;
                                            case Constant.CALL_RESPONSE:
                                                if (isSucceed == Constant.RESPONSE_SUCCEED) {
                                                    Log.e(TAG, "对方在线,创建sdp offer");
                                                    createOffer();
                                                } else if (isSucceed == Constant.RESPONSE_FAILURE) {
                                                    Log.e(TAG, "对方不在线,连接失败");
                                                }
                                                break;
                                            case Constant.INCALL:
                                                isIncall();
                                                break;
                                            case Constant.INCALL_RESPONSE:
                                                if (isSucceed == Constant.RESPONSE_SUCCEED) {
                                                    createOffer();
                                                    Log.e(TAG, "对方同意接听");
                                                } else if (isSucceed == Constant.RESPONSE_FAILURE) {
                                                    Log.e(TAG, "对方拒绝接听");
                                                }
                                                break;
                                            case Constant.OFFER:
                                                //收到对方offer sdp
                                                SessionDescription sessionDescription1 = model.getSessionDescription();
                                                peerConnection.setRemoteDescription(observer, sessionDescription1);
                                                createAnswer();
                                                break;
                                            case Constant.CANDIDATE:
                                                //服务端 发送 接收方sdpAnswer
                                                IceCandidate iceCandidate = model.getIceCandidate();
                                                if (iceCandidate != null) {
                                                    peerConnection.addIceCandidate(iceCandidate);
                                                }
                                                break;
                                        }
                                    }
                                }
                            }
                        }
    
                        @Override
                        public void onClose(int code, String reason, boolean remote) {
                            setText("已关闭");
                            Log.e(TAG, "onClose == code " + code + " reason == " + reason + " remote == " + remote);
                        }
    
                        @Override
                        public void onError(Exception ex) {
                            setText("onError == " + ex.getMessage());
                            Log.e(TAG, "onError== " + ex.getMessage());
                        }
                    };
                    webSocketClient.connect();
                } catch (Exception e) {
                    Log.d(TAG, "socket Exception : " + e.getMessage());
                }
            }

    5.2 创建websocket成功后开始创建PeerConnection,穿透服务器采用Google提供的("stun:")

        private void createPeerConnection() {
                //Initialising PeerConnectionFactory
                InitializationOptions initializationOptions = InitializationOptions.builder(this)
                        .setEnableInternalTracer(true)
                        .setFieldTrials("WebRTC-H264HighProfile/Enabled/")
                        .createInitializationOptions();
                PeerConnectionFactory.initialize(initializationOptions);
                //创建EglBase对象
                eglBase = EglBase.create();
                PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
                options.disableEncryption = true;
                options.disableNetworkMonitor = true;
                peerConnectionFactory = PeerConnectionFactory.builder()
                        .setVideoDecoderFactory(new DefaultVideoDecoderFactory(eglBase.getEglBaseContext()))
                        .setVideoEncoderFactory(new DefaultVideoEncoderFactory(eglBase.getEglBaseContext(), true, true))
                        .setOptions(options)
                        .createPeerConnectionFactory();
                // 配置STUN穿透服务器  转发服务器
                iceServers = new ArrayList<>();
                PeerConnection.IceServer iceServer = PeerConnection.IceServer.builder(Constant.STUN).createIceServer();
                iceServers.add(iceServer);
    
                streamList = new ArrayList<>();
    
                PeerConnection.RTCConfiguration configuration = new PeerConnection.RTCConfiguration(iceServers);
    
                PeerConnectionObserver connectionObserver = getObserver();
                peerConnection = peerConnectionFactory.createPeerConnection(configuration, connectionObserver);
    
    
                /*
                DataChannel.Init 可配参数说明:
                ordered:是否保证顺序传输;
                maxRetransmitTimeMs:重传允许的最长时间;
                maxRetransmits:重传允许的最大次数;
                */
                DataChannel.Init init = new DataChannel.Init();
                if (peerConnection != null) {
                    channel = peerConnection.createDataChannel(Constant.CHANNEL, init);
                }
                DateChannelObserver channelObserver = new DateChannelObserver();
                connectionObserver.setObserver(channelObserver);
                initView();
                initObserver();
            }

     

    • 5.2.1 初始化view
        private void initSurfaceview(SurfaceViewRenderer localSurfaceView) {
                localSurfaceView.init(eglBase.getEglBaseContext(), null);
                localSurfaceView.setMirror(true);
                localSurfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
                localSurfaceView.setKeepScreenOn(true);
                localSurfaceView.setZOrderMediaOverlay(true);
                localSurfaceView.setEnableHardwareScaler(false);
        }
    • 5.2.2 初始化音频和视频
        /**
         * 创建本地视频
         *
         * @param localSurfaceView
         */
        private void startLocalVideoCapture(SurfaceViewRenderer localSurfaceView) {
            VideoSource videoSource = peerConnectionFactory.createVideoSource(true);
            SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(Thread.currentThread().getName(), eglBase.getEglBaseContext());
            VideoCapturer videoCapturer = createVideoCapturer();
            videoCapturer.initialize(surfaceTextureHelper, this, videoSource.getCapturerObserver());
            videoCapturer.startCapture(Constant.VIDEO_RESOLUTION_WIDTH, Constant.VIDEO_RESOLUTION_HEIGHT, Constant.VIDEO_FPS); // width, height, frame per second
            videoTrack = peerConnectionFactory.createVideoTrack(Constant.VIDEO_TRACK_ID, videoSource);
            videoTrack.addSink(localSurfaceView);
            MediaStream localMediaStream = peerConnectionFactory.createLocalMediaStream(Constant.LOCAL_VIDEO_STREAM);
            localMediaStream.addTrack(videoTrack);
            peerConnection.addTrack(videoTrack, streamList);
            peerConnection.addStream(localMediaStream);
        }
    
        /**
         * 创建本地音频
         */
        private void startLocalAudioCapture() {
            //语音
            MediaConstraints audioConstraints = new MediaConstraints();
            //回声消除
            audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googEchoCancellation", "true"));
            //自动增益
            audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAutoGainControl", "true"));
            //高音过滤
            audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googHighpassFilter", "true"));
            //噪音处理
            audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNoiseSuppression", "true"));
            AudioSource audioSource = peerConnectionFactory.createAudioSource(audioConstraints);
            audioTrack = peerConnectionFactory.createAudioTrack(Constant.AUDIO_TRACK_ID, audioSource);
            MediaStream localMediaStream = peerConnectionFactory.createLocalMediaStream(Constant.LOCAL_AUDIO_STREAM);
            localMediaStream.addTrack(audioTrack);
            audioTrack.setVolume(Constant.VOLUME);
            peerConnection.addTrack(audioTrack, streamList);
            peerConnection.addStream(localMediaStream);
        }

    5.3 创建PeerConnection成功后通过信令传输交换各自信息,实现视频通话,调整后的流程如下图

    信令
        public static final String REGISTER = "register";//注册
        public static final String REGISTER_RESPONSE = "register_response";//注册回复
        public static final int RESPONSE_SUCCEED = 1;//1成功
        public static final int RESPONSE_FAILURE = 2;//2失败
        public static final String CALL = "call";//拨打
        public static final String CALL_RESPONSE = "call_response";//拨打回复
        public static final String INCALL = "incall";//接听
        public static final String INCALL_RESPONSE = "incall_response";//接听回复
        public static final String OFFER = "offer";//发送sdp信息
        public static final String CANDIDATE = "candidate";//ice互传
    流程内容: 客户端A,客户端B
    • 5.3.1 -->> A 和 B 都注册成功后,A 发起 call,服务端收到A的指令后转发给B, B接受后发给服务器,服务器转发给A
    • 5.3.2 -->> A createOffer 将自己的SessionDescription 发给服务器,服务器转发给B
    • 5.3.3 -->> B 收到后将A的SessionDescription 设置到自己的setRemoteDescription,同时createAnswer 发给服务器,服务器在转发A
    • 5.3.4 -->> A 收到后将B的SessionDescription 设置到自己的setRemoteDescription
    • 5.3.5 -->> A 在PeerConnectionObserver 的 onIceCandidate方法中,将收到的IceCandidate 发给服务端,服务器转发给B
    • 5.3.6 -->> B 收到后在将IceCandidate 设置到自己的addIceCandidate
    • 5.3.7 -->> B 在PeerConnectionObserver 的 onIceCandidate方法中,将收到的IceCandidate 发给服务端,服务器转发给A
    • 5.3.8 -->> A 收到后在将IceCandidate 设置到自己的addIceCandidate
    • 5.3.9 -->> A 和 B 在PeerConnectionObserver 的 onAddStream方法中,将远端的流媒体加入到本地的View
    • 5.3.10 -->> 交互已经结束,可以正常视频通话
    详细的流程,json 格式
           isSucceed 1成功 2失败
    	
    	"from": "11",  "fromName": "王安", "to": "12", "toName": "往事"
    		
    	1.注册 
    	from 发送服务端:register 
    	{
    	"from": "11",
    	"fromName": "王安",
    	"id": "register",
    	"isSucceed": 0
    	}
    	
    	from接收:register_response 
    	{
    	"id": "register_response",
    	"isSucceed": 1
    	}
    	
    	2.呼叫
    	from 发送服务端:call 
    	{
    	"from": "11",
    	"fromName": "王安",
    	"id": "call",
    	"isSucceed": 0,
    	"to": "12",
    	"toName": "往事"
    	}
    	
    	2.1服务端做判断
    	
    	如果不在线 直接返回 "isSucceed": 1
    	{
    	"id": "call_response",
    	"isSucceed": 1
    	}
    	 
    	在线 返回给to做判断
    	
    	to 接收:incall 
    	改变本地状态
    	{
    	"id": "incall",
    	"isSucceed": 0
    	}
    		to 发送服务端 拒绝:"isSucceed": 2
    		{
    			"from": "12",
    			"fromName": "往事",
    			"id": "incall",
    			"isSucceed": 2,
    			"to": "11",
    			"toName": "王安"
    		}
    			from接收:incall_response
    			做拒绝的操作
    			{
    				"id": "incall_response",
    				"from": "12",
    				"fromName": "往事",
    				"to": "11",
    				"toName": "王安",
    				"isSucceed": 2
    			}
    			
    	-------------------------------------------------
    		
    		to 发送服务端 同意:"isSucceed": 1
    		{
    			"from": "12",
    			"fromName": "往事",
    			"id": "incall",
    			"isSucceed": 1,
    			"to": "11",
    			"toName": "王安"
    		}
    			from接收 同意:incall_response
    			  
    			{
    				"id": "incall_response",
    				"from": "12",
    				"fromName": "往事",
    				"to": "11",
    				"toName": "王安",
    				"isSucceed": 1
    			}
    	
    	3.to同意后 from发送流
    	from 发送服务端:offer
    	{
    	"from": "11",
    	"fromName": "王安",
    	"id": "offer",
    	"isSucceed": 0,
    	"sessionDescription": {
    	"description": "v=0
    o=- 3285195603599485281 2 IN IP4 127.0.0.1
    s=-
    t=0 0
    a=group:BUNDLE video
    a=msid-semantic: WMS localstream
    m=video 9 RTP/AVPF 96 97 98 99 100 101 127 124 125
    c=IN IP4 0.0.0.0
    a=rtcp:9 IN IP4 0.0.0.0
    a=ice-ufrag:9Jqg
    a=ice-pwd:lM34EvVxjaPGBislEH9VccVu
    a=ice-options:trickle renomination
    a=mid:video
    a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
    a=extmap:13 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
    a=extmap:3 urn:3gpp:video-orientation
    a=extmap:2 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
    a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
    a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
    a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
    a=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07
    a=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space
    a=sendrecv
    a=rtcp-mux
    a=rtcp-rsize
    a=rtpmap:96 VP8/90000
    a=rtcp-fb:96 goog-remb
    a=rtcp-fb:96 transport-cc
    a=rtcp-fb:96 ccm fir
    a=rtcp-fb:96 nack
    a=rtcp-fb:96 nack pli
    a=rtpmap:97 rtx/90000
    a=fmtp:97 apt=96
    a=rtpmap:98 VP9/90000
    a=rtcp-fb:98 goog-remb
    a=rtcp-fb:98 transport-cc
    a=rtcp-fb:98 ccm fir
    a=rtcp-fb:98 nack
    a=rtcp-fb:98 nack pli
    a=rtpmap:99 rtx/90000
    a=fmtp:99 apt=98
    a=rtpmap:100 H264/90000
    a=rtcp-fb:100 goog-remb
    a=rtcp-fb:100 transport-cc
    a=rtcp-fb:100 ccm fir
    a=rtcp-fb:100 nack
    a=rtcp-fb:100 nack pli
    a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
    a=rtpmap:101 rtx/90000
    a=fmtp:101 apt=100
    a=rtpmap:127 red/90000
    a=rtpmap:124 rtx/90000
    a=fmtp:124 apt=127
    a=rtpmap:125 ulpfec/90000
    a=ssrc-group:FID 1791802911 305163890
    a=ssrc:1791802911 cname:8uTWIc2YbUNSY02u
    a=ssrc:1791802911 msid:localstream videtrack
    a=ssrc:1791802911 mslabel:localstream
    a=ssrc:1791802911 label:videtrack
    a=ssrc:305163890 cname:8uTWIc2YbUNSY02u
    a=ssrc:305163890 msid:localstream videtrack
    a=ssrc:305163890 mslabel:localstream
    a=ssrc:305163890 label:videtrack
    ",
    	"type": "OFFER"
    	},
    	"to": "12",
    	"toName": "往事"
    	}
    	
    	to接收:offer
    	{
    	"id": "offer",
    	"from": "11",
    	"fromName": "王安",
    	"to": "12",
    	"toName": "往事",
    	"sessionDescription": {
    	"type": "OFFER",
    	"description": "v=0
    o=- 3285195603599485281 2 IN IP4 127.0.0.1
    s=-
    t=0 0
    a=group:BUNDLE video
    a=msid-semantic: WMS localstream
    m=video 9 RTP/AVPF 96 97 98 99 100 101 127 124 125
    c=IN IP4 0.0.0.0
    a=rtcp:9 IN IP4 0.0.0.0
    a=ice-ufrag:9Jqg
    a=ice-pwd:lM34EvVxjaPGBislEH9VccVu
    a=ice-options:trickle renomination
    a=mid:video
    a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
    a=extmap:13 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
    a=extmap:3 urn:3gpp:video-orientation
    a=extmap:2 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
    a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
    a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
    a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
    a=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07
    a=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space
    a=sendrecv
    a=rtcp-mux
    a=rtcp-rsize
    a=rtpmap:96 VP8/90000
    a=rtcp-fb:96 goog-remb
    a=rtcp-fb:96 transport-cc
    a=rtcp-fb:96 ccm fir
    a=rtcp-fb:96 nack
    a=rtcp-fb:96 nack pli
    a=rtpmap:97 rtx/90000
    a=fmtp:97 apt=96
    a=rtpmap:98 VP9/90000
    a=rtcp-fb:98 goog-remb
    a=rtcp-fb:98 transport-cc
    a=rtcp-fb:98 ccm fir
    a=rtcp-fb:98 nack
    a=rtcp-fb:98 nack pli
    a=rtpmap:99 rtx/90000
    a=fmtp:99 apt=98
    a=rtpmap:100 H264/90000
    a=rtcp-fb:100 goog-remb
    a=rtcp-fb:100 transport-cc
    a=rtcp-fb:100 ccm fir
    a=rtcp-fb:100 nack
    a=rtcp-fb:100 nack pli
    a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
    a=rtpmap:101 rtx/90000
    a=fmtp:101 apt=100
    a=rtpmap:127 red/90000
    a=rtpmap:124 rtx/90000
    a=fmtp:124 apt=127
    a=rtpmap:125 ulpfec/90000
    a=ssrc-group:FID 1791802911 305163890
    a=ssrc:1791802911 cname:8uTWIc2YbUNSY02u
    a=ssrc:1791802911 msid:localstream videtrack
    a=ssrc:1791802911 mslabel:localstream
    a=ssrc:1791802911 label:videtrack
    a=ssrc:305163890 cname:8uTWIc2YbUNSY02u
    a=ssrc:305163890 msid:localstream videtrack
    a=ssrc:305163890 mslabel:localstream
    a=ssrc:305163890 label:videtrack
    "
    	},
    	"isSucceed": 0
    	}
    	
    	to接收到 offer 创建answer 创建完 to 发送服务端:offer
    	{
    	"from": "12",
    	"fromName": "往事",
    	"id": "offer",
    	"isSucceed": 0,
    	"sessionDescription": {
    	"description": "v=0
    o=- 8665372075017263288 2 IN IP4 127.0.0.1
    s=-
    t=0 0
    a=group:BUNDLE video
    a=msid-semantic: WMS localstream
    m=video 9 RTP/AVPF 96 97 98 99 100 101 127 124 125
    c=IN IP4 0.0.0.0
    a=rtcp:9 IN IP4 0.0.0.0
    a=ice-ufrag:zoQn
    a=ice-pwd:oBePBH1X7SehiT6Tb83SZV2U
    a=ice-options:trickle renomination
    a=mid:video
    a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
    a=extmap:13 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
    a=extmap:3 urn:3gpp:video-orientation
    a=extmap:2 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
    a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
    a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
    a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
    a=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07
    a=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space
    a=sendrecv
    a=rtcp-mux
    a=rtcp-rsize
    a=rtpmap:96 VP8/90000
    a=rtcp-fb:96 goog-remb
    a=rtcp-fb:96 transport-cc
    a=rtcp-fb:96 ccm fir
    a=rtcp-fb:96 nack
    a=rtcp-fb:96 nack pli
    a=rtpmap:97 rtx/90000
    a=fmtp:97 apt=96
    a=rtpmap:98 VP9/90000
    a=rtcp-fb:98 goog-remb
    a=rtcp-fb:98 transport-cc
    a=rtcp-fb:98 ccm fir
    a=rtcp-fb:98 nack
    a=rtcp-fb:98 nack pli
    a=rtpmap:99 rtx/90000
    a=fmtp:99 apt=98
    a=rtpmap:100 H264/90000
    a=rtcp-fb:100 goog-remb
    a=rtcp-fb:100 transport-cc
    a=rtcp-fb:100 ccm fir
    a=rtcp-fb:100 nack
    a=rtcp-fb:100 nack pli
    a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
    a=rtpmap:101 rtx/90000
    a=fmtp:101 apt=100
    a=rtpmap:127 red/90000
    a=rtpmap:124 rtx/90000
    a=fmtp:124 apt=127
    a=rtpmap:125 ulpfec/90000
    a=ssrc-group:FID 330040035 1917181902
    a=ssrc:330040035 cname:uxoKaX6C0J4LHdZ2
    a=ssrc:330040035 msid:localstream videtrack
    a=ssrc:330040035 mslabel:localstream
    a=ssrc:330040035 label:videtrack
    a=ssrc:1917181902 cname:uxoKaX6C0J4LHdZ2
    a=ssrc:1917181902 msid:localstream videtrack
    a=ssrc:1917181902 mslabel:localstream
    a=ssrc:1917181902 label:videtrack
    ",
    	"type": "ANSWER"
    	},
    	"to": "11",
    	"toName": "王安"
    	}
    	
    	4.ice交换
    	from 发送服务端:candidate
    	{
    	"from": "11",
    	"fromName": "王安",
    	"iceCandidate": {
    	"sdp": "candidate:3575964346 1 udp 2122260223 192.168.0.166 47369 typ host generation 0 ufrag 9Jqg network-id 3 network-cost 10",
    	"sdpMLineIndex": 0,
    	"sdpMid": "video",
    	"serverUrl": ""
    	},
    	"id": "candidate",
    	"isSucceed": 0,
    	"to": "12",
    	"toName": "往事"
    	}
    	
    	to 接收:candidate
    	
    	{
    	"from": "11",
    	"fromName": "王安",
    	"iceCandidate": {
    	"sdp": "candidate:3575964346 1 udp 2122260223 192.168.0.166 47369 typ host generation 0 ufrag 9Jqg network-id 3 network-cost 10",
    	"sdpMLineIndex": 0,
    	"sdpMid": "video",
    	"serverUrl": ""
    	},
    	"id": "candidate",
    	"isSucceed": 0,
    	"to": "12",
    	"toName": "往事"
    	}
    	
    	to 发送服务端:candidate
    	{
    	"from": "12",
    	"fromName": "往事",
    	"iceCandidate": {
    	"sdp": "candidate:559267639 1 udp 2122202367 ::1 55843 typ host generation 0 ufrag zoQn network-id 2",
    	"sdpMLineIndex": 0,
    	"sdpMid": "video",
    	"serverUrl": ""
    	},
    	"id": "candidate",
    	"isSucceed": 0,
    	"to": "11",
    	"toName": "王安"
    	}
    	
    	from接收:candidate
    	{
    	"from": "12",
    	"fromName": "往事",
    	"iceCandidate": {
    	"sdp": "candidate:559267639 1 udp 2122202367 ::1 55843 typ host generation 0 ufrag zoQn network-id 2",
    	"sdpMLineIndex": 0,
    	"sdpMid": "video",
    	"serverUrl": ""
    	},
    	"id": "candidate",
    	"isSucceed": 0,
    	"to": "11",
    	"toName": "王安"
    	}

    5.4 实现视频通话,部分截图

     

  • 相关阅读:
    acm常见算法及例题
    检测数据库各实例session分布情况的sql
    检测数据库大小的sql
    检测数据库各实例session 阻塞 tree的sql
    Oracle 的自治事务 AUTONOMOUS TRANSACTION
    Oracle的 NULL 与 空字符串''
    数据泵 expdp 参数 consistent=y
    mysql 的列转行
    mysql 的行转列
    jboss 到oracle 数据库连接无效的问题
  • 原文地址:https://www.cnblogs.com/x_wukong/p/12698371.html
Copyright © 2020-2023  润新知