基於WebRtc實現安卓視頻一對一聊天


WebRtc是谷歌2010年收購GlobalIPSolutions公司而獲得的一項實時語音對話或視頻對話的技術。之后谷歌將其開源,有很好的跨平台性。官方網址:https://webrtc.org/

最近由於公司項目需求,剛剛接觸webrtc,由於國內這方面的資料少之又少,學習起來也有點困難。這一個月來對webrtc也稍微有點了解吧,特此寫個博客紀念下,結合自己寫的小Demo給剛入坑的新人一點建議。

基本流程

 

 

使用webrtc###
1. Maven

<dependency>
<groupId>org.webrtc</groupId>
<artifactId>google-webrtc</artifactId>
<version>1.0.20723</version>
<type>pom</type>
</dependency>

 

注意:安卓6.0以上請自行處理CAMERA、RECORD_AUDIO、WRITE_EXTERNAL_STORAGE等危險權限。

介紹Webrtc一些關鍵類###
1. PeerConnectionFactory
webrtc核心類,用於創建其他關鍵類,稍后在做介紹。在使用PeerConnectionFactory之前,請先初始化,類似這樣。

PeerConnectionFactory.initialize(
PeerConnectionFactory.InitializationOptions.builder(getApplicationContext())
.setEnableVideoHwAcceleration(true)
.createInitializationOptions());

PeerConnectionFactory.InitializationOptions作為PeerConnectionFactory初始化傳入參數,該類采用構造模式,可以對初始化參數進行一些配置。初始化之后就可以創建PeerConnectionFactory實例了。

PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
mPeerConnectionFactory = new PeerConnectionFactory(options);

 


2. VideoCapturer
視頻捕捉器的一個頂級接口,它的的子接口為CameraVideoCapturer,封裝了安卓相機的使用方法,使用它們可以輕松的獲取設備相機數據,切換攝像頭,獲取攝像頭數量等。該對象的創建如下。

private CameraVideoCapturer createVideoCapture(Context context) {
CameraEnumerator enumerator;
if (Camera2Enumerator.isSupported(context)) {
enumerator = new Camera2Enumerator(context);
} else {
enumerator = new Camera1Enumerator(true);

final String[] deviceNames = enumerator.getDeviceNames();

for (String deviceName : deviceNames) {
if (enumerator.isFrontFacing(deviceName)) {
CameraVideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);

if (videoCapturer != null) {
return videoCapturer;
}
}
}

for (String deviceName : deviceNames) {
if (!enumerator.isFrontFacing(deviceName)) {
CameraVideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
return videoCapturer;
}
}
}
return null;
}

 

3. VideoSource/VideoTrack
VideoSource為視頻源,通過核心類PeerConnectionFactory創建,VideoTrack是對VideoSource的包裝,可以方便的將視頻源在本地進行播放,添加到MediaStream中進行網絡傳輸。

CameraVideoCapturer mVideoCapturer = createVideoCapture(this);
VideoSource videoSource = mPeerConnectionFactory.createVideoSource(mVideoCapturer);
VideoTrack mVideoTrack = mPeerConnectionFactory.createVideoTrack("videtrack", videoSource);


4. AudioSource/AudioTrack
AudioSource/AudioTrack和上面的VideoSource/VideoTrack類似,從名字上面就知道是對音頻的獲取和處理了,AudioSource的創建很簡單,直接用PeerConnectionFactory創建就可以了。

AudioSource audioSource = mPeerConnectionFactory.createAudioSource(new MediaConstraints());
AudioTrack mAudioTrack = mPeerConnectionFactory.createAudioTrack("audiotrack", audioSource);


AudioSource 創建的時候需要傳入MediaConstraints這個對象的實例,其用於對媒體的一些約束限制,創建的時候可以直接使用默認的。如果你想自己定義,就需要自己填入相應的鍵值對了。

MediaConstraints audioConstraints = new MediaConstraints();
//回聲消除
audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googEchoCancellation", "true"));
//自動增益 
audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAutoGainControl", "true"));
//高音過濾 
audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googHighpassFilter", "true"));
//噪音處理
audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNoiseSuppression", "true"));

 


5. MediaStream
音視頻的媒體流,通過PeerConnectionFactory創建,用於PeerConnection通過網絡傳輸發送給另一方。在媒體流傳輸之前,需要將前面獲取的VideoTrack和AudioTrack添加進去。

MediaStream mMediaStream = mPeerConnectionFactory.createLocalMediaStream("localstream");
mMediaStream.addTrack(mVideoTrack);
mMediaStream.addTrack(mAudioTrack);



6. PeerConnection
用於p2p網絡傳輸,雙方信令的交換。webrtc是基於p2p的,因此在雙方通信之前需要服務器幫助傳遞信令,並且需要添加STUN和TURN服務器網絡穿透。在雙方通道打開之后就可以將媒體流發送給另一方了。下面是PeerConnection的創建,並將媒體流添加到其中用於網絡傳輸。

PeerConnection peerConnection = mPeerConnectionFactory.createPeerConnection(iceServers, pcConstraints, this);
peerConnection.addStream(mMediaStream);



參數說明如下:
iceServers連接到外網和網絡穿用到的,添加STUN和TURN服務器可以幫助你連接。
constraints是一個MediaConstrains的實例。應該包含offerToRecieveAudio和offerToRecieveVideo。
observer是一個PeerConnectionObserver的實例,對PeerConnection的一些連接狀態的監聽。

信令交換###
在實現媒體流的網絡傳輸之前,需要交換雙方信令,將連接通道打開,下面介紹一下webrtc的信令交換機制。

A向B發起建立連接的請求,通過PeerConnection的createOffer()方法創建一個offer信令,創建成功后調用SdpObserver監聽中的onCreateSuccess()回調函數,調用PeerConnection的setLocalDescription方法設置自己的offer信令,同時將offer信令通過服務器轉發給B。

peerConnection.createOffer(sdpObserver, sdpMediaConstraints);

//SdpObserver
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
peerConnection.setLocalDescription(this, sessionDescription);
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("type", sessionDescription.type.canonicalForm());
jsonObject.put("description", sessionDescription.description);
} catch (JSONException e) {
e.printStackTrace();
}
mSocket.emit("SdpInfo", jsonObject.toString());
}

 


B收到A的offer信令后,創建一個SessionDescription(SDP描述符包含媒體信息,如分辨率、編解碼能力等)對象將A的offer信令解析出來,並調用PeerConnection的setRemoteDescription方法設置A的SDP描述符,然后B通過PeerConnection的createAnswer()方法創建一個answer信令,創建成功后調用SdpObserver監聽中的onCreateSuccess()回調函數,調用PeerConnection的setLocalDescription方法設置自己的answer信令,同時將answer信令通過服務器轉發給A。

@Override
public void call(Object... args) {
if (mPeer == null) {
mPeer = new Peer();
}
try {
JSONObject jsonObject = new JSONObject(args[0].toString());
SessionDescription description = new SessionDescription
(SessionDescription.Type.fromCanonicalForm(jsonObject.getString("type")),
jsonObject.getString("description"));
mPeer.peerConnection.setRemoteDescription(mPeer, description);
if (!isOffer) {
mPeer.peerConnection.createAnswer(mPeer, sdpConstraints);
}
} catch (JSONException e) {
e.printStackTrace();
}
}



A收到B的answer信令后,解析B的answer信令再調用PeerConnection的setRemoteDescription方法設置B的SDP描述符。這樣雙方的信令交換就算完成了。
在非局域網下,信令的交換還需要借助於STUN和TURN服務器網絡穿透,創建PeerConnection的時候需要傳入iceServers這個參數,這里面存放的就是穿透地址變換的服務器地址了,類似的,Ice穿透也需要信令的交換,過程大致如下。

當A和B創建好配置了iceServers的PeerConnection實例后,當網絡候可用時,回調PeerConnection.Observer的onIceCandidate()函數,在回調函數里面將IceCandidate對象發送給對方。
在收到對方的IceCandidate信令后,解析出來並用PeerConnection的addIceCandidate()方法設置對方的信令。

@Override
public void onIceCandidate(IceCandidate iceCandidate) {
try {
JSONObject jsonObject = new JSONObject();
jsonObject.put("label", iceCandidate.sdpMLineIndex);
jsonObject.put("id", iceCandidate.sdpMid);
jsonObject.put("candidate", iceCandidate.sdp);
mSocket.emit("IceInfo", jsonObject.toString());
} catch (JSONException e) {
e.printStackTrace();
}
}

@Override
public void call(Object... args) {
try {
JSONObject jsonObject = new JSONObject(args[0].toString());
IceCandidate candidate = null;
candidate = new IceCandidate(
jsonObject.getString("id"),
jsonObject.getInt("label"),
jsonObject.getString("candidate")
);
mPeer.peerConnection.addIceCandidate(candidate);
} catch (JSONException e) {
e.printStackTrace();
}
}

 

現在,雙方的連接通道就完全打開了,PeerConnection.Observer就會調用onAddStream()響應函數,里面包含對方的媒體流Mediastream,將媒體流播放就可以了。

@Override
public void onAddStream(MediaStream mediaStream) {
remoteVideoTrack = mediaStream.videoTracks.get(0);
remoteVideoTrack.addRenderer(new VideoRenderer(remoteView));
}


播放媒體流###
媒體流的播放需要用到webrtc封裝的控件SurfaceViewRenderer,它繼承於安卓的SurfaceView。
在播放之前,需要對該控件初始化和配置一些屬性。

localView = findViewById(R.id.localVideoView);
//創建EglBase對象
mEglBase = EglBase.create();
//初始化localView
localView.init(mEglBase.getEglBaseContext(), null);
localView.setKeepScreenOn(true);
localView.setMirror(true);
localView.setZOrderMediaOverlay(true);
localView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
localView.setEnableHardwareScaler(false);



注意:SurfaceViewRenderer的初始化需要在主線程

初始化完成后,將localView包裝成VideoRenderer對象並添加到VideoTrack中就可以進行播放了。

mVideoTrack.addRenderer(new VideoRenderer(localView));

Demo###
demo里面包含了用IoSocket簡單寫的java服務器(webrtc文件夾),里面的地址改成自己電腦的本機ip4地址即可測試。

demo地址:demo傳送門

附上demo運行效果圖

 

原文鏈接:https://blog.csdn.net/qq_35054800/article/details/78647545


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM