Android端实现1对1音视频实时通话

前言

在学习 WebRTC 的过程当中,学习的一个基本步骤是先经过 JS 学习 WebRTC的总体流程,在熟悉了总体流程以后,再学习其它端如何使用 WebRTC 进行互联互通。html

本文将讲解 Android 端是如何使用WebRTC的,至于 P2P 穿越、STUN/TURN/ICE、RTP/RTCP协议、DTLS等内容不作讲解。android

对这方面有兴趣的同窗能够看个人视频课「 WebRTC实时互动直播技术入门与实战web

申请权限

咱们要使用 WebRTC 进行音视频互动时须要申请访问硬件的权限,至少要申请如下三种权限:设计模式

  • Camera 权限
  • Record Audio 权限
  • Intenet 权限

在Android中,申请权限分为静态权限申请和动态权限申请,这对于作 Android 开发的同窗来讲已是习觉得常的事情了。下面咱们就看一下具体如何申请权限:安全

静态权限申请bash

在 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 的发展,对安全性要求愈来愈高。除了申请静态权限以外,还须要动态申请权限。代码以下:架构

void requestPermissions(String[] permissions, intrequestCode);

复制代码

实际上,对于权限这块的处理真正作细了要写很多代码,好在 Android 官方给咱们又提供了一个很是好用的库 EasyPermissions , 有了这个库咱们能够少写很多代码。使用 EasyPermissions 很是简单,在MainActivity中添加代码以下:异步

...

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);
}

...

复制代码

经过添加以上代码,就将权限申请好了,是否是很是简单?权限申请好了,咱们开始作第二步,看在 Android 下如何引入 WebRTC 库。

引入库

在咱们这个例子中要引入两个比较重要的库,第一个固然就是 WebRTC 库了,第二个是 socket.io 2库,用它来与信令服务器互联。

首先咱们看一下如何引入 WebRTC 库(我这里使用的是最新 Android Studio 3.3.2)。在 Module 级别的 build.gradle 文件中增长如下代码:

...
dependencies {
    ...
    implementation 'org.webrtc:google-webrtc:1.0.+'
    ...
}

复制代码

是否是很是简单?

接下来要引入 socket.io 2 库,用它来与咱们以前用 Nodejs 搭建的信令服务器进行对接。再加上前面用到的EasyPermissions库,因此真正的代码应写成下面的样子:

...
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 之旅了。

万物的开始

咱们都知道万物有个起源,咱们在开发 WebRTC 程序时也不例外,WebRTC程序的起源就是PeerConnectionFactory。这也是与使用 JS 开发 WebRTC 程序最大的不一样点之一,由于在 JS 中不须要使用 PeerConnectionFactory 来建立 PeerConnection 对象。

而在 Android/iOS 开发中,咱们使用的 WebRTC 中的大部分对象基本上都是经过 PeerConnectionFactory 建立出来的。下面这张图就清楚的表达了 PeerConnectionFactory 在 WebRTC 中的地位。

[

image

经过该图咱们能够知道,WebRTC中的核心对象 PeerConnection、LocalMediaStream、LocalVideoTrack、LocalAudioTrack都是经过 WebRTC 建立出来的。

PeerConnectionFactory的初始化与构造

在 WebRTC 中使用了大量的设计模式,对于 PeerConnectionFactory 也是如此。它自己就是工厂模式,而这个构造 PeerConnection 等核心对象的工厂又是经过 builder 模式构建出来的。

下面咱们就来看看如何构造 PeerConectionFactory。在咱们构造 PeerConnectionFactory 以前,首先要对其进行初始化,其代码以下:

PeerConnectionFactory.initialize(...);

复制代码

初始化以后,就能够经过 builder 模式来构造 PeerConnecitonFactory 对象了。

...

PeerConnectionFactory.Builder builder = 		
				PeerConnectionFactory.builder()
                	.setVideoEncoderFactory(encoderFactory)
                	.setVideoDecoderFactory(decoderFactory);

 ...

 return builder.createPeerConnectionFactory();

复制代码

经过上面的代码,你们也就可以理解为何 WebRTC 要使用 buider 模式来构造 PeerConnectionFactory 了吧?主要是方便调整建造 PeerConnectionFactory的组件,如编码器、解码器等。

从另一个角度咱们也能够了解到,要更换WebRTC引警的编解码器该从哪里设置了哈!

音视频数据源

有了PeerConnectionFactory对象,咱们就能够建立数据源了。实际上,数据源是 WebRTC 对音视频数据的一种抽象,表式数据能够从这里获取。

使用过 JS WebRTC API的同窗都很是清楚,在 JS中 VideoTrack 和 AudioTrack 就是数据源。而在 Android 开发中咱们能够知道 Video/AudioTrack 就是 Video/AudioSouce的封装,能够认为他们是等同的。

建立数据源的方式以下:

...
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);

...													

复制代码

数据源只是对数据的一种抽象,它是从哪里获取的数据呢?对于音频来讲,在建立 AudioSource时,就开始从音频设备捕获数据了。对于视频来讲咱们能够指定采集视频数据的设备,而后使用观察者模式从指定设备中获取数据。

接下来咱们就来看一下如何指定视频设备。

视频采集

在 Android 系统下有两种 Camera,一种称为 Camera1, 是一种比较老的采集视频数据的方式,别一种称为 Camera2, 是一种新的采集视频的方法。它们之间的最大区别是 Camera1使用同步方式调用API,Camera2使用异步方式,因此Camera2更高效。

咱们看一下 WebRTC 是如何指定具体的 Camera 的:

private VideoCapturer createVideoCapturer() {
        if (Camera2Enumerator.isSupported(this)) {
            return createCameraCapturer(new Camera2Enumerator(this));
        } else {
            return createCameraCapturer(new Camera1Enumerator(true));
        }
}

private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
        final String[] deviceNames = enumerator.getDeviceNames();

        // First, try to find front facing camera
        Log.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
        Log.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;
}

复制代码

上面代码的逻辑也比较简单:

  • 首先看 Android 设备是否支持 Camera2.
  • 若是支持就使用 Camera2, 若是不支持就使用 Camera1.
  • 在获到到具体的设备后,再看其是否有前置摄像头,若是有就使用
  • 若是没有有效的前置摄像头,则选一个非前置摄像头。

经过上面的方法就能够拿到使用的摄像头了,而后将摄像头与视频源链接起来,这样从摄像头获取的数据就源源不断的送到 VideoTrack 里了。

下面咱们来看看 VideoCapture 是如何与 VideoSource 关联到一块儿的:

...

mSurfaceTextureHelper = 
			SurfaceTextureHelper.create("CaptureThread",
										mRootEglBase.getEglBaseContext());

mVideoCapturer.initialize(mSurfaceTextureHelper,
 						  getApplicationContext(), 
 						  videoSource.getCapturerObserver());

...

mVideoTrack.setEnabled(true);
...

复制代码

上面的代码中,在初始化 VideoCaptuer 的时候,能够过观察者模式将 VideoCapture 与 VideoSource 联接到了一块儿。由于 VideoTrack 是 VideoSouce 的一层封装,因此此时咱们开启 VideoTrack 后就能够拿到视频数据了。

固然,最后还要调用一下 VideoCaptuer 对象的 startCapture 方法真正的打开摄像头,这样 Camera 才会真正的开始工做哈,代码以下:

@Override
protected void onResume() {
    super.onResume();
    mVideoCapturer.startCapture(VIDEO_RESOLUTION_WIDTH, 
    							VIDEO_RESOLUTION_HEIGHT, 
    							VIDEO_FPS);
}

复制代码

拿到了视频数据后,咱们如何将它展现出来呢?

渲染视频

在 Android 下 WebRTC 使用OpenGL ES 进行视频渲染,用于展现视频的控件是 WebRTC 对 Android 系统控件 SurfaceView 的封装。

WebRTC 封装后的 SurfaceView 类为 org.webrtc.SurfaceViewRenderer。在界面定义中应该定义两个SurfaceViewRenderer,一个用于显示本地视频,另外一个用于显示远端视频。

其定义以下:

...

<org.webrtc.SurfaceViewRenderer
        android:id="@+id/LocalSurfaceView"
        android: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 /* enabled */);

...

复制代码

其含义是:

  • 使用 OpenGL ES 的上下文初始化 View。
  • 设置图像的拉伸比例。
  • 设置图像显示时反转,否则视频显示的内容与实际内容正好相反。
  • 是否打开便件进行拉伸。

经过上面的设置,咱们的 view 就设置好了,对于远端的 Veiw 与本地 View 的设置是同样的,我这里就再也不赘述了。

接下来将从摄像头采集的数据设置到该view里就能够显示了。设置很是的简单,代码以下:

...
mVideoTrack.addSink(mLocalSurfaceView);
...

复制代码

对于远端来讲与本地视频的渲染显示是相似的,只不过数据源是从网络获取的。

经过以上讲解,你们应该对 WebRTC 如何采集数据、如何渲染数据有了基本的认识。下面咱们再看来下远端的数据是如何来的。

建立 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 略有区别,它是经过观察者模式来监听事件的。你们这点必定要注意!

双方都建立好 PeerConnecton 对象后,就会进行媒体协商,协商完成后,数据在底层就开始传输了。

信令驱动

在整个 WebRTC 双方交互的过程当中,其业务逻辑的核心是信令, 全部的模块都是经过信令串联起来的。

以 PeerConnection 对象的建立为例,该在何时建立 PeerConnection 对象呢?最好的时机固然是在用户加入房间以后了 。

下面咱们就来看一下,对于两人通信的状况,信令该如何设计。在咱们这个例子中,能够将信令分红两大类。第一类为客户端命令;第二类为服务端命令;

客户端命令有:

  • join: 用户加入房间
  • leave: 用户离开房间
  • message: 端到端命令(offer、answer、candidate)

服务端命令:

  • joined: 用户已加入
  • leaved: 用户已离开
  • other_joined:其它用户已加入
  • bye: 其它用户已离开
  • full: 房间已满

经过以上几条信令就能够实现一对一实时互动的要求,是否是很是的简单?

在本例子中咱们仍然是经过socket.io与以前搭建的信令服备器互联的。因为 socket.io 2 是跨平台的,因此不管是在 js 中,仍是在 Android 中,咱们均可以使用其客户端与服务器相联,很是的方便。

下面再来看一下,收到不一样信令后,客户端的状态变化:

[

image

客户端一开始的时候处于 Init/Leave 状态。当发送 join 消息,并收到服务端的 joined 后,其状态变为 joined。

此时,若是第二个用户加入到房间,则客户端的状态变为了 joined_conn, 也就是说此时双方能够进行实时互动了。

若是此时,该用户离开,则其状态就变成了 初始化状态。其它 case 你们能够根据上面的图自行理解了。

小结

本文首先介绍了在 Android 中使用 WebRTC 要需申请的权限,以及如何引入 WebRTC 库。而后从如何采集音视频数据、如何渲染、如何与对方创建链接等几个方面向你们详细介绍了如何在 Android 系统下开发一套 1对1的直播系统。

本文介绍的知识与我以前所写的经过 《Nodejs 搭建 WebRTC 信令服务器》完整的构成了一套 1对1直播系统。但愿经过本文的学习,同窗们能够快速的撑握 WebRTC 的使用,并根据本身的须要构建本身的直播系统。

谢谢!

参考

WebRTC实时互动直播技术入门与实战

相关文章
相关标签/搜索