Het materiaal van dit artikel is afkomstig van mijn .
Duplex spreekapparaat

In het verleden Het duplex spreekapparaat is aangekondigd, en in dit artikel zullen we het maken.
Het schema is afgebeeld in de titelafbeelding. De onderste filterketen vormt het zendpad, dat begint met de geluidskaart. Deze geeft signaalmonsters van de microfoon uit. Standaard gebeurt dit met een tempo van 8000 monsters per seconde. De datagrootte die de geluidfilters van de mediastreamer gebruiken, is 16 bits (dit is niet cruciaal; als je wilt, kun je filters schrijven die met een grotere resolutie werken). Gegevens zijn gegroepeerd in blokken van 160 monsters. Elk blok heeft dus een grootte van 320 bytes. Vervolgens geven we de gegevens door aan de ingang van de generator, die in de uitgeschakelde toestand 'doorzichtig' is voor de gegevens. Ik heb deze toegevoegd voor het geval je tijdens het debuggen moe wordt van het praten in de microfoon — je kunt de generator gebruiken om het pad met een tonale signaal te 'traceren'.
Na de generator gaat het signaal naar de coder, die onze 16-bits monsters volgens de µ-wet (standaard G.711) omzet naar acht bits. Aan de uitgang van de coder hebben we al een datablok van de helft van de grootte. Over het algemeen kunnen we gegevens ongecomprimeerd verzenden als we geen bandbreedte hoeven te besparen. Maar het is nuttig om de coder te gebruiken, aangezien Wireshark het geluid uit de RTP-stroom alleen kan afspelen wanneer het volgens de µ-wet of a-wet is gecomprimeerd.
Na de coder komen de verlichte datablokken aan bij de rtpsend-filter, die ze in een RTP-pakket plaatst, de vereiste vlaggen instelt en ze aan de mediastreamer geeft voor verzending over het netwerk als een UDP-pakket.
De bovenste filterketen vormt het ontvangpad; de RTP-pakketten die de mediastreamer uit het netwerk ontvangt, gaan naar de rtprecv-filter, aan de uitgang waarvan ze al verschijnen als datablokken, elk overeenkomend met één ontvangen pakket. Het blok bevat alleen nuttige gegevens, die in het vorige artikel met groene kleur zijn weergegeven.
Vervolgens komen de blokken aan bij de decoder-filter, die de een-byte monsters in lineaire 16-bits omzet. Deze kunnen al door de mediastreamer-filters worden verwerkt. In ons geval geven we ze gewoon door aan de geluidskaart, voor afspelen op de luidsprekers van je headset.
Laten we nu overgaan op de software-implementatie. Hiervoor zullen we de bestanden van de ontvanger en de zender samenvoegen, die we eerder hadden gescheiden. Eerder gebruikten we vaste instellingen voor poorten en adressen, maar nu moet het programma de instellingen kunnen gebruiken die we opgeven bij de opstart. Hiervoor voegen we functionaliteit toe voor het verwerken van opdrachtregelargumenten. Hierdoor kunnen we het IP-adres en de poort van het communicatieapparaat opgeven waarmee we verbinding willen maken.
Laten we eerst een structuur aan het programma toevoegen die de instellingen zal opslaan:
struct _app_vars
{
int local_port; /* Lokale poort. */
int remote_port; /* Poort van het communicatieapparaat op de externe computer. */
char remote_addr[128]; /* IP-adres van de externe computer. */
MSDtmfGenCustomTone dtmf_cfg; /* Instellingen van het testsignaalgenerator. */
};
typedef struct _app_vars app_vars;In het programma zal een structuur van dit type worden gedeclareerd met de naam vars.
Vervolgens voegen we de functie toe voor het parseren van de opdrachtregelargumenten:
/* Функция преобразования аргументов командной строки в
* настройки программы. */
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 resultaat van de parsering worden de opdrachtregelargumenten in de velden van de structuur vars geplaatst. De hoofdfunctie van de applicatie zal de verzend- en ontvangstpaden uit de filters samenstellen; na het aansluiten van de ticker zal de controle worden overgedragen aan een oneindige lus die, als de frequentie van de generator niet nul is, de testgenerator opnieuw zal opstarten — zodat deze continu werkt.
Deze herstarts zijn nodig voor de generator vanwege zijn constructie; om de een of andere reden kan hij geen signaal van meer dan 16 seconden uitstoten. Daarbij moet worden opgemerkt dat de duur wordt bepaald door een 32-bits getal.
Het programma zal er in zijn geheel als volgt uitzien:
/* Файл 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);
}
}Laten we compileren. Vervolgens kan het programma op twee computers worden uitgevoerd. Of op één, zoals ik nu ga doen. We starten TShark met de volgende argumenten:
$ sudo tshark -i lo -f "udp dst port 7010" -P -V -O RTP -o rtp.heuristic_rtp:TRUE -xAls er in de console alleen een bericht verschijnt over het starten van de vastlegging, is dat een goed teken - dat betekent dat onze poort waarschijnlijk niet door andere programma's wordt bezet. In een andere terminal starten we een exemplaar van het programma dat het "externe" communicatieapparaat imiteert door dit poortnummer op te geven:
$ ./mstest8 --port 9010 --lport 7010Zoals te zien is in de tekst van het programma, wordt standaard het IP-adres 127.0.0.1 (lokale loop) gebruikt.
In een andere terminal starten we een tweede exemplaar van het programma, dat een lokaal apparaat imiteert. We gebruiken een extra argument dat de werking van de ingebouwde testgenerator mogelijk maakt:
$ ./mstest8 --port 7010 --lport 9010 --gen 440Op dit moment zouden er in de console met TShark pakketten moeten verschijnen die naar het "afstands"-apparaat worden verzonden, en uit de luidspreker van de computer klinkt een continue toon.
Als alles volgens plan is verlopen, herstart je het tweede exemplaar van het programma, maar nu zonder de sleutel en het argument "—gen 440". Nu ben jij de generator. Daarna kun je in de microfoon praten; in de luidspreker of hoofdtelefoon moet je het bijbehorende geluid horen. Het is mogelijk dat er zelfs akoestische zelfoscillatie ontstaat, verlaag het volume van de luidspreker en het effect zal verdwijnen.
Als je het op twee computers hebt uitgevoerd en niet in de IP-adressen bent verward, dan wacht je hetzelfde resultaat – bidirectionele spraakcommunicatie van digitale kwaliteit.
In het volgende artikel leren we hoe we onze eigen filters – plugins – kunnen schrijven. Dankzij deze vaardigheid kun je de mediastreamer toepassen, niet alleen voor audio en video, maar ook in een andere specifieke context.
Bron: habr.com
