小工具知识网

当前位置: 首页 > 行业知识 > android webrtc入门教程一(简单一对一通话实现)

行业知识

android webrtc入门教程一(简单一对一通话实现)

2026-11-04 18:25:09 来源:互联网转载

webrtc Android入门非常的简单不要被那些博客给带乱了,我看了几篇这方面的博客都是给你零散的代码更本就不能实现通话,学这个要先从全局流程再到详细步骤来学习。

简单介绍下实现webrtc通话总体流程并且给出全部代码,复制粘贴即可

本文福利, 免费领取C++音视频学习资料包+学习路线大纲、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

1、用户A和用户B实现通信双方间建立链接最重要的是双方获取彼此的sdp信息和ice信息。

sdp就是一段文本描述,里面包含了当前本地设备所支持的一些信息,比如设备是否支持h264编码,传输协议是什么。

ice也是一段文本,是配合p2p打洞服务器stun/turn让双方知道各自的公网ip和端口,从而实现端对端通信。

对于实现一个简单入门通信案例来说,不要太去深入的理解每个名词的具体意思,只要能总的明白是做什么就行了,等入门后再去深入理解每个知识点。

简单理解就是sdp是设备描述文本,ice就是ip端口描述文本

2、sdp,ice的创建和使用

用户A,B都需要设置setLocationDescription和setRemoteDescription。

呼叫者调用createOffer创建sdp,被呼叫者调用createAnswer创建sdp

ice信息是在创建peerConnection后会自动从stun/turn服务器请求回调。

下面来具体实现

xml布局

               

1.引入webrtc Android 和websocket

implementation 'org.webrtc:google-webrtc:1.0.32006'//引入socket依赖implementation 'org.java-websocket:Java-WebSocket:1.4.0'

2.全局初始化

//在初次使用PeerConnectionFactory之前,必须调用静态方法initialize()对其进行全局的初始化与资源加载        PeerConnectionFactory.InitializationOptions initializationOptions =                PeerConnectionFactory.InitializationOptions                        .builder(this)                        .setEnableInternalTracer(true)// 启用内部追踪器,用来记录一些相关数据                        .createInitializationOptions();        PeerConnectionFactory.initialize(initializationOptions);

3.创建PeerConnectionFactory

 //-----------创建视频编码和解码器        VideoEncoderFactory encoderFactory = new DefaultVideoEncoderFactory(mRootEGL.getEglBaseContext(),                 true, true);        VideoDecoderFactory decoderFactory = new DefaultVideoDecoderFactory(mRootEGL.getEglBaseContext());        //-----------创建PeerConnectionFactory        AudioDeviceModule adm = JavaAudioDeviceModule.builder(this).createAudioDeviceModule();//音频配置当前JAVA实现,还有native        PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();        //options.disableEncryption : true表示不用数据加密        //options.disableNetworkMonitor : true表示禁用网络监视器        factory = PeerConnectionFactory.builder()                .setOptions(options)//设置网络配置,使用默认                .setAudioDeviceModule(adm)//设置音频采集和播放使用的配置,当前使用java中的audioTrack 和audioRecord                .setVideoEncoderFactory(encoderFactory)                .setVideoDecoderFactory(decoderFactory)                .createPeerConnectionFactory();

4.创建声音源

//配置音频约束        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 = factory.createAudioSource(audioConstraints);//创建音频轨道        localAudioTrack = factory.createAudioTrack("102", audioSource);

5.创建视频源

//ScreenCapturerAndroid 录屏 ;FileVideoCapturer文件        //从相机里获取视频流        CameraEnumerator enumerator = new Camera2Enumerator(this);        String[] deviceNames = enumerator.getDeviceNames();        //遍历所有摄像头找到前置摄像头        for (String deviceName : deviceNames) {            if (enumerator.isFrontFacing(deviceName)) {                videoCapturer = enumerator.createCapturer(deviceName, null);            }        }        assert videoCapturer != null;        videoSource = factory.createVideoSource(videoCapturer.isScreencast());        //创建视频轨道        videoTrack = factory.createVideoTrack("103", videoSource);

6.播放本地视频

 //因为使用的是opengl 渲染        surfaceTextureHelper = SurfaceTextureHelper.create("capture-thread", mRootEGL.getEglBaseContext());        videoCapturer.initialize(surfaceTextureHelper, this, videoSource.getCapturerObserver());        //开始录制        videoCapturer.startCapture(                720,//宽                1080,//高                25//fps 帧率        );        //播放本地视频        localRender.init(mRootEGL.getEglBaseContext(), null);        //SCALE_ASPECT_FILL 自动适应屏幕比例, 画面存在被裁剪的可能        //SCALE_ASPECT_FIT 自动适应画面比例,屏幕上可能存在黑边        //SCALE_ASPECT_BALANCED  视频尺寸非等比缩放。保证视频内容全部显示,且填满视窗。        localRender.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);        localRender.setMirror(true);//启用镜像        videoTrack.addSink(localRender);//最后播放

7.初始化远端render

//初始化远端render ,因为使用opengl渲染所以 必须在在主线程初始化        remoteRender.init(mRootEGL.getEglBaseContext(), null);        remoteRender.setMirror(true);//启用镜像        remoteRender.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);

8.安装ice服务器

//在ubunto服务器上安装coturn软件即可实现ice服务器sudo apt-get install libssl-devsudo apt-get install libevent-devgit clone https://github.com/coturn/coturncd coturn./configuremakesudo make install//启动服务器, wzp:123456  表示用户名:密码也可用通过配置文件来设置sudo nohup turnserver -L 0.0.0.0 -a -u wzp:123456 -v -f -r nort.text &//查看端口号,默认3478端口,sudo lsof -i:3478

9.创建peerConntion

//ice服务器列表List iceServers = new ArrayList();//添加一个turn服务器,turn服务器主要用户下面的stun服务器打洞失败的时候使用这个turn服务器转发数据流,可以添加多个iceServers.add(                PeerConnection.IceServer.builder("turn:**.**.**.**3478")//这是你服务器的地址                        .setUsername("wzp")//用户名                        .setPassword("123456")//密码                        .createIceServer());        //添加一个stun服务器,iceServers.add(PeerConnection.IceServer.builder("stun:**.**.**.**:3478").createIceServer());peerConnection = factory.createPeerConnection(iceServers, new PeerConnection.Observer() {            @Override            public void onSignalingChange(PeerConnection.SignalingState signalingState) {            }            @Override            public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {                //ICE 连接状态变化后回调            }            @Override            public void onIceConnectionReceivingChange(boolean b) {            }            @Override            public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {            }            @Override            public void onIceCandidate(IceCandidate iceCandidate) {                //自动请求stun/turn服务器后回调这个方法                //发送Ice信息给对端用户 ,下面的代码只是用于发送信息给远端用户,我使用的是websocket,自己可以用其他方式实现。最后结尾我会给出服务器端的代码。                JSONObject sendObj = new JSONObject();                try {                    sendObj.put("cmd", cmd_ice);                    sendObj.put("uid", uid);                    sendObj.put("remoteUid", remoteUid);                    sendObj.put("roomId",roomId);                    JSONObject msgObj = new JSONObject();                    msgObj.put("sdpMid", iceCandidate.sdpMid);                    msgObj.put("sdpMLineIndex", iceCandidate.sdpMLineIndex);                    msgObj.put("sdp", iceCandidate.sdp);                    sendObj.put("msg", msgObj);                    socket.send(sendObj.toString());                } catch (JSONException e) {                    throw new RuntimeException(e);                }            }            @Override            public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {            }            @Override            public void onAddStream(MediaStream mediaStream) {                //收到远端数据流信息                mediaStream.videoTracks.get(0).addSink(remoteRender);                mediaStream.audioTracks.get(0).setEnabled(true);            }            @Override            public void onRemoveStream(MediaStream mediaStream) {            }            @Override            public void onDataChannel(DataChannel dataChannel) {            }            @Override            public void onRenegotiationNeeded() {            }            @Override            public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {            }        });        //        List mediaStreamLabels = Collections.singletonList("ARDAMS");//        peerConnection.addTrack(videoTrack,mediaStreamLabels);//        peerConnection.addTrack(localAudioTrack,mediaStreamLabels);        //将本地流添加到peerConnection,远端的onAddStream回调将接受该数据流        MediaStream stream =  factory.createLocalMediaStream("110");        stream.addTrack(videoTrack);        stream.addTrack(localAudioTrack);        peerConnection.addStream(stream);

10.建立websocket链接,只是用于演示效果,所以这里只是简单的实现。重要的是如何简单的去理解流程

URI uri = null;//信令服务器地址        try {            uri = new URI("ws://192.168.2.134:8090");        } catch (Exception e) {        }        socket = new WebSocketClient(uri) {            @Override            public void onOpen(ServerHandshake handshakedata) {                if (isDestroyed()) {                    return;                }                Log.e(tag,"链接socket成功");            }            @Override            public void onMessage(String message) {                if (isDestroyed()) {                    return;                }                try {                    JSONObject msgObj = new JSONObject(message);                    String cmd = msgObj.getString("cmd");                    Log.e(tag,"收到消息:" + message);                    if (cmd.equals(cmd_new_peer)) {                        //有新人加入房间                        handleNewPeer(msgObj);                        return;                    }                    if (cmd.equals(cmd_offer)) {                        //收到offer请求                        handleOffer(msgObj);                        return;                    }                    if (cmd.equals(cmd_answer)) {                        //收到answer请求                        handleAnswer(msgObj);                        return;                    }                    if (cmd.equals(cmd_ice)) {                        //收到ice信息                        handleIce(msgObj);                    }                } catch (JSONException e) {                    throw new RuntimeException(e);                }            }            @Override            public void onClose(int code, String reason, boolean remote) {                if (isDestroyed()) {                    return;                }            }            @Override            public void onError(Exception ex) {                if (isDestroyed()) {                    return;                }                Log.e(tag,"socket错误" + ex.toString());            }        };        socket.connect();

11.发起通话者创建offer并且设置本地setLocalDescription,最后发送给被呼叫者

private void handleNewPeer(JSONObject msgObj) {        /*新人加入房间,创建offer,发起通话*/        try {            //被呼叫者的uid            remoteUid = msgObj.getString("uid");            MediaConstraints constraints = new MediaConstraints();            constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));            constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));            peerConnection.createOffer(new SdpObserver() {                private SessionDescription localSdp;                @Override                public void onCreateSuccess(SessionDescription sessionDescription) {                    //创建offer成功即成功                    localSdp = sessionDescription;                    peerConnection.setLocalDescription(this, sessionDescription);                }                @Override                public void onSetSuccess() {                    //setLocalDescription 成功后回调                    JSONObject sendObj = new JSONObject();                    try {                        sendObj.put("cmd", cmd_offer);                        sendObj.put("uid", uid);                        sendObj.put("remoteUid", remoteUid);                        sendObj.put("roomId",roomId);                        sendObj.put("msg", localSdp.description);                        socket.send(sendObj.toString());                    } catch (JSONException e) {                        throw new RuntimeException(e);                    }                }                @Override                public void onCreateFailure(String s) {                }                @Override                public void onSetFailure(String s) {                }            }, constraints);        } catch (JSONException e) {            throw new RuntimeException(e);        }    }

12.被呼叫者收到offer后设置setRemoteDescription ,创建answer并设置setLocalDescription,最后发送给呼叫者

 private void handleOffer(JSONObject msgObj) {        //收到offer, 当前身份是被呼叫者        try {            //发起通话者的uid            remoteUid = msgObj.getString("uid");            String sdpDescription = msgObj.getString("msg");            SessionDescription sdp = new SessionDescription(SessionDescription.Type.OFFER, sdpDescription);            peerConnection.setRemoteDescription(new SdpObserver() {                private boolean isCreateAnswer;                private String sdpDescription;                @Override                public void onCreateSuccess(SessionDescription sessionDescription) {                    //createAnswer 创建成功时候回调                    Log.e(tag,"创建answer成功");                    sdpDescription = sessionDescription.description;                    peerConnection.setLocalDescription(this, sessionDescription);                }                @Override                public void onSetSuccess() {                    //setRemoteDescription setLocalDescription 成功时候的回调                    if (!isCreateAnswer) {                        Log.e(tag,"onSetSuccess1");                        //还未创建answer 说明是setRemoteDescription回调                        isCreateAnswer = true;                        MediaConstraints constraints = new MediaConstraints();                        constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));                        constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));                        peerConnection.createAnswer(this, constraints);                    } else {                        Log.e(tag,"onSetSuccess2");                        //是setLocalDescription回调                        JSONObject sendObj = new JSONObject();                        try {                            sendObj.put("cmd", cmd_answer);                            sendObj.put("uid", uid);                            sendObj.put("remoteUid", remoteUid);                            sendObj.put("roomId",roomId);                            sendObj.put("msg", sdpDescription);                            socket.send(sendObj.toString());                        } catch (JSONException e) {                            throw new RuntimeException(e);                        }                    }                }                @Override                public void onCreateFailure(String s) {                }                @Override                public void onSetFailure(String s) {                }            }, sdp);        } catch (JSONException e) {            e.printStackTrace();            throw new RuntimeException(e);        }    }

13.发起通话者收到answer后设置setRemoteDescription

private void handleAnswer(JSONObject msgObj) {        //收到answer,当前是发起通话者        try {            String sdpDescription = msgObj.getString("msg");            SessionDescription sdp = new SessionDescription(SessionDescription.Type.ANSWER, sdpDescription);            peerConnection.setRemoteDescription(new SdpObserver() {                @Override                public void onCreateSuccess(SessionDescription sessionDescription) {                }                @Override                public void onSetSuccess() {                    // setRemoteDescription 设置成功                }                @Override                public void onCreateFailure(String s) {                }                @Override                public void onSetFailure(String s) {                }            }, sdp);        } catch (JSONException e) {            throw new RuntimeException(e);        }    }

14,各自收到对方发来的ice信息时候addIceCandidate

private void handleIce(JSONObject msgObj) {        //收到对方的ice信息        try {            Log.e(tag,"设置ICE信息");            JSONObject iceObj = msgObj.getJSONObject("msg");            IceCandidate iceCandidate = new IceCandidate(iceObj.getString("sdpMid"),                    iceObj.getInt("sdpMLineIndex"),                    iceObj.getString("sdp"));            peerConnection.addIceCandidate(iceCandidate);        } catch (JSONException e) {            Log.e(tag,"ice设置失败:" + e.getMessage());            throw new RuntimeException(e);        }    }

15.销毁

@Override    protected void onDestroy() {        if (socket != null && socket.isOpen()) {            socket.close();        }        socket = null;        if(peerConnection != null){            peerConnection.close();            peerConnection = null;        }        if(videoSource != null){            videoSource.dispose();            videoSource = null;        }        if (audioSource != null) {            audioSource.dispose();            audioSource = null;        }        if(videoCapturer != null){            try {                videoCapturer.stopCapture();            } catch (InterruptedException e) {                throw new RuntimeException(e);            }            videoCapturer.dispose();            videoCapturer = null;        }        if(surfaceTextureHelper != null){            surfaceTextureHelper.dispose();            surfaceTextureHelper = null;        }        localRender.release();        remoteRender.release();        if(factory != null){            factory.dispose();            factory = null;        }        if(mRootEGL != null){            mRootEGL.release();            mRootEGL = null;        }        super.onDestroy();    }

最后给出java全部源码

package com.example.rtcmy.ui;import androidx.appcompat.app.AppCompatActivity;import androidx.databinding.BindingConversion;import androidx.databinding.DataBindingUtil;import androidx.databinding.ObservableField;import android.database.Observable;import android.graphics.Color;import android.graphics.drawable.ColorDrawable;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.TextView;import com.example.rtcmy.R;import org.java_websocket.client.WebSocketClient;import org.java_websocket.handshake.ServerHandshake;import org.json.JSONException;import org.json.JSONObject;import org.webrtc.AudioSource;import org.webrtc.AudioTrack;import org.webrtc.Camera1Enumerator;import org.webrtc.Camera2Enumerator;import org.webrtc.CameraEnumerator;import org.webrtc.DataChannel;import org.webrtc.DefaultVideoDecoderFactory;import org.webrtc.DefaultVideoEncoderFactory;import org.webrtc.EglBase;import org.webrtc.IceCandidate;import org.webrtc.Logging;import org.webrtc.MediaConstraints;import org.webrtc.MediaStream;import org.webrtc.PeerConnection;import org.webrtc.PeerConnectionFactory;import org.webrtc.RendererCommon;import org.webrtc.RtpReceiver;import org.webrtc.ScreenCapturerAndroid;import org.webrtc.SdpObserver;import org.webrtc.SessionDescription;import org.webrtc.SurfaceTextureHelper;import org.webrtc.SurfaceViewRenderer;import org.webrtc.VideoCapturer;import org.webrtc.VideoDecoder;import org.webrtc.VideoDecoderFactory;import org.webrtc.VideoEncoderFactory;import org.webrtc.VideoSource;import org.webrtc.VideoTrack;import org.webrtc.audio.AudioDeviceModule;import org.webrtc.audio.JavaAudioDeviceModule;import java.net.URI;import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.UUID;public class SimpleMeetingActivity extends AppCompatActivity {    private WebSocketClient socket;    private String roomId = "1";//房间号    private String cmd_join_room = "cmd_join_room";//加入房间命令    private String cmd_new_peer = "cmd_new_peer";//有新人加入房间    private String cmd_offer = "cmd_offer";    private String cmd_answer = "cmd_answer";    private String cmd_ice = "cmd_ice";    private SurfaceViewRenderer remoteRender;    private SurfaceViewRenderer localRender;    private TextView ivStatus;    private View ivJoin;    private EglBase mRootEGL;    private PeerConnectionFactory factory;    private VideoCapturer videoCapturer;    private AudioSource audioSource;    private AudioTrack localAudioTrack;    private VideoSource videoSource;    private VideoTrack videoTrack;    private List iceServers = new ArrayList();    private SurfaceTextureHelper surfaceTextureHelper;    private PeerConnection peerConnection;    private String remoteUid;    private String uid = UUID.randomUUID().toString();    private String tag = "simpleWebrtc";    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_simple_metting);        ivStatus = findViewById(R.id.ivStatus);        ivJoin = findViewById(R.id.ivJoin);        remoteRender = findViewById(R.id.ivRemoteRender);        localRender = findViewById(R.id.ivLocalRender);        //开启日志        iceServers.add(                PeerConnection.IceServer.builder("turn:123.60.151.37:3478")                        .setUsername("wzp")                        .setPassword("123456")                        .createIceServer());        iceServers.add(PeerConnection.IceServer.builder("stun:123.60.151.37:3478").createIceServer());        mRootEGL = EglBase.create();        //在初次使用PeerConnectionFactory之前,必须调用静态方法initialize()对其进行全局的初始化与资源加载        PeerConnectionFactory.InitializationOptions initializationOptions =                PeerConnectionFactory.InitializationOptions                        .builder(this)                        .setEnableInternalTracer(true)// 启用内部追踪器,用来记录一些相关数据                        .createInitializationOptions();        PeerConnectionFactory.initialize(initializationOptions);        //-----------创建视频编码和解码器        VideoEncoderFactory encoderFactory = new DefaultVideoEncoderFactory(mRootEGL.getEglBaseContext(),                true, true);        VideoDecoderFactory decoderFactory = new DefaultVideoDecoderFactory(mRootEGL.getEglBaseContext());        //-----------创建PeerConnectionFactory        AudioDeviceModule adm = JavaAudioDeviceModule.builder(this).createAudioDeviceModule();//音频配置当前JAVA实现,还有native        PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();        //options.disableEncryption : true表示不用数据加密        //options.disableNetworkMonitor : true表示禁用网络监视器        factory = PeerConnectionFactory.builder()                .setOptions(options)//设置网络配置,使用默认                .setAudioDeviceModule(adm)//设置音频采集和播放使用的配置,当前使用java中的audioTrack 和audioRecord                .setVideoEncoderFactory(encoderFactory)                .setVideoDecoderFactory(decoderFactory)                .createPeerConnectionFactory();        //-----------创建声音源        //配置音频约束        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 = factory.createAudioSource(audioConstraints);        localAudioTrack = factory.createAudioTrack("102", audioSource);        //----------创建视频源        //ScreenCapturerAndroid 录屏 ;FileVideoCapturer文件        //从相机里获取视频流        CameraEnumerator enumerator = new Camera2Enumerator(this);        String[] deviceNames = enumerator.getDeviceNames();        //遍历所有摄像头找到前置摄像头        for (String deviceName : deviceNames) {            if (enumerator.isFrontFacing(deviceName)) {                videoCapturer = enumerator.createCapturer(deviceName, null);            }        }        assert videoCapturer != null;        videoSource = factory.createVideoSource(videoCapturer.isScreencast());        //创建视频轨道        videoTrack = factory.createVideoTrack("103", videoSource);        //因为使用的是opengl 渲染        surfaceTextureHelper = SurfaceTextureHelper.create("capture-thread", mRootEGL.getEglBaseContext());        videoCapturer.initialize(surfaceTextureHelper, this, videoSource.getCapturerObserver());        //开始录制        videoCapturer.startCapture(                720,//宽                1080,//高                25//fps 帧率        );        //播放本地视频        localRender.init(mRootEGL.getEglBaseContext(), null);        //SCALE_ASPECT_FILL 自动适应屏幕比例, 画面存在被裁剪的可能        //SCALE_ASPECT_FIT 自动适应画面比例,屏幕上可能存在黑边        //SCALE_ASPECT_BALANCED  视频尺寸非等比缩放。保证视频内容全部显示,且填满视窗。        localRender.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);        localRender.setMirror(true);//启用镜像        videoTrack.addSink(localRender);//最后播放        //初始化远端render ,因为使用opengl渲染所以 必须在在主线程初始化        remoteRender.init(mRootEGL.getEglBaseContext(), null);        remoteRender.setMirror(true);//启用镜像        remoteRender.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);        //创建peerConnection        peerConnection = factory.createPeerConnection(iceServers, new PeerConnection.Observer() {            @Override            public void onSignalingChange(PeerConnection.SignalingState signalingState) {            }            @Override            public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {                //ICE 连接状态变化后回调            }            @Override            public void onIceConnectionReceivingChange(boolean b) {            }            @Override            public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {            }            @Override            public void onIceCandidate(IceCandidate iceCandidate) {                //自动请求stun/turn服务器后回调这个方法                //发送Ice信息给对端用户                JSONObject sendObj = new JSONObject();                try {                    sendObj.put("cmd", cmd_ice);                    sendObj.put("uid", uid);                    sendObj.put("remoteUid", remoteUid);                    sendObj.put("roomId",roomId);                    JSONObject msgObj = new JSONObject();                    msgObj.put("sdpMid", iceCandidate.sdpMid);                    msgObj.put("sdpMLineIndex", iceCandidate.sdpMLineIndex);                    msgObj.put("sdp", iceCandidate.sdp);                    sendObj.put("msg", msgObj);                    socket.send(sendObj.toString());                } catch (JSONException e) {                    throw new RuntimeException(e);                }            }            @Override            public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {            }            @Override            public void onAddStream(MediaStream mediaStream) {                //收到远端数据流信息                mediaStream.videoTracks.get(0).addSink(remoteRender);                mediaStream.audioTracks.get(0).setEnabled(true);            }            @Override            public void onRemoveStream(MediaStream mediaStream) {            }            @Override            public void onDataChannel(DataChannel dataChannel) {            }            @Override            public void onRenegotiationNeeded() {            }            @Override            public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {            }        });//        List mediaStreamLabels = Collections.singletonList("ARDAMS");//        peerConnection.addTrack(videoTrack,mediaStreamLabels);//        peerConnection.addTrack(localAudioTrack,mediaStreamLabels);        //将本地流添加到peerConnection,远端的onAddStream回调将接受该数据流        MediaStream stream =  factory.createLocalMediaStream("110");        stream.addTrack(videoTrack);        stream.addTrack(localAudioTrack);        peerConnection.addStream(stream);        createSocket();        ivJoin.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                if (!socket.isOpen()) {                    return;                }                ivStatus.setText("正在加入房间...");                JSONObject sendObj = new JSONObject();                try {                    sendObj.put("cmd", cmd_join_room);                    sendObj.put("uid", uid);                    sendObj.put("roomId", roomId);                    socket.send(sendObj.toString());                } catch (JSONException e) {                    throw new RuntimeException(e);                }            }        });    }    private void createSocket() {        if (socket != null) {            socket.close();        }        URI uri = null;//信令服务器地址        try {            uri = new URI("ws://192.168.2.134:8090");        } catch (Exception e) {        }        socket = new WebSocketClient(uri) {            @Override            public void onOpen(ServerHandshake handshakedata) {                if (isDestroyed()) {                    return;                }                Log.e(tag,"链接socket成功");            }            @Override            public void onMessage(String message) {                if (isDestroyed()) {                    return;                }                try {                    JSONObject msgObj = new JSONObject(message);                    String cmd = msgObj.getString("cmd");                    Log.e(tag,"收到消息:" + message);                    if (cmd.equals(cmd_new_peer)) {                        //有新人加入房间                        handleNewPeer(msgObj);                        return;                    }                    if (cmd.equals(cmd_offer)) {                        //收到offer请求                        handleOffer(msgObj);                        return;                    }                    if (cmd.equals(cmd_answer)) {                        //收到answer请求                        handleAnswer(msgObj);                        return;                    }                    if (cmd.equals(cmd_ice)) {                        //收到ice信息                        handleIce(msgObj);                    }                } catch (JSONException e) {                    throw new RuntimeException(e);                }            }            @Override            public void onClose(int code, String reason, boolean remote) {                if (isDestroyed()) {                    return;                }            }            @Override            public void onError(Exception ex) {                if (isDestroyed()) {                    return;                }                Log.e(tag,"socket错误" + ex.toString());            }        };        socket.connect();    }    private void handleIce(JSONObject msgObj) {        //收到对方的ice信息        try {            Log.e(tag,"设置ICE信息");            JSONObject iceObj = msgObj.getJSONObject("msg");            IceCandidate iceCandidate = new IceCandidate(iceObj.getString("sdpMid"),                    iceObj.getInt("sdpMLineIndex"),                    iceObj.getString("sdp"));            peerConnection.addIceCandidate(iceCandidate);        } catch (JSONException e) {            Log.e(tag,"ice设置失败:" + e.getMessage());            throw new RuntimeException(e);        }    }    private void handleAnswer(JSONObject msgObj) {        //收到answer,当前是发起通话者        try {            String sdpDescription = msgObj.getString("msg");            SessionDescription sdp = new SessionDescription(SessionDescription.Type.ANSWER, sdpDescription);            peerConnection.setRemoteDescription(new SdpObserver() {                @Override                public void onCreateSuccess(SessionDescription sessionDescription) {                }                @Override                public void onSetSuccess() {                    // setRemoteDescription 设置成功                }                @Override                public void onCreateFailure(String s) {                }                @Override                public void onSetFailure(String s) {                }            }, sdp);        } catch (JSONException e) {            throw new RuntimeException(e);        }    }    private void handleOffer(JSONObject msgObj) {        //收到offer, 当前身份是被呼叫者        try {            //发起通话者的uid            remoteUid = msgObj.getString("uid");            String sdpDescription = msgObj.getString("msg");            SessionDescription sdp = new SessionDescription(SessionDescription.Type.OFFER, sdpDescription);            peerConnection.setRemoteDescription(new SdpObserver() {                private boolean isCreateAnswer;                private String sdpDescription;                @Override                public void onCreateSuccess(SessionDescription sessionDescription) {                    //createAnswer 创建成功时候回调                    Log.e(tag,"创建answer成功");                    sdpDescription = sessionDescription.description;                    peerConnection.setLocalDescription(this, sessionDescription);                }                @Override                public void onSetSuccess() {                    //setRemoteDescription setLocalDescription 成功时候的回调                    if (!isCreateAnswer) {                        Log.e(tag,"onSetSuccess1");                        //还未创建answer 说明是setRemoteDescription回调                        isCreateAnswer = true;                        MediaConstraints constraints = new MediaConstraints();                        constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));                        constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));                        peerConnection.createAnswer(this, constraints);                    } else {                        Log.e(tag,"onSetSuccess2");                        //是setLocalDescription回调                        JSONObject sendObj = new JSONObject();                        try {                            sendObj.put("cmd", cmd_answer);                            sendObj.put("uid", uid);                            sendObj.put("remoteUid", remoteUid);                            sendObj.put("roomId",roomId);                            sendObj.put("msg", sdpDescription);                            socket.send(sendObj.toString());                        } catch (JSONException e) {                            throw new RuntimeException(e);                        }                    }                }                @Override                public void onCreateFailure(String s) {                }                @Override                public void onSetFailure(String s) {                }            }, sdp);        } catch (JSONException e) {            e.printStackTrace();            throw new RuntimeException(e);        }    }    private void handleNewPeer(JSONObject msgObj) {        /*新人加入房间,创建offer,发起通话*/        try {            //被呼叫者的uid            remoteUid = msgObj.getString("uid");            MediaConstraints constraints = new MediaConstraints();            constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));            constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));            peerConnection.createOffer(new SdpObserver() {                private SessionDescription localSdp;                @Override                public void onCreateSuccess(SessionDescription sessionDescription) {                    //创建offer成功即成功                    localSdp = sessionDescription;                    peerConnection.setLocalDescription(this, sessionDescription);                }                @Override                public void onSetSuccess() {                    //setLocalDescription 成功后回调                    JSONObject sendObj = new JSONObject();                    try {                        sendObj.put("cmd", cmd_offer);                        sendObj.put("uid", uid);                        sendObj.put("remoteUid", remoteUid);                        sendObj.put("roomId",roomId);                        sendObj.put("msg", localSdp.description);                        socket.send(sendObj.toString());                    } catch (JSONException e) {                        throw new RuntimeException(e);                    }                }                @Override                public void onCreateFailure(String s) {                }                @Override                public void onSetFailure(String s) {                }            }, constraints);        } catch (JSONException e) {            throw new RuntimeException(e);        }    }    @Override    protected void onDestroy() {        if (socket != null && socket.isOpen()) {            socket.close();        }        socket = null;        if(peerConnection != null){            peerConnection.close();            peerConnection = null;        }        if(videoSource != null){            videoSource.dispose();            videoSource = null;        }        if (audioSource != null) {            audioSource.dispose();            audioSource = null;        }        if(videoCapturer != null){            try {                videoCapturer.stopCapture();            } catch (InterruptedException e) {                throw new RuntimeException(e);            }            videoCapturer.dispose();            videoCapturer = null;        }        if(surfaceTextureHelper != null){            surfaceTextureHelper.dispose();            surfaceTextureHelper = null;        }        localRender.release();        remoteRender.release();        if(factory != null){            factory.dispose();            factory = null;        }        if(mRootEGL != null){            mRootEGL.release();            mRootEGL = null;        }        super.onDestroy();    }}

服务器端node js全部源码

//简单信令服务器 npm install nodejs-websocketlet ws = require("nodejs-websocket")class Client {    constructor(uid,conn,roomId) {        this.uid = uid;        this.conn = conn;        this.conn.uid = this.uid;        this.roomId = roomId;        this.conn.roomId = roomId;    }}let roomMaps = new Mapfunction handleJoinRoom(receiveObj, conn) {    //有人加入房间    let uid = receiveObj.uid;    let roomId = receiveObj.roomId;    let room = roomMaps.get(roomId);    let client = new Client(uid,conn,roomId)    if(!room){        //创建房间        room = new Map    }    if(room.get(uid)){        //已经在房间了        console.log("已经在房间里了")        return    }    room.set(uid,client)    roomMaps.set(roomId,room);    console.log("加入房间了");    if(room.size > 1){        let clients = Array.from(room.keys())        clients.forEach(remoteUid => {            if(remoteUid !== uid){                //通知房间其他人有新人加入                let sendObj = {                    cmd: "cmd_new_peer",                    uid: uid,                    remoteUid                }                let remoteClient = room.get(remoteUid)                remoteClient.conn.sendText(JSON.stringify(sendObj))                console.log("新人发送成功");            }        })    }}function handleOffer(receiveObj) {    //转发offer    let remoteUid = receiveObj.remoteUid    let roomId = receiveObj.roomId    let room = roomMaps.get(roomId)    let client = room.get(remoteUid)    client.conn.sendText(JSON.stringify(receiveObj))}function handleAnswer(receiveObj) {    //转发answer;    let remoteUid = receiveObj.remoteUid    let roomId = receiveObj.roomId    let room = roomMaps.get(roomId)    let client = room.get(remoteUid)    client.conn.sendText(JSON.stringify(receiveObj))}function handleIce(receiveObj) {    //转发ice    let remoteUid = receiveObj.remoteUid    let roomId = receiveObj.roomId    let room = roomMaps.get(roomId)    let client = room.get(remoteUid)    client.conn.sendText(JSON.stringify(receiveObj))}function handleClose(conn) {    let uid = conn.uid    let roomId = conn.roomId    let room = roomMaps.get(roomId)    room.delete(uid)}ws.createServer(function (conn) {    //有客服端链接    conn.on("text",function (str) {        //收到消息        let receiveObj = JSON.parse(str);        console.log(str)        switch (receiveObj.cmd) {            case "cmd_join_room":                //有人加入                handleJoinRoom(receiveObj,conn)                break            case "cmd_offer":                //转发offer                handleOffer(receiveObj)                break            case "cmd_answer":                handleAnswer(receiveObj);                break            case "cmd_ice":                handleIce(receiveObj);                break        }    })    conn.on("close",function (code,reason) {        console.log("链接关闭")        handleClose(conn);    });    conn.on("error",function (err){        console.log(err);    })}).listen(8090)

本文福利, 免费领取C++音视频学习资料包+学习路线大纲、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓ 

上一篇:租房怎么去住房中心备案

下一篇:薪水怎么拼读?