The article material is taken from my .
Duplex Intercom Device

Previously A duplex intercom device has been announced, and we will create it here.
The diagram is shown in the title illustration. The lower filter chain constitutes the transmitting path, which starts with the sound card. It outputs signal samples from the microphone. By default, this occurs at a rate of 8000 samples per second. The data bit depth used by the media streamer's sound filters is 16 bits (this is not critical, if desired, one could write filters that operate with a higher bit depth). The data is grouped into blocks of 160 samples. Thus, each block has a size of 320 bytes. Next, we feed the data into the generator, which in the off state is "transparent" to the data. I added it just in case you get tired of speaking into the microphone during debugging — you will be able to use the generator to "shoot" the path with a tone signal.
After the generator, the simplified data blocks go to the encoder, which converts our 16-bit samples using the µ-law (G.711 standard) to eight bits. At the output of the encoder, we already have a data block that is half the size. Generally, we can transmit data uncompressed if we do not need to save bandwidth. However, it is useful to use the encoder since Wireshark can only play sound from the RTP stream when it is compressed using µ-law or A-law.
After the encoder, the lighter data blocks pass to the rtpsend filter, which will place them into an RTP packet, set the necessary flags, and send them to the media streamer for transmission over the network as a UDP packet.
The upper filter chain forms the receiving path, the RTP packets received by the media streamer from the network go to the rtprecv filter, where they appear as data blocks, each of which corresponds to one received packet. The block only contains payload data, which was shown in green in the illustration of the previous article.
Next, the blocks go to the decoder filter, which converts the single-byte samples within them to linear 16-bit samples. These can be processed by the media streamer's filters. In our case, we simply send them to the sound card for playback through your headset's speakers.
Now let's move on to the software implementation. For this, we will combine the receiver and transmitter files that we separated earlier. Previously, we used fixed port and address settings, but now we need the program to utilize the settings we specify at launch. To achieve this, we will add functionality for handling command line arguments. After that, we will be able to set the IP address and port of the intercom device we want to connect to.
First, let's add a structure to the program that will hold its settings:
struct _app_vars
{
int local_port; /* Local port. */
int remote_port; /* Port of the intercom device on the remote computer. */
char remote_addr[128]; /* IP address of the remote computer. */
MSDtmfGenCustomTone dtmf_cfg; /* Settings for the test signal generator. */
};
typedef struct _app_vars app_vars;A structure of this type will be declared in the program with the name vars.
Next, we'll add a function to parse command line arguments:
/* Функция преобразования аргументов командной строки в
* настройки программы. */
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]);
}
}
}As a result of the parsing, the command line arguments will be placed in the fields of the vars structure. The main function of the application will assemble the transmitter and receiver paths from the filters; after connecting the ticker, control will be transferred to an infinite loop which, if the generator frequency was set to non-zero, will restart the test generator — so that it operates continuously.
These restarts will be necessary for the generator due to its construction peculiarities; for some reason, it cannot output a signal longer than 16 seconds. It should be noted that the duration is set by a 32-bit number.
The entire program will look like this:
/* Файл 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);
}
}Let's compile it. Then, the program can be run on two computers. Or on one, as I will do now. We launch TShark with the following arguments:
$ sudo tshark -i lo -f "udp dst port 7010" -P -V -O RTP -o rtp.heuristic_rtp:TRUE -xIf the console launch field only shows the message about the start of capture, that’s a good sign — it means our port is likely not occupied by other programs. In another terminal, we launch an instance of the program that will simulate the "remote" intercom device specifying this port number:
$ ./mstest8 --port 9010 --lport 7010As evident from the program text, the default IP address used is 127.0.0.1 (local loopback).
In another terminal, we launch a second instance of the program that simulates a local device. We use an additional argument that allows the built-in test generator to work:
$ ./mstest8 --port 7010 --lport 9010 --gen 440At this moment, packets sent towards the 'remote' device should start appearing in the TShark console, and a continuous tone will be heard from the computer's speakers.
If everything went as planned, we restart the second instance of the program, but without the key and the argument '—gen 440'. You will now serve as the generator. After this, you can make some noise into the microphone; you should hear the corresponding sound from the speakers or headphones. Acoustic feedback might even occur, so lower the speaker volume to make the effect disappear.
If you launched on two computers and didn't get confused with the IP addresses, you will experience the same result — two-way voice communication of digital quality.
In the next article, we will learn to write our own filters — plugins. With this skill, you will be able to apply the media streamer not only for audio and video but also in any other specific area.
Source: habr.com
