Изучаем VoIP-движок Mediastreamer2. Часть 9

от автора

Материал статьи взят с моего дзен-канала.

Дуплексное переговорное устройство

В прошлой статье было анонсировано дуплексное переговорное устройство, а в этой мы его сделаем.

Схема изображена на заглавном рисунке. Нижняя цепь фильтров образует передающий тракт, который начинается со звуковой карты. Она выдает отсчеты сигнала с микрофона. По умолчанию это происходит в темпе 8000 отсчетов в секунду. Разрядность данных, которую используют звуковые фильтры медиастримера — 16 бит ( это не принципиально, при желании можно написать фильтры которые будут работать с большей разрядностью). Данные сгруппированы в блоки по 160 отсчетов. Таким образом каждый блок имеет размер 320 байт. Далее мы подаем данные на вход генератора, который в выключенном состоянии "прозрачен" для данных. Я его добавил на тот случай, когда вам при отладке надоест говорить в микрофон — вы сможете воспользоваться генератором, чтобы "прострелить" тракт тональным сигналом.

После генератора сигнал попадает на кодер, который преобразует наши 16-битные отсчеты по µ-закону (стандарт G.711) в восьмибитные. На выходе кодера мы уже имеем блок данных в два раза меньшего размера. В общем случае, мы можем передавать данные без сжатия, если нам не требуется экономить трафик. Но здесь полезно воспользоваться кодером, так как Wireshark может воспроизводить звук из RTP-потока только тогда когда он сжат по µ-закону или а-закону.

После кодера, полегчавшие блоки данных поступают на фильтр rtpsend, который положит их в RTP-пакет, установит необходимые флаги и отдаст их медиастримеру для передачи по сети в виде UDP-пакета.

Верхняя цепь фильтров образует приемный тракт, RTP-пакеты, полученные медиастримером из сети, поступают в фильтр rtprecv, на выходе которого, они появляются уже в виде блоков данных, каждый из которых соответствует одному принятому пакету. Блок содержит только данные полезной нагрузки, в прошлой статье на иллюстрации они были показаны зеленым цветом.

Далее блоки поступают на фильтр декодера, который преобразует находящиеся в них однобайтные отсчеты, в линейные, 16-битные. Которые уже можно обрабатывать фильтрами медиастримера. В нашем случае мы просто отдаем их на звуковую карту, для воспроизведения на динамиках вашей гарнитуры.

Теперь перейдем к программной реализации. Для этого мы объединим файлы приемника и передатчика, которые мы размежевали до этого. До этого мы использовали фиксированные настройки портов и адресов, но теперь нам нужно чтобы программа могла использовать те настройки, которые мы укажем при запуске. Для этого бы добавим функционал обработки аргументов командной строки. После чего мы сможем задавать IP-адрес и порт переговорного устройства, с которым мы хотим установить связь.

Сначала добавим в в программу структуру, которая будет хранить её настройки:

struct _app_vars {   int  local_port;              /* Локальный порт. */   int  remote_port;             /* Порт переговорного устройства на удаленном компьютере. */   char remote_addr[128];        /* IP-адрес удаленного компьютера. */   MSDtmfGenCustomTone dtmf_cfg; /* Настройки тестового сигнала генератора. */ };  typedef struct _app_vars app_vars;

В программе будет объявлена структура этого типа с именем vars.
Затем добавим функцию разбора аргументов командной строки:

/* Функция преобразования аргументов командной строки в * настройки программы. */ void  scan_args(int argc, char *argv[], app_vars *v) {     char i;     for (i=0; i<argc; i++)     {         if (!strcmp(argv[i], "--help"))         {             char *p=argv[0]; p=p + 2;             printf("  %s walkie talkie\n\n", p);             printf("--help      List of options.\n");             printf("--version   Version of application.\n");             printf("--addr      Remote abonent IP address string.\n");             printf("--port      Remote abonent port number.\n");             printf("--lport     Local port number.\n");             printf("--gen       Generator frequency.\n");             exit(0);         }          if (!strcmp(argv[i], "--version"))         {             printf("0.1\n");             exit(0);         }          if (!strcmp(argv[i], "--addr"))         {             strncpy(v->remote_addr, argv[i+1], 16);             v->remote_addr[16]=0;             printf("remote addr: %s\n", v->remote_addr);         }          if (!strcmp(argv[i], "--port"))         {             v->remote_port=atoi(argv[i+1]);             printf("remote port: %i\n", v->remote_port);         }          if (!strcmp(argv[i], "--lport"))         {             v->local_port=atoi(argv[i+1]);             printf("local port : %i\n", v->local_port);         }          if (!strcmp(argv[i], "--gen"))         {             v -> dtmf_cfg.frequencies[0] = atoi(argv[i+1]);                 printf("gen freq : %i\n", v -> dtmf_cfg.frequencies[0]);         }     } }

В результате разбора, аргументы командной строки будут помещены в поля структуры vars. Главная функция приложения будет собирать из фильтров передающий и приемный тракты, после подключения тикера управление будет передано бесконечному циклу который, если частота генератора была задана ненулевой, будет перезапускать тестовый генератор — чтобы он работал без остановки.

Эти перезапуски будут нужны генератору из-за его особенности построения, почему-то он не может выдавать сигнал длительностью более 16 секунд. При этом следует заметить, что длительность у него задается 32-битным числом.

Программа целиком будет выглядеть так:

/* Файл mstest8.c Имитатор переговорного устройства. */  #include <mediastreamer2/mssndcard.h> #include <mediastreamer2/dtmfgen.h> #include <mediastreamer2/msrtp.h>  /* Подключаем файл общих функций. */ #include "mstest_common.c"  /*----------------------------------------------------------*/ struct _app_vars {     int  local_port;              /* Локальный порт. */     int  remote_port;             /* Порт переговорного устройства на удаленном компьютере. */     char remote_addr[128];        /* IP-адрес удаленного компьютера. */     MSDtmfGenCustomTone dtmf_cfg; /* Настройки тестового сигнала генератора. */ };  typedef struct _app_vars app_vars;  /*----------------------------------------------------------*/ /* Создаем дуплексную RTP-сессию. */ RtpSession* create_duplex_rtp_session(app_vars v) {     RtpSession *session = create_rtpsession (v.local_port, v.local_port + 1, FALSE, RTP_SESSION_SENDRECV);     rtp_session_set_remote_addr_and_port(session, v.remote_addr, v.remote_port, v.remote_port + 1);     rtp_session_set_send_payload_type(session, PCMU);     return session; }  /*----------------------------------------------------------*/ /* Функция преобразования аргументов командной строки в * настройки программы. */ void  scan_args(int argc, char *argv[], app_vars *v) {     char i;     for (i=0; i<argc; i++)     {         if (!strcmp(argv[i], "--help"))         {             char *p=argv[0]; p=p + 2;             printf("  %s walkie talkie\n\n", p);             printf("--help      List of options.\n");             printf("--version   Version of application.\n");             printf("--addr      Remote abonent IP address string.\n");             printf("--port      Remote abonent port number.\n");             printf("--lport     Local port number.\n");             printf("--gen       Generator frequency.\n");             exit(0);         }          if (!strcmp(argv[i], "--version"))         {             printf("0.1\n");             exit(0);         }          if (!strcmp(argv[i], "--addr"))         {             strncpy(v->remote_addr, argv[i+1], 16);             v->remote_addr[16]=0;             printf("remote addr: %s\n", v->remote_addr);         }          if (!strcmp(argv[i], "--port"))         {             v->remote_port=atoi(argv[i+1]);             printf("remote port: %i\n", v->remote_port);         }          if (!strcmp(argv[i], "--lport"))         {             v->local_port=atoi(argv[i+1]);             printf("local port : %i\n", v->local_port);         }          if (!strcmp(argv[i], "--gen"))         {             v -> dtmf_cfg.frequencies[0] = atoi(argv[i+1]);                 printf("gen freq : %i\n", v -> dtmf_cfg.frequencies[0]);         }     } }  /*----------------------------------------------------------*/ int main(int argc, char *argv[]) {     /* Устанавливаем настройки по умолчанию. */     app_vars vars={5004, 7010, "127.0.0.1", {0}};      /* Устанавливаем настройки настройки программы в      * соответствии с аргументами командной строки. */     scan_args(argc, argv, &vars);      ms_init();      /* Создаем экземпляры фильтров передающего тракта. */     MSSndCard *snd_card =         ms_snd_card_manager_get_default_card(ms_snd_card_manager_get());     MSFilter *snd_card_read = ms_snd_card_create_reader(snd_card);     MSFilter *dtmfgen = ms_filter_new(MS_DTMF_GEN_ID);     MSFilter *rtpsend = ms_filter_new(MS_RTP_SEND_ID);      /* Создаем фильтр кодера. */     MSFilter *encoder = ms_filter_create_encoder("PCMU");      /* Регистрируем типы нагрузки. */     register_payloads();      /* Создаем дуплексную RTP-сессию. */     RtpSession* rtp_session= create_duplex_rtp_session(vars);     ms_filter_call_method(rtpsend, MS_RTP_SEND_SET_SESSION, rtp_session);      /* Соединяем фильтры передатчика. */     ms_filter_link(snd_card_read, 0, dtmfgen, 0);     ms_filter_link(dtmfgen, 0, encoder, 0);     ms_filter_link(encoder, 0, rtpsend, 0);      /* Создаем фильтры приемного тракта. */     MSFilter *rtprecv = ms_filter_new(MS_RTP_RECV_ID);     ms_filter_call_method(rtprecv, MS_RTP_RECV_SET_SESSION, rtp_session);      /* Создаем фильтр декодера, */     MSFilter *decoder=ms_filter_create_decoder("PCMU");      /* Создаем фильтр звуковой карты. */     MSFilter *snd_card_write = ms_snd_card_create_writer(snd_card);      /* Соединяем фильтры приёмного тракта. */     ms_filter_link(rtprecv, 0, decoder, 0);     ms_filter_link(decoder, 0,  snd_card_write, 0);      /* Создаем источник тактов - тикер. */     MSTicker *ticker = ms_ticker_new();      /* Подключаем источник тактов. */     ms_ticker_attach(ticker, snd_card_read);     ms_ticker_attach(ticker, rtprecv);      /* Если настройка частоты генератора отлична от нуля, то запускаем генератор. */        if (vars.dtmf_cfg.frequencies[0])     {         /* Настраиваем структуру, управляющую выходным сигналом генератора. */         vars.dtmf_cfg.duration = 10000;         vars.dtmf_cfg.amplitude = 1.0;     }      /* Организуем цикл перезапуска генератора. */     while(TRUE)     {         if(vars.dtmf_cfg.frequencies[0])         {             /* Включаем звуковой генератор. */             ms_filter_call_method(dtmfgen, MS_DTMF_GEN_PLAY_CUSTOM,                     (void*)&vars.dtmf_cfg);         }         /* Укладываем тред в спячку на 20мс, чтобы другие треды          * приложения получили время на работу. */         ms_usleep(20000);     } }

Компилируем. Далее программу можно запустить на двух компьютерах. Или на одном, как я буду делать сейчас. Запускаем TShark со следующими аргументами:

$ sudo tshark -i lo -f "udp dst port 7010" -P -V -O RTP -o rtp.heuristic_rtp:TRUE -x

Если поле запуска в консоли появится только сообщение о начале захвата, то это добрый знак -значит наш порт скорее всего не занят другими программами. В другом терминале запускаем экземпляр программы, который будет имитировать "удаленное" переговорное устройство указав ему этот номер порта:

$ ./mstest8 --port 9010 --lport 7010

Как видно из текста программы, по умолчанию используется IP-адрес 127.0.0.1 (локальная петля).

Еще в одном терминале запускаем второй экземпляр программы, который имитирует локальное устройство. Используем дополнительный аргумент, который разрешает работу встроенного тестового генератора:

$ ./mstest8  --port 7010 --lport 9010 --gen 440

В этот момент, в консоли с TShark должны начать мелькать пакеты передаваемые в сторону "удаленного" устройства, а из динамика компьютера раздастся непрерывный тональный сигнал.

Если все произошло как по писанному, то перезапускаем второй экземпляр программы, но уже без ключа и аргумента "—gen 440". Роль генератора теперь будете исполнять вы. После этого можно пошуметь в микрофон, в динамике или наушниках вы должны услышать соответствующий звук. Возможно даже возникнет акустическое самовозбуждение, убавьте громкость динамика и эффект пропадет.

Если у вы запускали на двух компьютерах и не запутались в IP-адресах, то вас ждет тот же результат — двусторонняя речевая связь цифрового качества.

В следующей статье мы научимся писать свои собственные фильтры — плагины, благодаря этому навыку вы сможете применить медиастример не только для звука и видео, но и в какой-нибудь иной специфической области.

ссылка на оригинал статьи https://habr.com/ru/post/496874/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *