关于WebRTC视频Android端实现的原理解读

这篇具有很好参考价值的文章主要介绍了关于WebRTC视频Android端实现的原理解读。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

简介:

进入公司之后做了第一个项目就是关于视频的,因为用的是别人提供的sdk,所以说很容易就能实现其中的功能,那么项目结尾的时候就想着不能光会用,起码得知道原理过程吧!那么下面就讲解一下本人对关于WebRTC的视频连接过程的一些讲解:

关于WebRTC这个库,虽然说它提供了点对点的通信,但是前提也是要双方都连接到服务器为基础,首先浏览器之间交换建立通信的元数据(其实也就是信令)必须要经过服务器,其次官方所说的NAT和防火墙也是需要经过服务器(其实可以理解成打洞,就是寻找建立连接的方式) 至于服务器那边,我不太懂也不多说。

Android端:

这里提供一个已经编译好的WebRTC项目,否则刚入门的小伙伴估计很难去自己编译。关于android客户端,你只需要了解RTCPeerConnection这个接口,该接口代表一个由本地计算机到远程端的WebRTC连接,提供了创建,保持,监控,关闭连接的方法的实现。 我们还需要搞懂两件事情:1、确定本机上的媒体流的特性,如分辨率、编码能力等(这个其实包含在SDP描述中,后面会讲解)2、连接两端的主机的网络地址(其实就是ICE Candidate)

原理(重要):

通过offer和answe交换SDP描述符:(比如A向B发起视频请求) 比如A和B需要建立点对点的连接,大概流程就是:两端先各自建立一个PeerConnection实例(这里称为pc),A通过pc所提供的createOffer()方法建立一个包含SDP描述符的offer信令,同样A通过pc提供的setLocalDescription()方法,将A的SDP描述符交给A的pc对象,A将offer信令通过服务器发送给B。B将A的offer信令中所包含的SDP描述符提取出来,通过pc所提供的setRemoteDescription()方法交给B的pc实例对象,B将pc所提供的createAnswer()方法建立一个包含B的SDP描述符answer信令,B通过pc提供的setLocalDescription()方法,将自己的SDP描述符交给自己的pc实例对象,然后将answer信令通过服务器发送给A,最后A接收到B的answer信令后,将其中的SDP描述符提取出来,调用setRemoteDescription()方法交给A自己的pc实例对象。

所以两端视频连接的过程大致就是上述流程,通过一系列的信令交换,A和B所创建的pc实例对象都包含A和B的SDP描述符,完成了以上两件事情中的第一件事情,那么第二件事情就是获取连接两端主机的网络地址啦,如下:

通过ICE框架建立NAT/防火墙穿越的连接(打洞) 这个网址应该是能从外界直接访问的,WebRTC使用了ICE框架来获得这个网址, PeerConnection在创立的时候可以将ICE服务器的地址传递进去,如:
 

 private void init(Context context) {
        PeerConnectionFactory.initializeAndroidGlobals(context, true, true, true);
        this.factory = new PeerConnectionFactory();
        this.iceServers.add(new IceServer("turn:turn.realtimecat.com:3478", "learningtech", "learningtech"));
    }
注意:“turn:turn.realtimecat.com:3478”这段字符其实就是该ICE服务器的地址。

当然这个地址也需要交换,还是以AB两位为例,交换的流程如下(PeerConnection简称PC): A、B各创建配置了ICE服务器的PC实例,并为其添加onicecandidate事件回调 当网络候选可用时,将会调用onicecandidate函数 在回调函数内部,A或B将网络候选的消息封装在ICE Candidate信令中,通过服务器中转,传递给对方 A或B接收到对方通过服务器中转所发送过来ICE Candidate信令时,将其解析并获得网络候选,将其通过PC实例的addIceCandidate()方法加入到PC实例中。

这样连接就建立完成了,可以向RTCPeerConnection中通过addStream()加入流来传输媒体流数据。将流加入到RTCPeerConnection实例中后,对方就可以通过onaddstream所绑定的回调函数监听到了。调用addStream()可以在连接完成之前,在连接建立之后,对方一样能监听到媒体流。

下面是我运用sdk所做的代码实现流程:

1、首先在界面布局中,xml文件中所要显示视频的地方写好GLSurfaceView控件,当然你也可以动态添加该控件(我写成了静态的了,这个随意)

2、首先先初始化该控件,即:(当然刚进入界面就初始化也可以,后面连接服务器之后再初始化也可以,顺序都行)

public void initPlayView(GLSurfaceView glSurfaceView) {
        VideoRendererGui.setView(glSurfaceView, (Runnable)null);
        this.isVideoRendererGuiSet = true;
    }

这一步就是要把glSurfaceView添加VideoRendererGui中,作为要显示的界面。

3、登录到视频服务器,这一步其实应该是最开始的(地2、3步骤顺序不限)

public void connect(String url) throws URISyntaxException {
        //先初始化配置网络ping的一些信息
        this.init(url);
        //然后在连接服务器
        this.client.connect();
    }

    private void init(String url) throws URISyntaxException {
        if (!this.init) {
            Options opts = new Options();
            opts.forceNew = true;
            opts.reconnection = false;
            opts.query = "user_id=" + this.username;
            this.client = IO.socket(url, opts);
            this.client.on("connect", new Listener() {
                public void call(Object... args) {
                    if (Token.this.mEventHandler != null) {
                        Message msg = Token.this.mEventHandler.obtainMessage(10010);
                        Token.this.mEventHandler.sendMessage(msg);
                    }
                }
            }).on("disconnect", new Listener() {
                public void call(Object... args) {
                    if (Token.this.mEventHandler != null) {
                        Message msg = Token.this.mEventHandler.obtainMessage(10014);
                        Token.this.mEventHandler.sendMessage(msg);
                    }
                }
            }).on("error", new Listener() {
                public void call(Object... args) {
                    if (Token.this.mEventHandler != null) {
                        Error error = null;
                        if (args.length > 0) {
                            try {
                                error = (Error) (new Gson()).fromJson((String) args[0], Error.class);
                            } catch (Exception var4) {
                                var4.printStackTrace();
                            }
                        }
                        Message msg = Token.this.mEventHandler.obtainMessage(10013, error);
                        Token.this.mEventHandler.sendMessage(msg);
                    }
                }
            }).on("connect_timeout", new Listener() {
                public void call(Object... args) {
                    if (Token.this.mEventHandler != null) {
                        Message msg = Token.this.mEventHandler.obtainMessage(10012);
                        Token.this.mEventHandler.sendMessage(msg);
                    }
                }
            }).on("connect_error", new Listener() {
                public void call(Object... args) {
                    if (Token.this.mEventHandler != null) {
                        Message msg = Token.this.mEventHandler.obtainMessage(10011);
                        Token.this.mEventHandler.sendMessage(msg);
                    }
                }
            }).on("message", new Listener() {
                public void call(Object... args) {
                    try {
                        Token.this.handleMessage(cn.niusee.chat.sdk.Message.parseMessage((JSONObject) args[0]));
                    } catch (MessageErrorException var3) {
                        var3.printStackTrace();
                    }
                }
            });
            this.init = true;
        }
    }

4、登录的时候,设置一下token的一些监听:

public interface OnTokenCallback {
    void onConnected();//视频连接成功的回调
    void onConnectFail();
    void onConnectTimeOut();
    void onError(Error var1);//视频连接错误的回调
    void onDisconnect();//视频断开的回调
    void onSessionCreate(Session var1);//视频打洞成功的回调
}

5、下面是我的登录连接服务器的代码:

public void login(String username) {
        try {
            SingleChatClient.getInstance(getApplication()).setOnConnectListener(new SingleChatClient.OnConnectListener() {
                @Override
                public void onConnect() {
//                    loadDevices();
                    Log.e(TAG, "连接视频服务器成功");
                    state.setText("登录视频服务器成功!");
                }
                @Override
                public void onConnectFail(String reason) {
                    Log.e(TAG, "连接视频服务器失败");
                    state.setText("登录视频服务器失败!" + reason);
                }
                @Override
                public void onSessionCreate(Session session) {
                    Log.e(TAG, "来电者名称:" + session.callName);
                    mSession = session;
                    accept.setVisibility(View.VISIBLE);
                    requestPermission(new String[]{Manifest.permission.CAMERA}, "请求设备权限", new GrantedResult() {
                        @Override
                        public void onResult(boolean granted) {
                            if(granted){
                                createLocalStream();
                            }else {
                                Toast.makeText(MainActivity.this,"权限拒绝",Toast.LENGTH_SHORT).show();
                            }
                        }
                    });
                    mSession.setOnSessionCallback(new OnSessionCallback() {
                        @Override
                        public void onAccept() {
                            Toast.makeText(MainActivity.this, "视频接收", Toast.LENGTH_SHORT).show();
                        }
                        @Override
                        public void onReject() {
                            Toast.makeText(MainActivity.this, "拒绝通话", Toast.LENGTH_SHORT).show();
                        }
                        @Override
                        public void onConnect() {
                            Toast.makeText(MainActivity.this, "视频建立成功", Toast.LENGTH_SHORT).show();
                        }

                        @Override
                        public void onClose() {
                            Log.e(TAG, "onClose  我是被叫方");
                            hangup();
                        }
                        @Override
                        public void onRemote(Stream stream) {
                            Log.e(TAG, "onRemote  我是被叫方");
                            mRemoteStream = stream;
                           mSingleChatClient.getChatClient().playStream(stream, new Point(0, 0, 100, 100, false));
                            mSingleChatClient.getChatClient().playStream(mLocalStream, new Point(72, 72, 25, 25, false));
                        }
                        @Override
                        public void onPresence(Message message) {
                        }
                    });
                }
            });
//            SingleChatClient.getInstance(getApplication()).connect(UUID.randomUUID().toString(), WEB_RTC_URL);
            Log.e("MainActicvity===",username);
            SingleChatClient.getInstance(getApplication()).connect(username, WEB_RTC_URL);
        } catch (URISyntaxException e) {
            e.printStackTrace();
            Log.d(TAG, "连接失败");
        }
    }

注意:

onSessionCreate(Session session)这个回调是当检测到有视频请求来的时候才会触发,所以这里可以设置当触发该回调是显示一个接受按钮,一个拒绝按钮,session中携带了包括对方的userName,以及各种信息(上面所说的SDP描述信息等),这个时候通过session来设置OnSessionCallback的回调信息,public interface OnSessionCallback {
    void onAccept();//用户同意
    void onReject();//用户拒绝
    void onConnect();//连接成功
    void onClose();//连接掉开
    void onRemote(Stream var1);//当远程流开启的时候,就是对方把他的本地流传过来的时候
    void onPresence(Message var1);//消息通道过来的action消息,action是int型,远程控制的时候可以使用这个int型信令发送指令
}

注意:

 @Override
    public void onRemote(Stream stream) {
    Log.e(TAG, "onRemote  我是被叫方");
    mRemoteStream = stream;
    mSingleChatClient.getChatClient().playStream(stream, new Point(0, 0, 100, 100, false));
    mSingleChatClient.getChatClient().playStream(mLocalStream, new Point(72, 72, 25, 25, false));
} 

这里当执行远程流回调过来的时候,就可以显示对方的画面,并且刷新显示自己的本地流小窗口。(最重要的前提是,如果想让对方收到自己发送的本地流,必须要自己先调用playStream,这样对方才能通过onRemote回调收到你发送的本地流)

6、当A主动请求B开始视频聊天时,则需要手动调用:
 

private void call() {
        try {
            Log.e("MainActivity===","对方username:"+userName);
            mSession = mSingleChatClient.getToken().createSession(userName);
            //userName是指对方的用户名,并且这里要新建session对象,因为你是主动发起呼叫的,如果是被呼叫的则在onSessionCreate(Session session)回调中会拿到session对象的。(主叫方和被叫方不太一样)
        } catch (SessionExistException e) {
            e.printStackTrace();
        }
        requestPermission(new String[]{Manifest.permission.CAMERA}, "请求设备相机权限", new GrantedResult() {
            @Override
            public void onResult(boolean granted) {
                if(granted){//表示用户允许
                    createLocalStream();//权限允许之后,首先打开本地流,以及摄像头开启
                }else {//用户拒绝
                    Toast.makeText(MainActivity.this,"权限拒绝",Toast.LENGTH_SHORT).show();
                    return;
                }
            }
        });
        mSession.setOnSessionCallback(new OnSessionCallback() {
            @Override
            public void onAccept() {
                Toast.makeText(MainActivity.this, "通话建立成功", Toast.LENGTH_SHORT).show();
            }
            @Override
            public void onReject() {
                Toast.makeText(MainActivity.this, "对方拒绝了您的视频通话请求", Toast.LENGTH_SHORT).show();
            }
            @Override
            public void onConnect() {
            }
            @Override
            public void onClose() {
                mSingleChatClient.getToken().closeSession(userName);
                Log.e(TAG, "onClose  我是呼叫方");
                hangup();
                Toast.makeText(MainActivity.this, "对方已中断视频通话", Toast.LENGTH_SHORT).show();
            }
            @Override
            public void onRemote(Stream stream) {
                mStream = stream;
                Log.e(TAG, "onRemote  我是呼叫方");
                Toast.makeText(MainActivity.this, "视频建立成功", Toast.LENGTH_SHORT).show();
                mSingleChatClient.getChatClient().playStream(stream, new Point(0, 0, 100, 100, false));
                mSingleChatClient.getChatClient().playStream(mLocalStream, new Point(72, 72, 25, 25, false));
            }
            @Override
            public void onPresence(Message message) {
            }
        });
        if (mSession != null) {
            mSession.call();//主动开启呼叫对方
        }
    }

创建本地流:

private void createLocalStream() {
        if (mLocalStream == null) {
            try {
                String camerName = CameraDeviceUtil.getFrontDeviceName();
                if(camerName==null){
                    camerName = CameraDeviceUtil.getBackDeviceName();
                }
                mLocalStream = mSingleChatClient.getChatClient().createStream(camerName,
                        new Stream.VideoParameters(640, 480, 12, 25), new Stream.AudioParameters(true, false, true, true), null);
            } catch (StreamEmptyException | CameraNotFoundException e) {
                e.printStackTrace();
            }
        } else {
            mLocalStream.restart();
        }
        mSingleChatClient.getChatClient().playStream(mLocalStream, new Point(72, 72, 25, 25, false));
    }

总结:

以上只是简单的讲述原理以及sdk的用法(如果你想了解该sdk,可以在下面的评论中留言,我会发给你的),以后会重点讲解更细节的原理,但是有一点更为重要的难题,就是关于多网互通的问题,及A方为联通4G状态,B方为电信WIFI状态,或者B方为移动4G状态,这种不同网络运营商之间,互通可能存在问题,之前进行测试的时候,进行专门的抓包调试过,结果显示当A为联通4G的时候,向B(移动4G)发起视频的时候,A是一直处在打洞状态,但是一直打洞不通,并没有走转发(即互联网),理论上来说,走转发是最后一种情况,即前面的所有方式都不通,那么转发是肯定通的,但是转发要涉及到架设中转服务器,这个中转服务器需要大量的带宽才能够可以保证视频连接,所以目前的视频默认支持内网(同一wifi下),或者同一网络运营商之间的互通,至于其他的不同网络运营商之间的互通并不保证百分百互通,所以这个是个难题。

作者:艾神一不小心
原文 关于WebRTC视频Android端实现的原理解读 - 掘金

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

 文章来源地址https://www.toymoban.com/news/detail-850183.html

到了这里,关于关于WebRTC视频Android端实现的原理解读的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android WebRtc+SRS/ZLM视频通话(1):虚拟机安装Ubuntu

    来自奔三人员的焦虑日志 秉着没事找事的原则,这里直接从服务器安装开始说起,也当记录自己这一路以来的愚昧之举,由于没有物理服务器,这里以虚拟机为例进行操作。 下载并安装虚拟机软件。建议使用 VMware 或 VirtualBox,它们是免费的虚拟机软件,我这里下的是VMware,

    2024年02月03日
    浏览(46)
  • Android webrtc实战(一)录制本地视频并播放,附带详细的基础知识讲解

    目录 一、创建PeerConnectionFactory 初始化 构建对象 二、创建AudioDeviceModule AudioDeviceModule JavaAudioDeviceModule 构建对象 setAudioAttributes setAudioFormat setAudioSource 创建录制视频相关对象 创建VideoSource 创建VideoCapturer 创建VideoTrack 播放视频 切换前后置摄像头 别忘了申请权限 完整代码 本系列

    2024年02月16日
    浏览(56)
  • 视频直播新时代,低延时直播交互,Web,Android,WebRtc推流拉流测试

    直播现在已经深入了生活,学习,工作和娱乐方方面面,由于前些年的技术所限,传统rtmp,flv,m3u8 技术让直播快速启动项目产品,但也有很多不足,特别的交互式直播,一直是其中的痛点,延时较大(1-10秒不等) 让用户即想用,已不是太爽,随技术进步,Webrtc已经切入了视

    2024年01月19日
    浏览(58)
  • Android-WebRTC-实现摄像头显示

    EglBase是什么? 它提供了一个接口,用于在Android平台上创建和管理EGL(嵌入式系统图形库)上下文,以便在WebRTC中进行图像和视频的处理和渲染。 Capturer, Source, Track, Sink分别是什么? Capturer(采集器)是指用于采集音频或视频数据的设备或软件。它可以是麦克风、摄像头或其

    2024年02月15日
    浏览(53)
  • Android平台一对一音视频通话方案对比:WebRTC VS RTMP VS RTSP

    一对一音视频通话使用场景 一对一音视频通话都需要稳定、清晰和流畅,以确保良好的用户体验,常用的使用场景如下: 社交应用 :社交应用是一种常见的使用场景,用户可以通过音视频通话进行面对面的交流; 在线教育: 老师和学生可以通过音视频通话功能进行实时互

    2024年02月13日
    浏览(53)
  • WebRTC音视频原理

    WebRTC,网页即时通讯(Web Real-Time Communication),是直接在 Web 浏览器内驱动实时通信(语音、视频和任意数据)方法的API。它于2011年6月1日开源并在Google、Mozilla、Opera支持下被纳入万维网联盟的W3C推荐标准,并于 2011 年标准化,是谷歌开源的一款产品。 WebRTC 实现了浏览器快速

    2023年04月08日
    浏览(73)
  • Webrtc android的编译步骤

    首先下载depot_tools 按官方教程来 https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up 这步骤操作完后会产生这两个目录 首先运行下一些相关依赖 Generate projects using GN. Make sure your current working directory is src/ of your workspace. Then run: You can

    2024年02月09日
    浏览(45)
  • WebRTC 系列(二、本地通话,H5、Android、iOS)

    有了上一篇 WebRTC 简介的基础,我们知道了 WebRTC 的工作流程,接下来就是需要用代码去实现这个流程了。对于不同端,实现起来的难易程度可能略微不同(实践中我感觉 iOS 端是资料最少的),但是整体流程是一样的。 问:要把大象装冰箱,总共分几步?答:三步。 初始化

    2024年02月11日
    浏览(44)
  • WebRTC实战-第二章-使用WebRTC实现音视频通话

    、 什么是WebRTC|WebRTC入门到精通必看|快速学会音视频通话原理|WebRTC超全资料分享FFmpeg/rtmp/hls/rtsp/SRS WebRTC **WebRTC详细指南** http://www.vue5.com/webrtc/webrtc.html WEBRTC三种类型(Mesh、MCU 和 SFU)的多方通信架构 WebRTC API包括媒体捕获,音频和视频编码和解码,传输层和会话管理 。 假设

    2023年04月12日
    浏览(53)
  • SpringBoot+WebSocket+Vue+PeerJs实现WebRTC视频通话功能,Vue视频通话,web视频通话,webrtc视频通话

    博主正在担任一款电商app的全栈开发,其中涉及到一个 视频通话 功能。但是由于业务需求及成本考虑,不能使用第三方提供的SDK进行开发。所以博主选择使用PeerJs+WebSocket来实现这个功能。 WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定

    2024年02月08日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包