亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

WebRTC入門教程(三) | Android 端如何使用 WebRTC

番茄西紅柿 / 3211人閱讀

摘要:下面我們就看一下具體如何申請(qǐng)權(quán)限靜態(tài)權(quán)限申請(qǐng)?jiān)陧?xiàng)目中的中增加以下代碼動(dòng)態(tài)權(quán)限申請(qǐng)隨著的發(fā)展,對(duì)安全性要求越來(lái)越高。其定義如下通過(guò)上面的代碼我們就將顯示視頻的定義好了。當(dāng)發(fā)送消息,并收到服務(wù)端的后,其狀態(tài)變?yōu)椤?/p>

作者:李超,如遇到相關(guān)問(wèn)題,可以點(diǎn)擊這里與作者直接交流。

前言

在學(xué)習(xí) WebRTC 的過(guò)程中,學(xué)習(xí)的一個(gè)基本步驟是先通過(guò) JS 學(xué)習(xí) WebRTC的整體流程,在熟悉了整體流程之后,再學(xué)習(xí)其它端如何使用 WebRTC 進(jìn)行互聯(lián)互通。

我們已經(jīng)在前面分享了信令服務(wù)器的搭建和 STUN/TURN服務(wù)器的搭建:

rtcdeveloper.com/t/topic/133…

rtcdeveloper.com/t/topic/137…

本文將講解 Android 端是如何使用WebRTC的,至于 P2P 穿越、STUN/TURN/ICE、RTP/RTCP協(xié)議、DTLS等內(nèi)容不做講解。

對(duì)這方面有興趣的同學(xué)可以多帶帶再聯(lián)系我。

申請(qǐng)權(quán)限

我們要使用 WebRTC 進(jìn)行音視頻互動(dòng)時(shí)需要申請(qǐng)?jiān)L問(wèn)硬件的權(quán)限,至少要申請(qǐng)以下三種權(quán)限:

Camera 權(quán)限

Record Audio 權(quán)限

Intenet 權(quán)限

在Android中,申請(qǐng)權(quán)限分為靜態(tài)權(quán)限申請(qǐng)和動(dòng)態(tài)權(quán)限申請(qǐng),這對(duì)于做 Android 開(kāi)發(fā)的同學(xué)來(lái)說(shuō)已經(jīng)是習(xí)以為常的事情了。下面我們就看一下具體如何申請(qǐng)權(quán)限:

靜態(tài)權(quán)限申請(qǐng)

在 Android 項(xiàng)目中的 AndroidManifest.xml 中增加以下代碼:

...








...

動(dòng)態(tài)權(quán)限申請(qǐng)

隨著 Android 的發(fā)展,對(duì)安全性要求越來(lái)越高。除了申請(qǐng)靜態(tài)權(quán)限之外,還需要?jiǎng)討B(tài)申請(qǐng)權(quán)限。代碼如下:

void requestPermissions(String[] permissions, intrequestCode);

實(shí)際上,對(duì)于權(quán)限這塊的處理真正做細(xì)了要寫不少代碼,好在 Android 官方給我們又提供了一個(gè)非常好用的庫(kù) EasyPermissions , 有了這個(gè)庫(kù)我們可以少寫不少代碼。使用 EasyPermissions 非常簡(jiǎn)單,在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);
}

...

通過(guò)添加以上代碼,就將權(quán)限申請(qǐng)好了,是不是非常簡(jiǎn)單?權(quán)限申請(qǐng)好了,我們開(kāi)始做第二步,看在 Android 下如何引入 WebRTC 庫(kù)。

引入庫(kù)

在我們這個(gè)例子中要引入兩個(gè)比較重要的庫(kù),第一個(gè)當(dāng)然就是 WebRTC 庫(kù)了,第二個(gè)是 socket.io 庫(kù),用它來(lái)與信令服務(wù)器互聯(lián)。

首先我們看一下如何引入 WebRTC 庫(kù)(我這里使用的是最新 Android Studio 3.3.2)。在 Module 級(jí)別的 build.gradle 文件中增加以下代碼:

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

是不是非常簡(jiǎn)單?

接下來(lái)要引入 socket.io 庫(kù),用它來(lái)與我們之前用 Nodejs 搭建的信令服務(wù)器進(jìn)行對(duì)接。再加上前面用到的EasyPermissions庫(kù),所以真正的代碼應(yīng)寫成下面的樣子:

...
dependencies {
    ...
    implementation io.socket:socket.io-client:1.0.0
    implementation org.webrtc:google-webrtc:1.0.+
    implementation pub.devrel:easypermissions:1.1.3
}

通過(guò)上面的方式我們就將需要引入的庫(kù)全部引入進(jìn)來(lái)了。下面就可以開(kāi)始真的 WebRTC 之旅了。

萬(wàn)物的開(kāi)始

我們都知道萬(wàn)物有個(gè)起源,我們?cè)陂_(kāi)發(fā) WebRTC 程序時(shí)也不例外,WebRTC程序的起源就是PeerConnectionFactory。這也是與使用 JS 開(kāi)發(fā) WebRTC 程序最大的不同點(diǎn)之一,因?yàn)樵?JS 中不需要使用 PeerConnectionFactory 來(lái)創(chuàng)建 PeerConnection 對(duì)象。

而在 Android/iOS 開(kāi)發(fā)中,我們使用的 WebRTC 中的大部分對(duì)象基本上都是通過(guò) PeerConnectionFactory 創(chuàng)建出來(lái)的。下面這張圖就清楚的表達(dá)了 PeerConnectionFactory 在 WebRTC 中的地位。

通過(guò)該圖我們可以知道,WebRTC中的核心對(duì)象 PeerConnection、LocalMediaStream、LocalVideoTrack、LocalAudioTrack都是通過(guò) WebRTC 創(chuàng)建出來(lái)的。

PeerConnectionFactory的初始化與構(gòu)造

在 WebRTC 中使用了大量的設(shè)計(jì)模式,對(duì)于 PeerConnectionFactory 也是如此。它本身就是工廠模式,而這個(gè)構(gòu)造 PeerConnection 等核心對(duì)象的工廠又是通過(guò) builder 模式構(gòu)建出來(lái)的。

下面我們就來(lái)看看如何構(gòu)造 PeerConectionFactory。在我們構(gòu)造 PeerConnectionFactory 之前,首先要對(duì)其進(jìn)行初始化,其代碼如下:

PeerConnectionFactory.initialize(...);

初始化之后,就可以通過(guò) builder 模式來(lái)構(gòu)造 PeerConnecitonFactory 對(duì)象了。

...

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

 return builder.createPeerConnectionFactory();

通過(guò)上面的代碼,大家也就能夠理解為什么 WebRTC 要使用 buider 模式來(lái)構(gòu)造 PeerConnectionFactory 了吧?主要是方便調(diào)整建造 PeerConnectionFactory的組件,如編碼器、解碼器等。

從另外一個(gè)角度我們也可以了解到,要更換WebRTC引警的編解碼器該從哪里設(shè)置了哈!

音視頻數(shù)據(jù)源

有了PeerConnectionFactory對(duì)象,我們就可以創(chuàng)建數(shù)據(jù)源了。實(shí)際上,數(shù)據(jù)源是 WebRTC 對(duì)音視頻數(shù)據(jù)的一種抽象,表式數(shù)據(jù)可以從這里獲取。

使用過(guò) JS WebRTC API的同學(xué)都非常清楚,在 JS中 VideoTrack 和 AudioTrack 就是數(shù)據(jù)源。而在 Android 開(kāi)發(fā)中我們可以知道 Video/AudioTrack 就是 Video/AudioSouce的封裝,可以認(rèn)為他們是等同的。

創(chuàng)建數(shù)據(jù)源的方式如下:

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

數(shù)據(jù)源只是對(duì)數(shù)據(jù)的一種抽象,它是從哪里獲取的數(shù)據(jù)呢?對(duì)于音頻來(lái)說(shuō),在創(chuàng)建 AudioSource時(shí),就開(kāi)始從音頻設(shè)備捕獲數(shù)據(jù)了。對(duì)于視頻來(lái)說(shuō)我們可以指定采集視頻數(shù)據(jù)的設(shè)備,然后使用觀察者模式從指定設(shè)備中獲取數(shù)據(jù)。

接下來(lái)我們就來(lái)看一下如何指定視頻設(shè)備。

視頻采集

在 Android 系統(tǒng)下有兩種 Camera,一種稱為 Camera1, 是一種比較老的采集視頻數(shù)據(jù)的方式,別一種稱為 Camera2, 是一種新的采集視頻的方法。它們之間的最大區(qū)別是 Camera1使用同步方式調(diào)用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;
}
    

上面代碼的邏輯也比較簡(jiǎn)單:

首先看 Android 設(shè)備是否支持 Camera2.

如果支持就使用 Camera2, 如果不支持就使用 Camera1.

在獲到到具體的設(shè)備后,再看其是否有前置攝像頭,如果有就使用

如果沒(méi)有有效的前置攝像頭,則選一個(gè)非前置攝像頭。

通過(guò)上面的方法就可以拿到使用的攝像頭了,然后將攝像頭與視頻源連接起來(lái),這樣從攝像頭獲取的數(shù)據(jù)就源源不斷的送到 VideoTrack 里了。

下面我們來(lái)看看 VideoCapture 是如何與 VideoSource 關(guān)聯(lián)到一起的:

...

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

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

...

mVideoTrack.setEnabled(true);
...

上面的代碼中,在初始化 VideoCaptuer 的時(shí)候,可以過(guò)觀察者模式將 VideoCapture 與 VideoSource 聯(lián)接到了一起。因?yàn)?VideoTrack 是 VideoSouce 的一層封裝,所以此時(shí)我們開(kāi)啟 VideoTrack 后就可以拿到視頻數(shù)據(jù)了。

當(dāng)然,最后還要調(diào)用一下 VideoCaptuer 對(duì)象的 startCapture 方法真正的打開(kāi)攝像頭,這樣 Camera 才會(huì)真正的開(kāi)始工作哈,代碼如下:

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

拿到了視頻數(shù)據(jù)后,我們?nèi)绾螌⑺故境鰜?lái)呢?

渲染視頻

在 Android 下 WebRTC 使用OpenGL ES 進(jìn)行視頻渲染,用于展示視頻的控件是 WebRTC 對(duì) Android 系統(tǒng)控件 SurfaceView 的封裝。

WebRTC 封裝后的 SurfaceView 類為 org.webrtc.SurfaceViewRenderer。在界面定義中應(yīng)該定義兩個(gè)SurfaceViewRenderer,一個(gè)用于顯示本地視頻,另一個(gè)用于顯示遠(yuǎn)端視頻。

其定義如下:

...

"@+id/LocalSurfaceView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />

"@+id/RemoteSurfaceView"
    android:layout_width="120dp"
    android:layout_height="160dp"
    android:layout_gravity="top|end"
    android:layout_margin="16dp"/>

...

通過(guò)上面的代碼我們就將顯示視頻的 View 定義好了。光定義好這兩個(gè)View 還不夠,還要對(duì)它做進(jìn)一步的設(shè)置:

...

mLocalSurfaceView.init(mRootEglBase.getEglBaseContext(), null);
mLocalSurfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
mLocalSurfaceView.setMirror(true);
mLocalSurfaceView.setEnableHardwareScaler(false /* enabled */);

...

其含義是:

使用 OpenGL ES 的上下文初始化 View。

設(shè)置圖像的拉伸比例。

設(shè)置圖像顯示時(shí)反轉(zhuǎn),不然視頻顯示的內(nèi)容與實(shí)際內(nèi)容正好相反。

是否打開(kāi)便件進(jìn)行拉伸。

通過(guò)上面的設(shè)置,我們的 view 就設(shè)置好了,對(duì)于遠(yuǎn)端的 Veiw 與本地 View 的設(shè)置是一樣的,我這里就不再贅述了。

接下來(lái)將從攝像頭采集的數(shù)據(jù)設(shè)置到該view里就可以顯示了。設(shè)置非常的簡(jiǎn)單,代碼如下:

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

對(duì)于遠(yuǎn)端來(lái)說(shuō)與本地視頻的渲染顯示是類似的,只不過(guò)數(shù)據(jù)源是從網(wǎng)絡(luò)獲取的。

通過(guò)以上講解,大家應(yīng)該對(duì) WebRTC 如何采集數(shù)據(jù)、如何渲染數(shù)據(jù)有了基本的認(rèn)識(shí)。下面我們?cè)倏磥?lái)下遠(yuǎn)端的數(shù)據(jù)是如何來(lái)的。

創(chuàng)建 PeerConnection

要想從遠(yuǎn)端獲取數(shù)據(jù),我們就必須創(chuàng)建 PeerConnection 對(duì)象。該對(duì)象的用處就是與遠(yuǎn)端建立聯(lián)接,并最終為雙方通訊提供網(wǎng)絡(luò)通道。

我們來(lái)看下如何創(chuàng)建 PeerConnecion 對(duì)象。

...
PeerConnection.RTCConfiguration rtcConfig = 
				new PeerConnection.RTCConfiguration(iceServers);
...
PeerConnection connection =
                mPeerConnectionFactory.createPeerConnection(rtcConfig,
                                                            mPeerConnectionObserver);
        
...
connection.addTrack(mVideoTrack, mediaStreamLabels);
connection.addTrack(mAudioTrack, mediaStreamLabels);
...

PeerConnection 對(duì)象的創(chuàng)建還是要使我們之前講過(guò)的 PeerConnectionFactory 來(lái)創(chuàng)建。WebRTC 在建立連接時(shí)使用 ICE 架構(gòu),一些參數(shù)需要在創(chuàng)建 PeerConnection 時(shí)設(shè)置進(jìn)去。

另外,當(dāng) PeerConnection 對(duì)象創(chuàng)建好后,我們應(yīng)該將本地的音視頻軌添加進(jìn)去,這樣 WebRTC 才能幫我們生成包含相應(yīng)媒體信息的 SDP,以便于后面做媒體能力協(xié)商使用。

通過(guò)上面的方式,我們就將 PeerConnection 對(duì)象創(chuàng)建好了。與 JS 中的 PeerConnection 對(duì)象一樣,當(dāng)其創(chuàng)建好之后,可以監(jiān)聽(tīng)一些我們感興趣有事件了,如收到 Candidate 事件時(shí),我們要與對(duì)方進(jìn)行交換。

PeerConnection 事件的監(jiān)聽(tīng)與 JS 還是有一點(diǎn)差別的。在 JS 中,監(jiān)聽(tīng) PeerConnection的相關(guān)事件非常直接,直接實(shí)現(xiàn)peerconnection.onXXX就好了。而 Android 中的方式與 JS 略有區(qū)別,它是通過(guò)觀察者模式來(lái)監(jiān)聽(tīng)事件的。大家這點(diǎn)一定要注意!

雙方都創(chuàng)建好 PeerConnecton 對(duì)象后,就會(huì)進(jìn)行媒體協(xié)商,協(xié)商完成后,數(shù)據(jù)在底層就開(kāi)始傳輸了。

信令驅(qū)動(dòng)

在整個(gè) WebRTC 雙方交互的過(guò)程中,其業(yè)務(wù)邏輯的核心是信令, 所有的模塊都是通過(guò)信令串聯(lián)起來(lái)的。

以 PeerConnection 對(duì)象的創(chuàng)建為例,該在什么時(shí)候創(chuàng)建 PeerConnection 對(duì)象呢?最好的時(shí)機(jī)當(dāng)然是在用戶加入房間之后了 。

下面我們就來(lái)看一下,對(duì)于兩人通訊的情況,信令該如何設(shè)計(jì)。在我們這個(gè)例子中,可以將信令分成兩大類。第一類為客戶端命令;第二類為服務(wù)端命令;

客戶端命令有:

join: 用戶加入房間

leave: 用戶離開(kāi)房間

message: 端到端命令(offer、answer、candidate)

服務(wù)端命令:

joined: 用戶已加入

leaved: 用戶已離開(kāi)

other_joined:其它用戶已加入

bye: 其它用戶已離開(kāi)

full: 房間已滿

通過(guò)以上幾條信令就可以實(shí)現(xiàn)一對(duì)一實(shí)時(shí)互動(dòng)的要求,是不是非常的簡(jiǎn)單?

在本例子中我們?nèi)匀皇峭ㄟ^(guò)socket.io與之前搭建的信令服備器互聯(lián)的。由于 socket.io 是跨平臺(tái)的,所以無(wú)論是在 js 中,還是在 Android 中,我們都可以使用其客戶端與服務(wù)器相聯(lián),非常的方便。

下面再來(lái)看一下,收到不同信令后,客戶端的狀態(tài)變化:

客戶端一開(kāi)始的時(shí)候處于 Init/Leave 狀態(tài)。當(dāng)發(fā)送 join 消息,并收到服務(wù)端的 joined 后,其狀態(tài)變?yōu)?joined。

此時(shí),如果第二個(gè)用戶加入到房間,則客戶端的狀態(tài)變?yōu)榱?joined_conn, 也就是說(shuō)此時(shí)雙方可以進(jìn)行實(shí)時(shí)互動(dòng)了。

如果此時(shí),該用戶離開(kāi),則其狀態(tài)就變成了 初始化狀態(tài)。其它 case 大家可以根據(jù)上面的圖自行理解了。

小結(jié)

本文首先介紹了在 Android 中使用 WebRTC 要需申請(qǐng)的權(quán)限,以及如何引入 WebRTC 庫(kù)。然后從如何采集音視頻數(shù)據(jù)、如何渲染、如何與對(duì)方建立連接等幾個(gè)方面向大家詳細(xì)介紹了如何在 Android 系統(tǒng)下開(kāi)發(fā)一套 1對(duì)1的直播系統(tǒng)。

本文介紹的知識(shí)與我之前所寫的通過(guò) 《Nodejs 搭建 WebRTC 信令服務(wù)器》完整的構(gòu)成了一套 1對(duì)1直播系統(tǒng)。希望通過(guò)本文的學(xué)習(xí),同學(xué)們可以快速的撐握 WebRTC 的使用,并根據(jù)自己的需要構(gòu)建自己的直播系統(tǒng)。

謝謝!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/7054.html

相關(guān)文章

  • 使用WebRTC搭建前視頻聊天室——入門

    摘要:在處于使用了設(shè)備的私有網(wǎng)絡(luò)中的主機(jī)之間需要建立連接時(shí)需要使用穿越技術(shù)。目前已經(jīng)有很多穿越技術(shù),但沒(méi)有一項(xiàng)是完美的,因?yàn)榈男袨槭欠菢?biāo)準(zhǔn)化的。 什么是WebRTC? 眾所周知,瀏覽器本身不支持相互之間直接建立信道進(jìn)行通信,都是通過(guò)服務(wù)器進(jìn)行中轉(zhuǎn)。比如現(xiàn)在有兩個(gè)客戶端,甲和乙,他們倆想要通信,首先需要甲和服務(wù)器、乙和服務(wù)器之間建立信道。甲給乙發(fā)送消息時(shí),甲先將消息發(fā)送到服務(wù)器上,服務(wù)器對(duì)甲...

    Carl 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<