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實時互動直播技術入門與實戰

相關文章
相關標籤/搜索