Android WebRTC 入门教程 -- 模拟p2p本地视频传输
Posted 夏至的稻穗
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android WebRTC 入门教程 -- 模拟p2p本地视频传输相关的知识,希望对你有一定的参考价值。
Android WebRTC 入门教程(一) – 使用相机
Android WebRTC 入门教程(二) – 模拟p2p本地视频传输
源码工程: https://github.com/LillteZheng/WebRTCDemo
今天要实现的效果:
上一章中,我们学习了 PeerConnectionFactory ,VideoCapturer 等知识实现了相机的预览。
这一章,学习一个比较重要的知识,Peerconnection 点对点传输,把本地摄像头的数据,采集之后,通过 Peerconnection 建立连接,把摄像头数据,传递给另一个 surface 显示,
效果如下:
动态太大,看视频连接 https://live.csdn.net/v/242968
device-2022-09-30-142040
一. PeerConnection
PeerConnection也就是Peer-to-Peer connection(P2P) ,顾名思义,这个类代表点对点之间的连接,以及音视频数据的传输。
PeerConnection 会通过 Sdp(SessionDescription) 和 ICE(IceCandidate) 进行媒体协商,实际就是你设备支持哪些音视频编解码格式,如果两者都支持,那么就算协商成功了。
此时通道就已经建立成功,可以从 Peerconnection 中的 MediaStream 去拿数据流了。
这里先熟悉几个只是点:
1.1 SDP (SessionDescription)
会话描述符,像 RTSP 这类协议,都需要进行媒体协商,际就是你的设备所支持的音视频编解码器、使用的传输协议、SSRC等信息…通过信令服务器透传给对方。如果双方都支持,那么就算协商成功了。
- Offer: 呼叫方发送端的 SDP 消息叫做 offer
- Answer:被呼叫方发送端的 SDP 消息叫做 Answer
1.2 ICE(IceCandidate)
进行媒体协商后,通道此时还未建立,就需要交换 IceCandidate,其实是一个整合STUN和TURN的框架, 给它提供STUN和TURN服务器地址, 它会自动选择优先级高的进行NAT穿透,选择一条稳定的通道。
二. 实现
首先,先创建两个 SurfaceViewRender ,一个用来摄像头渲染,一个用来接收:
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="trans"
android:text="开始本地传输"
/>
<org.webrtc.SurfaceViewRenderer
android:id="@+id/localRender"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<org.webrtc.SurfaceViewRenderer
android:id="@+id/remoteRender"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="5dp"
android:layout_weight="1"/>
</androidx.appcompat.widget.LinearLayoutCompat>
而 remoteRender 的配置,因为我们需要等待数据渲染,所以只需要初始化等待即可:
/**
* 配置接收端的渲染surface
*/
fun configRemoteRender(eglBaseContext:EglBase.Context)
remoteRender.holder.addCallback(object:SurfaceHolder.Callback
override fun surfaceCreated(holder: SurfaceHolder)
//等待接收数据,这里只要初始化即可
remoteRender.init(eglBaseContext,null)
remoteRender.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
remoteRender.setEnableHardwareScaler(false)
override fun surfaceChanged(
holder: SurfaceHolder,
format: Int,
width: Int,
height: Int
)
override fun surfaceDestroyed(holder: SurfaceHolder)
)
为了方便解释,这里的发送端使用 local,接收端使用 remote,他们的流程图如下:
2.1 添加 MediaStream
获取相机数据,上一章中,我们已经实践过了,代码不变,还未看过的可以参考上一章。
Android WebRTC 入门教程(一) – 使用相机
代码如下:
createCameraCapture()?.let camera->
....
// 添加渲染接收端器到轨道中,画面开始呈现
videoTrack.addSink(localRender)
// 创建 mediastream
localMediaStream = peerConnectionFactory.createLocalMediaStream("MediaStream")
// 将视频轨添加到 mediastram 中,等待连接时传输
localMediaStream.addTrack(videoTrack)
2.2 创建 Peerconnection
要进行点对点传输,就需要创建 Peerconnection ,使用 PeerconnectionFactory 创建,如
val iceServers: List<IceServer> = ArrayList()
//内部会转成 RTCConfiguration
localPeerConnection = peerConnectionFactory.createPeerConnection(iceServers,object:PeerObserver()
override fun onAddStream(strem: MediaStream?)
super.onAddStream(strem)
override fun onIceCandidate(p0: IceCandidate?)
super.onIceCandidate(p0)
)
remotePeerConnection = peerConnectionFactory.createPeerConnection(iceServers,object :PeerObserver()
override fun onAddStream(strem: MediaStream?)
super.onAddStream(strem)
override fun onIceCandidate(p0: IceCandidate?)
super.onIceCandidate(p0)
)
其中 PeerObserver 继承 PeerConnection.Observer ,减少一些方法的重写。
2.2 Create offer
此时,他们还没有任何联系。
根据时序图,我们需要先创建 sdp offer,并发送给接收端。
首先先添加媒体流
localPeerConnection?.addStream(localMediaStream)
然后创建 offer,此时就要进行 sdp 媒体交换:
这个是双方协商的过程,因此会设计到两个比较特殊的方法,setLocalDescription 和setRemoteDescription,双方在创建sdp 之后,都需要使用 setLocalDescription 把 sdp 保存到本地,然后对方使用 setRemoteDescription,把对方的 sdp 保存到远程。
具体方法如下:
//客户端创建 offer
localPeerConnection?.createOffer(object:SdpObserver("创建local offer")
override fun onCreateSuccess(p0: SessionDescription?)
super.onCreateSuccess(p0)
Log.d(TAG, "创建 local offer success: ")
/**
* 此时调用setLocalDescription()方法将该Offer保存到本地Local域,
* 然后将Offer发送给对方
*/
localPeerConnection?.setLocalDescription(SdpObserver("local 设置本地 sdp"),p0)
//2. 同时,接收端 setRemoteDescription 把 offer 保存到远端域
remotePeerConnection?.setRemoteDescription(SdpObserver("把 sdp 给到 remote"),p0)
//3. 此时remote 已经接收端了 offer ,所以创建 answer
remotePeerConnection?.createAnswer(object:SdpObserver("创建 remote answer")
override fun onCreateSuccess(p0: SessionDescription?)
super.onCreateSuccess(p0)
Log.d(TAG, " 创建 remote answer success: ")
/**
* 4. 此时调用setLocalDescription()方法将该 answer 保存到本地 remote 域,
* 然后将 answer 发送给对方
*/
remotePeerConnection?.setLocalDescription(SdpObserver("remote 设置本地 sdp"),p0)
//5. local 把 answer 保存到它的 remote 端,此时 sdp 交换结束
localPeerConnection?.setRemoteDescription(SdpObserver("把 sdp 给到 local"),p0)
, MediaConstraints())
, MediaConstraints())
我们以连接上的Client端为呼叫方先发起Offer请求,通过createOffer()创建一个Offer SDP。创建成功后,会在SdpObserver中收到onCreateSuccess回调,此时调用setLocalDescription()方法将该Offer保存到本地Local域,然后将Offer发送给对方。
被呼叫方接收到Offer后,通过setRemoteDescription()方法将Offer保存到它的Remote域,并通过createAnswer()创建Answer SDP,创建成功后同样调用setLocalDescription()方法将Answer消息保存到本地的Local域,然后回复给呼叫方。
最后,呼叫方将收到Answer消息,并通过setRemoteDescription()方法,将Answer保存到它的Remote域。至此,整个媒体协商的过程结束。
其中 SdpObserver 继承 webrtc 的 SdpObserver ,减少一些方法的重写。
三. 建立点对点连接
媒体协商结束,我们的点对点并没有真正的简历。此时 PeerConnection.Observer 会回调 onIceCandidate ,我们需要将各自的 Candidate 添加给对方。
同时,因为 local 作为视频流的一方,onAddStream 不需要理会,但 remote 作为接收方,则需要把 MediaStream 中的流拿出来,放到 SurfaceViewRender 去渲染。
代码如下:
localPeerConnection = peerConnectionFactory.createPeerConnection(iceServers,object:PeerObserver()
override fun onAddStream(strem: MediaStream?)
super.onAddStream(strem)
override fun onIceCandidate(p0: IceCandidate?)
super.onIceCandidate(p0)
//透传给另外一端
remotePeerConnection?.addIceCandidate(p0)
)
remotePeerConnection = peerConnectionFactory.createPeerConnection(iceServers,object :PeerObserver()
override fun onAddStream(strem: MediaStream?)
super.onAddStream(strem)
//拿到视频流,添加 sink ,SurfaceViewRender 会直接渲染
strem?.let
runOnUiThread
it.videoTracks[0].addSink(remoteRender)
override fun onIceCandidate(p0: IceCandidate?)
super.onIceCandidate(p0)
//透传给另外一端
localPeerConnection?.addIceCandidate(p0)
)
基本上学完这样,我们已经可以进行不同端传输音视频了。
只是sdp 的协商,改成其他信令服务器。
参考:
https://codezjx.com/posts/webrtc-android-demo/#more
以上是关于Android WebRTC 入门教程 -- 模拟p2p本地视频传输的主要内容,如果未能解决你的问题,请参考以下文章
Webrtc入门——基于阿里云ubuntu 最新webrtc Android平台编译详细说明