Asterisk® SCF™ - PJSIP Negociação Avançada de CODEC
Este post não está completo e nem o software em 15 de julho de 2020. Ainda há muitas coisas para imprementar e/ou testar.
- Direct Media (Mídia Direta);
- 100rel/early media;
- re-INVITES;
- Fax;
- Multi-Stream (Transmissão Múltipla);
- Deferred SDP (Deferimento do SDP);
- ARI Channel Operations (Operações do Canal ARI);
- Interoperabilidade com outros drivers de canal;
- Função do Dialplan;
- etc.
Bem, ainda estamos trabalhando nisso.
Introdução
Com o lançamento do Asterisk® SCF™ versão 18, vem um novo processo de Negociação Avançada de CODEC. Isso não apenas cria novas oportunidades de configuração, mas também refatora completamente o próprio processo de negociação. O resultado é um processo de negociação mais fácil de entender, imprementado em muito menos código. O driver do canal PJSIP é atualmente o único a adotar o novo processo, mas seus outros drivers podem ser alterados para usá-lo no futuro. No entanto, eles teriam que adotar a interface do STREAMS.
Arquitetura
Ao pensar no processo de negociação, estamos realmente falando sobre a negociação de topologias de fluxo entre duas entidades. Para cada canal, existe uma topologia que contém um ou mais fluxos que descrevem a mídia. Na verdade, o processo de negociação acontece stream a stream, mas vamos usar a " chamada de áudio simples " como exemplo. A topologia conteria apenas um fluxo de " áudio ", mas esse fluxo poderia, é claro, permitir vários codecs.
Uma vez que o Asterisk® SCF™ é um Agente de Usuário Back-2-Back, não há praticamente nenhum cenário (mesmo com Mídia Direta) onde as partes chamadoras e chamadas negociam diretamente umas com as outras. Temos 2 canais e o CORE do Asterisk® SCF™ envolvido. Com isso em mente, se Alice chama Bob, Alice negocia com o driver de canal de Alice, o driver de canal de Alice negocia com o driver de canal de Bob via CORE do Asterisk® SCF™, então o driver de canal de Bob negocia com Bob. Diante disso, existem 4 pontos de controle onde podemos alterar o comportamento do processo de negociação...
- Depois de recebermos a oferta de SDP de Alice, mas antes de o driver enviar a oferta ao CORE do Asterisk® SCF™. (incoming_offer);
- Depois que o CORE do Asterisk® SCF™ enviou a oferta para o canal de Bob, mas antes de enviarmos uma oferta SDP para Bob. (outgoind_offer);
- Depois de recebermos a resposta SDP de Bob, mas antes de enviá-la ao CORE do Asterisk® SCF™. (incoming_answer);
- Após o CORE do Asterisk® SCF™ ter enviado a resposta para o canal de Alice, mas antes de enviarmos uma resposta SDP para Alice. (outgoing_answer).
Em todos esses casos, temos duas topologias que podemos alternar, uma " pendente "(pending) e uma " configurada " (configured), e várias operações de seleção e filtragem que podemos realizar nelas. O resultado é uma topologia " resolvida " (resolved). O processo de resolução é controlado por quatro parâmetros...
- prefer: Qual da lista de CODECs no stream nós preferimos? O pendente ou o configurado?
- incoming_offer: Este é bastante óbvio. Pendente é a topologia criada analisando a oferta do SDP de Alice e Configurada é aquela criada a partir da lista de CODECs permitidos no terminal de Alice.
- output_offer: Este é um tanto óbvio. Pendente é o resultado da resolução incoming_offer de Alice que foi recebida do CORE do Asterisk® SCF™ e Configurado é aquele criado a partir da lista de CODECs permitidos no terminal de Bob.
- incoming_answer: Este é menos óbvio. Pendente é o resultado da análise da resposta SDP de Bob e Configurado é o que enviamos a Bob na oferta.
- outgoing_answer: Este também é menos óbvio. Pendente é o resultado da resolução incoming_answer de Bob e Configurado é o resultado da resolução incoming_offer de Alice.
- operation: Agora que sabemos qual topologia preferimos, que operação queremos realizar nelas?
- union: Combinamos os codecs de ambas as topologias começando com a lista preferencial e adicionando ao final os codecs da lista não preferencial que ainda não estavam na lista preferencial. Basicamente, estamos preservando a ordem da topologia preferida.
- intersect: Começamos com a lista preferida novamente, mas descartamos quaisquer CODECs que não estejam em ambas as listas. Isso mantém apenas os CODECs comuns enquanto preserva a ordem da lista preferencial.
- only_preferred: Apenas usamos a lista de preferidos e descartamos completamente a lista de não preferidos.
- only_nonpreferred: Apenas usamos a lista de não preferidos e descartamos completamente a lista de preferidos.
- keep: Agora que temos uma lista filtrada e ordenada, o que guardamos dela?
- all: Mantenha-os todos;
- first: Passe apenas pelo primeiro CODEC na lista resultante.
- Transcode: Finalmente, permitimos a transcodificação ou não?
- allow: Permite que a chamada prossiga mesmo se a lista resultante não tiver CODECs nela.
- prevent: NÃO permite que a chamada prossiga se a lista resultante não tiver CODECs nela.
Os quatros pontos de controle e seus parâmetros são todos configurados nos terminais PJSIP. Os parâmetros do ponto de controle são nomeados...
- codec_prefs_incoming_offer;
- codec_prefs_outgoing_offer;
- codec_prefs_incoming_answer;
- codec_prefs_outgoing_answer.
Os parâmetros são especificados como pares <nome>:<valor> separados por vírgulas (o espaço em branco é ignorado). Aqui está um exemplo, incluindo uma declaração de permissão, mostrando os padrões para cada ponto de controle...
Padrões de preferência de negociação de CODEC
allow = !all,g722,ulaw
codec_prefs_incoming_offer = prefer: pending, operation: union, keep: all, transcode: allow
codec_prefs_outgoing_offer = prefer: pending, operation: intersect, keep: all, transcode: allow
codec_prefs_outgoing_offer = prefer: pending, operation: union, keep: all, transcode: allow
codec_prefs_outgoing_offer = prefer: pending, operation: union, keep: all, transcode: allow
Você notará que os padrões sempre preferem a topologia "pendente", portanto, em nosso exemplo, o que Alice envia em sua oferta SDP define o cenário.
Realmente Simples
Vamos começar com uma chamada básica em que Alice e Bob são configurados com suas configurações padrão e ambos com um parâmetro "allow" definido como "!all,ulaw,g722".
- Alice faz uma oferta => Asterisk® SCF™ => Bob:
- Alice envia um INVITE com uma oferta SDP contendo ulaw e g722 nessa ordem.
- O canal de Alice resolve essa topologia com os codecs configurados de seu endpoint e as preferências de incoming_offer. Como preferimos Pendentes e a operação é union (união), o resultado do primeiro estágio não surpreende: ulaw,g722. Vamos manter ambos os CODECs e a transcodificação realmente não importa neste momento.
- O canal de Alice então envia a chamada para o CORE do Asterisk® SCF™ junto com a topologia resolvida.
- O CORE do Asterisk® SCF™ invoca o dialplan que cria o canal de saída de Bob e encaminha a topologia resolvida para ele.
- O canal de Bob agora resolve a topologia que veio do CORE do Asterisk® SCF™ (pendente) com os CODECs configurados de seu próprio terminal e as preferências de output_offer. A única diferença entre suas preferências e as de Alice é a operação. Como ambas as topologias já são iguais, o resultado ainda é ulaw,g722.
- Com base nisso, o canal de Bob cria um INVITE de saída com uma oferta SDP contendo ulaw,g722 nessa ordem.
- Bob aceita a oferta => Asterisk® SCF™ => Alice.
- Bob responde a oferta com uma resposta do tipo SDP contendo apenas ulaw.
- O canal de Bob resolve a topologia de resposta recebida (pendente) com a topologia enviada para Bob (configurada) que tem ulaw,g722 e as preferências de resposta recebida de Bob. Como a operação é union (união), a topologia resolvida contém apenas ulaw.
- O driver de canal de Bob passa a topologia resolvida de volta ao CORE do Asterisk® SCF™, pois indica que a chamada está sendo atendida (answered).
- O CORE do Asterisk® SCF™ passa a topologia resolvida de volta para o canal de Alice, uma vez que informa que Bob respondeu (answered).
- O canal de Alice resolve a topologia do CORE do Asterisk® SCF™ (pendente), com a topologia que originalmente enviou para o CORE do Asterisk® SCF™ (configurado) que tinha ulaw,g722 e as preferências de outgoing_answer de Alice. Novamente, como a operação é union (união), a topologia resolvida contém apenas ulaw.
- O canal de Alice então envia a resposta SDP de volta para Alice com apenas ulaw.
Uma mudança na ordem
Sabemos que os telefones de Alice e Bob podem suportar g722, mas seus telefones sempre listam ulaw primeiro. Então, como podemos fazê-los usar o g722? Vamos fazer algumas alterações de configuração...
- Começamos alterando as preferências de oferta de entrada de Alice para preferir a topologia configurada em vez da pendente.
- codec_prefs_incoming_offer = prefer: configured, operation: union, keep: all, transcode: allow
- Em seguida, configuramos os CODECs de Alice para g722,ulaw.
- allow = !all,g722,ulaw
Agora vamos ligar...
- Alice faz uma oferta => Asterisk® SCF™ => Bob:
- Alice envia um INVITE com uma oferta SDP contendo ulaw e g722 nessa ordem.
- O canal de Alice resolve essa topologia (pendente) com os CODECs configurados de seu terminal (g722,ulaw) e as preferências de incoming_offer. Como preferimos configurado e a operação é union (união), o resultado do primeiro estágio agora é g722,ulaw.
O restante da chamada flui como antes, exceto que g722,ulaw é a topologia pendente. Pode haver um " gotcha " (te peguei). A RFC3264 afirma que um agente de usuário que recebe uma oferta NÃO DEVE alterar a ordem dos codecs ao criar sua resposta. Não diz que NÃO DEVE alterar o pedido. Portanto, é possível, embora altamente improvável, que Bob possa responder a g722,ulaw com ulaw,g722. Se for esse o caso, você pode forçar Bob a usar g722 definindo seu parâmetro de manutenção de outgoing_offer como primeiro. Desta forma, enviaremos apenas g722. Claro, se ele não suporta g722, você não deveria tê-lo configurado em seu terminal em primeiro lugar. 😂
Transcodificação
Isso é onde fica complicado...
- Digamos que o terminal de Alice esteja configurado apenas com alaw como CODEC, mas ela envia apenas ulaw,g722. Se a topologia resolvida estiver vazia, como seria se a operação fosse union (união), a chamada é encerrada imediatamente com um 488 Not Acceptable Here. Não importa qual é o parâmetro de transcodificação definido porque, neste ponto, nem sabemos qual é o canal de saída.
- Agora vamos supor que o endpoint de Alice tenha ulaw,g722. Como o terminal dela também tinha ulaw,g722, nós o enviamos para o CORE do Asterisk® SCF™. O endpoint de Bob, no entanto, tinha apenas um CODEC, alaw! E sua operação era union (união). Isso resultaria em uma topologia resolvida vazia. Para que a transcodificação seja considerada, o parâmetro de transcodificação de incoming_offer de Alice e o parâmetro de transcodificação de outgoing_offer de Bob DEVEM ser configurados para permitir. Se um deles for "impedir", a chamada será encerrada. Se ambos forem permitidos, enviaremos uma oferta a Bob com alaw como CODEC.
- Se Bob responder sem CODECs, a chamada será encerrada. Novamente, porém, você provavelmente não deveria ter configurado o endpoint de Bob com um CODEC não suportado. Caso contrário, a topologia resolvida (alaw) é passada de volta para o CORE do Asterisk® SCF™.
- O canal de Alice obtém a topologia do núcleo (pendente) e a resolve em relação ao que foi enviado ao CORE do Asterisk® SCF™ (configurado) e às preferências de outgoing_answer de seu terminal. Se a topologia resultante estiver vazia, como seria neste caso, o parâmetro de transcodificação output_answer será verificado. Se for permitido, o canal usará a topologia originalmente enviada ao CORE do Asterisk® SCF™ para construir a resposta de saída e simplesmente ignorará a topologia resolvida. Se o parâmetro transcode for prevent (o que provavelmente foi uma configuração incorreta), a chamada será encerrada.
Implementação nos bastidores
A implementação antiga tinha negociação de CODEC espalhada por chan_pjsip, res_pjsip_session e res_pjsip_sdp_rtp. A ACN tenta consolidar toda a negociação de CODECs em chan_pjsip, mas ainda há resquícios nos outros módulos que precisarão ser refatorados. Um bom exemplo é a função "set_caps" em res_pjsip_sdp_rtp. Ele é chamado tanto nas respostas recebidas quanto nas respostas enviadas, mas na verdade não queremos que ele seja executado para respostas enviadas, pois ele tenta definir os limites para o que estava na oferta recebida original. Isso não é bom. Tudo funciona como pretendido, mas é um código inútil. Outro problema é que muitas das funções nos módulos res_pjsip são reutilizadas e não têm ideia do contexto em que são executadas. Por exemplo, apply_negotiated_sdp é executado para respostas recebidas e enviadas (é assim que set_caps é executado duas vezes). De qualquer forma...
- Alice faz uma oferta => Asterisk® SCF™ => Bob:
- incoming_offer: Isso é tratado em chan_pjsip:chan_pjsip_incoming_request( ) antes que o canal seja realmente criado. Essa função é chamada por sessão por meio do suplemento handle_incoming_request. A função resolve a topologia criada a partir da oferta de Alice com os parâmetros e CODECs do endpoint e do endpoint de Alice. Supondo que haja fluxos ativos deixados na topologia resolvida, a função redefine a topologia pendente na sessão para ser a topologia resolvida e chama chan_pjsip_new, que define a topologia no canal junto com os limites de formato nativo e os formatos de leitura e gravação. NOTA: Não definimos os CODECs de instância RTP aqui, mas deveríamos. Se a resolução falhar, terminamos com um 488.
- Eventualmente, trabalhamos até app_dial:dial_exec_full, que cria o canal de saída de Bob com ast_request_with_stream_topology( ) passando na topologia do canal de Alice.
- outgoing_offer: ast_request_with_stream_topology( ) chama chan_pjsip_request_with_stream_topology( ). Em seguida, isso resolve a topologia de solicitação (pendente) com os parâmetros output_offer do endpoint de Bob e os CODECs de endpoint do endpoint de Bob. Supondo que existam fluxos ativos deixados na topologia, a função chama chan_pjsip_new( ) que define a topologia do canal de Bob, limites de formato nativo e formatos de leitura/gravação. Mesma observação acima, devemos definir os CODECs de instância RTP aqui, mas ainda não. Se a topologia resolvida, incluindo a aplicação de opções de transcodificação, não tiver mais fluxos ativos, retornamos uma causa de AST_CAUSE_FAILURE para app_dial e bail, o que faz com que um 503 seja enviado para Alice.
- dial_exec_full finalmente chama chan_pjsip_call( ) cuja tarefa call( ) chama ast_sip_session_create_invite( ) então ast_sip_session_send)request( ).
- Bob aceita a oferta => Asterisk® SCF™ => Alice.
- incoming_answer: Quando Bob envia um 200OK, pjproject chama nosso callback session_inv_on_media_update( ) que então chama res_pjsip_session:handle_negotiated_sdp( ). Isso define a topologia ativa para aquela recebida de Bob. Eventualmente, pjproject sinaliza uma mudança de estado de transação com base no recebimento de 200OK, que aciona os suplementos handle_incoming_response da sessão, um dos quais é chan_pjsip_incoming_response_after_media( ). Isso resolve a topologia ativa que veio de Bob, com a topologia que enviamos a Bob usando as preferências de resposta recebida do endpoint de Bob. A função então chama ast_queue_control_data com um tipo de quadro AST_CONTROL_ANSWER e a topologia resolvida como os dados.
- app_dial:wait_for_answer( ) recebe o quadro ANSWER e coloca a topologia na estrutura de configuração da ponte. Isso é passado para features:ast_bridge_call( ) e para baixo para pre_bridge_setup( ) que chama ast_raw_answer_with_stream_topology( ). Isso, por sua vez, chama chan_pjsip_answer_with_stream_topology no canal de Alice.
- output_answer: a tarefa de resposta de chan_pjsip_answer_with_stream_topology faz a resolução final usando a topologia ativa de Bob, a topologia pendente de Alice que foi originalmente enviada para o CORE do Asterisk® SCF™ e os parâmetros de output_answer do terminal de Alice.
Deixe um comentário