Bahan artikel diambil dari saya
interkom dupleks
Pada masa lalu
Rajah ditunjukkan dalam rajah tajuk. Rantaian penapis yang lebih rendah membentuk laluan penghantaran, yang bermula dari kad bunyi. Ia menyediakan sampel isyarat daripada mikrofon. Secara lalai, ini berlaku pada kadar 8000 sampel sesaat. Kedalaman bit data yang digunakan oleh penapis audio pemancar media ialah 16 bit (ini tidak penting; jika anda mahu, anda boleh menulis penapis yang akan berfungsi dengan kedalaman bit yang lebih tinggi). Data dikumpulkan ke dalam blok 160 sampel. Oleh itu, setiap blok bersaiz 320 bait. Seterusnya, kami menyuap data ke input penjana, yang, apabila dimatikan, adalah "telus" kepada data. Saya menambahnya sekiranya anda bosan bercakap dengan mikrofon semasa penyahpepijatan - anda boleh menggunakan penjana untuk "menembak" laluan dengan isyarat nada.
Selepas penjana, isyarat pergi ke pengekod, yang menukar sampel 16-bit kami mengikut undang-undang Β΅ (standard G.711) kepada lapan-bit. Pada output pengekod, kami sudah mempunyai blok data separuh saiz. Secara umum, kita boleh menghantar data tanpa pemampatan jika kita tidak perlu menjimatkan trafik. Tetapi di sini adalah berguna untuk menggunakan pengekod, kerana Wireshark boleh menghasilkan semula audio daripada aliran RTP hanya apabila ia dimampatkan mengikut undang-undang Β΅ atau undang-undang.
Selepas pengekod, blok data yang lebih ringan dihantar ke penapis rtpsend, yang akan memasukkannya ke dalam paket RTP, menetapkan bendera yang diperlukan dan memberikannya kepada penstrim media untuk penghantaran melalui rangkaian dalam bentuk paket UDP.
Rantaian atas penapis membentuk laluan penerimaan; paket RTP yang diterima oleh penstrim media dari rangkaian memasuki penapis rtprecv, pada output yang ia muncul dalam bentuk blok data, setiap satunya sepadan dengan satu paket yang diterima. Blok hanya mengandungi data muatan; dalam artikel sebelumnya ia ditunjukkan dalam warna hijau dalam ilustrasi.
Seterusnya, blok dihantar ke penapis penyahkod, yang menukar sampel bait tunggal yang terkandung di dalamnya kepada yang linear, 16-bit. Yang sudah boleh diproses oleh penapis strim media. Dalam kes kami, kami hanya menghantarnya ke kad bunyi untuk main semula pada pembesar suara set kepala anda.
Sekarang mari kita beralih kepada pelaksanaan perisian. Untuk melakukan ini, kami akan menggabungkan fail penerima dan pemancar yang kami pisahkan sebelum ini. Sebelum ini, kami menggunakan tetapan tetap untuk port dan alamat, tetapi kini kami memerlukan program untuk dapat menggunakan tetapan yang kami tentukan semasa permulaan. Untuk melakukan ini, kami akan menambah fungsi untuk memproses hujah baris arahan. Selepas itu kami akan dapat menetapkan alamat IP dan port interkom yang kami ingin buat sambungan.
Mula-mula, mari tambahkan struktur pada program yang akan menyimpan tetapannya:
struct _app_vars
{
int local_port; /* ΠΠΎΠΊΠ°Π»ΡΠ½ΡΠΉ ΠΏΠΎΡΡ. */
int remote_port; /* ΠΠΎΡΡ ΠΏΠ΅ΡΠ΅Π³ΠΎΠ²ΠΎΡΠ½ΠΎΠ³ΠΎ ΡΡΡΡΠΎΠΉΡΡΠ²Π° Π½Π° ΡΠ΄Π°Π»Π΅Π½Π½ΠΎΠΌ ΠΊΠΎΠΌΠΏΡΡΡΠ΅ΡΠ΅. */
char remote_addr[128]; /* IP-Π°Π΄ΡΠ΅Ρ ΡΠ΄Π°Π»Π΅Π½Π½ΠΎΠ³ΠΎ ΠΊΠΎΠΌΠΏΡΡΡΠ΅ΡΠ°. */
MSDtmfGenCustomTone dtmf_cfg; /* ΠΠ°ΡΡΡΠΎΠΉΠΊΠΈ ΡΠ΅ΡΡΠΎΠ²ΠΎΠ³ΠΎ ΡΠΈΠ³Π½Π°Π»Π° Π³Π΅Π½Π΅ΡΠ°ΡΠΎΡΠ°. */
};
typedef struct _app_vars app_vars;
Program ini akan mengisytiharkan struktur jenis ini dipanggil vars.
Seterusnya, mari tambah fungsi untuk menghuraikan argumen baris arahan:
/* Π€ΡΠ½ΠΊΡΠΈΡ ΠΏΡΠ΅ΠΎΠ±ΡΠ°Π·ΠΎΠ²Π°Π½ΠΈΡ Π°ΡΠ³ΡΠΌΠ΅Π½ΡΠΎΠ² ΠΊΠΎΠΌΠ°Π½Π΄Π½ΠΎΠΉ ΡΡΡΠΎΠΊΠΈ Π²
* Π½Π°ΡΡΡΠΎΠΉΠΊΠΈ ΠΏΡΠΎΠ³ΡΠ°ΠΌΠΌΡ. */
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]);
}
}
}
Hasil daripada penghuraian, argumen baris arahan akan diletakkan dalam medan struktur vars. Fungsi utama aplikasi adalah untuk mengumpul laluan penghantaran dan penerimaan daripada penapis; selepas menyambungkan ticker, kawalan akan dipindahkan ke gelung tak terhingga yang, jika frekuensi penjana ditetapkan kepada bukan sifar, akan memulakan semula penjana ujian supaya ia berfungsi tanpa henti.
Penjana memerlukan permulaan semula ini kerana reka bentuknya; atas sebab tertentu ia tidak dapat menghasilkan isyarat yang bertahan lebih daripada 16 saat. Perlu diingatkan bahawa tempohnya ditentukan oleh nombor 32-bit.
Keseluruhan program akan kelihatan seperti ini:
/* Π€Π°ΠΉΠ» 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);
}
}
Mari kita susun. Kemudian program ini boleh dijalankan pada dua komputer. Atau pada satu, seperti yang akan saya lakukan sekarang. Kami melancarkan TShark dengan hujah berikut:
$ sudo tshark -i lo -f "udp dst port 7010" -P -V -O RTP -o rtp.heuristic_rtp:TRUE -x
Jika medan pelancaran dalam konsol hanya memaparkan mesej tentang permulaan tangkapan, maka ini adalah petanda yang baik - ini bermakna pelabuhan kami kemungkinan besar tidak diduduki oleh program lain. Di terminal lain, kami melancarkan contoh program yang akan mensimulasikan interkom "jauh" dengan menyatakan nombor port ini:
$ ./mstest8 --port 9010 --lport 7010
Seperti yang dapat dilihat daripada teks program, alamat IP lalai ialah 127.0.0.1 (local loopback).
Di terminal lain, kami melancarkan contoh kedua program, yang mensimulasikan peranti tempatan. Kami menggunakan hujah tambahan yang membenarkan penjana ujian terbina dalam berfungsi:
$ ./mstest8 --port 7010 --lport 9010 --gen 440
Pada masa ini, paket yang dihantar ke peranti "jauh" harus mula berkelip dalam konsol dengan TShark, dan nada berterusan akan kedengaran daripada pembesar suara komputer.
Jika semuanya berlaku seperti yang ditulis, maka kami mulakan semula salinan kedua program, tetapi tanpa kunci dan hujah "βgen 440". Anda kini akan memainkan peranan penjana. Selepas ini, anda boleh membuat bunyi ke dalam mikrofon; anda harus mendengar bunyi yang sepadan dalam pembesar suara atau fon kepala. Pengujaan diri akustik mungkin berlaku; kecilkan kelantangan pembesar suara dan kesannya akan hilang.
Jika anda menjalankannya pada dua komputer dan tidak keliru tentang alamat IP, maka hasil yang sama menanti anda - komunikasi suara berkualiti digital dua hala.
Dalam artikel seterusnya kita akan belajar cara menulis penapis kita sendiri - pemalam, terima kasih kepada kemahiran ini anda akan dapat menggunakan penstrim media bukan sahaja untuk audio dan video, tetapi juga di beberapa kawasan khusus lain.
Sumber: www.habr.com