Der Inhalt dieses Artikels stammt von meinem .
Duplex-Gesprächsgerät

Im letzten Ein Duplex-Gesprächsgerät wurde angekündigt und in diesem werden wir es umsetzen.
Das Schema ist im Titelbild dargestellt. Die untere Filterkette bildet den Übertragungsweg, der mit der Soundkarte beginnt. Diese gibt Signalwerte vom Mikrofon aus. Standardmäßig geschieht dies mit einer Rate von 8000 Abtastungen pro Sekunde. Die Auflösung der Daten, die die Audiofilter des Medienstreamers verwenden, beträgt 16 Bit (dies ist nicht entscheidend; bei Bedarf können Filter geschrieben werden, die mit höherer Auflösung arbeiten). Die Daten sind in Blöcke von 160 Abtastungen gruppiert. Jeder Block hat somit eine Größe von 320 Byte. Danach leiten wir die Daten zum Eingang des Generators weiter, der im ausgeschalteten Zustand "transparent" für die Daten ist. Ich habe ihn hinzugefügt, falls es Ihnen beim Debuggen langweilig wird, in das Mikrofon zu sprechen — Sie können den Generator nutzen, um den Weg mit einem tonalen Signal "durchzuschießen".
Nach dem Generator gelangt das Signal zum Kodierer, der unsere 16-Bit-µ-Gesetz-Werte (Standard G.711) in 8-Bit-Werte umwandelt. Am Ausgang des Kodierers haben wir bereits einen Datenblock, der nur halb so groß ist. Im Allgemeinen können wir Daten unkomprimiert übertragen, wenn wir den Datenverkehr nicht einsparen müssen. Aber hier ist es nützlich, den Kodierer zu verwenden, da Wireshark Audio aus dem RTP-Stream nur dann wiedergeben kann, wenn es nach dem µ-Gesetz oder dem a-Gesetz komprimiert ist.
Nach dem Kodierer gelangen die leichteren Datenblöcke zum Filter rtpsend, der sie in ein RTP-Paket einfügt, die erforderlichen Flags setzt und sie dem Media-Streamer zur Übertragung im Netzwerk als UDP-Paket übergibt.
Die obere Filterkette bildet den Empfangspfad. Die RTP-Pakete, die der Media-Streamer aus dem Netzwerk erhält, gelangen in den Filter rtprecv, an dessen Ausgang sie bereits als Datenblöcke erscheinen, wobei jeder Block einem empfangenen Paket entspricht. Der Block enthält nur die Nutzdaten, die in dem vorherigen Artikel in der Illustration grün markiert wurden.
Die Blöcke gelangen nun zum Dekodierungsfilter, der die darin enthaltenen einbyteigen Messwerte in lineare 16-Bit-Werte umwandelt. Diese können anschließend von den Medienstreamern verarbeitet werden. In unserem Fall geben wir sie einfach an die Soundkarte weiter, um sie über die Lautsprecher Ihres Headsets wiederzugeben.
Nun kommen wir zur programatischen Implementierung. Dazu kombinieren wir die bereits getrennten Dateien des Empfängers und des Senders. Zuvor haben wir feste Port- und Adresskonfigurationen verwendet, jetzt muss das Programm jedoch die Einstellungen nutzen, die wir beim Start angeben. Dafür fügen wir eine Funktion zur Verarbeitung von Befehlszeilenargumenten hinzu. Dadurch können wir die IP-Adresse und den Port des Sprachgeräts angeben, mit dem wir eine Verbindung herstellen möchten.
Zunächst fügen wir der Anwendung eine Struktur hinzu, die deren Einstellungen speichert:
struct _app_vars
{
int local_port; /* Lokaler Port. */
int remote_port; /* Port des Gesprächsgeräts auf dem entfernten Computer. */
char remote_addr[128]; /* IP-Adresse des entfernten Computers. */
MSDtmfGenCustomTone dtmf_cfg; /* Einstellungen des Testton-Generators. */
};
typedef struct _app_vars app_vars;Im Programm wird eine Struktur dieses Typs mit dem Namen vars deklariert.
Dann fügen wir die Funktion zur Analyse der Befehlszeilenargumente hinzu:
/* Функция преобразования аргументов командной строки в
* настройки программы. */
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]);
}
}
}Als Ergebnis der Analyse werden die Befehlszeilenargumente in die Felder der Struktur vars eingefügt. Die Hauptfunktion der Anwendung wird die Übertragungs- und Empfangsstrecken aus den Filtern zusammenstellen, nach dem Anschluss des Tickers wird die Kontrolle an eine unendliche Schleife übergeben, die, wenn die Frequenz des Generators auf ungleich null gesetzt wurde, den Testgenerator neu starten wird — damit er ohne Unterbrechung arbeitet.
Diese Neustarts werden für den Generator notwendig sein, da er eine Besonderheit in seiner Konstruktion hat, aus irgendeinem Grund kann er kein Signal länger als 16 Sekunden erzeugen. Dabei ist zu beachten, dass die Dauer durch eine 32-Bit-Zahl festgelegt wird.
Das Programm wird insgesamt so aussehen:
/* Файл 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);
}
}Kompiliere. Anschließend kann das Programm auf zwei Computern oder auf einem einzigen, wie ich es jetzt machen werde, gestartet werden. Lass uns TShark mit den folgenden Argumenten starten:
$ sudo tshark -i lo -f "udp dst port 7010" -P -V -O RTP -o rtp.heuristic_rtp:TRUE -xWenn im Konsolenstartfeld nur die Nachricht über den Beginn der Erfassung erscheint, ist das ein gutes Zeichen – das bedeutet, dass unser Port wahrscheinlich nicht von anderen Programmen genutzt wird. In einem anderen Terminal starten wir eine Programmkopie, die ein "entferntes" Kommunikationseinheit simuliert und ihr diese Portnummer zuweist:
$ ./mstest8 --port 9010 --lport 7010Wie aus dem Programmtext hervorgeht, wird standardmäßig die IP-Adresse 127.0.0.1 (lokale Schleife) verwendet.
In einem weiteren Terminal starten wir eine zweite Instanz des Programms, die ein lokales Gerät simuliert. Wir verwenden ein zusätzliches Argument, das die Arbeit mit dem integrierten Testgenerator erlaubt:
$ ./mstest8 --port 7010 --lport 9010 --gen 440In diesem Moment sollten in der TShark-Konsole die Pakete erscheinen, die in Richtung des "entfernten" Geräts gesendet werden, und aus den Lautsprechern des Computers ertönt ein kontinuierlicher Tond.
Wenn alles nach Plan gelaufen ist, starten wir die zweite Instanz des Programms erneut, jedoch ohne den Schlüssel und das Argument "—gen 440". Nun werden Sie die Rolle des Generators übernehmen. Danach können Sie ins Mikrofon sprechen; über die Lautsprecher oder Kopfhörer sollten Sie den entsprechenden Ton hören. Es kann sogar zu akustischem Selbstschwingen kommen, reduzieren Sie die Lautstärke des Lautsprechers, und der Effekt wird verschwinden.
Wenn Sie auf zwei Computern gestartet haben und sich nicht in den IP-Adressen verwirrt haben, erwartet Sie dasselbe Ergebnis — bidirektionale Sprachkommunikation in digitaler Qualität.
Im nächsten Artikel lernen wir, wie wir eigene Filter — Plugins schreiben. Mit diesem Wissen können Sie den Mediastreamer nicht nur für Audio und Video, sondern auch in anderen spezifischen Bereichen einsetzen.
Quelle: habr.com
