Android Studio下使用WebRTC简要流程

1.添加WebRTC库

  implementation 'org.webrtc:google-webrtc:1.0.32006'

2.配置xml,添加Surface用于展示相机画面

    <org.webrtc.SurfaceViewRenderer
        android:id="@+id/webrtc_surface_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <org.webrtc.SurfaceViewRenderer
        android:id="@+id/webrtc_surface_remote_view"
        android:layout_width="90dp"
        android:layout_height="160dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_margin="30dp"
        />
  • 可能使用到的参数

    public class Constant {
        // 穿透服务器地址
        public static final String STUN = "stun:stun.l.google.com:19302";
    
        public static final String CHANNEL = "channel";
    
        public static final int VIDEO_RESOLUTION_WIDTH = 720;
        public static final int VIDEO_RESOLUTION_HEIGHT = 480;
        public static final int VIDEO_FPS = 30;
        //声音调节
        public static final int VOLUME = 10;
    
        public static final String VIDEO_TRACK_ID = "videtrack";
        public static final String AUDIO_TRACK_ID = "audiotrack";
    
        public static final String LOCAL_VIDEO_STREAM = "localVideoStream";
        public static final String LOCAL_AUDIO_STREAM = "localAudioStream";
    }
    

3.创建PeerConnectionFactory、PeerConnection

public void createPeerConnection() {
    // 初始化 PeerConnectionFactory
    PeerConnectionFactory.InitializationOptions initializationOptions = PeerConnectionFactory
            .InitializationOptions.builder(Utils.getApp())
            .setEnableInternalTracer(true)
            .setFieldTrials("WebRTC-H264HighProfile/Enabled/")
            .createInitializationOptions();
    PeerConnectionFactory.initialize(initializationOptions);
    //创建EglBase对象
    eglBaseContext = EglBase.create().getEglBaseContext();
    PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
    options.disableEncryption = true;
    options.disableNetworkMonitor = true;
    peerConnectionFactory = PeerConnectionFactory.builder()
            .setVideoDecoderFactory(new DefaultVideoDecoderFactory(eglBaseContext))
            .setVideoEncoderFactory(new DefaultVideoEncoderFactory(eglBaseContext, 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);
    // 初始化Surface
    initSurface();
    // 初始化Sdp回调
    initObserver();
}
  • PeerConnectionObserver 实现 PeerConnection.Observer

    @NonNull
    private PeerConnectionObserver getObserver() {
        return new PeerConnectionObserver() {
            @Override
            public void onIceCandidate(IceCandidate iceCandidate) {
                super.onIceCandidate(iceCandidate);
                LogUtils.eTag(TAG, "创建iceCandidate");
                // setIceCandidate(iceCandidate);
                // 添加对方的ice
                peerConnection.addIceCandidate(iceCandidate);
            }

            @Override
            public void onAddStream(MediaStream mediaStream) {
                super.onAddStream(mediaStream);
                LogUtils.eTag(TAG, "onAddStream : " + mediaStream.toString());
                List<VideoTrack> videoTracks = mediaStream.videoTracks;
                if (videoTracks != null && videoTracks.size() > 0) {
                    VideoTrack videoTrack = videoTracks.get(0);
                    if (videoTrack != null) {
                        videoTrack.addSink(remoteSurfaceView);
                    }
                }
                List<AudioTrack> audioTracks = mediaStream.audioTracks;
                if (audioTracks != null && audioTracks.size() > 0) {
                    AudioTrack audioTrack = audioTracks.get(0);
                    if (audioTrack != null) {
                        audioTrack.setVolume(Constant.VOLUME);
                    }
                }
            }
        };
    }

4. 初始化本地surface、音视频轨

    public void initSurface() {
        // 设置视频显示
        initSurfaceView(localSurfaceView);
        initSurfaceView(remoteSurfaceView);
        startLocalVideoCapture(localSurfaceView);
        startLocalAudioCapture();
    }
    private void initSurfaceView(SurfaceViewRenderer localSurfaceView) {
        localSurfaceView.init(eglBaseContext, null);
        localSurfaceView.setMirror(true);
        localSurfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
        localSurfaceView.setKeepScreenOn(true);
        localSurfaceView.setZOrderMediaOverlay(true);
        localSurfaceView.setEnableHardwareScaler(false);
    }
  • 配置本地视频轨

    private void startLocalVideoCapture(SurfaceViewRenderer localSurfaceView) {
        VideoSource videoSource = peerConnectionFactory.createVideoSource(true);
        SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(Thread.currentThread().getName()
                , eglBaseContext);
        VideoCapturer videoCapturer = createCameraCapturer();
        videoCapturer.initialize(surfaceTextureHelper, ActivityUtils.getTopActivity(), 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);
    }
  • 配置相机捕获

    /**
         * 判断使用Camera1还是Camera2
         * @return VideoCapturer
         */
        private VideoCapturer createCameraCapturer() {
            Context context = ActivityUtils.getTopActivity();
            if (Camera2Enumerator.isSupported(context)) {
                return createCameraCapturer(new Camera2Enumerator(context));
            } else {
                return createCameraCapturer(new Camera1Enumerator(true));
            }
        }
    
        private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
            final String[] deviceNames = enumerator.getDeviceNames();
    
            // 首先,尝试找到前置摄像头
            LogUtils.eTag(TAG, "尝试查找前置摄像头...");
            for (String deviceName : deviceNames) {
                if (enumerator.isFrontFacing(deviceName)) {
                    LogUtils.eTag(TAG, "前置摄像头捕捉器创建成功");
                    VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
                    if (videoCapturer != null) {
                        return videoCapturer;
                    }
                }
            }
    
            // 没有找到前置摄像头,试试别的
            LogUtils.eTag(TAG, "Looking for other cameras.");
            for (String deviceName : deviceNames) {
                if (!enumerator.isFrontFacing(deviceName)) {
                    Logging.d(TAG, "Creating other camera capturer.");
                    VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
                    if (videoCapturer != null) {
                        return videoCapturer;
                    }
                }
            }
            return null;
        }
    
  • 配置本地音频轨

    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.创建本地Sdp回调,发送给对方

  • MySdpObserver 实现SdpObserver

    private void initObserver() {
        observer = new MySdpObserver() {
            @Override
            public void onCreateSuccess(SessionDescription sessionDescription) {
                // 将会话描述设置在本地
                peerConnection.setLocalDescription(this, sessionDescription);
                SessionDescription localDescription = peerConnection.getLocalDescription();
                SessionDescription.Type type = localDescription.type;
                LogUtils.eTag(TAG, "onCreateSuccess == " + " type == " + type);
                // 将offer发送给服务器
                if (type == SessionDescription.Type.OFFER) {
                    // 发送呼叫
                    offer(sessionDescription);
                } else if (type == SessionDescription.Type.ANSWER) {
                    // 发送应答
                    answer(sessionDescription);
                } else if (type == SessionDescription.Type.PRANSWER) {
                    // 发送再次应答
                }
            }
        };
    }

6.所有配置完成

在PeerConnectionObserver的onAddStream对方音视频轨成功时,画面已建立。

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

推荐阅读更多精彩内容