WebRTC 通话流程详解

WebRTC 服务器

  • 信令服务器(signaling)
  • 转发服务器(TURN)
  • 穿透服务器(STUN)

官方提供的实现原理,如下图

集成到 Android 客户端

  • 摄像头,录音,SD卡读取权限动态获取
  • 在 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'
}

WebRTC 使用

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

  • 创建 PeerConnection
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();
    }

  • 初始化 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);
}

  • 初始化音频和视频
/**
 * 创建本地视频
 *
 * @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);
}

创建 WebRTC 调整后的流程图

代码自取:

https://github.com/taxiao213/Webrtc_Android

推荐阅读
相关专栏
开源技术
106 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和声网 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。