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