赞
踩
WebRTC 是一种视频聊天和会议开发技术。它允许您在移动设备和浏览器之间创建点对点连接以传输媒体流。您可以在关于WebRTC的文章中找到更多关于它的工作原理及其一般原则的详细信息。
创建 WebRTC 连接包括两个步骤:
首先,请注意,在连接开始时,为了在设备之间交换数据,使用了信令机制。信令机制可以是任何用于传输数据的通道,例如套接字。
假设我们要在两个设备之间建立视频连接。为此,我们需要在它们之间建立逻辑连接。
使用会话描述协议 (SDP) 为这一对等方建立逻辑连接:
创建一个 PeerConnection 对象。
在 SDP 提议上形成一个对象,其中包含有关即将到来的会话的数据,并使用信令机制将其发送给对话者。
val peerConnectionFactory: PeerConnectionFactory lateinit var peerConnection: PeerConnection fun createPeerConnection(iceServers: List<PeerConnection.IceServer>) { val rtcConfig = PeerConnection.RTCConfiguration(iceServers) peerConnection = peerConnectionFactory.createPeerConnection( rtcConfig, object : PeerConnection.Observer { ... } )!! } fun sendSdpOffer() { peerConnection.createOffer( object : SdpObserver { override fun onCreateSuccess(sdpOffer: SessionDescription) { peerConnection.setLocalDescription(sdpObserver, sdpOffer) signaling.sendSdpOffer(sdpOffer) } ... }, MediaConstraints() ) }
反过来,对于另一个peer:
fun onSdpOfferReceive(sdpOffer: SessionDescription) {// Saving the received SDP-offer peerConnection.setRemoteDescription(sdpObserver, sdpOffer) sendSdpAnswer() } // FOrming and sending SDP-answer fun sendSdpAnswer() { peerConnection.createAnswer( object : SdpObserver { override fun onCreateSuccess(sdpOffer: SessionDescription) { peerConnection.setLocalDescription(sdpObserver, sdpOffer) signaling.sendSdpAnswer(sdpOffer) } … }, MediaConstraints() ) }
第一个节点收到 SDP 应答后,保留它。
fun onSdpAnswerReceive(sdpAnswer: SessionDescription) {
peerConnection.setRemoteDescription(sdpObserver, sdpAnswer)
sendSdpAnswer()
}
成功交换 SessionDescription 对象后,认为逻辑连接已建立。
我们现在需要在设备之间建立物理连接,这通常是一项非常重要的任务。通常,Internet 上的设备没有公共地址,因为它们位于路由器和防火墙后面。为了解决这个问题,WebRTC 使用了 ICE(交互式连接建立)技术。
Stun 和 Turn 服务器是 ICE 的重要组成部分。它们有一个目的——在没有公共地址的设备之间建立连接。
设备向 Stun 服务器发出请求并接收其公共地址作为响应。然后,使用信号机制将其发送给对话者。在对话者执行相同操作后,设备会识别彼此的网络位置并准备好相互传输数据。
在某些情况下,路由器可能具有“对称 NAT”限制。此限制不允许设备之间的直接连接。在这种情况下,使用 Turn 服务器。它充当中介,所有数据都通过它。 在Mozilla 的 WebRTC文档中阅读更多内容。
正如我们所见,STUN 和 TURN 服务器在建立设备之间的物理连接方面发挥着重要作用。正是出于这个目的,我们在创建 PeerConnection 对象时,传递一个包含可用 ICE 服务器的列表。
为了建立物理连接,一个对等点生成 ICE 候选对象 - 包含有关如何在网络上找到设备的信息的对象,并通过信令机制将它们发送给对等点。
lateinit var peerConnection: PeerConnection
fun createPeerConnection(iceServers: List<PeerConnection.IceServer>) {
val rtcConfig = PeerConnection.RTCConfiguration(iceServers)
peerConnection = peerConnectionFactory.createPeerConnection(
rtcConfig,
object : PeerConnection.Observer {
override fun onIceCandidate(iceCandidate: IceCandidate) {
signaling.sendIceCandidate(iceCandidate)
} …
}
)!!
}
然后第二个对等点通过信令机制接收第一个对等点的候选 ICE 并为自己保留它们。它还生成自己的 ICE 候选人并将其发回。
fun onIceCandidateReceive(iceCandidate: IceCandidate) {
peerConnection.addIceCandidate(iceCandidate)
}
现在对等点已经交换了他们的地址,您可以开始发送和接收数据。
该库在与对话者建立逻辑和物理连接后,调用 onAddTrack 标头并将包含对话者的 VideoTrack 和 AudioTrack 的 MediaStream 对象传入其中。
fun createPeerConnection(iceServers: List<PeerConnection.IceServer>) { val rtcConfig = PeerConnection.RTCConfiguration(iceServers) peerConnection = peerConnectionFactory.createPeerConnection( rtcConfig, object : PeerConnection.Observer { override fun onIceCandidate(iceCandidate: IceCandidate) { … } override fun onAddTrack( rtpReceiver: RtpReceiver?, mediaStreams: Array<out MediaStream> ) { onTrackAdded(mediaStreams) } … } )!! }
接下来,我们必须从 MediaStream 中检索 VideoTrack 并将其显示在屏幕上。
private fun onTrackAdded(mediaStreams: Array<out MediaStream>) {
val videoTrack: VideoTrack? = mediaStreams.mapNotNull {
it.videoTracks.firstOrNull()
}.firstOrNull()
displayVideoTrack(videoTrack)
…
}
要显示 VideoTrack,您需要向它传递一个实现 VideoSink 接口的对象。为此,该库提供了 SurfaceViewRenderer 类。
fun displayVideoTrack(videoTrack: VideoTrack?) {
videoTrack?.addSink(binding.surfaceViewRenderer)
}
为了获得对话者的声音,我们不需要做任何额外的事情——图书馆为我们做了一切。但是,如果我们想要微调声音,我们可以获取一个 AudioTrack 对象并使用它来更改音频设置。
var audioTrack: AudioTrack? = null
private fun onTrackAdded(mediaStreams: Array<out MediaStream>) {
…
audioTrack = mediaStreams.mapNotNull {
it.audioTracks.firstOrNull()
}.firstOrNull()
}
例如,我们可以使对话者静音,如下所示:
fun muteAudioTrack() {
audioTrack.setEnabled(false)
}
从您的设备发送视频和音频也开始于创建 PeerConnection 对象并发送 ICE 候选对象。但与从对话者接收视频流时创建SDPOffer不同,在这种情况下,我们必须首先创建一个MediaStream对象,其中包括AudioTrack和VideoTrack。
为了发送我们的音频和视频流,我们需要创建一个 PeerConnection 对象,然后使用信令机制来交换 IceCandidate 和 SDP 数据包。但不是从库中获取媒体流,我们必须从我们的设备获取媒体流并将其传递给库,以便将其传递给我们的对话者。
fun createLocalConnection() { localPeerConnection = peerConnectionFactory.createPeerConnection( rtcConfig, object : PeerConnection.Observer { ... } )!! val localMediaStream = getLocalMediaStream() localPeerConnection.addStream(localMediaStream) localPeerConnection.createOffer( object : SdpObserver { ... }, MediaConstraints() ) }
现在我们需要创建一个 MediaStream 对象并将 AudioTrack 和 VideoTrack 对象传递给它。
val context: Context
private fun getLocalMediaStream(): MediaStream? {
val stream = peerConnectionFactory.createLocalMediaStream("user")
val audioTrack = getLocalAudioTrack()
stream.addTrack(audioTrack)
val videoTrack = getLocalVideoTrack(context)
stream.addTrack(videoTrack)
return stream
}
接收音轨:
private fun getLocalAudioTrack(): AudioTrack {
val audioConstraints = MediaConstraints()
val audioSource = peerConnectionFactory.createAudioSource(audioConstraints)
return peerConnectionFactory.createAudioTrack("user_audio", audioSource)
}
接收 VideoTrack 稍微困难一点。首先,获取设备所有摄像头的列表。
lateinit var capturer: CameraVideoCapturer
private fun getLocalVideoTrack(context: Context): VideoTrack {
val cameraEnumerator = Camera2Enumerator(context)
val camera = cameraEnumerator.deviceNames.firstOrNull {
cameraEnumerator.isFrontFacing(it)
} ?: cameraEnumerator.deviceNames.first()
...
}
接下来,创建一个 CameraVideoCapturer 对象,该对象将捕获图像。
private fun getLocalVideoTrack(context: Context): VideoTrack { ... capturer = cameraEnumerator.createCapturer(camera, null) val surfaceTextureHelper = SurfaceTextureHelper.create( "CaptureThread", EglBase.create().eglBaseContext ) val videoSource = peerConnectionFactory.createVideoSource(capturer.isScreencast ?: false) capturer.initialize(surfaceTextureHelper, context, videoSource.capturerObserver) ... }
现在,拿到CameraVideoCapturer后,开始抓图,添加到MediaStream。
private fun getLocalMediaStream(): MediaStream? { ... val videoTrack = getLocalVideoTrack(context) stream.addTrack(videoTrack) return stream } private fun getLocalVideoTrack(context: Context): VideoTrack { ... capturer.startCapture(1024, 720, 30) return peerConnectionFactory.createVideoTrack("user0_video", videoSource) }
创建 MediaStream 并将其添加到 PeerConnection 后,该库形成一个 SDP 报价,并且上述 SDP 数据包交换通过信令机制进行。当这个过程完成后,对话者将开始接收我们的视频流。恭喜,此时连接已建立。
我们已经考虑了一对一的连接。WebRTC 还允许您创建多对多连接。在最简单的形式中,这与一对一连接的方式完全相同。不同之处在于 PeerConnection 对象,以及 SDP 数据包和 ICE-candidate 交换,不是为每个参与者完成一次。这种方法有缺点:
在这种情况下,WebRTC 可以与负责上述任务的媒体服务器结合使用。客户端的流程与直接连接对话者设备的过程完全相同,但媒体流不会发送给所有参与者,而只会发送给媒体服务器。媒体服务器将其重新传输给其他参与者。
我们考虑了在 Android 上创建 WebRTC 连接的最简单方法。如果看完后你还是不明白,那就把所有的步骤都再一遍一遍,自己尝试去实现——一旦你掌握了关键点,在实践中使用这个技术就不成问题了。如果您想了解更多关于这项技术的信息,请查看我们的WebRTC安全指南。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。