Appels
** NOTE: cette page détaille le principe des comptes Jami. Pour les comptes SIP, le protocole SIP est utilisé.**
Faisons un appel à Jami!
Le côté du Daemon
Lorsqu’il crée un appel entre deux pairs, Jami utilise principalement des protocoles connus tels que ICE, SIP ou TLS. Cependant, pour le faire distribuer, le processus de création d’un appel est un peu différent. Pour résumer, quand quelqu’un veut contacter un de ses contacts, voici ce qu’il fera:
Rechercher la présence de contacts sur le DHT (pour plus de détails, voir Gestion des contacts)
Une fois le contact trouvé, envoyez une demande d’appel, en annonçant les candidats connus (l’IP de chaque interface réseau + adresses de relais (TURN) + adresses réflexives (UPnP, public).
Attendez la réponse du contact (il répondra à ses adresses connues).
En fait, deux sessions ICE sont négociées. Une (privé) en TCP, une en UDP (en tant que rétroaction).
Ensuite, la prise est cryptée en TLS (si TCP) ou DTLS (si UDP).
Le contact est désormais en mesure d’accepter ou de refuser l’appel. Lorsqu’il accepte, un transport ICE (UDP seulement pour l’instant) est négocié pour créer 4 nouvelles prises pour les médias (2 pour l’audio, 2 pour la vidéo).
L’appel est maintenant vivant!
Échange de candidats à l’ICE
Tout commence vraiment dans jamiaccount.cpp
(JamiAccount::startOutgoingCall
). Une fois que les deux objets ICE sont prêts et que le contact est trouvé via le DHT, la demande d’appel pour le contact est créée. Cette demande contient toutes les informations nécessaires à la session ICE à distance définie par:
dht::IceCandidates(callvid, blob)
où callvid
est un numéro aléatoire utilisé pour identifier l’appel et où le blob contient deux messages ICE concatenés (IceTransport::packIceMsg
en ice_transport.cpp
) contenant le mot de passe de la session, le ufrag et les candidats ICE.) comme:
0d04b935
7c33834e7cf944bf0e367b42
H6e6ca382 1 UDP 2130706431 2607:fad8:4:6:9eb6:d0ff:dead:c0de 14133 typ host
H42c1g477 1 UDP 2130706431 fe80::9eb6:d0ff:fee7:1412 14133 typ host
Hc0a8027e 1 UDP 2130706431 192.168.0.123 34567 typ host
Sc0a8027e 1 UDP 1694498815 X.X.X.X 32589 typ srflx
0d04b932
7c33834e7cf944bf0e367b47
H6e6ca682 1 TCP 2130706431 2607:fad8:4:6:9eb6:d0ff:dead:c0de 50693 typ host tcptype passive
H6e6ca682 1 TCP 2130706431 2607:fad8:4:6:9eb6:d0ff:dead:c0de 9 typ host tcptype active
H42c1b577 1 TCP 2130706431 fe80::9eb6:d0ff:fee7:1412 50693 typ host tcptype passive
H42c1b577 1 TCP 2130706431 fe80::9eb6:d0ff:fee7:1412 9 typ host tcptype active
Hc0a8007e 1 TCP 2130706431 192.168.0.123 42751 typ host tcptype passive
Hc0a8007e 1 TCP 2130706431 192.168.0.123 9 typ host tcptype active
Sc0a8007e 1 TCP 1694498815 X.X.X.X 42751 typ srflx tcptype passive
et est envoyé par l’intermédiaire du DHT dans un message crypté pour le dispositif à hash(call:xxxxxx)
où xxxxxx
est l’id du dispositif. Le pair répondra exactement au même endroit (mais crypté pour le dispositif d’expédition) son propre dht::IceCandidates
. Voir JamiAccount::replyToIncomingIceMsg
pour plus de détails.
La session ICE est créée des deux côtés lorsqu’ils ont tous les candidats (donc pour l’expéditeur, lorsque la réponse du contact est reçue).
Les négociations ICE
Les appels en attente sont gérés par JamiAccount::handlePendingCallList()
, qui attend d’abord que la négociation TCP se termine (et si elle échoue, attend celle de l’UDP). Le code de la négociation ICE est principalement géré par [pjproject] (https://github.com/pjsip/pjproject) mais pour Jami, la partie intéressante est située dans ice_transport.cpp
. En outre, nous ajoutons quelques correctifs/fonctionnalités importants en plus de pjproject qui n’ont pas été fusionnés en amont pour le moment (par exemple, ICE sur TCP). Ces correctifs sont présents dans contrib/src/pjproject
.
Chiffrer la socket de contrôle
Une fois que la prise est créée et gérée par une instance IceTransport, elle est ensuite enveloppée dans un SipTransport correspondant à un TlsIceTransport. Le code principal est situé dans JamiAccount::handlePendingCall()
et l’enveloppe est effectuée dans SipTransportBroker::getTlsIceTransport
. Enfin, notre session est gérée par TlsSession en daemon/src/security/tls_session.cpp
et utilise la bibliothèque GnuTLS.
Ainsi, la prise de contrôle sera un TLS (1.3 si votre version et celle de vos pairs gnutls le supportent) si une prise TCP est négociée. Si une prise UDP est négociée à la place (en raison des restrictions du pare-feu / problème dans la négociation / etc.), la prise utilisera DTLS (encore géré par les mêmes parties).
La prise de contrôle est utilisée pour transmettre des paquets SIP, comme des invitations, des messages personnalisés (Jami envoie la carte V de votre profil sur cette prise au début de l’appel, ou la rotation de la caméra), des messages texte.
Articles connexes:
https://jami.net/improved-video-rotation-support/
https://jami.net/peer-to-peer-file-sharing-support-in-jami/
Sockets multimédia
Les sockets multimédia sont des sockets SRTP où la clé est négociée par l’intermédiaire de la session TLS créée précédemment. ** A FAIRE**
Architecture
A FAIRE
Multi-stream
Depuis la version 13.3.0 de Daemon, le multi-stream est entièrement pris en charge. Cette fonctionnalité permet aux utilisateurs de partager plusieurs vidéos pendant un appel en même temps. Dans les parties suivantes, nous décrirons toutes les modifications connexes.
le plâtre
La première partie est de négocier suffisamment de flux de médias. En fait, chaque flux de médias utilise 2 prises UDP. Nous examinons trois scénarios:
Si c’est l’hôte d’une conférence qui veut ajouter des médias, il n’y a plus rien à négocier, parce que nous mélangons déjà les vidéos dans un flux.
Si nous sommes à 1:1, pour l’instant, comme il n’y a pas d’informations de conférence, multi-stream n’est pas pris en charge.
Sinon, deux nouvelles prises sont négociées pour de nouveaux médias.
Pour que pjsip puisse générer plus de prises par session ICE, PJ_ICE_COMP_BITS
a été modifié en 5
(qui correspond à 2^5
, donc 32 flux).
Déprécier switchInput, prendre en charge requestMediaChange
Dans le daemon, l’ancienne API switchInput
est maintenant DEPRECATED; la même pour switchSecondaryInput
:
<method name="switchInput" tp:name-for-bindings="switchInput">
<tp:docstring>
Switch input for the specified call
</tp:docstring>
<arg type="s" name="accountId" direction="in"/>
<arg type="s" name="callId" direction="in"/>
<arg type="s" name="input" direction="in"/>
<arg type="b" direction="out" />
</method>
<method name="switchSecondaryInput" tp:name-for-bindings="switchSecondaryInput">
<tp:added version="11.0.0"/>
<tp:docstring>
Switch secondary input for the specified conference
</tp:docstring>
<arg type="s" name="accountId" direction="in" />
<arg type="s" name="conferenceId" direction="in"/>
<arg type="s" name="input" direction="in"/>
<arg type="b" direction="out" />
</method>
requestMediaChange
remplace ce terme, tant pour les appels que pour les conférences:
<method name="requestMediaChange" tp:name-for-bindings="requestMediaChange">
<tp:added version="11.0.0"/>
<tp:docstring>
<p>Request changes in the media of the specified call.</p>
</tp:docstring>
<arg type="s" name="accountId" direction="in" />
<arg type="s" name="callId" direction="in">
<tp:docstring>
The ID of the call.
</tp:docstring>
</arg>
<annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="VectorMapStringString"/>
<arg type="aa{ss}" name="mediaList" direction="in">
<tp:docstring>
A list of media attributes to apply.
</tp:docstring>
</arg>
<arg type="b" name="requestMediaChangeSucceeded" direction="out"/>
</method>
Compatibilité
Si un appel est effectué avec un peer où la version du daemon est < 13.3.0, multi-stream n’est pas activé et l’ancien comportement est utilisé (1 vidéo seulement).
Identification des courants
Parce qu’il peut y avoir plusieurs flux maintenant, chaque flux multimédia est identifié par son identifiant, et le format est «
Retour
Le XML a été mis à jour pour ajouter le flux recherché:
<?xml version="1.0" encoding="utf-8" ?>
<media_control>
<vc_primitive>
<stream_id>{}</stream_id>
<to_encoder>
<device_orientation>0</device_orientation>
</to_encoder>
</vc_primitive>
</media_control>
Le cadre de la clé
Le XML a été mis à jour pour ajouter le flux recherché:
<?xml version="1.0" encoding="utf-8" ?>
<media_control>
<vc_primitive>
<stream_id>{}</stream_id>
<to_encoder><picture_fast_update/></to_encoder>
</vc_primitive>
</media_control>
Activité vocale
Le XML a été mis à jour pour ajouter le flux recherché:
<?xml version="1.0" encoding="utf-8" ?>
<media_control>
<vc_primitive>
<stream_id>{}</stream_id>
<to_encoder>
<voice_activity>true</voice_activity>
</to_encoder>
</vc_primitive>
</media_control>
Conférence
Reflected changes are documented here.
Client
Même si le back-end prend en charge jusqu’à 32 supports simultanément, sauf pour les clients personnalisés, nous recommandons actuellement de ne donner la possibilité de partager qu’une caméra et une vidéo en même temps.
Dans le client-qt, la partie intéressante est dans AvAdapter
(méthodes comme isCapturing
, shareAllScreens
, stopSharing
). Dans la logique de la bibliothèque, addMedia
et removeMedia
dans le callModel
utilisent directement le requestMediaChange
et peuvent être utilisés comme référence de conception.