2015-10-04 12 views
22

Sto cercando di implementare la chat video a 3 vie all'interno di un'app Android utilizzando lo WebRTC Native Code package for Android (ovvero non utilizzando una WebView). Ho scritto un server di segnalazione usando node.js e ho usato la libreria Gottox socket.io java client all'interno dell'app client per connettermi al server, scambiare pacchetti SDP e stabilire una connessione di chat video a 2 vie.Come implementare la videochat di videoconferenza a 3 vie con il codice nativo WebRTC per Android?

Tuttavia ora ho problemi ad andare oltre a una chiamata a 3 vie. L'app AppRTCDemo fornita con il pacchetto di codice nativo WebRTC mostra solo le chiamate bidirezionali (se una terza parte tenta di entrare in una stanza viene restituito un messaggio "stanza piena").

Secondo this answer (che non si riferisce specificamente ad Android), dovrei farlo creando più PeerConnections, in modo che ogni partecipante di chat si connetterà agli altri 2 partecipanti.

Tuttavia, quando creo più di un PeerConnectionClient (una classe Java che racchiude un PeerConection, che è implementato sul lato nativo in libjingle_peerconnection_so.so), esiste un'eccezione generata dalla libreria risultante da un conflitto con entrambi di loro cercando di accedere alla videocamera:

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) 

Ciò si verifica quando l'inizializzazione del client locale ancor prima di tentare di stabilire una connessione quindi non è legato alla node.js, socket.io o una qualsiasi delle cose server di segnalazione.

Come ottengo più PeerConnections per condividere la fotocamera in modo che possa inviare lo stesso video a più di un pari?

Un'idea che avevo era di implementare una sorta di classe di telecamere singleton per sostituire VideoCapturerAndroid che potrebbe essere condivisa tra più connessioni, ma non sono nemmeno sicuro che funzionerebbe e mi piacerebbe sapere se c'è un modo per effettuare chiamate a 3 vie utilizzando l'API prima di iniziare a fare hacking all'interno della libreria.

E 'possibile e se sì, come?

Aggiornamento:

Ho provato condivisione di un oggetto VideoCapturerAndroid tra più PeerConnectionClients, creando per la prima connessione solo e passando nella funzione di inizializzazione per le successive, ma che portato a questa "Capturer può solo essere preso una volta! " eccezione durante la creazione di un secondo VideoTrack dall'oggetto VideoCapturer per la seconda connessione peer:

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) 

che condividono l'oggetto VideoTrack tra PeerConnectionClients ha provocato questo errore dal codice nativo:

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. 

Condivisione della MediaStream tra i risultati PeerConnectionClients l'app si chiude bruscamente, senza alcun messaggio di errore visualizzato nel logcat.

+0

Posso chiedere cosa significa "video a 3 vie"? – SilentKnight

+0

@ SilentKnight una videoconferenza con 3 partecipanti – samgak

+0

@ Samgak Ciao. Puoi condividere la soluzione completa? Ho avuto problemi nel collegare più audio. – GensaGames

risposta

15

Il problema che si sta avendo è che PeerConnectionClient è non un wrapper PeerConnection si contiene un PeerConnection.

Ho notato che questa domanda non ha avuto risposta, quindi volevo vedere se potevo dare una mano. Ho esaminato il codice sorgente e PeerConnectionClient è molto codificato per un singolo peer remoto.Si avrebbe bisogno di creare una collezione di oggetti PeerConnection piuttosto che questa linea:

private PeerConnection peerConnection; 

Se si guarda intorno un po 'di più si nota che diventa un po' più complicato che poi.

La logica MediaStream in createPeerConnectionInternal dovrebbe essere fatto solo una volta ed è necessario condividere il flusso tra gli oggetti PeerConnection come questo:

peerConnection.addStream(mediaStream); 

puoi consultare il WebRTC spec o dare un'occhiata a questo stackoverflow domanda per confermare che il tipo PeerConnection è stato progettato per gestire solo un peer. È anche un po 'vagamente implicito here.

Così si mantengono solo oggetto MediaStream:

private MediaStream mediaStream; 

Così ancora una volta l'idea principale è un oggetto MediaStream e, come molti oggetti PeerConnection quando si dispone di coetanei che si desidera connettersi. Quindi non utilizzerai più oggetti PeerConnectionClient, ma piuttosto modifichi PeerConnectionClient singolo per incapsulare la gestione multi-client. Se si desidera utilizzare una progettazione di più oggetti PeerConnectionClient per qualsiasi motivo, è sufficiente astrarre la logica del flusso multimediale (e tutti i tipi di supporto che dovrebbero essere creati una volta sola) al di fuori di essa.

Sarà inoltre necessario per mantenere le tracce video multiple a distanza piuttosto che quella esistente:

private VideoTrack remoteVideoTrack; 

Si sarebbe ovviamente solo la cura per rendere l'una telecamera locale e creare più renderizzatori per le connessioni remote.

Spero che questa informazione sia sufficiente per rimetterti in carreggiata.

+0

Grazie per la risposta.Ho già provato a condividere un oggetto MediaStream tra più PeerConnectionClients senza successo, proverò il tuo suggerimento di utilizzare un singolo PeerConnectionClient con più PeerConnections – samgak

+0

Le eccezioni ricevute provengono esattamente da questo. Utilizzo di più PeerConnectionClients. Questo proverebbe a usare la fotocamera due volte. Mi rendo conto che hai detto che hai cercato di ridirigere quella parte. Sto solo partendo dal presupposto che qualcosa deve essere stato lasciato fuori nel refactor perché c'è una buona quantità di logica che avresti bisogno di spostare. Hai avuto solo un flusso? Perché userà il videoCapturer mediaStream.addTrack (createVideoTrack (videoCapturer)); –

+0

Sì, ho solo un flusso, ho provato a condividerlo tra PeerConnectionClients semplicemente creando uno dopo l'altro e passando il MediaStream creato dal primo nel resto (nel qual caso la linea di codice nel tuo commento non viene eseguita) . Non è elegante ma stavo solo cercando di capire quali cose devono essere condivise e quali sono per connessione e arrivare allo stadio in cui posso inizializzare i PeerConnectionClients senza errori (prima di collegarmi effettivamente a qualsiasi peer). – samgak

6

Con l'aiuto di risposta Matthew Sanders' sono riuscito a farlo funzionare, quindi in questa risposta ho intenzione di descrivere più in dettaglio un modo di adattare il codice di esempio per supportare video conferenza chiamando:

Most delle modifiche devono essere apportate in PeerConnectionClient, ma anche nella classe che utilizza PeerConnectionClient, che è dove si comunica con il server di segnalazione e impostare le connessioni.

All'interno PeerConnectionClient, i seguenti Stati variabili devono essere conservati per-collegamento:

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; 

Nella mia domanda ho bisogno di un massimo di 3 connessioni (per una chiacchierata a 4 vie), quindi ho solo memorizzato un array di ciascuno, ma è possibile inserirli tutti all'interno di un oggetto e disporre di una matrice di oggetti.

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]; 

ho aggiunto un campo connectionId alle classi PCObserver e SDPObserver, e dentro il costruttore PeerConnectionClient mi assegnati gli oggetti osservatori nella matrice e impostare il campo connectionId per ogni oggetto osservatore suo indice nell'array. Tutti i metodi di PCObserver e SDPObserver che fanno riferimento alle variabili membro elencate sopra devono essere modificati per indicizzare nell'array appropriato utilizzando il campo connectionId.

Il PeerConnectionClient callback devono essere modificate:

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); 
} 

E anche i seguenti PeerConnectionClient metodi:

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) 

Come con i metodi delle classi di osservatori, tutte queste funzioni devono essere modificate per utilizzare lo connectionId per indicizzare l'array appropriato di oggetti per connessione, anziché fare riferimento ai singoli oggetti che erano in precedenza. Eventuali invocazioni delle funzioni di callback devono essere cambiate anche per passare il connectionId indietro.

ho sostituito createPeerConnection con una nuova funzione chiamata createMultiPeerConnection, che viene passato un array di VideoRenderer.Callbacks oggetti di visualizzazione del flusso video remoto, invece di uno solo. La funzione chiama createMediaConstraintsInternal() una volta e createPeerConnectionInternal() per ciascuna delle PeerConnection s, a ciclo da 0 a MAX_CONNECTIONS - 1. L'oggetto mediaStream viene creato solo alla prima chiamata a createPeerConnectionInternal(), semplicemente avvolgendo il codice di inizializzazione in un controllo if(mediaStream == null).

Una complicazione che ho riscontrato è stata quando l'app si è arrestata e le istanze PeerConnection sono state chiuse e lo MediaStream eliminato. Nel codice di esempio lo mediaStream viene aggiunto a PeerConnection utilizzando addStream(mediaStream), ma la corrispondente funzione removeStream(mediaStream) non viene mai chiamata (viene invece chiamato dispose()). Tuttavia ciò crea problemi (un riferimento di riferimento in MediaStreamInterface nel codice nativo) quando c'è più di uno oggetto di condivisione dispose() finalizza lo MediaStream, che dovrebbe avvenire solo quando l'ultimo PeerConnection viene chiuso. Chiamare il numero removeStream() e close() non è sufficiente, perché non spegne completamente lo PeerConnection e questo porta a un arresto anomalo del sistema quando si smaltisce l'oggetto PeerConnectionFactory. L'unica soluzione che ho trovato è stato quello di aggiungere il seguente codice alla classe PeerConnection:

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

e quindi chiamando queste funzioni quando finalizzare ogni PeerConnection tranne l'ultimo:

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

e spegnere l'ultimo in questo modo:

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

Dopo aver modificato PeerConnectionClient, è necessario modificare il codice di segnalazione per impostare il conn ections nell'ordine corretto, passando l'indice di connessione corretto a ciascuna funzione e gestendo le callback in modo appropriato. Ho fatto questo mantenendo un hash tra gli ID socket socket.io e un id di connessione. Quando un nuovo cliente si unisce alla stanza, ciascuno dei membri esistenti invia un'offerta al nuovo cliente e riceve una risposta a sua volta. È inoltre necessario inizializzare più oggetti VideoRenderer.Callbacks, passarli nell'istanza PeerConnectionClient e dividere lo schermo come si desidera per una chiamata in conferenza.

+0

Sono felice di poterti aiutare, e ancora più felice di vedere la tua risposta dettagliata che ha risolto il tuo problema! –

+0

@samgak Bel lavoro, questo. Posso chiederti come puoi raggiungere freePeerConnection() e freeObserver()? Queste funzioni sono private e PeerConnection non ha un costruttore pubblico come viene creato da PeerConnectionFactory. Quindi immagino di non poterlo estendere e l'unico modo che vedo è copiare l'intera libreria e modificarla. –

+1

@OliverHausler sì sfortunatamente è quello che dovevo fare. Non sono riuscito a trovare un modo per chiudere PeerConnections senza che si verifichino arresti anomali senza modificare la classe PeerConnection nella libreria. – samgak

Problemi correlati