Android之WebRTC介紹(二)

WebRTC提供了點對點之間的通訊,但並不意味着WebRTC不須要服務器。暫且不說基於服務器的一些擴展業務,WebRTC至少有兩件事必需要用到服務器:
1. 瀏覽器之間交換創建通訊的元數據(信令)必須經過服務器
2. 爲了穿越NAT和防火牆
此處,咱們使用XMPP協議實現信令,採用openfire當作服務器,經過openfire服務器+Smack API實現信令的傳遞。
所以,在創建PeerConnection實例以後,想要使用其創建一個點對點的信道,咱們須要作兩件事:
1. 肯定本機上的媒體流的特性,好比分辨率、編解碼能力啥的(SDP描述符)
2. 鏈接兩端的主機的網絡地址(ICE Candidate)
經過offer和answer交換SDP描述符
大體上在兩個用戶(甲和乙)之間創建點對點鏈接流程應該是這個樣子(這裏不考慮錯誤的狀況,PeerConnection簡稱PC):
甲和乙各自創建一個PC實例
甲經過PC所提供的createOffer()方法創建一個包含甲的SDP描述符的offer信令
甲經過PC所提供的setLocalDescription()方法,將甲的SDP描述符交給甲的PC實例
甲將offer信令經過服務器發送給乙
乙將甲的offer信令中所包含的的SDP描述符提取出來,經過PC所提供的setRemoteDescription()方法交給乙的PC實例
乙經過PC所提供的createAnswer()方法創建一個包含乙的SDP描述符answer信令
乙經過PC所提供的setLocalDescription()方法,將乙的SDP描述符交給乙的PC實例
乙將answer信令經過服務器發送給甲
甲接收到乙的answer信令後,將其中乙的SDP描述符提取出來,調用setRemoteDescripttion()方法交給甲本身的PC實例
經過在這一系列的信令交換以後,甲和乙所建立的PC實例都包含甲和乙的SDP描述符了,完成了兩件事的第一件。咱們還須要完成第二件事——獲取鏈接兩端主機的網絡地址
經過ICE框架創建NAT/防火牆穿越的鏈接
這個網絡地址應該是能從外界直接訪問,WebRTC使用ICE框架來得到這個地址。PeerConnection在創立的時候能夠將ICE服務器的地址傳遞進去,如:
 private List<PeerConnection.IceServer> getIceServers(String url,String user,String credential)
    {
        PeerConnection.IceServer turn = new PeerConnection.IceServer(
                url,user,credential);
        LinkedList<PeerConnection.IceServer> iceServers = new LinkedList<PeerConnection.IceServer>();
        iceServers.add(turn);
        return iceServers;
    }
//iceServer List對象獲取
        List<PeerConnection.IceServer> iceServers = getIceServers(CoturnData.url,
                CoturnData.userName,CoturnData.credential);
        pcConstraints = new MediaConstraints();
        pcConstraints.optional.add(new MediaConstraints.KeyValuePair(
                "DtlsSrtpKeyAgreement", "true"));
        pcConstraints.mandatory.add(new
                MediaConstraints.KeyValuePair("VoiceActivityDetection", "false"));
        pc = factory.createPeerConnection(iceServers,pcConstraints,pcObserver);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
固然這個地址也須要交換,仍是以甲乙兩位爲例,交換的流程以下(PeerConnection簡稱PC):
甲、乙各建立配置了ICE服務器的PC實例,併爲其添加onicecandidate事件回調
當網絡候選可用時,將會調用onicecandidate函數
在回調函數內部,甲或乙將網絡候選的消息封裝在ICE Candidate信令中,經過服務器中轉,傳遞給對方
甲或乙接收到對方經過服務器中轉所發送過來ICE Candidate信令時,將其解析並得到網絡候選,將其經過PC實例的addIceCandidate()方法加入到PC實例中
這樣鏈接就創立完成了,能夠向RTCPeerConnection中經過addStream()加入流來傳輸媒體流數據。將流加入到RTCPeerConnection實例中後,對方就能夠經過onaddstream所綁定的回調函數監聽到了。調用addStream()能夠在鏈接完成以前,在鏈接創建以後,對方同樣能監聽到媒體流
代碼實現:
public class VideoCallActivity extends ParentActivity{
    public static final String VIDEO_TRACK_ID = "video_track_id";
    public static final String AUDIO_TRACK_ID = "audio_track_id";
    public static final String LOCAL_MEDIA_STREAM_ID = "local_media_stream_id";
    private String mServiceName;    //XMPP服務器名稱
    private GLSurfaceView mGLSurfaceView;
    private GLSurfaceView mGLSurfaceViewRemote;
    private PeerConnection pc;
    private final PCObserver pcObserver = new PCObserver();
    private final SDPObserver sdpObserver = new SDPObserver();
    private MediaConstraints sdpMediaConstraints;
    private MediaConstraints pcConstraints;
    private String remoteName;
    IceCandidate remoteIceCandidate;
    private boolean mIsInited;
    private boolean mIsCalled;
    PeerConnectionFactory factory;
    VideoCapturer videoCapturer;
    VideoSource videoSource;
    VideoRenderer localVideoRenderer;
    VideoRenderer remoteVideoRenderer;
    AudioManager audioManager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_call);
        //打開揚聲器
        audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
        audioManager.setSpeakerphoneOn(true);
        mServiceName = connection.getServiceName();
        mGLSurfaceView = (GLSurfaceView) findViewById(R.id.glsurfaceview);
//        mGLSurfaceViewRemote = (GLSurfaceView) findViewById(R.id.glsurfaceview_remote);
        //檢查初始化音視頻設備是否成功
        if (!PeerConnectionFactory.initializeAndroidGlobals(this,true,true,true,null))
        {
            Log.e("init","PeerConnectionFactory init fail!");
            return;
        }
//        Intent intent = getIntent().getBundleExtra()
        //Media條件信息SDP接口
        sdpMediaConstraints = new MediaConstraints();
        //接受遠程音頻
        sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
                "OfferToReceiveAudio", "true"));
        //接受遠程視頻
        sdpMediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair(
                "OfferToReceiveVideo", "true"));
        factory = new PeerConnectionFactory();
        //iceServer List對象獲取
        List<PeerConnection.IceServer> iceServers = getIceServers(CoturnData.url,
                CoturnData.userName,CoturnData.credential);
        pcConstraints = new MediaConstraints();
        pcConstraints.optional.add(new MediaConstraints.KeyValuePair(
                "DtlsSrtpKeyAgreement", "true"));
        pcConstraints.mandatory.add(new
                MediaConstraints.KeyValuePair("VoiceActivityDetection", "false"));
        pc = factory.createPeerConnection(iceServers,pcConstraints,pcObserver);
        mIsInited = false;
        mIsCalled=false;
        boolean offer=getIntent().getBooleanExtra("createOffer",false);
        //offer:若是offer爲true表示主叫方初始化,若是爲false表示被叫方初始化。
        remoteName = getIntent().getStringExtra("remoteName");
        if(!offer)
        {
            initialSystem();
        }
        else {
            callRemote(remoteName);
        }
        //當VideoActivity已經打開時,處理後續的intent傳過來的數據。
        processExtraData();
    }
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
        processExtraData();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //通話結束,發送通話結束消息
        videoCallEnded();
        //釋放資源
        videoCapturer.dispose();
        videoSource.stop();
        if (pc != null) {
            pc.dispose();
            pc = null;
        }
        audioManager.setSpeakerphoneOn(false);
    }
    private void videoCallEnded() {
        String chatJid = remoteName+"@"+mServiceName;
        Message message = new Message();
        VideoInvitation videoInvitation = new VideoInvitation();
        videoInvitation.setTypeText("video-ended");
        message.addExtension(videoInvitation);
        Chat chat = createChat(chatJid);
        try {
            chat.sendMessage(message);
        } catch (SmackException.NotConnectedException e) {
            e.printStackTrace();
        }
    }
    private void processExtraData() {
        Intent intent = getIntent();
        //獲取SDP數據
        String sdpType = intent.getStringExtra("type");
        String sdpDescription = intent.getStringExtra("description");
        if (sdpType != null)
        {
            SessionDescription.Type type = SessionDescription.Type.fromCanonicalForm(sdpType);
            SessionDescription sdp = new SessionDescription(type,sdpDescription);
            if (pc == null)
            {
                Log.e("pc","pc == null");
            }
            pc.setRemoteDescription(sdpObserver,sdp);
            //若是是offer,則被叫方createAnswer
            if (sdpType.equals("offer"))
            {
                mIsCalled = true;
                pc.createAnswer(sdpObserver,sdpMediaConstraints);
            }
        }
        //獲取ICE Candidate數據
        String iceSdpMid = intent.getStringExtra("sdpMid");
        int iceSdpMLineIndex = intent.getIntExtra("sdpMLineIndex",-1);
        String iceSdp = intent.getStringExtra("sdp");
        if (iceSdpMid != null)
        {
            IceCandidate iceCandidate = new IceCandidate(iceSdpMid,iceSdpMLineIndex,iceSdp);
            if (remoteIceCandidate == null)
            {
                remoteIceCandidate = iceCandidate;
            }
            //下面這步放到函數drainRemoteCandidates()中
            /*//添加遠端的IceCandidate到pc
            pc.addIceCandidate(iceCandidate);*/
        }
        //結束activity
        boolean videoEnded = intent.getBooleanExtra("videoEnded",false);
        if (videoEnded)
        {
            finish();
        }
    }
    private void callRemote(String remoteName) {
        initialSystem();
        //createOffer
        pc.createOffer(sdpObserver,sdpMediaConstraints);
    }
    private void initialSystem() {
        if (mIsInited)
        {
            return;
        }
        //獲取前置攝像頭本地視頻流
        String frontDeviceName = VideoCapturerAndroid.getNameOfFrontFacingDevice();
//        String frontDeviceName = "Camera 1, Facing front, Orientation 0";
        Log.e("CameraName","CameraName: "+frontDeviceName);
        videoCapturer = VideoCapturerAndroid.create(frontDeviceName);
        if (videoCapturer == null)
        {
            Log.e("open","fail to open camera");
            return;
        }
        //視頻
        MediaConstraints mediaConstraints = new MediaConstraints();
        videoSource = factory.createVideoSource(videoCapturer, mediaConstraints);
        VideoTrack localVideoTrack = factory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
        //音頻
        MediaConstraints audioConstraints = new MediaConstraints();
        AudioSource audioSource = factory.createAudioSource(audioConstraints);
        AudioTrack localAudioTrack = factory.createAudioTrack(AUDIO_TRACK_ID,audioSource);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
            }
        };
        VideoRendererGui.setView(mGLSurfaceView,runnable);
        try {
            //改爲ScalingType.SCALE_ASPECT_FILL能夠顯示雙方視頻,可是顯示比例不美觀,而且不知道最後一個參數true和false的含義。
            localVideoRenderer = VideoRendererGui.createGui(0,0,30,30, VideoRendererGui.ScalingType.SCALE_ASPECT_FILL,true);
            remoteVideoRenderer = VideoRendererGui.createGui(0,0,100,100, VideoRendererGui.ScalingType.SCALE_FILL,true);
            localVideoTrack.addRenderer(localVideoRenderer);
        } catch (Exception e) {
            e.printStackTrace();
        }
        MediaStream localMediaStream = factory.createLocalMediaStream(LOCAL_MEDIA_STREAM_ID);
        localMediaStream.addTrack(localAudioTrack);
        localMediaStream.addTrack(localVideoTrack);
        pc.addStream(localMediaStream);
    }
    private List<PeerConnection.IceServer> getIceServers(String url,String user,String credential)
    {
        PeerConnection.IceServer turn = new PeerConnection.IceServer(
                url,user,credential);
        LinkedList<PeerConnection.IceServer> iceServers = new LinkedList<PeerConnection.IceServer>();
        iceServers.add(turn);
        return iceServers;
    }
    private class PCObserver implements PeerConnection.Observer
    {
        @Override
        public void onSignalingChange(PeerConnection.SignalingState signalingState) {
        }
        @Override
        public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
        }
        @Override
        public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
        }
        //發送ICE候選到其餘客戶端
        @Override
        public void onIceCandidate(final IceCandidate iceCandidate) {
            //利用XMPP發送iceCandidate到其餘客戶端
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    String chatJid = remoteName+"@"+mServiceName;
                    Message message = new Message();
                    IceCandidateExtensionElement iceCandidateExtensionElement=
                            new IceCandidateExtensionElement();
                    iceCandidateExtensionElement.setSdpMidText(iceCandidate.sdpMid);
                    iceCandidateExtensionElement.setSdpMLineIndexText(iceCandidate.sdpMLineIndex);
                    iceCandidateExtensionElement.setSdpText(iceCandidate.sdp);
                    message.addExtension(iceCandidateExtensionElement);
                    Chat chat = createChat(chatJid);
                    try {
                        chat.sendMessage(message);
                    } catch (SmackException.NotConnectedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        //Display a media stream from remote
        @Override
        public void onAddStream(final MediaStream mediaStream) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (pc == null)
                    {
                        Log.e("onAddStream","pc == null");
                        return;
                    }
                    if (mediaStream.videoTracks.size()>1 || mediaStream.audioTracks.size()>1)
                    {
                        Log.e("onAddStream","size > 1");
                        return;
                    }
                    if (mediaStream.videoTracks.size() == 1)
                    {
                        VideoTrack videoTrack = mediaStream.videoTracks.get(0);
                        videoTrack.addRenderer(remoteVideoRenderer);
                    }
                }
            });
        }
        @Override
        public void onRemoveStream(final MediaStream mediaStream) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mediaStream.videoTracks.get(0).dispose();
                }
            });
        }
        @Override
        public void onDataChannel(DataChannel dataChannel) {
        }
        @Override
        public void onRenegotiationNeeded() {
        }
    }
    private class SDPObserver implements SdpObserver
    {
        @Override
        public void onCreateSuccess(final SessionDescription sessionDescription) {
            //sendMessage(offer);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    String chatJid = remoteName+"@"+mServiceName;
                    Message message = new Message();
                    SDPExtensionElement sdpExtensionElement = new SDPExtensionElement();
                    sdpExtensionElement.setTypeText(sessionDescription.type.canonicalForm());
                    sdpExtensionElement.setDescriptionText(sessionDescription.description);
                    message.addExtension(sdpExtensionElement);
                    Chat chat = createChat(chatJid);
                    try {
                        chat.sendMessage(message);
                    } catch (SmackException.NotConnectedException e) {
                        e.printStackTrace();
                    }
                    pc.setLocalDescription(sdpObserver,sessionDescription);
                }
            });
        }
        @Override
        public void onSetSuccess() {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    //主叫方
                    if (!mIsCalled)
                    {
                        if (pc.getRemoteDescription() != null)
                        {
                            drainRemoteCandidates();
                        }
                    }
                    //被叫方
                    else
                    {
                        //若是被叫方尚未createAnswer
                        if (pc.getLocalDescription() == null)
                        {
                            Log.e("SDPObserver", "SDPObserver create answer");
                        }
                        else
                        {
                            drainRemoteCandidates();
                        }
                    }
                }
            });
        }
        private void drainRemoteCandidates() {
            if (remoteIceCandidate == null)
            {
                Log.e("SDPObserver","remoteIceCandidate == null");
                return;
            }
            pc.addIceCandidate(remoteIceCandidate);
            Log.e("IceCanditate","添加IceCandidate成功");
            remoteIceCandidate = null;
        }
        @Override
        public void onCreateFailure(String s) {
            Log.e("SDPObserver","onCreateFailure");
        }
        @Override
        public void onSetFailure(String s) {
            Log.e("SDPObserver","onSetFailure");
        }
    }
}
————————————————
版權聲明:本文爲CSDN博主「程序員的自我救贖」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接及本聲明。
原文連接:https://blog.csdn.net/u011026329/article/details/50582690
相關文章
相關標籤/搜索