摘要:下面我們就看一下具體如何申請(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 中的地位。
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
摘要:在處于使用了設(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ì)甲...
閱讀 844·2023-04-25 19:43
閱讀 4109·2021-11-30 14:52
閱讀 3920·2021-11-30 14:52
閱讀 4024·2021-11-29 11:00
閱讀 3918·2021-11-29 11:00
閱讀 4036·2021-11-29 11:00
閱讀 3752·2021-11-29 11:00
閱讀 6599·2021-11-29 11:00