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á:
Buscar a presença do contato no DHT (para obter mais detalhes, consulte Gestão de contatos)
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).
Aguarde a resposta do contato (eles responderão seus endereços conhecidos).
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).
Em seguida, o socket é criptografado em TLS (se TCP) ou DTLS (se UDP).
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).
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:
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.
Se estamos em 1:1, por enquanto, como não há informações de conferência, multi-stream não é suportado.
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 é “
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.