摘要:下面我們就看一下具體如何申請權限靜態權限申請在項目中的中增加以下代碼動態權限申請隨著的發展,對安全性要求越來越高。其定義如下通過上面的代碼我們就將顯示視頻的定義好了。當發送消息,并收到服務端的后,其狀態變為。
前言作者:李超,如遇到相關問題,可以點擊這里與作者直接交流。
在學習 WebRTC 的過程中,學習的一個基本步驟是先通過 JS 學習 WebRTC的整體流程,在熟悉了整體流程之后,再學習其它端如何使用 WebRTC 進行互聯互通。
我們已經在前面分享了信令服務器的搭建和 STUN/TURN服務器的搭建:
rtcdeveloper.com/t/topic/133…
rtcdeveloper.com/t/topic/137…
本文將講解 Android 端是如何使用WebRTC的,至于 P2P 穿越、STUN/TURN/ICE、RTP/RTCP協議、DTLS等內容不做講解。
對這方面有興趣的同學可以多帶帶再聯系我。
申請權限我們要使用 WebRTC 進行音視頻互動時需要申請訪問硬件的權限,至少要申請以下三種權限:
Camera 權限
Record Audio 權限
Intenet 權限
在Android中,申請權限分為靜態權限申請和動態權限申請,這對于做 Android 開發的同學來說已經是習以為常的事情了。下面我們就看一下具體如何申請權限:
靜態權限申請
在 Android 項目中的 AndroidManifest.xml 中增加以下代碼:
......
動態權限申請
隨著 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 庫,用它來與信令服務器互聯。
首先我們看一下如何引入 WebRTC 庫(我這里使用的是最新 Android Studio 3.3.2)。在 Module 級別的 build.gradle 文件中增加以下代碼:
...
dependencies {
...
implementation org.webrtc:google-webrtc:1.0.+
...
}
是不是非常簡單?
接下來要引入 socket.io 庫,用它來與我們之前用 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 中的地位。
通過該圖我們可以知道,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,一個用于顯示本地視頻,另一個用于顯示遠端視頻。
其定義如下:
...
"@+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 還不夠,還要對它做進一步的設置:
...
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 是跨平臺的,所以無論是在 js 中,還是在 Android 中,我們都可以使用其客戶端與服務器相聯,非常的方便。
下面再來看一下,收到不同信令后,客戶端的狀態變化:
客戶端一開始的時候處于 Init/Leave 狀態。當發送 join 消息,并收到服務端的 joined 后,其狀態變為 joined。
此時,如果第二個用戶加入到房間,則客戶端的狀態變為了 joined_conn, 也就是說此時雙方可以進行實時互動了。
如果此時,該用戶離開,則其狀態就變成了 初始化狀態。其它 case 大家可以根據上面的圖自行理解了。
小結本文首先介紹了在 Android 中使用 WebRTC 要需申請的權限,以及如何引入 WebRTC 庫。然后從如何采集音視頻數據、如何渲染、如何與對方建立連接等幾個方面向大家詳細介紹了如何在 Android 系統下開發一套 1對1的直播系統。
本文介紹的知識與我之前所寫的通過 《Nodejs 搭建 WebRTC 信令服務器》完整的構成了一套 1對1直播系統。希望通過本文的學習,同學們可以快速的撐握 WebRTC 的使用,并根據自己的需要構建自己的直播系統。
謝謝!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/7054.html
摘要:在處于使用了設備的私有網絡中的主機之間需要建立連接時需要使用穿越技術。目前已經有很多穿越技術,但沒有一項是完美的,因為的行為是非標準化的。 什么是WebRTC? 眾所周知,瀏覽器本身不支持相互之間直接建立信道進行通信,都是通過服務器進行中轉。比如現在有兩個客戶端,甲和乙,他們倆想要通信,首先需要甲和服務器、乙和服務器之間建立信道。甲給乙發送消息時,甲先將消息發送到服務器上,服務器對甲...
閱讀 713·2023-04-25 19:43
閱讀 3907·2021-11-30 14:52
閱讀 3784·2021-11-30 14:52
閱讀 3852·2021-11-29 11:00
閱讀 3783·2021-11-29 11:00
閱讀 3868·2021-11-29 11:00
閱讀 3557·2021-11-29 11:00
閱讀 6103·2021-11-29 11:00