Xmpp作为WebRTC信令的音视频通话02-创建PeerConnection

创建PeerConnection的操作都封装在PeerConnectionClient中,而且创建PeerConnection的操作必须是单线程的,而PeerConnection是由PeerConnectionFactory完成的。

1、创建PeerConnectionFactory

我们前面说过PeerConnectionFactory一个用来生成PeerConnection的工厂类,同时负责初始化全局和底层交互。

/**
 * 创建PeerConnectionFactory的方法
 * @param context
 * @param localRender   本地视频
 * @param renderEGLContext    
 * @param peerConnectionParameters
 */
public void createPeerConnectionFactory(
        final Context context,
        final VideoRenderer.Callbacks localRender,
        final EglBase.Context renderEGLContext,
        final PeerConnectionParameters peerConnectionParameters) {
    this.peerConnectionParameters = peerConnectionParameters;
    this.localRender = localRender;
    videoCallEnabled = peerConnectionParameters.videoCallEnabled;
    if(localRender==null)
        videoCallEnabled = false;
    // Reset variables to initial states.
    factory = null;
    preferIsac = false;
    videoCapturerStopped = false;
    isError = false;
    mediaStream = null;
    videoCapturer = null;
    renderVideo = true;
    localVideoTrack = null;
    enableAudio = true;
    localAudioTrack = null;
    this.videoWidth = peerConnectionParameters.videoWidth;
    this.videoHeight = peerConnectionParameters.videoHeight;
    this.videoFps = peerConnectionParameters.videoFps;
    statsTimer = new Timer();
        //用于保存对端的IceCandidate
    queuedRemoteCandidates = new ConcurrentHashMap<String,LinkedList<IceCandidate>>();
    if(isRTCClosed) return;

    executor.execute(new Runnable() {
        @Override
        public void run() {
            if(isRTCClosed) return;
            try {
                                //创建媒体约束,创建PeerConnection时用
                createMediaConstraintsInternal();
                createPeerConnectionFactoryInternal(context, renderEGLContext);
            }
            catch (Exception e){
                reportError("Failed to create peer connection: " + e.getMessage());
                return;
            }
        }
    });
}

主要是根据PeerConnectionParameters中的参数创建媒体约束

/**
 * 创建媒体约束
 */
private void createMediaConstraintsInternal() {
    // Create peer connection constraints.
    pcConstraints = new MediaConstraints();
    //是否允许呼叫自己
    if (peerConnectionParameters.loopback) {
        pcConstraints.optional.add(
                new MediaConstraints.KeyValuePair(DTLS_SRTP_KEY_AGREEMENT_CONSTRAINT, "false"));
    } else {
        pcConstraints.optional.add(
                new MediaConstraints.KeyValuePair(DTLS_SRTP_KEY_AGREEMENT_CONSTRAINT, "true"));
    }
    pcConstraints.optional.add(new MediaConstraints.KeyValuePair(
            "RtpDataChannels", "true"));
    if (videoCallEnabled) {
        videoWidth = peerConnectionParameters.videoWidth;
        videoHeight = peerConnectionParameters.videoHeight;
        videoFps = peerConnectionParameters.videoFps;

        if (videoWidth == 0 || videoHeight == 0) {
            videoWidth = HD_VIDEO_WIDTH;
            videoHeight = HD_VIDEO_HEIGHT;
        }

        if (videoFps == 0) {
            videoFps = 30;
        }
        Logging.d(TAG, "Capturing format: " + videoWidth + "x" + videoHeight + "@" + videoFps);
    }

    audioConstraints = new MediaConstraints();

    if (peerConnectionParameters.noAudioProcessing) {
        Log.d(TAG, "Disabling audio processing");
        audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
                AUDIO_ECHO_CANCELLATION_CONSTRAINT, "false"));
        audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
                AUDIO_AUTO_GAIN_CONTROL_CONSTRAINT, "false"));
        audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
                AUDIO_HIGH_PASS_FILTER_CONSTRAINT, "false"));
        audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
                AUDIO_NOISE_SUPPRESSION_CONSTRAINT, "false"));
    }
    // Create SDP constraints.
    sdpMediaConstraints = new MediaConstraints();
    sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
            "OfferToReceiveAudio", "true"));
    if (videoCallEnabled || peerConnectionParameters.loopback) {
        sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
                "OfferToReceiveVideo", "true"));
    } else {
        sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
                "OfferToReceiveVideo", "false"));
    }
}

创建PeerConnectionFactory实例和MediaStream实例

/**
 * createPeerConnectionFactory
 * @param context
 * @param renderEGLContext
 */
private void createPeerConnectionFactoryInternal(Context context, final EglBase.Context renderEGLContext) {
    PeerConnectionFactory.initializeInternalTracer();
    if (peerConnectionParameters.tracing) {
        PeerConnectionFactory.startInternalTracingCapture(
                Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
                        + "webrtc-trace.txt");
    }
    Log.d(TAG, "Create peer connection factory. Use video: " +
            peerConnectionParameters.videoCallEnabled);

    isError = false;

    String fieldTrials = "";
    if (peerConnectionParameters.videoFlexfecEnabled) {
        fieldTrials += VIDEO_FLEXFEC_FIELDTRIAL;
        Log.d(TAG, "Enable FlexFEC field trial.");
    }
    fieldTrials += VIDEO_VP8_INTEL_HW_ENCODER_FIELDTRIAL;
    preferredVideoCodec = VIDEO_CODEC_VP8;
    if (videoCallEnabled && peerConnectionParameters.videoCodec != null) {
        switch (peerConnectionParameters.videoCodec) {
            case VIDEO_CODEC_VP8:
                preferredVideoCodec = VIDEO_CODEC_VP8;
                break;
            case VIDEO_CODEC_VP9:
                preferredVideoCodec = VIDEO_CODEC_VP9;
                break;
            case VIDEO_CODEC_H264_BASELINE:
                preferredVideoCodec = VIDEO_CODEC_H264;
                break;
            case VIDEO_CODEC_H264_HIGH:
                // TODO(magjed): Strip High from SDP when selecting Baseline instead of using field trial.
                fieldTrials += VIDEO_H264_HIGH_PROFILE_FIELDTRIAL;
                preferredVideoCodec = VIDEO_CODEC_H264;
                break;
            default:
                preferredVideoCodec = VIDEO_CODEC_VP8;
        }
    }
    // Initialize field trials.
    Log.d(TAG, "Preferred video codec: " + preferredVideoCodec);
    PeerConnectionFactory.initializeFieldTrials(fieldTrials);

    preferIsac = false;
    if (peerConnectionParameters.audioCodec != null
            && peerConnectionParameters.audioCodec.equals(AUDIO_CODEC_ISAC)) {
        preferIsac = true;
    }
    if (!peerConnectionParameters.useOpenSLES) {
        Log.d(TAG, "Disable OpenSL ES audio even if device supports it");
        WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true /* enable */);
    } else {
        Log.d(TAG, "Allow OpenSL ES audio if device supports it");
        WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(false);
    }

    if (peerConnectionParameters.disableBuiltInAEC) {
        Log.d(TAG, "Disable built-in AEC even if device supports it");
        WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
    } else {
        Log.d(TAG, "Enable built-in AEC if device supports it");
        WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(false);
    }

    if (peerConnectionParameters.disableBuiltInAGC) {
        Log.d(TAG, "Disable built-in AGC even if device supports it");
        WebRtcAudioUtils.setWebRtcBasedAutomaticGainControl(true);
    } else {
        Log.d(TAG, "Enable built-in AGC if device supports it");
        WebRtcAudioUtils.setWebRtcBasedAutomaticGainControl(false);
    }

    if (peerConnectionParameters.disableBuiltInNS) {
        Log.d(TAG, "Disable built-in NS even if device supports it");
        WebRtcAudioUtils.setWebRtcBasedNoiseSuppressor(true);
    } else {
        Log.d(TAG, "Enable built-in NS if device supports it");
        WebRtcAudioUtils.setWebRtcBasedNoiseSuppressor(false);
    }

    WebRtcAudioRecord.setErrorCallback(new WebRtcAudioRecord.WebRtcAudioRecordErrorCallback() {
        @Override
        public void onWebRtcAudioRecordInitError(String errorMessage) {
            Log.e(TAG, "onWebRtcAudioRecordInitError: " + errorMessage);
            reportError(errorMessage);
        }

        @Override
        public void onWebRtcAudioRecordStartError(
                WebRtcAudioRecord.AudioRecordStartErrorCode errorCode, String errorMessage) {
            Log.e(TAG, "onWebRtcAudioRecordStartError: " + errorCode + ". " + errorMessage);
            reportError(errorMessage);
        }

        @Override
        public void onWebRtcAudioRecordError(String errorMessage) {
            Log.e(TAG, "onWebRtcAudioRecordError: " + errorMessage);
            reportError(errorMessage);
        }
    });

    WebRtcAudioTrack.setErrorCallback(new WebRtcAudioTrack.WebRtcAudioTrackErrorCallback() {
        @Override
        public void onWebRtcAudioTrackInitError(String errorMessage) {
            reportError(errorMessage);
        }

        @Override
        public void onWebRtcAudioTrackStartError(String errorMessage) {
            reportError(errorMessage);
        }

        @Override
        public void onWebRtcAudioTrackError(String errorMessage) {
            reportError(errorMessage);
        }
    });
    PeerConnectionFactory.initializeAndroidGlobals(context, peerConnectionParameters.videoCodecHwAcceleration);
    if (options != null) {
        Log.d(TAG, "Factory networkIgnoreMask option: " + options.networkIgnoreMask);
    }
    //创建PeerConnectionFactory
    factory = new PeerConnectionFactory(options);
    Log.d(TAG, "Peer connection factory created.");

    //创建本地媒体流
    mediaStream = factory.createLocalMediaStream("ARDAMS");
    if (videoCallEnabled) {
        //创建videoCapturer 
        videoCapturer = createVideoCapturer();
        if (videoCapturer == null) {
            Log.e(TAG,"Failed to open camera");
        }
        else {
            //创建视频轨道并添加到媒体流中
            mediaStream.addTrack(createVideoTrack(videoCapturer));
        }
    }
    //创建音频轨道并添加到媒体流中
    //如果videoCallEnabled == true的话,此时的mediaStream中有两条轨道
    mediaStream.addTrack(createAudioTrack());
    if (videoCallEnabled) {
        Log.d(TAG, "EGLContext: " + renderEGLContext);
                //视频硬件加速时需要用到renderEGLContext
        factory.setVideoHwAccelerationOptions(renderEGLContext, renderEGLContext);
    }
}

2、创建PeerConnection

在前面的步骤中已经创建了PeerConnectionFactory的实例,

/**
 * 
 * @param peerName    对方
 * @param remoteRender    远端的视频流
 * @param iceServers    IceServer
 * @param isCallout    呼叫还是接听
 * @param iceTransportsType
 * @param events    创建PeerConnection的回调
 */
public void createPeerConnection(
        final String peerName,
        final VideoRenderer.Callbacks remoteRender,
        final List<PeerConnection.IceServer> iceServers,
        final boolean isCallout,
        final PeerConnection.IceTransportsType iceTransportsType,
        final PeerConnectionEvents events) {
    Log.e(TAG,"createPeerConnection...");
    this.peerName = peerName;
    this.isCallout = isCallout;
    //this.executor = Executors.newSingleThreadScheduledExecutor();
    this.remoteRender = remoteRender;
    isError = false;
    this.events = events;
    if (peerConnectionParameters == null) {
        Log.e(TAG, "Creating peer connection without initializing factory.");
        return;
    }
    executor.execute(new Runnable() {
        @Override
        public void run() {
            createPeerConnectionInternal(mediaStream, iceServers, iceTransportsType);
            peerConnections.put(peerName,peerConnection);
        }
    });
}

创建PeerConnection并添加本地媒体流,创建PeerConnection需要3个参数:RTCConfiguration rtcConfig, MediaConstraints constraints, Observer observer。
constraints在创建factory实例时已经创建完成,rtcConfig在方法中完成,observer需要实现。

/**
 * 
 * @param localmediaStream    本地视频流
 * @param iceServers      IceServer
 * @param iceTransportsType    转发还是P2P
 */
private void createPeerConnectionInternal(MediaStream localmediaStream, List<PeerConnection.IceServer> iceServers, PeerConnection.IceTransportsType iceTransportsType) {

    PeerConnection.RTCConfiguration rtcConfig =
            new PeerConnection.RTCConfiguration(iceServers);
    rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED;

    if(PreferenceUtil.getInstance().getString("BundlePolicy","0").equals("0")){
        rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.BALANCED;
    }else if(PreferenceUtil.getInstance().getString("BundlePolicy","0").equals("1")) {
        rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
    }else if(PreferenceUtil.getInstance().getString("BundlePolicy","0").equals("2")){
        rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.MAXCOMPAT;
    }
    if(PreferenceUtil.getInstance().getString("RtcpMuxPolicy","0").equals("0")){
        rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.NEGOTIATE;
    }else if(PreferenceUtil.getInstance().getString("RtcpMuxPolicy","0").equals("1")){
        rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE;
    }

    rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY;
    rtcConfig.iceTransportsType = iceTransportsType;

    rtcConfig.keyType = PeerConnection.KeyType.ECDSA;

    Log.d(TAG, "createPeerConnection begin .");

    //创建peerConnection实例
    peerConnection = factory.createPeerConnection(rtcConfig, pcConstraints, pcObserver);

    Log.d(TAG, "createPeerConnection finish .");
    if (dataChannelEnabled) {
        DataChannel.Init init = new DataChannel.Init();
        init.ordered = true;
        init.negotiated = false;
        init.maxRetransmits = -1;
        init.maxRetransmitTimeMs = -1;
        init.id = -1;
        init.protocol = "";
        dataChannel = peerConnection.createDataChannel("ApprtcDemo data", init);
    }

    Logging.enableTracing("logcat:", EnumSet.of(Logging.TraceLevel.TRACE_DEFAULT));
    Logging.enableLogToDebugOutput(Logging.Severity.LS_ERROR);
    mediaStream = localmediaStream;
    peerConnection.addStream(mediaStream);//将本地视频流添加到peerConnection

    Log.d(TAG, "Peer connection created.");
}

3、实现PeerConnection.Observer

作为PeerConnectionClient的内部类,创建PeerConnection过程中的回调

private class PCObserver implements PeerConnection.Observer{

    @Override
    public void onSignalingChange(PeerConnection.SignalingState signalingState) {
        Log.d(TAG,"SignalingState: " + signalingState);
    }

    @Override
    public void onIceConnectionChange(final PeerConnection.IceConnectionState iceConnectionState) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG,"IceConnectionState: " + iceConnectionState);
                if (iceConnectionState == PeerConnection.IceConnectionState.CONNECTED) {
                    events.onIceConnected();
                } else if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED) {
                    events.onIceDisconnected();
                } else if (iceConnectionState == PeerConnection.IceConnectionState.FAILED) {
                }
            }
        });
    }

    @Override
    public void onIceConnectionReceivingChange(final boolean b) {
        Log.d(TAG,"IceConnectionReceiving changed to " + b);
    }

    @Override
    public void onIceGatheringChange(final PeerConnection.IceGatheringState iceGatheringState) {
        Log.d(TAG,"IceGatheringState: " + iceGatheringState);
    }

    @Override
    public void onIceCandidate(final IceCandidate iceCandidate) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG,"onIceCandidate: ");
                events.onIceCandidate(iceCandidate);
            }
        });
    }

    @Override
    public void onIceCandidatesRemoved(final IceCandidate[] iceCandidates) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG,"onIceCandidatesRemoved: ");
                events.onIceCandidatesRemoved(iceCandidates);
            }
        });
    }

    @Override
    public void onAddStream(final MediaStream mediaStream) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG,"onAddStream " + peerConnection);
                if (peerConnection == null || isError) {
                    return;
                }
                if (mediaStream.audioTracks.size() > 1 || mediaStream.videoTracks.size() > 1) {
                    Log.d(TAG,"Weird-looking stream: " + mediaStream);
                    return;
                }
                if(mediaStream.audioTracks.size() == 1){
                    remoteAudioTrack = mediaStream.audioTracks.get(0);
                }
                if (mediaStream.videoTracks.size() == 1) {
                    remoteVideoTrack = mediaStream.videoTracks.get(0);
                    remoteVideoTrack.setEnabled(true);
                    remoteVideoTrack.addRenderer(new VideoRenderer(remoteRender));
                }
            }
        });
    }

    @Override
    public void onRemoveStream(final MediaStream mediaStream) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG,"onRemoveStream");
                remoteAudioTrack = null;
                remoteVideoTrack = null;
            }
        });
    }

    @Override
    public void onDataChannel(final DataChannel dataChannel) {
        Log.d(TAG,"dc rev!");
    }

    @Override
    public void onRenegotiationNeeded() {
        Log.d(TAG,"onRenegotiationNeeded!");
    }

    @Override
    public void onAddTrack(final RtpReceiver rtpReceiver, final MediaStream[] mediaStreams) {
        Log.d(TAG,"onAddTrack!");
    }
}

4、总结

  至此已经创建了PeerConnection对象,将音视频数据封装成MediaStream添加到PeerConnection中。创建PeerConnection对象之后就可以调用PeerConnection的几个方法了,但是还需要包装一下,以后再说!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352

推荐阅读更多精彩内容