Materialul articolului provine de pe canalul meu .
Dispozitiv de conversație duplex

În trecut, a fost anunțat un dispozitiv de conversație duplex, iar în aceasta îl vom realiza.
Schema este prezentată în ilustrația de deschidere. Circuitul inferior al filtrului formează traseul de transmisie, care începe de la placa de sunet. Aceasta emite semnale de la microfon. În mod implicit, acest lucru se întâmplă la o rată de 8000 de eșantioane pe secundă. Precizarea datelor folosite de filtrele de sunet ale mediastreamer-ului este de 16 biți (acesta nu este un aspect esențial, dacă doriți, puteți scrie filtre care să funcționeze cu o precizare mai mare). Datele sunt grupate în blocuri de 160 de eșantioane. Astfel, fiecare bloc are o dimensiune de 320 de octeți. Apoi, trimitem datele la intrarea generatorului, care în stare oprită este „transparent” pentru date. L-am adăugat în cazul în care, în timpul depanării, vă plictisiți să vorbiți în microfon — veți putea folosi generatorul pentru a „provoca” traseul cu un semnal tonal.
După generator, semnalul ajunge la encoder, care convertește eșantioanele noastre de 16 biți folosind µ-ley (standardul G.711) în eșantioane de 8 biți. La ieșirea encoder-ului, avem deja un bloc de date de două ori mai mic. În general, putem transmite datele fără comprimare, dacă nu avem nevoie să economisim lățime de bandă. Dar aici este util să folosim encoder-ul, deoarece Wireshark poate reproduce sunetul din fluxul RTP doar atunci când este comprimat folosind µ-ley sau a-ley.
După encoder, blocurile de date mai ușoare ajung la filtrul rtpsend, care le va plasa într-un pachet RTP, va seta flag-urile necesare și le va trimite către mediastreamer pentru transmitere prin rețea sub formă de pachet UDP.
Circuitul superior al filtrului formează traseul de recepție, pachetele RTP primite de mediastreamer din rețea ajung la filtrul rtprecv, la ieșirea căruia ele apar deja sub formă de blocuri de date, fiecare corespunzând unui pachet primit. Blocul conține doar datele utile, în articolul anterior ele au fost prezentate cu culoarea verde.
Apoi, blocurile ajung la filtrul decoder-ului, care convertește eșantioanele de un byte în eșantioane liniare de 16 biți. Acestea pot fi deja procesate de filtrele mediastreamer-ului. În cazul nostru, le dăm pur și simplu la placa de sunet, pentru a fi redate pe difuzoarele căștilor dumneavoastră.
Acum să trecem la implementarea programului. Pentru aceasta, vom combina fișierele receptorului și transmițătorului pe care le-am separat anterior. Anterior, am folosit setări fixe pentru porturi și adrese, dar acum trebuie să ne asigurăm că programul poate utiliza acele setări pe care le vom specifica la pornire. Pentru aceasta, vom adăuga funcționalitatea de procesare a argumentelor din linia de comandă. După aceea, vom putea specifica adresa IP și portul dispozitivului de comunicație cu care dorim să stabilim o conexiune.
Mai întâi, să adăugăm în program o structură care va stoca setările acesteia:
struct _app_vars
{
int local_port;
int remote_port;
char remote_addr[128];
MSDtmfGenCustomTone dtmf_cfg;
};
typedef struct _app_vars app_vars;În program va fi declarată o structură de acest tip numită vars.
Apoi, vom adăuga o funcție de analiză a argumentelor din linia de comandă:
/* Функция преобразования аргументов командной строки в
* настройки программы. */
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 talkienn", 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.1n");
exit(0);
}
if (!strcmp(argv[i], "--addr"))
{
strncpy(v->remote_addr, argv[i+1], 16);
v->remote_addr[16]=0;
printf("remote addr: %sn", v->remote_addr);
}
if (!strcmp(argv[i], "--port"))
{
v->remote_port=atoi(argv[i+1]);
printf("remote port: %in", v->remote_port);
}
if (!strcmp(argv[i], "--lport"))
{
v->local_port=atoi(argv[i+1]);
printf("local port : %in", v->local_port);
}
if (!strcmp(argv[i], "--gen"))
{
v -> dtmf_cfg.frequencies[0] = atoi(argv[i+1]);
printf("gen freq : %in", v -> dtmf_cfg.frequencies[0]);
}
}
}Ca rezultat al analizei, argumentele din linia de comandă vor fi plasate în câmpurile structurii vars. Funcția principală a aplicației va construi traseele de transmisie și recepție din filtre, iar după conectarea ticker-ului, controlul va fi transferat unui ciclu infinit care, dacă frecvența generatorului a fost setată la un număr nenul, va reporni generatorul de test — pentru a funcționa neîntrerupt.
Aceste reporniri vor fi necesare generatorului din cauza modului său de funcționare, deoarece nu poate emite un semnal cu o durată mai mare de 16 secunde. Totuși, trebuie menționat că durata acestuia este specificată printr-un număr de 32 de biți.
Programul în întregime va arăta astfel:
/* Файл 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 talkienn", 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.1n");
exit(0);
}
if (!strcmp(argv[i], "--addr"))
{
strncpy(v->remote_addr, argv[i+1], 16);
v->remote_addr[16]=0;
printf("remote addr: %sn", v->remote_addr);
}
if (!strcmp(argv[i], "--port"))
{
v->remote_port=atoi(argv[i+1]);
printf("remote port: %in", v->remote_port);
}
if (!strcmp(argv[i], "--lport"))
{
v->local_port=atoi(argv[i+1]);
printf("local port : %in", v->local_port);
}
if (!strcmp(argv[i], "--gen"))
{
v -> dtmf_cfg.frequencies[0] = atoi(argv[i+1]);
printf("gen freq : %in", 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);
}
}Compilăm. Ulterior, programul poate fi rulat pe două computere. Sau pe unul singur, așa cum voi face acum. Pornim TShark cu următoarele argumente:
$ sudo tshark -i lo -f "udp dst port 7010" -P -V -O RTP -o rtp.heuristic_rtp:TRUE -xDacă în consola de pornire apare doar mesajul de început al captării, atunci acesta este un semn bun - înseamnă că portul nostru nu este usor ocupat de alte programe. Într-un alt terminal, rulăm un exemplar al programului care va imita un dispozitiv de comunicație "de la distanță" specificându-i acest număr de port:
$ .\/mstest8 --port 9010 --lport 7010Așa cum se poate observa din textul programului, adresa IP utilizată implicit este 127.0.0.1 (bucle locale).
Într-un alt terminal, lansăm a doua instanță a programului, care imită un dispozitiv local. Folosim un argument suplimentar care permite funcționarea generatorului de teste încorporat:
$ ./mstest8 --port 7010 --lport 9010 --gen 440În acest moment, în consola TShark ar trebui să înceapă să apară pachete transmise către dispozitivul "de la distanță", iar din difuzorul computerului va răsuna un semnal tonal continuu.
Dacă totul a decurs conform celor scrise, repornim a doua instanță a programului, dar fără cheia și argumentul "--gen 440". Rolul generatorului va fi îndeplinit de voi. După aceasta, puteți face zgomot în microfon; în difuzor sau căști, ar trebui să auziți sunetul corespunzător. Este posibil chiar să apară un autocvasi-excitare acustică, așa că reduceți volumul difuzorului și efectul va dispărea.
Dacă ați rulat pe două computere și nu v-ați încurcat în adresele IP, veți obține același rezultat — comunicare bidirecțională de calitate digitală.
În articolul următor, vom învăța să scriem filtre proprii — pluginuri, datorită acestei abilități veți putea aplica media streamer nu numai pentru sunet și video, ci și într-un alt domeniu specific.
Sursa: habr.com
