Ang materyal ng artikulo ay kinuha mula sa aking
Duplex intercom
Sa huli
Ang diagram ay ipinapakita sa figure ng pamagat. Ang mas mababang kadena ng mga filter ay bumubuo sa landas ng paghahatid, na nagsisimula sa sound card. Nagbibigay ito ng mga sample ng signal mula sa mikropono. Bilang default, nangyayari ito sa bilis na 8000 sample bawat segundo. Ang bit depth ng data na ginagamit ng mga media streamer audio filter ay 16 bits (hindi ito mahalaga; kung gusto mo, maaari kang magsulat ng mga filter na gagana nang may mas mataas na bit depth). Ang data ay pinagsama-sama sa mga bloke ng 160 sample. Kaya, ang bawat bloke ay 320 bytes ang laki. Susunod, pinapakain namin ang data sa input ng generator, na, kapag naka-off, ay "transparent" sa data. Idinagdag ko ito kung sakaling mapagod ka sa pakikipag-usap sa mikropono habang nagde-debug - maaari mong gamitin ang generator upang "i-shoot" ang landas na may signal ng tono.
Pagkatapos ng generator, ang signal ay mapupunta sa encoder, na nagko-convert sa aming 16-bit na mga sample ayon sa Β΅-law (G.711 standard) sa walong-bit na mga sample. Sa output ng encoder, mayroon na kaming data block na kalahati ng laki. Sa pangkalahatan, maaari kaming magpadala ng data nang walang compression kung hindi namin kailangang i-save ang trapiko. Ngunit narito, kapaki-pakinabang na gumamit ng isang encoder, dahil ang Wireshark ay maaaring magparami ng audio mula sa isang RTP stream lamang kapag ito ay na-compress ayon sa Β΅-law o a-law.
Pagkatapos ng encoder, ang mas magaan na mga bloke ng data ay ipinapadala sa rtpsend filter, na maglalagay sa kanila sa isang RTP packet, itakda ang mga kinakailangang flag at ibigay ang mga ito sa media streamer para sa paghahatid sa network sa anyo ng isang UDP packet.
Ang itaas na kadena ng mga filter ay bumubuo sa landas ng pagtanggap; Ang mga RTP packet na natanggap ng media streamer mula sa network ay pumasok sa rtprecv filter, sa output kung saan lumilitaw ang mga ito sa anyo ng mga bloke ng data, na ang bawat isa ay tumutugma sa isang natanggap na packet. Ang block ay naglalaman lamang ng data ng payload; sa nakaraang artikulo ay ipinakita ang mga ito sa berde sa ilustrasyon.
Susunod, ang mga bloke ay ipinadala sa decoder filter, na nagko-convert sa mga single-byte na sample na nakapaloob sa mga ito sa mga linear, 16-bit na mga sample. Na maaaring maproseso na ng mga filter ng media streamer. Sa aming kaso, ipinapadala lang namin sila sa sound card para sa pag-playback sa mga speaker ng iyong headset.
Ngayon ay lumipat tayo sa pagpapatupad ng software. Para magawa ito, pagsasamahin namin ang mga file ng receiver at transmitter na pinaghiwalay namin noon. Bago ito, gumamit kami ng mga nakapirming setting para sa mga port at address, ngunit ngayon kailangan namin ng program upang magamit ang mga setting na aming tinukoy sa pagsisimula. Para magawa ito, magdaragdag kami ng functionality para sa pagproseso ng mga argumento ng command line. Pagkatapos nito, magagawa naming itakda ang IP address at port ng intercom kung saan nais naming magtatag ng isang koneksyon.
Una, magdagdag tayo ng istraktura sa program na mag-iimbak ng mga setting nito:
struct _app_vars
{
int local_port; /* ΠΠΎΠΊΠ°Π»ΡΠ½ΡΠΉ ΠΏΠΎΡΡ. */
int remote_port; /* ΠΠΎΡΡ ΠΏΠ΅ΡΠ΅Π³ΠΎΠ²ΠΎΡΠ½ΠΎΠ³ΠΎ ΡΡΡΡΠΎΠΉΡΡΠ²Π° Π½Π° ΡΠ΄Π°Π»Π΅Π½Π½ΠΎΠΌ ΠΊΠΎΠΌΠΏΡΡΡΠ΅ΡΠ΅. */
char remote_addr[128]; /* IP-Π°Π΄ΡΠ΅Ρ ΡΠ΄Π°Π»Π΅Π½Π½ΠΎΠ³ΠΎ ΠΊΠΎΠΌΠΏΡΡΡΠ΅ΡΠ°. */
MSDtmfGenCustomTone dtmf_cfg; /* ΠΠ°ΡΡΡΠΎΠΉΠΊΠΈ ΡΠ΅ΡΡΠΎΠ²ΠΎΠ³ΠΎ ΡΠΈΠ³Π½Π°Π»Π° Π³Π΅Π½Π΅ΡΠ°ΡΠΎΡΠ°. */
};
typedef struct _app_vars app_vars;
Ang programa ay magdedeklara ng isang istraktura ng ganitong uri na tinatawag na vars.
Susunod, magdagdag tayo ng function para i-parse ang mga argumento ng command line:
/* Π€ΡΠ½ΠΊΡΠΈΡ ΠΏΡΠ΅ΠΎΠ±ΡΠ°Π·ΠΎΠ²Π°Π½ΠΈΡ Π°ΡΠ³ΡΠΌΠ΅Π½ΡΠΎΠ² ΠΊΠΎΠΌΠ°Π½Π΄Π½ΠΎΠΉ ΡΡΡΠΎΠΊΠΈ Π²
* Π½Π°ΡΡΡΠΎΠΉΠΊΠΈ ΠΏΡΠΎΠ³ΡΠ°ΠΌΠΌΡ. */
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]);
}
}
}
Bilang resulta ng pag-parse, ang mga argumento ng command line ay ilalagay sa mga field ng vars structure. Ang pangunahing pag-andar ng application ay upang mangolekta ng pagpapadala at pagtanggap ng mga landas mula sa mga filter; pagkatapos ikonekta ang ticker, ang kontrol ay ililipat sa isang walang katapusang loop na kung saan, kung ang dalas ng generator ay nakatakda sa non-zero, ay muling simulan ang pagsubok generator upang gumagana ito nang walang tigil.
Kakailanganin ng generator ang mga pag-restart na ito dahil sa disenyo nito; sa ilang kadahilanan ay hindi ito makagawa ng signal na tumatagal ng higit sa 16 na segundo. Dapat tandaan na ang tagal nito ay tinukoy ng isang 32-bit na numero.
Ang buong programa ay magiging ganito:
/* Π€Π°ΠΉΠ» 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);
}
}
Mag-compile tayo. Pagkatapos ang programa ay maaaring tumakbo sa dalawang computer. O sa isa, gaya ng gagawin ko ngayon. Inilunsad namin ang TShark na may mga sumusunod na argumento:
$ sudo tshark -i lo -f "udp dst port 7010" -P -V -O RTP -o rtp.heuristic_rtp:TRUE -x
Kung ang patlang ng paglunsad sa console ay nagpapakita lamang ng isang mensahe tungkol sa pagsisimula ng pagkuha, kung gayon ito ay isang magandang senyales - nangangahulugan ito na ang aming port ay malamang na hindi inookupahan ng iba pang mga programa. Sa isa pang terminal, naglulunsad kami ng isang instance ng program na gagayahin ang isang "remote" na intercom sa pamamagitan ng pagtukoy sa port number na ito:
$ ./mstest8 --port 9010 --lport 7010
Tulad ng makikita mula sa teksto ng programa, ang default na IP address ay 127.0.0.1 (lokal na loopback).
Sa isa pang terminal, naglulunsad kami ng pangalawang pagkakataon ng programa, na ginagaya ang isang lokal na device. Gumagamit kami ng karagdagang argumento na nagpapahintulot sa built-in na test generator na gumana:
$ ./mstest8 --port 7010 --lport 9010 --gen 440
Sa sandaling ito, ang mga packet na ipinadala patungo sa "remote" na aparato ay dapat magsimulang mag-flash sa console gamit ang TShark, at isang tuluy-tuloy na tono ang maririnig mula sa speaker ng computer.
Kung nangyari ang lahat tulad ng nakasulat, pagkatapos ay i-restart namin ang pangalawang kopya ng programa, ngunit walang susi at argumento "βgen 440". Gagampanan mo na ngayon ang papel ng generator. Pagkatapos nito, maaari kang gumawa ng ingay sa mikropono; dapat mong marinig ang kaukulang tunog sa speaker o mga headphone. Maaaring mangyari ang acoustic self-excitation; hinaan ang volume ng speaker at mawawala ang epekto.
Kung pinatakbo mo ito sa dalawang computer at hindi nalilito tungkol sa mga IP address, ang parehong resulta ay naghihintay sa iyo - two-way na digital na kalidad na komunikasyon ng boses.
Sa susunod na artikulo matututunan natin kung paano magsulat ng sarili nating mga filter - mga plugin, salamat sa kasanayang ito, magagamit mo ang media streamer hindi lamang para sa audio at video, kundi pati na rin sa ilang iba pang partikular na lugar.
Pinagmulan: www.habr.com