Materiał artykułu pochodzi z mojego .
Urządzenie do komunikacji duplex

W poprzedniej Zostało ogłoszone urządzenie do komunikacji duplex, a w tym artykule je zaprezentujemy.
Schemat przedstawiony jest na rysunku głównym. Dolny tor filtrów tworzy tor nadawczy, który zaczyna się od karty dźwiękowej. Wydaje ona próbki sygnału z mikrofonu. Domyślnie odbywa się to z prędkością 8000 próbek na sekundę. Rozdzielczość danych, którą wykorzystują filtry strumienia mediów, wynosi 16 bitów (nie jest to krytyczne, jeśli zajdzie taka potrzeba, można napisać filtry, które będą działały z wyższą rozdzielczością). Dane są grupowane w bloki po 160 próbek. Zatem każdy blok ma rozmiar 320 bajtów. Następnie podajemy dane na wejście generatora, który w wyłączonym stanie jest "przezroczysty" dla danych. Dodałem go na wypadek, gdyby podczas debugowania mówienie do mikrofonu stało się dla Ciebie uciążliwe — możesz skorzystać z generatora, aby "przesuwać" tor sygnałem tonowym.
Po generatorze sygnał trafia do kodera, który przekształca nasze 16-bitowe próbki według prawa µ (standard G.711) na próbki ośmiobitowe. Na wyjściu kodera mamy już blok danych o połowę mniejszy. W ogólnym przypadku możemy przesyłać dane bez kompresji, jeśli nie musimy oszczędzać ruchu. Ale tutaj warto skorzystać z kodera, ponieważ Wireshark może odtwarzać dźwięk z strumienia RTP tylko wtedy, gdy jest on skompresowany według prawa µ lub a.
Po kodzie, odciążone bloki danych trafiają do filtra rtpsend, który umieszcza je w pakiecie RTP, ustawia niezbędne flagi i przekazuje je do strumienia mediów do przesyłania przez sieć w postaci pakietu UDP.
Górny tor filtrów tworzy tor odbiorczy, pakiety RTP, które otrzymuje strumień medialny z sieci, trafiają do filtra rtprecv, na wyjściu którego pojawiają się już w postaci bloków danych, z których każdy odpowiada jednemu odebranemu pakietowi. Blok zawiera tylko dane użyteczne, które w poprzednim artykule były przedstawione na ilustracji w zielonym kolorze.
Następnie bloki trafiają do filtra dekodera, który przekształca znajdujące się w nich jednobajtowe próbki w liniowe, 16-bitowe. Które można już przetwarzać przez filtry strumienia mediów. W naszym przypadku po prostu oddajemy je na kartę dźwiękową, aby odtworzyć je przez głośniki Twojego zestawu słuchawkowego.
Teraz przejdźmy do implementacji oprogramowania. W tym celu połączymy pliki odbiornika i nadajnika, które wcześniej rozdzieliliśmy. Wcześniej używaliśmy stałych ustawień portów i adresów, ale teraz potrzebujemy, aby program mógł korzystać z tych ustawień, które wskażemy podczas uruchamiania. W tym celu dodamy funkcjonalność obsługi argumentów wiersza poleceń. Po tym będziemy mogli ustawić adres IP i port urządzenia, z którym chcemy nawiązać połączenie.
Na początku dodamy do programu strukturę, która będzie przechowywać jego ustawienia:
struct _app_vars
{
int local_port; /* Lokalny port. */
int remote_port; /* Port urządzenia na zdalnym komputerze. */
char remote_addr[128]; /* Adres IP zdalnego komputera. */
MSDtmfGenCustomTone dtmf_cfg; /* Ustawienia sygnału testowego generatora. */
};
typedef struct _app_vars app_vars;W programie zostanie zadeklarowana struktura tego typu o nazwie vars.
Następnie dodamy funkcję do analizy argumentów wiersza poleceń:
/* Функция преобразования аргументов командной строки в
* настройки программы. */
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]);
}
}
}W wyniku analizy, argumenty wiersza poleceń zostaną umieszczone w polach struktury vars. Główna funkcja aplikacji zbierze z filtrów toru nadawczego i odbiorczego, po podłączeniu zegara kontrola zostanie przekazana do nieskończonej pętli, która, jeśli częstotliwość generatora została ustawiona na wartość różną od zera, zrestartuje generator testowy — aby działał bez przerwy.
Te restartowania będą potrzebne generatorowi ze względu na jego szczególną budowę, z niewiadomego powodu nie może on wydawać sygnału dłuższego niż 16 sekund. Przy tym należy zauważyć, że długość sygnału jest ustawiana za pomocą 32-bitowej liczby.
Program w całości będzie wyglądał tak:
/* Файл 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);
}
}Kompilujemy. Następnie program można uruchomić na dwóch komputerach. Lub na jednym, co będę teraz robił. Uruchamiamy TShark z następującymi argumentami:
$ sudo tshark -i lo -f "udp dst port 7010" -P -V -O RTP -o rtp.heuristic_rtp:TRUE -xJeśli w oknie terminala pojawi się tylko wiadomość o rozpoczęciu przechwytywania, to dobry znak — oznacza to, że nasz port najprawdopodobniej nie jest zajęty przez inne programy. W innym terminalu uruchamiamy instancję programu, który będzie symulować "zdalne" urządzenie negocjacyjne, podając mu ten numer portu:
$ ./mstest8 --port 9010 --lport 7010Jak widać z kodu programu, domyślnie używany jest adres IP 127.0.0.1 (lokalna pętla).
W innym terminalu uruchamiamy drugi egzemplarz programu, który imituje lokalne urządzenie. Używamy dodatkowego argumentu, który umożliwia działanie wbudowanego testowego generatora:
$ ./mstest8 --port 7010 --lport 9010 --gen 440W tym momencie w konsoli TShark powinny pojawiać się pakiety wysyłane w kierunku "zdalnego" urządzenia, a z głośnika komputera wydobywa się ciągły ton.
Jeśli wszystko poszło zgodnie z planem, uruchamiamy drugi egzemplarz programu ponownie, ale tym razem bez klucza i argumentu "—gen 440". Teraz rolę generatora będzie odgrywaćcie wy. Po tym można porozmawiać przez mikrofon, w głośnikach lub słuchawkach powinniście usłyszeć odpowiedni dźwięk. Możliwe, że pojawi się zjawisko akustycznego samozapłonu, zmniejszcie głośność głośnika, a efekt zniknie.
Jeśli uruchamialiście na dwóch komputerach i nie pomyliliście adresów IP, czeka was ten sam wynik — dwukierunkowa komunikacja głosowa w cyfrowej jakości.
W następnym artykule nauczymy się pisać własne filtry — wtyczki, dzięki tej umiejętności będziecie mogli zastosować mediastreamer nie tylko do dźwięku i wideo, ale także w innej specyficznej dziedzinie.
Źródło: habr.com
