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:

  1. Rechercher la présence de contacts sur le DHT (pour plus de détails, voir Gestion des contacts)

  2. 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).

  3. Attendez la réponse du contact (il répondra à ses adresses connues).

  4. En fait, deux sessions ICE sont négociées. Une (privé) en TCP, une en UDP (en tant que rétroaction).

  5. Ensuite, la prise est cryptée en TLS (si TCP) ou DTLS (si UDP).

  6. 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).

  7. 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)

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) 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:

  1. 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.

  2. 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.

  3. 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 « _ »; par exemple: « audio_0 », « video_2 », etc.

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

Les modifications reflétées sont documentées ici.

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.