WebRTC 在 Android 端实现一对一通信
在 Android 端,我们将按以下几个步骤实现 WebRTC 一对一通信:
- 申请权限
- 引入 WebRTC 库
- 构造 PeerConnectionFactory
- 创建音视频源
- 视频收罗
- 视频渲染
- 创建 PeerConnection
- 建立信令系统
申请权限
至少需要申请三种权限:
- CAMERA 权限:用于收罗视频数据
- RECORD_AUDIO 权限:用于收罗音频数据
- INTERNET 权限:用于通过网卡传输媒体数据
在Android中,申请权限分为静态权限申请和动态权限申请。
申请静态权限
在 Android 项目中的 AndroidManifest.xml 中增长以下代码:
- <uses-feature android:name="android.hardware.camera" />
- <uses-feature android:glEsVersion="0x00020000" android:required="true" />
- <uses-permission android:name="android.permission.CAMERA" />
- <uses-permission android:name="android.permission.RECORD_AUDIO" />
- <uses-permission android:name="android.permission.INTENET" />
复制代码 申请动态权限
随着 Android 的发展,对安全性要求越来越高。除了申请静态权限之外,还需要动态申请权限。
API:
- void requestPermissions(String[] permissions, intrequestCode);
复制代码 实际上,对于权限这块的处理真正做细了要写不少代码,好在 Android 官方给我们又提供了一个非常好用的库 EasyPermissions,有了这个库我们可以少写不少代码。利用 EasyPermissions 非常简单,在MainActivity 文件的 onCreate() 方法中调用 requestPermissions() 方法即可,固然还要实现 onRequestPermissionsResult() 回调方法。
- protected void onCreate (Bundle savedInstanceState)
- {
- String[] perms = {
- Manifest.permission.CAMERA,
- Manifest.permission.RECORD_AUDIO
- };
-
- if (!EasyPermissions.hasPermissions(this, perms)) {
- EasyPermissions.requestPermissions(
- this,
- "Need permissions for camera & microphone",
- 0,
- perms);
- }
- }
- @Override
- public void onRequestPermissionsResult(
- int requestCode,
- String[] permissions,
- int[] grantResults)
- {
- super.onRequestPermissionsResult
- requestCode,
- permissions,
- grantResults);
-
- EasyPermissions.onRequestPermissionsResult(
- requestCode,
- permissions,
- grantResults,
- this);
- }
复制代码 引入 WebRTC 库
在 Module 级别的 build.gradle 文件中增长以下代码:
- dependencies {
- implementation 'io.socket:socket.io-client:1.0.0'
- implementation 'org.webrtc:google-webrtc:1.0.+'
- implementation 'pub.devrel:easypermissions:1.1.3'
- }
复制代码 第一个是 WebRTC 库,第二个是 socket.io 库,用它来与信令服务器互联,第三个是前面用到的 EasyPermissions 库。
构造 PeerConnectionFactory
在 WebRTC 中利用了大量的计划模式,对于 PeerConnectionFactory 也是云云。它自己就是工厂模式,而这个构造 PeerConnection 等焦点对象的工厂又是通过构建者模式构建出来的。
在我们构造 PeerConnectionFactory 之前,起首要对其举行初始化,然后可以通过构建者模式来构造 PeerConnecitonFactory 对象了。
- PeerConnectionFactory.initialize(...);
- PeerConnectionFactory.Builder builder =
- PeerConnectionFactory.builder()
- .setOptions(options)
- .setAudioDeviceModule(adm)
- .setVideoEncoderFactory(encoderFactory)
- .setVideoDecoderFactory(decoderFactory);
- return builder.createPeerConnectionFactory();
复制代码 通过上面的代码,大家也就能够明白为什么 WebRTC 要利用 buider 模式来构造 PeerConnectionFactory 了吧?主要是方便调整建造 PeerConnectionFactory 的组件,如编码器、解码器等。
从另外一个角度我们也可以了解到,要更换WebRTC引擎的编解码器该从那里设置了。
创建音视频源
有了PeerConnectionFactory对象,我们利用它就可以创建数据源了。
实际上,数据源是 WebRTC 对音视频数据的一种抽象,主要是让上层逻辑和底层的音视频设备之间解耦。数据源可以从不同的音视频设备中获取数据,并将数据输出给上层的 Track。
创建数据源的方式如下:
- VideoSource videoSource = mPeerConnectionFactory
- .createVideoSource(false);
- mVideoTrack = mPeerConnectionFactory
- .createVideoTrack(VIDEO_TRACK_ID, videoSource);
- AudioSource audioSource = mPeerConnectionFactory
- .createAudioSource(new MediaConstraints());
- mAudioTrack = mPeerConnectionFactory
- .createAudioTrack(AUDIO_TRACK_ID, audioSource);
复制代码 先创建 VideoSource 和 AudioSource,再绑定到对应的 VideoTrack 和 AudioTrack 上,相当于为 VideoSource/AudioSource 指定了输出。
对于音频来说,在创建 AudioSource 时,就开始从默认的音频设备捕获数据了;对于视频来说,我们还需要指定收罗视频数据的设备,然后利用观察者模式从指定设备中获取数据。
视频收罗
在 Android 系统下有两种 Camera,一种称为 Camera1,是一种比较老的收罗视频数据的方式,另一种称为 Camera2,是一种新的收罗视频的方法。它们之间的最大区别是 Camera1利用同步方式调用API,Camera2利用异步方式,以是Camera2更高效。
我们看一下 WebRTC 是怎样选择控制摄像头的系统的:
- private VideoCapturer createVideoCapturer()
- {
- if (Camera2Enumerator.isSupported(this)) {
- return createCameraCapturer(new Camera2Enumerator(this));
- } else {
- return createCameraCapturer(new Camera1Enumerator(true));
- }
- }
复制代码 逻辑很简单,看 Android 设备是否支持 Camera2,如果支持就利用 Camera2,否则利用 Camera1。
一般情况下,移动端有两个摄像头,默认利用前置摄像头。通过 CameraEnumerator 类,我们可以获取 Android 系统上全部的摄像头,还能通过 isFrontFacing() 方法检测出该摄像头是前置的还是后置的。
- private VideoCapturer createCameraCapturer(CameraEnumerator enumerator)
- {
- final String[] deviceNames = enumerator.getDeviceNames();
- // First, try to find front facing camera
- Logging.d(TAG, "Looking for front facing cameras.");
- for (String deviceName : deviceNames)
- {
- if (enumerator.isFrontFacing(deviceName))
- {
- Logging.d(TAG, "Creating front facing camera capturer.");
- VideoCapturer videoCapturer =
- enumerator.createCapturer(deviceName, null);
- if (videoCapturer != null)
- {
- return videoCapturer;
- }
- }
- }
- // Front facing camera not found, try something else
- Logging.d(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;
- }
复制代码 在获到到具体的设备后,再看其是否有前置摄像头,如果有就利用第一个前置摄像头作为默认摄像头,否则利用第一个后置摄像头作为默认摄像头。
现在 VideoSource 与 VideoTrack 已经关联在一起了,且 VideoCapturer 也创建好了。接下来只需要将 VideoCapturer 与 VideoSource 再次关联在一起,VideoTrack 就可以源源不停地从设备上获取数据了。
- mSurfaceTextureHelper =
- SurfaceTextureHelper.create("CaptureThread",
- mRootEglBase.getEglBaseContext());
- mVideoCapturer.initialize(mSurfaceTextureHelper,
- getApplicationContext(),
- videoSource.getCapturerObserver());
- mVideoTrack.setEnabled(true);
复制代码 VideoCapturer 与 VideoSource 通过 VideoCapturer 的 initialize() 函数关联在一起。在 Android 系统中,必须为 Camera 设置一个 Surface 才气开启摄像头,并从中获取数据。CapturerObserver 是 VideoCaptuer 的观察者,videoSource 可以通过它从 VideoCaptuer 中获取数据。
固然,最后还要调用一下 VideoCaptuer 对象的 startCapture 方法真正的打开摄像头,如许 Camera 才会真正的开始工作:
- @Override
- protected void onResume()
- {
- super.onResume();
- mVideoCapturer.startCapture(
- VIDEO_RESOLUTION_WIDTH,
- VIDEO_RESOLUTION_HEIGHT,
- VIDEO_FPS);
- }
复制代码 收罗的分辨率要符合16:9/9:16/4:3/3:4,帧率通常设置为15帧。
视频渲染
Android 系统下,WebRTC 利用 OpenGL ES 举行视频渲染。根本步骤为:
- 先将视频从主内存复制到 GPU 上。
- 通过 OpenGL ES 管道渲染到 GPU 的内存中。
- 输出给显卡并终极显示在手机屏幕上。
用于展示视频的控件是 WebRTC 对 Android 系统控件 SurfaceView 的封装。WebRTC 封装后的 SurfaceView 类为 org.webrtc.SurfaceViewRenderer。
在界面界说中应该界说两个SurfaceViewRenderer,一个用于显示本地视频,另一个用于显示远端视频。
- <org.webrtc.SurfaceViewRenderer
- android:id="@+id/LocalSurfaceView"
- ndroid:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center" />
- <org.webrtc.SurfaceViewRenderer
- android:id="@+id/RemoteSurfaceView"
- android:layout_width="120dp"
- android:layout_height="160dp"
- android:layout_gravity="top|end"
- android:layout_margin="16dp"/>
复制代码 通过上面的代码我们就将显示视频的 View 界说好了。光界说好这两个View 还不敷,还要对它做进一步的设置:
- mLocalSurfaceView.init(mRootEglBase.getEglBaseContext(), null);
- mLocalSurfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
- mLocalSurfaceView.setMirror(true);
- mLocalSurfaceView.setEnableHardwareScaler(false);
复制代码 其含义是:
- 利用 OpenGL ES 的上下文初始化 View。
- 设置图像的拉伸比例,SCALE_ASPECT_FILL 表示将视频按比例添补到 View 中。
- 设置图像显示时按纵轴反转,不然视频显示的内容与实际内容恰恰相反。
- 是否打开便件举行拉伸,设置为不打开。
通过上面的设置,我们的 View 就设置好了,对于远端的 View 与本地 View 的设置是一样的。
接下来将从摄像头收罗的数据设置到该 View 里就可以显示了:
- mVideoTrack.addSink(mLocalSurfaceView);
复制代码 上面代码的含义是将 mLocalSurfaceView 设置为 VideoTrack 的输出。
WebRTC 通过 Capturer 收罗到视频数据后,会交给 VideoSource,VideoSource 作为 VideoTrack 的源又会将数据转发给 VideoTrack。而将 View 设置为 VideoTrack 的输出后,终极视频就会在 View 中展示。
对于远端来说与本地视频的渲染显示是类似的,只不过数据源是从网络获取的。
创建 PeerConnection
要想从远端获取数据,我们就必须创建 PeerConnection 对象。该对象的用处就是与远端建立联接,并终极为两边通讯提供网络通道。
我们来看下怎样创建 PeerConnecion 对象。
- PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);
- PeerConnection connection = mPeerConnectionFactory
- .createPeerConnection(rtcConfig, mPeerConnectionObserver);
-
- connection.addTrack(mVideoTrack, mediaStreamLabels);
- connection.addTrack(mAudioTrack, mediaStreamLabels);
复制代码 PeerConnection 对象的创建还是要利用我们之前讲过的 PeerConnectionFactory 来创建。WebRTC 在建立连接时利用 ICE 架构,一些参数需要在创建 PeerConnection 时设置进去。另外,当 PeerConnection 对象创建好后,我们应该将本地的音视频轨添加进去,如许 WebRTC 才气帮我们生成包含相应媒体信息的 SDP,以便于后面做媒体本事协商利用。通过上面的方式,我们就将 PeerConnection 对象创建好了。
与 JS 中的 PeerConnection 对象一样,当其创建好之后,可以监听一些我们感兴趣有事件了,如收到 Candidate 事件时,我们要与对方举行交换。PeerConnection 事件的监听与 JS 还是有一点差异的。在 JS 中,监听 PeerConnection的相关事件非常直接,直接实现peerconnection.onXXX就好了。而 Android 中的方式与 JS 略有区别,它是通过观察者模式来监听事件的。
- mPeerConnectionObserver = new PeerConnection.Observer() {
- // 与 onicecandidate 方法对应
- @Override
- public void onIceCandidate(IceCandidate iceCandidate) {
- ...
- }
- // 与 ontrack 方法对应
- @Override
- public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
- ...
- }
- }
复制代码 建立信令系统
为了与 JS 端互通,Android 端必须利用与 JS 端一样的信令系统。这套系统是由信令、信令状态机构成的,与信令服务器的互联仍由 socket.io 库实现。
两边都创建好 PeerConnecton 对象后,就会举行媒体协商、交换 Candidate 等,都完成后,数据在底层就开始传输了。
参考
- https://webrtc.org.cn/20190419_tutorial3_webrtc_android/
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |