WebRTC源码解读一

此文章为个人学习和应用WebRTC的学习理解,有不对的地方希望大家提出来共同学习和进步,谢谢。

PeerConnectionFactory/PeerConnection:整个WebRTC中最核心的类,有了这个类才能获得音视频相关的其他操作。

PeerConnectionFactory类中包含了各种音视频数据的初始化。

PeerConnectionFactory.initializeAndroidGlobals  中初始化了是否初始化音视频,是否硬件加速,是否支持硬件渲染等内容。

PeerConnectionFactory简化的类图如下: 

获取媒体流

第一步:获取视频源videoSource

 
  1. String frontCameraName = VideoCapturerAndroid.getNameOfFrontFacingDevice();

  2. VideoCapturer videoCapturer = VideoCapturerAndroid.create(frontCameraName);

  3. VideoSource videoSource = factory.createVideoSource(videoCapturer,videoConstraints);

  • 1
  • 2
  • 3

其中videoConstraints是对视频流的一些限制,按如下方法创建。

 
  1. MediaConstraints videoConstraints = new MediaConstraints();

  2. videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxHeight", Integer.toString(pcParams.videoHeight)));

  3. videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxWidth", Integer.toString(pcParams.videoWidth)));

  4. videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair("maxFrameRate", Integer.toString(pcParams.videoFps)));

  5. videoConstraints.mandatory.add(new MediaConstraints.KeyValuePair("minFrameRate", Integer.toString(pcParams.videoFps)));

  • 1
  • 2
  • 3
  • 4
  • 5

第二步:获取音频源audioSource

音频源的获取简单许多:

AudioSource audioSource = factory.createAudioSource(new MediaConstraints());
  • 1

第三步:获得封装VideoTrack/AudioTrack

VideoTrack/AudioTrack 是 VideoSource/AudioSource 的封装,方便他们的播放和传输:

 
  1. VideoTrack videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource);

  2. AudioTrack audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource);

  • 1
  • 2

第四步:获取媒体流localMS

其实 VideoTrack/AudioTrack 已经可以播放了,不过我们先不考虑本地播放。那么如果要把他们发送到对方客户端,我们需要把他们添加到媒体流中:

 
  1. MediaStream localMS=factory.createLocalMediaStream("ARDAMS");

  2. localMS.addTrack(videoTrack);

  3. localMS.addTrack(audeoTrack);

  • 1
  • 2
  • 3

然后,如果有建立好的连接通道,我们就可以把 localMS 发送出去了。

建立连接通道

WebRTC是基于P2P的,但是在连接通道建立好之前,我们仍然需要服务器帮助传递信令,而且需要服务器帮助进行网络穿透。大体需要如下几个步骤。

第一步:创建PeerConnection的对象。

 
  1. PeerConnection pc = factory.createPeerConnection(

  2. iceServers,//ICE服务器列表

  3. pcConstraints,//MediaConstraints

  4. context);//上下文,可做监听

PeerConnectionClient:PeerConnection的实现,有了这个类才能进行音视频相关数据通讯;

iceServers 我们下面再说。 
pcConstraints是媒体限制,可以添加如下约束:

 
  1. pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));

  2. pcConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));

  3. pcConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));

  • 1
  • 2
  • 3

监听器建议同时实现SdpObserver、PeerConnection.Observer两个接口。

第二步:信令交换

建立连接通道时我们需要在WebRTC两个客户端之间进行一些信令交换,我们以A作为发起端,B作为响应端(A call B,假设服务器和A、B已经连接好,并且只提供转发功能,PeerConnection对象为pc ):

  • A向B发出一个“init”请求(我觉得这步没有也行)。
  • B收到后“init”请求后,调用pc.createOffer()方法创建一个包含SDP描述符(包含媒体信息,如分辨率、编解码能力等)的offer信令。
  • offer信令创建成功后会调用SdpObserver监听中的onCreateSuccess()响应函数,在这里B会通过pc.setLocalDescription将offer信令(SDP描述符)赋给自己的PC对象,同时将offer信令发送给A 。
  • A收到B的offer信令后,利用pc.setRemoteDescription()方法将B的SDP描述赋给A的PC对象。
  • A在onCreateSuccess()监听响应函数中调用pc.setLocalDescription将answer信令(SDP描述符)赋给自己的PC对象,同时将answer信令发送给B 。
  • B收到A的answer信令后,利用pc.setRemoteDescription()方法将A的SDP描述赋给B的PC对象。

这样,A、B之间就完成里了信令交换。

第三步:通过ICE框架穿透NAT/防火墙

如果在局域网内,信令交换后就已经可以传递媒体流了,但如果双方不在同一个局域网,就需要进行NAT/防火墙穿透(我是在局域网下测试的,没有穿透,但还是把这方面内容介绍下)。

WebRTC使用ICE框架来保证穿透。ICE全名叫交互式连接建立(Interactive Connectivity Establishment),一种综合性的NAT/FW穿越技术,它是一种框架,可以整合各种NAT/FW穿越技术如STUN、TURN(Traversal Using Relay NAT 中继NAT实现的穿透)。ICE会先使用STUN,尝试建立一个基于UDP的连接,如果失败了,就会去TCP(先尝试HTTP,然后尝试HTTPS),如果依旧失败ICE就会使用一个中继的TURN服务器。使用STUN服务器穿透的结构如下: 
 
我们可以使用Google的stun服务器:stun:stun.l.google.com:19302(Google嘛,翻墙你懂得,当然如果有精力可以自己搭建一个stun服务器),那么我们怎么把这个地址告诉WebRTC呢,还记得之前的iceServers吗,就是在创建PeerConnection对象的时候需要的参数,iceServers里面存放的就是进行穿透地址变换的服务器地址,添加方法如下(保险起见可以多添加几个服务器地址,如果有的话):

iceServers.add(new PeerConnection.IceServer("stun:stun.l.google.com:19302"));
  • 1

然后这个stun服务器地址也需要通过信令交换,同样以A、B客户端为例过程如下:

  • A、B分别创建PC实例pc(配置了穿透服务器地址) 。
  • 当网络候选可用时,PeerConnection.Observer监听会调用onIceCandidate()响应函数并提供IceCandidate(里面包含穿透所需的信息)的对象。在这里,我们可以让A、B将IceCandidate对象的内容发送给对方。
  • A、B收到对方发来的candidate信令后,利用pc.addIceCandidate()方法将穿透信息赋给各自的PeerConnection对象。

至此,连接通道完全打通,然后我们只需要将之前获取的媒体流localMS赋给pc即可:

pc.addStream(localMS);//也可以先添加,连接通道打通后一样会触发监听响应。
  • 1

在连接通道正常的情况下,对方的PeerConnection.Observer监听就会调用onAddStream()响应函数并提供接收到的媒体流。

播放媒体流

WebRTC提供了一种很方便的播放方式:VideoRendererGui,首先设置VideoRendererGui,具体方法如下:

 
  1. GLSurfaceView videoView = (GLSurfaceView) findViewById(R.id.glview_call);

  2. VideoRendererGui.setView(videoView, runnable);//surface准备好后会调用runnable里的run()函数

  • 1
  • 2

然后创建一个VideoRenderer对象,并将其赋给videoTrack:

 
  1. VideoRenderer renderer = VideoRendererGui.createGui(x, y, width, height);//设置界面

  2. videoTrack.addRenderer(renderer);

  • 1
  • 2

WebRTC允许我们实现自己的渲染,我们只需通过VideoRendererGui获取VideoRenderer.Callbacks的对象,渲染后把其作为参数传入到VideoRenderer的构造方法即可。

此外利用VideoRenderer.Callbacks,我们可以动态调整播放界面,如下:

 
  1. VideoRenderer.Callbacks cbRenderer = VideoRendererGui.create(x, y, width, height, scalingType, mirror);//设置界面

  2. videoTrack.addRenderer(new VideoRenderer(cbRenderer ));

  3. VideoRendererGui.update(cbRenderer ,x, y, width, height, scalingType);//调整界面

  • 1
  • 2
  • 3

信令服务器

信令服务器主要是在客户端打通连接通道前传递信令的,在客户端开启P2P通道后,这个服务器关了也不会影响媒体流传输。

我是用ProjectRTC作为服务器,这个项目里还包括PC客户端的实现,不过我们不用管它们,ProjectRTC项目根目录下的app.js是入口文件,里面设置必要参数,如网口等。我们需要关注的文件是app文件夹下的:socketHandler.js 和 streams.js 文件。

socketHandler.js 是服务器用来和客户端交互的接口,里面的实现网口的监听,每有新的连接接入,都在这里进行存储。通过分析这个文件可以发现,所有连接的socket都存放在sockets对象中,标志是socket.id,socket的收发函数也是在这里定。

streams.js是一个存储的工具类,里面有两个成员:id和name,这个文件用来存放已经准备好打通连接通道的客户端的信息,name是客户端的名字,id是连接对应客户端的socket的id 。

如果我们要实现客户端的信令交互,只需要修改这两个文件即可(实际上基本不用改)。


主要的API有VideoCapturerAndroid, VideoRenderer, MediaStream, PeerConnection 和 PeerConnectionFactory

类图

PeerConnectionFactory中initializeAndroidGlobals()返回布尔值,true表示一切OK,false表示有失败。

如果一切ok,可以使用PeerConnectionFactory 的构造方法创建工厂: 

PeerConnectionFactory peerConnectionFactory = new PeerConnectionFactory(); 

有了peerConnectionFactory实例,就可以从用户设备获取视频和音频,最终将其渲染到屏幕上。

VideoCapturerAndroid & CameraEnumerationAndroid

VideoCapturerAndroid是VideoCapturer接口的实现,封装了一系列Camera API,为访问摄像头设备的流信息提供了方便。要创建VideoCapturerAndroid的实例,首先需要通过CameraEnumerationAndroid类获取摄像头设备基本信息,如数量、名称。如下:

 
  1. // Returns the number of camera devices

  2. CameraEnumerationAndroid.getDeviceCount();

  3.  
  4. // Returns the name of the camera with camera index. Returns null if the

  5. // camera can not be used.

  6. CameraEnumerationAndroid.getDeviceName(0);

  7.  
  8. // Returns the front face device name

  9. CameraEnumerationAndroid.getNameOfFrontFacingDevice();

  10. // Returns the back facing device name

  11. CameraEnumerationAndroid.getNameOfBackFacingDevice();

  12. // Creates a VideoCapturerAndroid instance for the device name

  13. VideoCapturerAndroid.create(name);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

有了包含摄像流信息的VideoCapturerAndroid实例,就可以创建从本地设备获取到的包含视频流信息的MediaStream,从而发送给另一端。但做这些之前,我们首先研究下如何将自己的视频显示到应用上面。

VideoSource & VideoTrack

从VideoCapturer实例中获取一些有用信息,或者要达到最终目标————为连接端获取合适的媒体流,或者仅仅是将它渲染给用户,我们需要了解VideoSource 和 VideoTrack类。

VideoSource允许方法开启、停止设备捕获视频。这在为了延长电池寿命而禁止视频捕获的情况下比较有用。

VideoTrack 是简单的添加VideoSource到MediaStream 对象的一个封装。

我们通过代码看看它们是如何一起工作的。capturer是VideoCapturer的实例,videoConstraints是MediaConstraints的实例。

 
  1. // First we create a VideoSource

  2. VideoSource videoSource =

  3. peerConnectionFactory.createVideoSource(capturer, videoConstraints);

  4.  
  5. // Once we have that, we can create our VideoTrack

  6. // Note that VIDEO_TRACK_ID can be any string that uniquely

  7. // identifies that video track in your application

  8. VideoTrack localVideoTrack =

  9. peerConnectionFactory.createVideoTrack(VIDEO_TRACK_ID, videoSource);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

AudioSource & AudioTrack

AudioSource和AudioTrack与VideoSource和VideoTrack相似,只是不需要AudioCapturer 来获取麦克风,audioConstraints是 MediaConstraints的一个实例。

 
  1. // First we create an AudioSource

  2. AudioSource audioSource =

  3. peerConnectionFactory.createAudioSource(audioConstraints);

  4.  
  5. // Once we have that, we can create our AudioTrack

  6. // Note that AUDIO_TRACK_ID can be any string that uniquely

  7. // identifies that audio track in your application

  8. AudioTrack localAudioTrack =

  9. peerConnectionFactory.createAudioTrack(AUDIO_TRACK_ID, audioSource);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

VideoRenderer

通过把VideoRenderer.Callbacks的实现作为参数传入VideoRenderer的构造方法,WebRTC允许实现自己的渲染。另外,它提供了一种非常好的默认方式VideoRendererGui。简而言之,VideoRendererGui是一个GLSurfaceView ,使用它可以绘制自己的视频流。我们通过代码看一下它是如何工作的,以及如何添加renderer 到 VideoTrack。

 
  1. // To create our VideoRenderer, we can use the

  2. // included VideoRendererGui for simplicity

  3. // First we need to set the GLSurfaceView that it should render to

  4. GLSurfaceView videoView = (GLSurfaceView) findViewById(R.id.glview_call);

  5.  
  6. // Then we set that view, and pass a Runnable

  7. // to run once the surface is ready

  8. VideoRendererGui.setView(videoView, runnable);

  9.  
  10. // Now that VideoRendererGui is ready, we can get our VideoRenderer

  11. VideoRenderer renderer = VideoRendererGui.createGui(x, y, width, height);

  12.  
  13. // And finally, with our VideoRenderer ready, we

  14. // can add our renderer to the VideoTrack.

  15. localVideoTrack.addRenderer(renderer);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

也可以通过SurfaceViewRenderer创建VideoRenderer的实例并添加到VideoTrack。SurfaceViewRenderer是一个SurfaceView并实现了VideoRenderer.Callbacks接口。

 
  1. SurfaceViewRenderer localRender = (SurfaceViewRenderer) findViewById(R.id.local_video_view);

  2.  
  3. VideoRenderer renderer = new VideoRenderer(localRender);

  4.  
  5. localVideoTrack.addRenderer(renderer);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

MediaConstraints

MediaConstraints是MediaStream中音频和视频轨道的各种约束。对于大多数需要MediaConstraints的方法,一个简单的MediaConstraints实例就可以做到。

MediaConstraints audioConstraints = new MediaConstraints();
  • 1
  • 1

MediaStream

现在可以在本地看见自己了,接下来就要想办法让对方看见自己。这需要创建MediaStream,然后将其添加到PeerConnection 传送给对方。接下来我们就研究如何添加本地的VideoTrack 和AudioTrack来创建一个合适的MediaStream。

 
  1. // We start out with an empty MediaStream object,

  2. // created with help from our PeerConnectionFactory

  3. // Note that LOCAL_MEDIA_STREAM_ID can be any string

  4. MediaStream mediaStream = peerConnectionFactory.createLocalMediaStream(LOCAL_MEDIA_STREAM_ID);

  5.  
  6. // Now we can add our tracks.

  7. mediaStream.addTrack(localVideoTrack);

  8. mediaStream.addTrack(localAudioTrack);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

我们现在有了包含视频流和音频流的MediaStream实例,而且在屏幕上显示了我们的脸庞。现在就该把这些信息传送给对方了。

PeerConnection

现在我们有了自己的MediaStream,就可以开始连接远端了。这可以通过PeerConnection实现。创建PeerConnection很简单,只需要PeerConnectionFactory的协助即可。

PeerConnection peerConnection = peerConnectionFactory.createPeerConnection( iceServers, constraints,  observer);
  • 1
  • 1

参数的作用如下:

 
  1. iceServers

  2. 连接到外部设备或者网络时需要用到这个参数。在这里添加STUN 和 TURN 服务器就允许进行连接,即使在网络条件很差的条件下。

  3.  
  4. constraints

  5. MediaConstraints的一个实例,应该包含offerToRecieveAudio 和 offerToRecieveVideo

  6.  
  7. observer

  8. PeerConnection.Observer的一个实例。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

PeerConnection 包含了addStream、addIceCandidate、createOffer、createAnswer、getLocalDescription、setRemoteDescription 和其他类似方法。我们快速浏览一下这几个重要的方法,看它们是如何工作的。 
addStream 
这个是用来将MediaStream 添加到PeerConnection中的,如同它的命名一样。如果你想要对方看到你的视频、听到你的声音,就需要用到这个方法。

addIceCandidate 
一旦内部IceFramework发现有candidates允许其他方连接你时,就会创建IceCandidates 。当通过PeerConnectionObserver.onIceCandidate传递数据到对方时,需要通过任何一个你选择的信号通道获取到对方的IceCandidates。使用addIceCandidate 添加它们到PeerConnection,以便PeerConnection可以通过已有信息试图连接对方。

createOffer/createAnswer 
这两个方法用于原始通话的建立。如你所知,在WebRTC中,已经有了caller和callee的概念,一个是呼叫,一个是应答。createOffer是caller使用的,它需要一个sdpObserver,它允许获取和传输会话描述协议Session Description Protocol (SDP)给对方,还需要一个MediaConstraint。一旦对方得到了这个请求,它将创建一个应答并将其传输给caller。SDP是用来给对方描述期望格式的数据(如video、formats、codecs、encryption、resolution、 size等)。一旦caller收到这个应答信息,双方就相互建立的通信需求达成了一致,如视频、音频、解码器等。

setLocalDescription/setRemoteDescription 
这个是用来设置createOffer和createAnswer产生的SDP数据的,包含从远端获取到的数据。它允许内部PeerConnection 配置链接以便一旦开始传输音频和视频就可以开始真正工作。

PeerConnection.Observer

这个接口提供了一种监测PeerConnection事件的方法,例如收到MediaStream时,或者发现iceCandidates 时,或者需要重新建立通讯时。这个接口必须被实现,以便你可以有效处理收到的事件,例如当对方变为可见时,向他们发送信号iceCandidates。

调用顺序

发起呼叫

接受呼叫

关闭连接

猜你喜欢

转载自blog.csdn.net/qq_21743659/article/details/107758259