Chamadas

**Nota: esta página detalha o princípio das contas Jami. * *

Vamos fazer uma chamada em Jami!

Lado da demônio

Ao criar uma chamada entre dois pares, a Jami usa principalmente protocolos conhecidos, como ICE, SIP ou TLS. No entanto, para torná-lo distribuído, o processo de criação de uma chamada é um pouco diferente. Para resumir, quando alguém quiser entrar em contato com um de seus contatos, é isso que ele fará:

  1. Buscar a presença do contato no DHT (para obter mais detalhes, consulte Gestão de contatos)

  2. Quando o contato for encontrado, envie uma solicitação de chamada, anunciando os candidatos conhecidos (o ip de cada interface de rede + endereços de retransmissão (TURN) + endereços reflexivos (UPnP, públicos).

  3. Aguarde a resposta do contato (eles responderão seus endereços conhecidos).

  4. Negociar a tomada através do ICE. Na verdade, duas sessões do ICE são negociadas. Uma (preferida) no TCP, uma no UDP (como um fallback).

  5. Em seguida, o socket é criptografado em TLS (se TCP) ou DTLS (se UDP).

  6. O contato agora pode aceitar ou recusar a chamada. Quando aceitam, um transporte ICE (UDP apenas por enquanto) é negociado para criar 4 novas tomadas para os meios de comunicação (2 para áudio, 2 para vídeo).

  7. A chamada está viva!

Candidatos de intercâmbio de ICE

Tudo realmente começa em jamiaccount.cpp (JamiAccount::startOutgoingCall). Uma vez que ambos os objetos ICE estiverem prontos e quando o contato for encontrado através do DHT, o pedido de chamada para o contato é elaborado.

dht::IceCandidates(callvid,  blob)

onde callvid é um número aleatório utilizado para identificar a chamada e o blob contém duas mensagens ICE concatenadas (IceTransport::packIceMsg em ice_transport.cpp) contendo a senha da sessão, os candidatos ufrag e ICE.) como:

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

e é enviado através do DHT em uma mensagem criptografada para o dispositivo para hash(callto:xxxxxx) onde xxxxxx é o id do dispositivo. O peer responderá no mesmo local exatamente (mas criptografado para o dispositivo de remissão) seu próprio dht::IceCandidates. Veja JamiAccount::replyToIncomingIceMsg para mais detalhes.

A sessão ICE é criada em ambos os lados quando eles têm todos os candidatos (portanto, para o remetente, quando a resposta do contato é recebida).

Negociação do ICE

As chamadas pendentes são gerenciadas por JamiAccount::handlePendingCallList(), que primeiro espera que a negociação TCP termine (e se ela falhar, espera pela negociação UDP). O código para a negociação do ICE é gerenciado principalmente pelo pjproject, mas para o Jami, a parte interessante está localizada no ice_transport.cpp. Além disso, adicionamos alguns patches/recursos importantes sobre o pjproject que ainda não foram incorporados ao upstream (por exemplo, ICE sobre TCP). Esses patches estão presentes em contrib/src/pjproject.

Encripta a tomada de controle

Uma vez que o socket é criado e gerido por uma instância de IceTransport, ele é então envolto em um SipTransport correspondente a um TlsIceTransport. O código principal está localizado em JamiAccount::handlePendingCall() e o envolvimento é feito em SipTransportBroker::getTlsIceTransport. Finalmente, nossa sessão é gerida por TlsSession em daemon/src/security/tls_session.cpp e usa a biblioteca GnuTLS.

Assim, o controle socket será um TLS (1.3 se sua versão e seus pares gnutls suportam) se um socket TCP é negociado. Se um socket UDP é negociado em vez (devido a restrições de firewall / problema na negociação / etc), o socket vai usar DTLS (ainda gerenciado pelas mesmas partes).

A tomada de controle é usada para transmitir pacotes SIP, como convites, mensagens personalizadas (Jami envia o VCard do seu perfil nesta tomada no início da chamada, ou a rotação da câmera), mensagens de texto.

Artigos relacionados:

  • https://jami.net/improved-video-rotation-support/

  • https://jami.net/peer-to-peer-file-sharing-support-in-jami/

Soquetes de mídia

Os soquetes de mídia são soquetes SRTP em que a chave é negociada por meio da sessão TLS criada anteriormente. . PARA FAZER

Arquitetura

PARA FAZER

Múltiplos fluxos

Desde a versão 13.3.0 da daemon, multi-stream é totalmente suportado. Este recurso permite que os usuários compartilhem vários vídeos durante uma chamada ao mesmo tempo. Nas seguintes partes, descrevemos todas as mudanças relacionadas.

pjsip

A primeira parte é negociar fluxos de mídia suficientes. Na verdade, cada fluxo de mídia usa 2 sockets UDP. Consideramos três cenários:

  1. Se é o anfitrião de uma conferência que quer adicionar mídia, não há mais nada para negociar, porque já misturamos os vídeos num único fluxo.

  2. Se estamos em 1:1, por enquanto, como não há informações de conferência, multi-stream não é suportado.

  3. Caso contrário, 2 novos soquetes são negociados para novas mídias.

Para que o pjsip seja capaz de gerar mais sockets por sessão ICE, PJ_ICE_COMP_BITS foi modificado para 5 (que corresponde a 2^5, portanto, 32 fluxos).

Deprecar interruptorInput, solicitação de suporteMediaChange

No daemon, a antiga API switchInput é agora DEPRECATED; igual para 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 substitui esta, tanto para as chamadas como para as conferências:

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

Compatibilidade

Se uma chamada for feita com um peer onde a versão do daemon é < 13.3.0, multi-stream não é habilitada e o comportamento antigo é usado (apenas 1 vídeo).

Identificação dos fluxos

Como agora podem haver vários fluxos, cada fluxo de mídia é identificado pelo seu identificador, e o formato é “_”; por exemplo: “audio_0”, “video_2”, etc.

Rotatividade

O XML foi atualizado para adicionar o fluxo desejado:

<?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>

Quadro-chave

O XML foi atualizado para adicionar o fluxo desejado:

<?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>

Atividade de voz

O XML foi atualizado para adicionar o fluxo desejado:

<?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>

Conferência

As alterações refletidas estão documentadas aqui.

Cliente

Mesmo que o back-end suporta até 32 mídias ao mesmo tempo, exceto para clientes personalizados, atualmente recomendamos dar apenas a capacidade de compartilhar uma câmera e um vídeo ao mesmo tempo.

No client-qt, a parte interessante é em AvAdapter (métodos como isCapturing, shareAllScreens, stopSharing). Na lógica da biblioteca, addMedia e removeMedia no callModel usam diretamente o requestMediaChange e podem ser usados como referência de design.