摘要:下面我們就看一下具體如何申請權(quán)限靜態(tài)權(quán)限申請在項目中的中增加以下代碼動態(tài)權(quán)限申請隨著的發(fā)展,對安全性要求越來越高。其定義如下通過上面的代碼我們就將顯示視頻的定義好了。當(dāng)發(fā)送消息,并收到服務(wù)端的后,其狀態(tài)變?yōu)椤?/p>
前言作者:李超,如遇到相關(guān)問題,可以點擊這里與作者直接交流。
在學(xué)習(xí) WebRTC 的過程中,學(xué)習(xí)的一個基本步驟是先通過 JS 學(xué)習(xí) WebRTC的整體流程,在熟悉了整體流程之后,再學(xué)習(xí)其它端如何使用 WebRTC 進行互聯(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)容不做講解。
對這方面有興趣的同學(xué)可以多帶帶再聯(lián)系我。
申請權(quán)限我們要使用 WebRTC 進行音視頻互動時需要申請訪問硬件的權(quán)限,至少要申請以下三種權(quán)限:
Camera 權(quán)限
Record Audio 權(quán)限
Intenet 權(quán)限
在Android中,申請權(quán)限分為靜態(tài)權(quán)限申請和動態(tài)權(quán)限申請,這對于做 Android 開發(fā)的同學(xué)來說已經(jīng)是習(xí)以為常的事情了。下面我們就看一下具體如何申請權(quán)限:
靜態(tài)權(quán)限申請
在 Android 項目中的 AndroidManifest.xml 中增加以下代碼:
......
動態(tài)權(quán)限申請
隨著 Android 的發(fā)展,對安全性要求越來越高。除了申請靜態(tài)權(quán)限之外,還需要動態(tài)申請權(quán)限。代碼如下:
void requestPermissions(String[] permissions, intrequestCode);
實際上,對于權(quán)限這塊的處理真正做細(xì)了要寫不少代碼,好在 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);
}
...
通過添加以上代碼,就將權(quán)限申請好了,是不是非常簡單?權(quán)限申請好了,我們開始做第二步,看在 Android 下如何引入 WebRTC 庫。
引入庫在我們這個例子中要引入兩個比較重要的庫,第一個當(dāng)然就是 WebRTC 庫了,第二個是 socket.io 庫,用它來與信令服務(wù)器互聯(lián)。
首先我們看一下如何引入 WebRTC 庫(我這里使用的是最新 Android Studio 3.3.2)。在 Module 級別的 build.gradle 文件中增加以下代碼:
...
dependencies {
...
implementation org.webrtc:google-webrtc:1.0.+
...
}
是不是非常簡單?
接下來要引入 socket.io 庫,用它來與我們之前用 Nodejs 搭建的信令服務(wù)器進行對接。再加上前面用到的EasyPermissions庫,所以真正的代碼應(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
}
通過上面的方式我們就將需要引入的庫全部引入進來了。下面就可以開始真的 WebRTC 之旅了。
萬物的開始我們都知道萬物有個起源,我們在開發(fā) WebRTC 程序時也不例外,WebRTC程序的起源就是PeerConnectionFactory。這也是與使用 JS 開發(fā) WebRTC 程序最大的不同點之一,因為在 JS 中不需要使用 PeerConnectionFactory 來創(chuàng)建 PeerConnection 對象。
而在 Android/iOS 開發(fā)中,我們使用的 WebRTC 中的大部分對象基本上都是通過 PeerConnectionFactory 創(chuàng)建出來的。下面這張圖就清楚的表達(dá)了 PeerConnectionFactory 在 WebRTC 中的地位。
通過該圖我們可以知道,WebRTC中的核心對象 PeerConnection、LocalMediaStream、LocalVideoTrack、LocalAudioTrack都是通過 WebRTC 創(chuàng)建出來的。
PeerConnectionFactory的初始化與構(gòu)造
在 WebRTC 中使用了大量的設(shè)計模式,對于 PeerConnectionFactory 也是如此。它本身就是工廠模式,而這個構(gòu)造 PeerConnection 等核心對象的工廠又是通過 builder 模式構(gòu)建出來的。
下面我們就來看看如何構(gòu)造 PeerConectionFactory。在我們構(gòu)造 PeerConnectionFactory 之前,首先要對其進行初始化,其代碼如下:
PeerConnectionFactory.initialize(...);
初始化之后,就可以通過 builder 模式來構(gòu)造 PeerConnecitonFactory 對象了。
...
PeerConnectionFactory.Builder builder =
PeerConnectionFactory.builder()
.setVideoEncoderFactory(encoderFactory)
.setVideoDecoderFactory(decoderFactory);
...
return builder.createPeerConnectionFactory();
通過上面的代碼,大家也就能夠理解為什么 WebRTC 要使用 buider 模式來構(gòu)造 PeerConnectionFactory 了吧?主要是方便調(diào)整建造 PeerConnectionFactory的組件,如編碼器、解碼器等。
從另外一個角度我們也可以了解到,要更換WebRTC引警的編解碼器該從哪里設(shè)置了哈!
音視頻數(shù)據(jù)源有了PeerConnectionFactory對象,我們就可以創(chuàng)建數(shù)據(jù)源了。實際上,數(shù)據(jù)源是 WebRTC 對音視頻數(shù)據(jù)的一種抽象,表式數(shù)據(jù)可以從這里獲取。
使用過 JS WebRTC API的同學(xué)都非常清楚,在 JS中 VideoTrack 和 AudioTrack 就是數(shù)據(jù)源。而在 Android 開發(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ù)源只是對數(shù)據(jù)的一種抽象,它是從哪里獲取的數(shù)據(jù)呢?對于音頻來說,在創(chuàng)建 AudioSource時,就開始從音頻設(shè)備捕獲數(shù)據(jù)了。對于視頻來說我們可以指定采集視頻數(shù)據(jù)的設(shè)備,然后使用觀察者模式從指定設(shè)備中獲取數(shù)據(jù)。
接下來我們就來看一下如何指定視頻設(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;
}
上面代碼的邏輯也比較簡單:
首先看 Android 設(shè)備是否支持 Camera2.
如果支持就使用 Camera2, 如果不支持就使用 Camera1.
在獲到到具體的設(shè)備后,再看其是否有前置攝像頭,如果有就使用
如果沒有有效的前置攝像頭,則選一個非前置攝像頭。
通過上面的方法就可以拿到使用的攝像頭了,然后將攝像頭與視頻源連接起來,這樣從攝像頭獲取的數(shù)據(jù)就源源不斷的送到 VideoTrack 里了。
下面我們來看看 VideoCapture 是如何與 VideoSource 關(guān)聯(lián)到一起的:
...
mSurfaceTextureHelper =
SurfaceTextureHelper.create("CaptureThread",
mRootEglBase.getEglBaseContext());
mVideoCapturer.initialize(mSurfaceTextureHelper,
getApplicationContext(),
videoSource.getCapturerObserver());
...
mVideoTrack.setEnabled(true);
...
上面的代碼中,在初始化 VideoCaptuer 的時候,可以過觀察者模式將 VideoCapture 與 VideoSource 聯(lián)接到了一起。因為 VideoTrack 是 VideoSouce 的一層封裝,所以此時我們開啟 VideoTrack 后就可以拿到視頻數(shù)據(jù)了。
當(dāng)然,最后還要調(diào)用一下 VideoCaptuer 對象的 startCapture 方法真正的打開攝像頭,這樣 Camera 才會真正的開始工作哈,代碼如下:
@Override
protected void onResume() {
super.onResume();
mVideoCapturer.startCapture(VIDEO_RESOLUTION_WIDTH,
VIDEO_RESOLUTION_HEIGHT,
VIDEO_FPS);
}
拿到了視頻數(shù)據(jù)后,我們?nèi)绾螌⑺故境鰜砟兀?/p> 渲染視頻
在 Android 下 WebRTC 使用OpenGL ES 進行視頻渲染,用于展示視頻的控件是 WebRTC 對 Android 系統(tǒng)控件 SurfaceView 的封裝。
WebRTC 封裝后的 SurfaceView 類為 org.webrtc.SurfaceViewRenderer。在界面定義中應(yīng)該定義兩個SurfaceViewRenderer,一個用于顯示本地視頻,另一個用于顯示遠(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"/>
...
通過上面的代碼我們就將顯示視頻的 View 定義好了。光定義好這兩個View 還不夠,還要對它做進一步的設(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è)置圖像顯示時反轉(zhuǎn),不然視頻顯示的內(nèi)容與實際內(nèi)容正好相反。
是否打開便件進行拉伸。
通過上面的設(shè)置,我們的 view 就設(shè)置好了,對于遠(yuǎn)端的 Veiw 與本地 View 的設(shè)置是一樣的,我這里就不再贅述了。
接下來將從攝像頭采集的數(shù)據(jù)設(shè)置到該view里就可以顯示了。設(shè)置非常的簡單,代碼如下:
... mVideoTrack.addSink(mLocalSurfaceView); ...
對于遠(yuǎn)端來說與本地視頻的渲染顯示是類似的,只不過數(shù)據(jù)源是從網(wǎng)絡(luò)獲取的。
通過以上講解,大家應(yīng)該對 WebRTC 如何采集數(shù)據(jù)、如何渲染數(shù)據(jù)有了基本的認(rèn)識。下面我們再看來下遠(yuǎn)端的數(shù)據(jù)是如何來的。
創(chuàng)建 PeerConnection要想從遠(yuǎn)端獲取數(shù)據(jù),我們就必須創(chuàng)建 PeerConnection 對象。該對象的用處就是與遠(yuǎn)端建立聯(lián)接,并最終為雙方通訊提供網(wǎng)絡(luò)通道。
我們來看下如何創(chuàng)建 PeerConnecion 對象。
... PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers); ... PeerConnection connection = mPeerConnectionFactory.createPeerConnection(rtcConfig, mPeerConnectionObserver); ... connection.addTrack(mVideoTrack, mediaStreamLabels); connection.addTrack(mAudioTrack, mediaStreamLabels); ...
PeerConnection 對象的創(chuàng)建還是要使我們之前講過的 PeerConnectionFactory 來創(chuàng)建。WebRTC 在建立連接時使用 ICE 架構(gòu),一些參數(shù)需要在創(chuàng)建 PeerConnection 時設(shè)置進去。
另外,當(dāng) PeerConnection 對象創(chuàng)建好后,我們應(yīng)該將本地的音視頻軌添加進去,這樣 WebRTC 才能幫我們生成包含相應(yīng)媒體信息的 SDP,以便于后面做媒體能力協(xié)商使用。
通過上面的方式,我們就將 PeerConnection 對象創(chuàng)建好了。與 JS 中的 PeerConnection 對象一樣,當(dāng)其創(chuàng)建好之后,可以監(jiān)聽一些我們感興趣有事件了,如收到 Candidate 事件時,我們要與對方進行交換。
PeerConnection 事件的監(jiān)聽與 JS 還是有一點差別的。在 JS 中,監(jiān)聽 PeerConnection的相關(guān)事件非常直接,直接實現(xiàn)peerconnection.onXXX就好了。而 Android 中的方式與 JS 略有區(qū)別,它是通過觀察者模式來監(jiān)聽事件的。大家這點一定要注意!
雙方都創(chuàng)建好 PeerConnecton 對象后,就會進行媒體協(xié)商,協(xié)商完成后,數(shù)據(jù)在底層就開始傳輸了。
信令驅(qū)動在整個 WebRTC 雙方交互的過程中,其業(yè)務(wù)邏輯的核心是信令, 所有的模塊都是通過信令串聯(lián)起來的。
以 PeerConnection 對象的創(chuàng)建為例,該在什么時候創(chuàng)建 PeerConnection 對象呢?最好的時機當(dāng)然是在用戶加入房間之后了 。
下面我們就來看一下,對于兩人通訊的情況,信令該如何設(shè)計。在我們這個例子中,可以將信令分成兩大類。第一類為客戶端命令;第二類為服務(wù)端命令;
客戶端命令有:
join: 用戶加入房間
leave: 用戶離開房間
message: 端到端命令(offer、answer、candidate)
服務(wù)端命令:
joined: 用戶已加入
leaved: 用戶已離開
other_joined:其它用戶已加入
bye: 其它用戶已離開
full: 房間已滿
通過以上幾條信令就可以實現(xiàn)一對一實時互動的要求,是不是非常的簡單?
在本例子中我們?nèi)匀皇峭ㄟ^socket.io與之前搭建的信令服備器互聯(lián)的。由于 socket.io 是跨平臺的,所以無論是在 js 中,還是在 Android 中,我們都可以使用其客戶端與服務(wù)器相聯(lián),非常的方便。
下面再來看一下,收到不同信令后,客戶端的狀態(tài)變化:
客戶端一開始的時候處于 Init/Leave 狀態(tài)。當(dāng)發(fā)送 join 消息,并收到服務(wù)端的 joined 后,其狀態(tài)變?yōu)?joined。
此時,如果第二個用戶加入到房間,則客戶端的狀態(tài)變?yōu)榱?joined_conn, 也就是說此時雙方可以進行實時互動了。
如果此時,該用戶離開,則其狀態(tài)就變成了 初始化狀態(tài)。其它 case 大家可以根據(jù)上面的圖自行理解了。
小結(jié)本文首先介紹了在 Android 中使用 WebRTC 要需申請的權(quán)限,以及如何引入 WebRTC 庫。然后從如何采集音視頻數(shù)據(jù)、如何渲染、如何與對方建立連接等幾個方面向大家詳細(xì)介紹了如何在 Android 系統(tǒng)下開發(fā)一套 1對1的直播系統(tǒng)。
本文介紹的知識與我之前所寫的通過 《Nodejs 搭建 WebRTC 信令服務(wù)器》完整的構(gòu)成了一套 1對1直播系統(tǒng)。希望通過本文的學(xué)習(xí),同學(xué)們可以快速的撐握 WebRTC 的使用,并根據(jù)自己的需要構(gòu)建自己的直播系統(tǒng)。
謝謝!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/7054.html
摘要:在處于使用了設(shè)備的私有網(wǎng)絡(luò)中的主機之間需要建立連接時需要使用穿越技術(shù)。目前已經(jīng)有很多穿越技術(shù),但沒有一項是完美的,因為的行為是非標(biāo)準(zhǔn)化的。 什么是WebRTC? 眾所周知,瀏覽器本身不支持相互之間直接建立信道進行通信,都是通過服務(wù)器進行中轉(zhuǎn)。比如現(xiàn)在有兩個客戶端,甲和乙,他們倆想要通信,首先需要甲和服務(wù)器、乙和服務(wù)器之間建立信道。甲給乙發(fā)送消息時,甲先將消息發(fā)送到服務(wù)器上,服務(wù)器對甲...
閱讀 734·2023-04-25 19:43
閱讀 3980·2021-11-30 14:52
閱讀 3806·2021-11-30 14:52
閱讀 3870·2021-11-29 11:00
閱讀 3801·2021-11-29 11:00
閱讀 3902·2021-11-29 11:00
閱讀 3580·2021-11-29 11:00
閱讀 6182·2021-11-29 11:00