基于WebRtc实现安卓视频一对一聊天
WebRtc是谷歌2010年收购GlobalIPSolutions公司而获得的一项实时语音对话或视频对话的技术。之后谷歌将其开源,有很好的跨平台性。官方网址:https://webrtc.org/
最近由于公司项目需求,刚刚接触webrtc,由于国内这方面的资料少之又少,学习起来也有点困难。这一个月来对webrtc也稍微有点了解吧,特此写个博客纪念下,结合自己写的小Demo给刚入坑的新人一点建议。
基本流程
使用webrtc###
1. Maven
<dependency> <groupId>org.webrtc</groupId> <artifactId>google-webrtc</artifactId> <version>1.0.20723</version> <type>pom</type> </dependency>
注意:安卓6.0以上请自行处理CAMERA、RECORD_AUDIO、WRITE_EXTERNAL_STORAGE等危险权限。
介绍Webrtc一些关键类###
1. PeerConnectionFactory
webrtc核心类,用于创建其他关键类,稍后在做介绍。在使用PeerConnectionFactory之前,请先初始化,类似这样。
PeerConnectionFactory.initialize( PeerConnectionFactory.InitializationOptions.builder(getApplicationContext()) .setEnableVideoHwAcceleration(true) .createInitializationOptions()); PeerConnectionFactory.InitializationOptions作为PeerConnectionFactory初始化传入参数,该类采用构造模式,可以对初始化参数进行一些配置。初始化之后就可以创建PeerConnectionFactory实例了。 PeerConnectionFactory.Options options = new PeerConnectionFactory.Options(); mPeerConnectionFactory = new PeerConnectionFactory(options);
2. VideoCapturer
视频捕捉器的一个顶级接口,它的的子接口为CameraVideoCapturer,封装了安卓相机的使用方法,使用它们可以轻松的获取设备相机数据,切换摄像头,获取摄像头数量等。该对象的创建如下。
private CameraVideoCapturer createVideoCapture(Context context) { CameraEnumerator enumerator; if (Camera2Enumerator.isSupported(context)) { enumerator = new Camera2Enumerator(context); } else { enumerator = new Camera1Enumerator(true); final String[] deviceNames = enumerator.getDeviceNames(); for (String deviceName : deviceNames) { if (enumerator.isFrontFacing(deviceName)) { CameraVideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null); if (videoCapturer != null) { return videoCapturer; } } } for (String deviceName : deviceNames) { if (!enumerator.isFrontFacing(deviceName)) { CameraVideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null); if (videoCapturer != null) { return videoCapturer; } } } return null; }
3. VideoSource/VideoTrack
VideoSource为视频源,通过核心类PeerConnectionFactory创建,VideoTrack是对VideoSource的包装,可以方便的将视频源在本地进行播放,添加到MediaStream中进行网络传输。
CameraVideoCapturer mVideoCapturer = createVideoCapture(this); VideoSource videoSource = mPeerConnectionFactory.createVideoSource(mVideoCapturer); VideoTrack mVideoTrack = mPeerConnectionFactory.createVideoTrack("videtrack", videoSource);
4. AudioSource/AudioTrack
AudioSource/AudioTrack和上面的VideoSource/VideoTrack类似,从名字上面就知道是对音频的获取和处理了,AudioSource的创建很简单,直接用PeerConnectionFactory创建就可以了。
AudioSource audioSource = mPeerConnectionFactory.createAudioSource(new MediaConstraints()); AudioTrack mAudioTrack = mPeerConnectionFactory.createAudioTrack("audiotrack", audioSource);
AudioSource 创建的时候需要传入MediaConstraints这个对象的实例,其用于对媒体的一些约束限制,创建的时候可以直接使用默认的。如果你想自己定义,就需要自己填入相应的键值对了。
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"));
5. MediaStream
音视频的媒体流,通过PeerConnectionFactory创建,用于PeerConnection通过网络传输发送给另一方。在媒体流传输之前,需要将前面获取的VideoTrack和AudioTrack添加进去。
MediaStream mMediaStream = mPeerConnectionFactory.createLocalMediaStream("localstream"); mMediaStream.addTrack(mVideoTrack); mMediaStream.addTrack(mAudioTrack);
6. PeerConnection
用于p2p网络传输,双方信令的交换。webrtc是基于p2p的,因此在双方通信之前需要服务器帮助传递信令,并且需要添加STUN和TURN服务器网络穿透。在双方通道打开之后就可以将媒体流发送给另一方了。下面是PeerConnection的创建,并将媒体流添加到其中用于网络传输。
PeerConnection peerConnection = mPeerConnectionFactory.createPeerConnection(iceServers, pcConstraints, this); peerConnection.addStream(mMediaStream);
参数说明如下:
iceServers连接到外网和网络穿用到的,添加STUN和TURN服务器可以帮助你连接。
constraints是一个MediaConstrains的实例。应该包含offerToRecieveAudio和offerToRecieveVideo。
observer是一个PeerConnectionObserver的实例,对PeerConnection的一些连接状态的监听。
信令交换###
在实现媒体流的网络传输之前,需要交换双方信令,将连接通道打开,下面介绍一下webrtc的信令交换机制。
A向B发起建立连接的请求,通过PeerConnection的createOffer()方法创建一个offer信令,创建成功后调用SdpObserver监听中的onCreateSuccess()回调函数,调用PeerConnection的setLocalDescription方法设置自己的offer信令,同时将offer信令通过服务器转发给B。
peerConnection.createOffer(sdpObserver, sdpMediaConstraints); //SdpObserver @Override public void onCreateSuccess(SessionDescription sessionDescription) { peerConnection.setLocalDescription(this, sessionDescription); JSONObject jsonObject = new JSONObject(); try { jsonObject.put("type", sessionDescription.type.canonicalForm()); jsonObject.put("description", sessionDescription.description); } catch (JSONException e) { e.printStackTrace(); } mSocket.emit("SdpInfo", jsonObject.toString()); }
B收到A的offer信令后,创建一个SessionDescription(SDP描述符包含媒体信息,如分辨率、编解码能力等)对象将A的offer信令解析出来,并调用PeerConnection的setRemoteDescription方法设置A的SDP描述符,然后B通过PeerConnection的createAnswer()方法创建一个answer信令,创建成功后调用SdpObserver监听中的onCreateSuccess()回调函数,调用PeerConnection的setLocalDescription方法设置自己的answer信令,同时将answer信令通过服务器转发给A。
@Override public void call(Object... args) { if (mPeer == null) { mPeer = new Peer(); } try { JSONObject jsonObject = new JSONObject(args[0].toString()); SessionDescription description = new SessionDescription (SessionDescription.Type.fromCanonicalForm(jsonObject.getString("type")), jsonObject.getString("description")); mPeer.peerConnection.setRemoteDescription(mPeer, description); if (!isOffer) { mPeer.peerConnection.createAnswer(mPeer, sdpConstraints); } } catch (JSONException e) { e.printStackTrace(); } }
A收到B的answer信令后,解析B的answer信令再调用PeerConnection的setRemoteDescription方法设置B的SDP描述符。这样双方的信令交换就算完成了。
在非局域网下,信令的交换还需要借助于STUN和TURN服务器网络穿透,创建PeerConnection的时候需要传入iceServers这个参数,这里面存放的就是穿透地址变换的服务器地址了,类似的,Ice穿透也需要信令的交换,过程大致如下。
当A和B创建好配置了iceServers的PeerConnection实例后,当网络候可用时,回调PeerConnection.Observer的onIceCandidate()函数,在回调函数里面将IceCandidate对象发送给对方。
在收到对方的IceCandidate信令后,解析出来并用PeerConnection的addIceCandidate()方法设置对方的信令。
@Override public void onIceCandidate(IceCandidate iceCandidate) { try { JSONObject jsonObject = new JSONObject(); jsonObject.put("label", iceCandidate.sdpMLineIndex); jsonObject.put("id", iceCandidate.sdpMid); jsonObject.put("candidate", iceCandidate.sdp); mSocket.emit("IceInfo", jsonObject.toString()); } catch (JSONException e) { e.printStackTrace(); } } @Override public void call(Object... args) { try { JSONObject jsonObject = new JSONObject(args[0].toString()); IceCandidate candidate = null; candidate = new IceCandidate( jsonObject.getString("id"), jsonObject.getInt("label"), jsonObject.getString("candidate") ); mPeer.peerConnection.addIceCandidate(candidate); } catch (JSONException e) { e.printStackTrace(); } }
现在,双方的连接通道就完全打开了,PeerConnection.Observer就会调用onAddStream()响应函数,里面包含对方的媒体流Mediastream,将媒体流播放就可以了。
@Override public void onAddStream(MediaStream mediaStream) { remoteVideoTrack = mediaStream.videoTracks.get(0); remoteVideoTrack.addRenderer(new VideoRenderer(remoteView)); }
播放媒体流###
媒体流的播放需要用到webrtc封装的控件SurfaceViewRenderer,它继承于安卓的SurfaceView。
在播放之前,需要对该控件初始化和配置一些属性。
localView = findViewById(R.id.localVideoView); //创建EglBase对象 mEglBase = EglBase.create(); //初始化localView localView.init(mEglBase.getEglBaseContext(), null); localView.setKeepScreenOn(true); localView.setMirror(true); localView.setZOrderMediaOverlay(true); localView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT); localView.setEnableHardwareScaler(false);
注意:SurfaceViewRenderer的初始化需要在主线程
初始化完成后,将localView包装成VideoRenderer对象并添加到VideoTrack中就可以进行播放了。
mVideoTrack.addRenderer(new VideoRenderer(localView));
Demo###
demo里面包含了用IoSocket简单写的java服务器(webrtc文件夹),里面的地址改成自己电脑的本机ip4地址即可测试。
demo地址:demo传送门
附上demo运行效果图
原文链接:https://blog.csdn.net/qq_35054800/article/details/78647545