如何使用WebRTC Native Code for Android实现三方电话会议video聊天?

我试图使用Android的WebRTC本地代码包 (即不使用WebView)在Android应用程序内部实现3路video聊天。 我使用node.js编写了一个信号服务器,并使用客户端应用程序中的Gottox socket.io java客户端库连接到服务器,交换SDP数据包并build立双向video聊天连接。

但是现在我遇到的问题超出了三方的要求。 WebRTC本地代码包附带的AppRTCDemo应用程序仅演示双向通话(如果第三方尝试join房间,则会返回“房间已满”消息)。

根据这个答案 (与Android无关),我应该创build多个PeerConnections,所以每个聊天参与者将连接到另外两个参与者。

但是,当我创build多个PeerConnectionClient(一个包装PeerConection的Java类,它在libjingle_peerconnection_so.so中的本地实现)时,会从库中引发exception,这是由于它们试图访问摄像机:

E/VideoCapturerAndroid(21170): startCapture failed E/VideoCapturerAndroid(21170): java.lang.RuntimeException: Fail to connect to camera service E/VideoCapturerAndroid(21170): at android.hardware.Camera.native_setup(Native Method) E/VideoCapturerAndroid(21170): at android.hardware.Camera.<init>(Camera.java:548) E/VideoCapturerAndroid(21170): at android.hardware.Camera.open(Camera.java:389) E/VideoCapturerAndroid(21170): at org.webrtc.VideoCapturerAndroid.startCaptureOnCameraThread(VideoCapturerAndroid.java:528) E/VideoCapturerAndroid(21170): at org.webrtc.VideoCapturerAndroid.access$11(VideoCapturerAndroid.java:520) E/VideoCapturerAndroid(21170): at org.webrtc.VideoCapturerAndroid$6.run(VideoCapturerAndroid.java:514) E/VideoCapturerAndroid(21170): at android.os.Handler.handleCallback(Handler.java:733) E/VideoCapturerAndroid(21170): at android.os.Handler.dispatchMessage(Handler.java:95) E/VideoCapturerAndroid(21170): at android.os.Looper.loop(Looper.java:136) E/VideoCapturerAndroid(21170): at org.webrtc.VideoCapturerAndroid$CameraThread.run(VideoCapturerAndroid.java:484) 

在尝试build立连接之前初始化本地客户端时会发生这种情况,所以它与node.js,socket.io或任何信号服务器的东西都没有关系。

我如何获得多个PeerConnections共享相机,以便我可以将同一个video发送给多个对等设备?

我有一个想法是实现某种单身相机类来取代VideoCapturerAndroid,可以在多个连接之间共享,但我甚至不知道这将工作,我想知道是否有办法做3-在我开始在库内部进行黑客攻击之前,使用API​​进行双向调用。

这是可能的,如果是这样,怎么样?

更新:

我试图在多个PeerConnectionClient之间共享一个VideoCapturerAndroid对象,只为第一个连接创build它,并将它传递给后续的初始化函数,但是这导致了这个“Capturer只能被采取一次! 从VideoCapturer对象为第二个对等连接创build第二个VideoTrack时出现exception:

 E/AndroidRuntime(18956): FATAL EXCEPTION: Thread-1397 E/AndroidRuntime(18956): java.lang.RuntimeException: Capturer can only be taken once! E/AndroidRuntime(18956): at org.webrtc.VideoCapturer.takeNativeVideoCapturer(VideoCapturer.java:52) E/AndroidRuntime(18956): at org.webrtc.PeerConnectionFactory.createVideoSource(PeerConnectionFactory.java:113) E/AndroidRuntime(18956): at com.example.rtcapp.PeerConnectionClient.createVideoTrack(PeerConnectionClient.java:720) E/AndroidRuntime(18956): at com.example.rtcapp.PeerConnectionClient.createPeerConnectionInternal(PeerConnectionClient.java:482) E/AndroidRuntime(18956): at com.example.rtcapp.PeerConnectionClient.access$20(PeerConnectionClient.java:433) E/AndroidRuntime(18956): at com.example.rtcapp.PeerConnectionClient$2.run(PeerConnectionClient.java:280) E/AndroidRuntime(18956): at android.os.Handler.handleCallback(Handler.java:733) E/AndroidRuntime(18956): at android.os.Handler.dispatchMessage(Handler.java:95) E/AndroidRuntime(18956): at android.os.Looper.loop(Looper.java:136) E/AndroidRuntime(18956): at com.example.rtcapp.LooperExecutor.run(LooperExecutor.java:56) 

尝试在PeerConnectionClient之间共享VideoTrack对象导致本机代码出现此错误:

 E/libjingle(19884): Local fingerprint does not match identity. E/libjingle(19884): P2PTransportChannel::Connect: The ice_ufrag_ and the ice_pwd_ are not set. E/libjingle(19884): Local fingerprint does not match identity. E/libjingle(19884): Failed to set local offer sdp: Failed to push down transport description: Local fingerprint does not match identity. 

在PeerConnectionClients之间共享MediaStream会导致应用突然closures,Logcat中不会显示任何错误消息。

Solutions Collecting From Web of "如何使用WebRTC Native Code for Android实现三方电话会议video聊天?"

您遇到的问题是PeerConnectionClient 不是 PeerConnection的包装,它包含 PeerConnection。

我注意到这个问题没有回答,所以我想看看我能否帮忙。 我查看了源代码,并且PeerConnectionClient对于单个远程对等体而言非常困难。 您需要创buildPeerConnection对象的集合,而不是以下行:

 private PeerConnection peerConnection; 

如果再仔细观察一下,你会发现它会变得更复杂一些。

createPeerConnectionInternal中的mediaStream逻辑应该只做一次,并且需要像这样在PeerConnection对象之间共享stream:

 peerConnection.addStream(mediaStream); 

您可以查阅WebRTC规范或查看此计算器问题以确认PeerConnectiontypes仅用于处理一个同位体。 这里也有些模糊的暗示。

所以你只能维护一个mediaStream对象:

 private MediaStream mediaStream; 

因此,主要思想是一个MediaStream对象,以及与您想要连接的对等体一样多的PeerConnection对象。 因此,您不会使用多个PeerConnectionClient对象,而是修改单个PeerConnectionClient以封装多客户端处理。 如果您确实想出于任何原因使用多个PeerConnectionClient对象的devise,则只需将媒体stream逻辑(以及只应创build一次的任何支持types)抽象出来即可。

您还需要维护多个远程video轨道,而不是现有video轨道:

 private VideoTrack remoteVideoTrack; 

你显然只关心渲染一个本地摄像头并为远程连接创build多个渲染器。

我希望这是足够的信息让你回到正轨。

在Matthew Sanders的回答帮助下,我设法解决了这个问题,所以在这个答案中,我将更详细地描述调整示例代码以支持video会议通话的一种方法:

大多数更改需要在PeerConnectionClient ,但也需要在使用PeerConnectionClient的类中进行,后者与信号服务器进行通信并build立连接。

PeerConnectionClient里面,需要按连接存储以下成员variables:

 private VideoRenderer.Callbacks remoteRender; private final PCObserver pcObserver = new PCObserver(); private final SDPObserver sdpObserver = new SDPObserver(); private PeerConnection peerConnection; private LinkedList<IceCandidate> queuedRemoteCandidates; private boolean isInitiator; private SessionDescription localSdp; private VideoTrack remoteVideoTrack; 

在我的应用程序中,我最多需要3个连接(用于4路聊天),所以我只是存储了每个连接的数组,但是您可以将它们全部放在一个对象中并有一个对象数组。

 private static final int MAX_CONNECTIONS = 3; private VideoRenderer.Callbacks[] remoteRenders; private final PCObserver[] pcObservers = new PCObserver[MAX_CONNECTIONS]; private final SDPObserver[] sdpObservers = new SDPObserver[MAX_CONNECTIONS]; private PeerConnection[] peerConnections = new PeerConnection[MAX_CONNECTIONS]; private LinkedList<IceCandidate>[] queuedRemoteCandidateLists = new LinkedList[MAX_CONNECTIONS]; private boolean[] isConnectionInitiator = new boolean[MAX_CONNECTIONS]; private SessionDescription[] localSdps = new SessionDescription[MAX_CONNECTIONS]; private VideoTrack[] remoteVideoTracks = new VideoTrack[MAX_CONNECTIONS]; 

我向PCObserverSDPObserver类添加了一个connectionId字段,而在PeerConnectionClient构造函数中,我分配了数组中的观察者对象,并将每个观察者对象的connectionId字段设置为其在数组中的索引。 所有引用上面列出的成员variables的PCObserverSDPObserver的方法都应该改为使用connectionId字段索引到适当的数组中。

PeerConnectionClientcallback需要改变:

 public static interface PeerConnectionEvents { public void onLocalDescription(final SessionDescription sdp, int connectionId); public void onIceCandidate(final IceCandidate candidate, int connectionId); public void onIceConnected(int connectionId); public void onIceDisconnected(int connectionId); public void onPeerConnectionClosed(int connectionId); public void onPeerConnectionStatsReady(final StatsReport[] reports); public void onPeerConnectionError(final String description); } 

还有以下PeerConnectionClient方法:

 private void createPeerConnectionInternal(int connectionId) private void closeConnectionInternal(int connectionId) private void getStats(int connectionId) public void createOffer(final int connectionId) public void createAnswer(final int connectionId) public void addRemoteIceCandidate(final IceCandidate candidate, final int connectionId) public void setRemoteDescription(final SessionDescription sdp, final int connectionId) private void drainCandidates(int connectionId) 

与观察者类中的方法一样,所有这些函数都需要更改为使用connectionId索引到每个连接对象的适当数组中,而不是引用它们之前的单个对象。 任何callback函数的调用也需要被改变来传递connectionId

我用一个名为createMultiPeerConnection的新函数replace了createPeerConnection ,它传递了一个VideoRenderer.Callbacks对象的数组来显示远程videostream,而不是一个。 该函数为每个PeerConnection调用createMediaConstraintsInternal() once和createPeerConnectionInternal() ,循环次数从0MAX_CONNECTIONS - 1mediaStream对象仅在首次调用createPeerConnectionInternal() ,只需将初始化代码包装在if(mediaStream == null)检查中即可。

我遇到的一个复杂情况是应用程序closures时, PeerConnection实例closures, MediaStream处理MediaStream 。 在示例代码中,使用addStream(mediaStream)mediaStream添加到PeerConnection ,但不会调用相应的removeStream(mediaStream)函数( dispose()调用dispose() )。 然而,当有多个PeerConnection共享一个MediaStream对象时,会产生一些问题(在本地代码中的MediaStreamInterface中引用计数断言),因为dispose()完成MediaStream ,只有在最后一个PeerConnectionclosures时才会发生。 调用removeStream()close()是不够的,因为它不能完全closuresPeerConnection并且在处理PeerConnectionFactory对象时会导致断言崩溃。 我唯一能find的解决方法是将下面的代码添加到PeerConnection类中:

 public void freeConnection() { localStreams.clear(); freePeerConnection(nativePeerConnection); freeObserver(nativeObserver); } 

然后在确定除最后一个PeerConnection之外的每个PeerConnection时调用这些函数:

 peerConnections[connectionId].removeStream(mediaStream); peerConnections[connectionId].close(); peerConnections[connectionId].freeConnection(); peerConnections[connectionId] = null; 

并closures最后一个像这样:

 peerConnections[connectionId].dispose(); peerConnections[connectionId] = null; 

修改PeerConnectionClient ,需要更改信令代码,以正确的顺序build立连接,将正确的连接索引传递给每个函数,并适当地处理callback。 我通过在socket.io套接字标识和连接标识之间维护一个哈希来实现这一点。 当新客户join房间时,每个现有成员都会向新客户发送一个提议,并依次接收答案。 还需要初始化多个VideoRenderer.Callbacks对象,将它们传递给PeerConnectionClient实例,然后将屏幕分割为不同的电话会议。