Explorando o mecanismo VoIP do Mediastreamer2. Parte 6

O material do artigo foi retirado do meu canal zen.

Transmitindo um sinal de áudio via fluxo RTP

Explorando o mecanismo VoIP do Mediastreamer2. Parte 6

No passado статье Montamos um circuito de controle remoto a partir de um gerador de tons e um detector de tons que operam dentro do mesmo programa. Neste artigo aprenderemos como utilizar o protocolo RTP (RFC 3550 - RTP: um protocolo de transporte para aplicações em tempo real) para receber/transmitir um sinal de áudio através de uma rede Ethernet.

Protocolo RTP (Protocolo de Tempo Real) traduzido significa protocolo em tempo real, serve para transmitir áudio, vídeo, dados, tudo que requer transmissão em tempo real. Tomemos um sinal de áudio como exemplo. A flexibilidade do protocolo é tal que permite transmitir um sinal de áudio com uma qualidade pré-determinada.

A transmissão é realizada por meio de pacotes UDP, o que significa que a perda de pacotes durante a transmissão é bastante aceitável. Cada pacote contém um cabeçalho RTP especial e um bloco de dados do sinal transmitido. O cabeçalho contém um identificador de fonte de sinal selecionado aleatoriamente, informações sobre o tipo de sinal que está sendo transmitido e um número de sequência de pacote exclusivo para que os pacotes possam ser organizados na ordem correta durante a decodificação, independentemente da ordem em que foram entregues pelo rede. O cabeçalho também pode conter informações adicionais, a chamada extensão, que permite que o cabeçalho seja adaptado para uso em uma tarefa específica da aplicação.

O bloco de dados contém a carga útil do pacote. A organização interna do conteúdo depende do tipo de carga, podendo ser amostras de um sinal mono, um sinal estéreo, uma linha de imagem de vídeo, etc.

O tipo de carga é indicado por um número de sete bits. Recomendação RFC3551 (Perfil RTP para conferências de áudio e vídeo com controle mínimo) estabelece vários tipos de carga; a tabela correspondente fornece uma descrição dos tipos de carga e o significado dos códigos pelos quais são designados. Alguns códigos não estão estritamente vinculados a nenhum tipo de carga; eles podem ser usados ​​para designar uma carga arbitrária.

O tamanho de um bloco de dados é limitado acima pelo tamanho máximo do pacote que pode ser transmitido em uma determinada rede sem segmentação (parâmetro MTU). Em geral, isso não ultrapassa 1500 bytes. Assim, para aumentar a quantidade de dados transmitidos por segundo, você pode aumentar o tamanho do pacote até um certo ponto, e então será necessário aumentar a frequência de envio dos pacotes. Em um streamer de mídia, esta é uma configuração configurável. Por padrão é 50 Hz, ou seja, 50 pacotes por segundo. Chamaremos a sequência de pacotes RTP transmitidos de fluxo RTP.

Para iniciar a transmissão de dados entre a fonte e o receptor, basta que o transmissor conheça o endereço IP do receptor e o número da porta que utiliza para receber. Aqueles. sem quaisquer procedimentos preliminares, a fonte começa a transmitir os dados, e o receptor, por sua vez, está pronto para recebê-los e processá-los imediatamente. De acordo com o padrão, o número da porta utilizada para transmitir ou receber um fluxo RTP deve ser par.

Nas situações em que é impossível saber antecipadamente o endereço do receptor, são utilizados servidores onde os receptores deixam o seu endereço, podendo o transmissor solicitá-lo referindo-se a algum nome único do receptor.

Nos casos em que a qualidade do canal de comunicação ou as capacidades do receptor são desconhecidas, é organizado um canal de feedback através do qual o receptor pode informar o transmissor sobre suas capacidades, o número de pacotes perdidos, etc. Este canal usa o protocolo RTCP. O formato dos pacotes transmitidos neste canal é definido na RFC 3605. Relativamente poucos dados são transmitidos através deste canal, 200 a 300 bytes por segundo, portanto, em geral, sua presença não é onerosa. O número da porta para a qual os pacotes RTCP são enviados deve ser ímpar e um número maior que o número da porta de onde vem o fluxo RTP. Em nosso exemplo, não usaremos este canal, pois as capacidades do receptor e do canal obviamente excedem nossas necessidades, até agora modestas.

Em nosso programa, o circuito de transmissão de dados, diferentemente do exemplo anterior, será dividido em duas partes: um caminho de transmissão e um caminho de recepção. Para cada parte faremos nossa própria fonte de relógio, conforme mostrado na imagem do título.

A comunicação unidirecional entre eles será realizada utilizando o protocolo RTP. Neste exemplo, não precisamos de uma rede externa, pois tanto o transmissor quanto o receptor estarão localizados no mesmo computador - os pacotes trafegarão dentro dele.

Para estabelecer um fluxo RTP, o streamer de mídia usa dois filtros: MS_RTP_SEND e MS_RTP_RECV. O primeiro transmite o segundo e recebe o fluxo RTP. Para que esses filtros funcionem, eles precisam passar um ponteiro para um objeto de sessão RTP, que pode converter um fluxo de blocos de dados em um fluxo de pacotes RTP ou fazer o oposto. Como o formato de dados interno do streamer de mídia não corresponde ao formato de dados do pacote RTP, antes de transferir os dados para MS_RTP_SEND, você precisa usar um filtro codificador que converte amostras de sinal de áudio de 16 bits em amostras de sinal de áudio de oito bits codificadas de acordo com o u-lei (mu-lei). No lado receptor, o filtro decodificador executa a função oposta.

Abaixo está o texto do programa que implementa o esquema mostrado na figura (os símbolos # antes das diretivas include foram removidos, não esqueça de incluí-los):

/* Файл mstest6.c Имитатор пульта управления и приемника. */
#include <mediastreamer2/msfilter.h>
#include <mediastreamer2/msticker.h>
#include <mediastreamer2/dtmfgen.h>
#include <mediastreamer2/mssndcard.h>
#include <mediastreamer2/msvolume.h>
#include <mediastreamer2/mstonedetector.h>
#include <mediastreamer2/msrtp.h>
#include <ortp/rtpsession.h>
#include <ortp/payloadtype.h>
/* Подключаем заголовочный файл с функциями управления событиями
* медиастримера.*/
include <mediastreamer2/mseventqueue.h>
#define PCMU 0
/* Функция обратного вызова, она будет вызвана фильтром, как только он
обнаружит совпадение характеристик входного сигнала с заданными. */
static void tone_detected_cb(void *data, MSFilter *f, unsigned int event_id,
MSToneDetectorEvent *ev)
{
printf("Принята команда: %sn", ev->tone_name);
}
/*----------------------------------------------------------------------------*/
/* Функция регистрации типов полезных нагрузок. */
void register_payloads(void)
{
/*Регистрируем типы нагрузок в таблице профилей. Позднее, по индексу
взятому из заголовка RTP-пакета из этой таблицы будут извлекаться
параметры нагрузки, необходимые для декодирования данных пакета. */
rtp_profile_set_payload (&av_profile, PCMU, &payload_type_pcm8000);
}
/*----------------------------------------------------------------------------*/
/* Эта функция создана из функции create_duplex_rtpsession() в audiostream.c
медиастримера2. */
static RtpSession *
create_rtpsession (int loc_rtp_port, int loc_rtcp_port,
bool_t ipv6, RtpSessionMode mode)
{
RtpSession *rtpr;
rtpr = rtp_session_new ((int) mode);
rtp_session_set_scheduling_mode (rtpr, 0);
rtp_session_set_blocking_mode (rtpr, 0);
rtp_session_enable_adaptive_jitter_compensation (rtpr, TRUE);
rtp_session_set_symmetric_rtp (rtpr, TRUE);
rtp_session_set_local_addr (rtpr, ipv6 ? "::" : "0.0.0.0", loc_rtp_port,
loc_rtcp_port);
rtp_session_signal_connect (rtpr, "timestamp_jump",
(RtpCallback) rtp_session_resync, 0);
rtp_session_signal_connect (rtpr, "ssrc_changed",
(RtpCallback) rtp_session_resync, 0);
rtp_session_set_ssrc_changed_threshold (rtpr, 0);
rtp_session_set_send_payload_type(rtpr, PCMU);
/* По умолчанию выключаем RTCP-сессию, так как наш пульт не будет использовать её. */
rtp_session_enable_rtcp (rtpr, FALSE);
return rtpr;
}
/*----------------------------------------------------------------------------*/
int main()
{
ms_init();
/* Создаем экземпляры фильтров. */
MSFilter *voidsource = ms_filter_new(MS_VOID_SOURCE_ID);
MSFilter *dtmfgen = ms_filter_new(MS_DTMF_GEN_ID);
MSFilter *volume = ms_filter_new(MS_VOLUME_ID);
MSSndCard *card_playback =
ms_snd_card_manager_get_default_card(ms_snd_card_manager_get());
MSFilter *snd_card_write = ms_snd_card_create_writer(card_playback);
MSFilter *detector = ms_filter_new(MS_TONE_DETECTOR_ID);
/* Очищаем массив находящийся внутри детектора тонов, он описывает
* особые приметы разыскиваемых сигналов.*/
ms_filter_call_method(detector, MS_TONE_DETECTOR_CLEAR_SCANS, 0);
/* Подключаем к фильтру функцию обратного вызова. */
ms_filter_set_notify_callback(detector,
(MSFilterNotifyFunc)tone_detected_cb, NULL);
/* Создаем массив, каждый элемент которого описывает характеристику
* одного из тонов, который требуется обнаруживать: Текстовое имя
* данного элемента, частота в герцах, длительность в миллисекундах,
* минимальный уровень относительно 0,775В. */
MSToneDetectorDef scan[6]=
{
{"V+",440, 100, 0.1}, /* Команда "Увеличить громкость". */
{"V-",540, 100, 0.1}, /* Команда "Уменьшить громкость". */
{"C+",640, 100, 0.1}, /* Команда "Увеличить номер канала". */
{"C-",740, 100, 0.1}, /* Команда "Уменьшить номер канала". */
{"ON",840, 100, 0.1}, /* Команда "Включить телевизор". */
{"OFF", 940, 100, 0.1}/* Команда "Выключить телевизор". */
};
/* Передаем "приметы" сигналов детектор тонов. */
int i;
for (i = 0; i < 6; i++)
{
ms_filter_call_method(detector, MS_TONE_DETECTOR_ADD_SCAN,
&scan[i]);
}
/* Создаем фильтры кодера и декодера */
MSFilter *encoder = ms_filter_create_encoder("PCMU");
MSFilter *decoder=ms_filter_create_decoder("PCMU");
/* Регистрируем типы нагрузки. */
register_payloads();
/* Создаем RTP-сессию передатчика. */
RtpSession *tx_rtp_session = create_rtpsession (8010, 8011, FALSE, RTP_SESSION_SENDONLY);
rtp_session_set_remote_addr_and_port(tx_rtp_session,"127.0.0.1", 7010, 7011);
rtp_session_set_send_payload_type(tx_rtp_session, PCMU);
MSFilter *rtpsend = ms_filter_new(MS_RTP_SEND_ID);
ms_filter_call_method(rtpsend, MS_RTP_SEND_SET_SESSION, tx_rtp_session);
/* Создаем RTP-сессию приемника. */
MSFilter *rtprecv = ms_filter_new(MS_RTP_RECV_ID);
RtpSession *rx_rtp_session = create_rtpsession (7010, 7011, FALSE, RTP_SESSION_RECVONLY);
ms_filter_call_method(rtprecv, MS_RTP_RECV_SET_SESSION, rx_rtp_session);
/* Создаем источники тактов - тикеры. */
MSTicker *ticker_tx = ms_ticker_new();
MSTicker *ticker_rx = ms_ticker_new();
/* Соединяем фильтры передатчика. */
ms_filter_link(voidsource, 0, dtmfgen, 0);
ms_filter_link(dtmfgen, 0, volume, 0);
ms_filter_link(volume, 0, encoder, 0);
ms_filter_link(encoder, 0, rtpsend, 0);
/* Соединяем фильтры приёмника. */
ms_filter_link(rtprecv, 0, decoder, 0);
ms_filter_link(decoder, 0, detector, 0);
ms_filter_link(detector, 0, snd_card_write, 0);
/* Подключаем источник тактов. */
ms_ticker_attach(ticker_tx, voidsource);
ms_ticker_attach(ticker_rx, rtprecv);
/* Настраиваем структуру, управляющую выходным сигналом генератора. */
MSDtmfGenCustomTone dtmf_cfg;
dtmf_cfg.tone_name[0] = 0;
dtmf_cfg.duration = 1000;
dtmf_cfg.frequencies[0] = 440;
/* Будем генерировать один тон, частоту второго тона установим в 0. */
dtmf_cfg.frequencies[1] = 0;
dtmf_cfg.amplitude = 1.0;
dtmf_cfg.interval = 0.;
dtmf_cfg.repeat_count = 0.;
/* Организуем цикл сканирования нажатых клавиш. Ввод нуля завершает
* цикл и работу программы. */
char key='9';
printf("Нажмите клавишу команды, затем ввод.n"
"Для завершения программы введите 0.n");
while(key != '0')
{
key = getchar();
if ((key >= 49) && (key <= 54))
{
printf("Отправлена команда: %cn", key);
/* Устанавливаем частоту генератора в соответствии с
* кодом нажатой клавиши. */
dtmf_cfg.frequencies[0] = 440 + 100*(key-49);
/* Включаем звуковой генератор c обновленной частотой. */
ms_filter_call_method(dtmfgen, MS_DTMF_GEN_PLAY_CUSTOM,
(void*)&dtmf_cfg);
}
/* Укладываем тред в спячку на 20мс, чтобы другие треды
* приложения получили время на работу. */
ms_usleep(20000);
}
}

Compilamos e executamos. O programa funcionará como no exemplo anterior, mas os dados serão transmitidos através de um fluxo RTP.

No próximo artigo dividiremos este programa em duas aplicações independentes - um receptor e um transmissor e os lançaremos em terminais diferentes. Ao mesmo tempo, aprenderemos como analisar pacotes RTP usando o programa TShark.

Fonte: habr.com

Adicionar um comentário