Esplorante la Mediastreamer2 VoIP-motoron. Parto 6

La materialo de la artikolo estas prenita el mia zen kanalo.

Transdonante sonsignalon per RTP-rivereto

Esplorante la Mediastreamer2 VoIP-motoron. Parto 6

En la lasta artikolo Ni kunvenis teleregilcirkviton de tongeneratoro kaj tondetektilo, kiuj funkcias ene de la sama programo. En ĉi tiu artikolo ni lernos kiel uzi la RTP-protokolon (RFC 3550 - RTP: Transporta Protokolo por Realtempaj Aplikoj) por ricevado/elsendado de sonsignalo tra Ethernet-reto.

RTP-protokolo (Reala Tempo Protokolo) tradukita signifas realtempan protokolon, ĝi estas uzata por transdoni sonojn, filmetojn, datumojn, ĉion, kio postulas transsendon en reala tempo. Ni prenu sonsignalon kiel ekzemplon. La fleksebleco de la protokolo estas tia, ke ĝi permesas vin transdoni sonsignalon kun antaŭdeterminita kvalito.

La dissendo estas farita per UDP-pakaĵetoj, kio signifas, ke pakaĵetperdo estas sufiĉe akceptebla dum dissendo. Ĉiu pakaĵeto enhavas specialan RTP-kapon kaj datumblokon de la elsendita signalo. La kaplinio enhavas hazarde elektitan signalfontidentigilon, informojn pri la speco de signalo estanta elsendita, kaj unikan pakaĵetsekvencnumeron tiel ke la pakaĵetoj povas esti aranĝitaj en la ĝusta sinsekvo dum malkodado, nekonsiderante la ordo en kiu ili estis liveritaj fare de la reto. La kaplinio ankaŭ povas enhavi pliajn informojn, la tielnomitan etendon, kiu permesas al la kaplinio esti adaptita por uzo en specifa aplikaĵotasko.

La datumbloko enhavas la utilan ŝarĝon de la pakaĵeto. La interna organizo de la enhavo dependas de la tipo de ŝarĝo, ĝi povas esti specimenoj de mono signalo, stereosignalo, videobildlinio, ktp.

La ŝarĝospeco estas indikita per sep-bita nombro. Rekomendo RFC3551 (RTP-Profilo por Audio- kaj Video-Konferencoj kun Minimuma Kontrolo) establas plurajn specojn de ŝarĝo; la responda tabelo disponigas priskribon de la specoj de ŝarĝo kaj la signifo de la kodoj per kiuj ili estas indikitaj. Kelkaj kodoj ne estas strikte ligitaj al iu speco de ŝarĝo; ili povas esti uzitaj por indiki arbitran ŝarĝon.

La grandeco de datumbloko estas limigita supre per la maksimuma pakaĵetgrandeco kiu povas esti elsendita sur antaŭfiksita reto sen segmentado (MTU-parametro). Ĝenerale, ĉi tio ne estas pli ol 1500 bajtoj. Tiel, por pliigi la kvanton de datumoj transdonitaj sekundo, vi povas pliigi la pakaĵgrandecon ĝis certa punkto, kaj tiam vi devos pliigi la oftecon de sendado de pakaĵoj. En amaskomunikila streamer, ĉi tio estas agordebla agordo. Defaŭlte ĝi estas 50 Hz, t.e. 50 pakoj por sekundo. Ni nomos la sekvencon de elsenditaj RTP-pakoj RTP-rivereto.

Por komenci transdoni datumojn inter la fonto kaj la ricevilo, sufiĉas, ke la dissendilo konas la IP-adreson de la ricevilo kaj la haveno-numeron, kiun ĝi uzas por ricevi. Tiuj. sen iuj antaŭaj proceduroj, la fonto komencas transdoni datumojn, kaj la ricevilo, siavice, estas preta tuj ricevi kaj prilabori ĝin. Laŭ la normo, la havena numero uzata por transdoni aŭ ricevi RTP-fluon devas esti ebena.

En situacioj kie estas maleble scii la adreson de la ricevilo anticipe, serviloj estas uzitaj kie riceviloj forlasas sian adreson, kaj la dissendilo povas peti ĝin rilatante al iu unika nomo de la ricevilo.

En kazoj kie la kvalito de la komunika kanalo aŭ la kapabloj de la ricevilo estas nekonataj, religkanalo estas organizita tra kiu la ricevilo povas informi la dissendilon pri siaj kapabloj, la nombro da pakaĵetoj kiujn ĝi maltrafis, ktp. Ĉi tiu kanalo uzas la protokolon RTCP. La formato de pakoj transdonitaj en ĉi tiu kanalo estas difinita en RFC 3605. Relative malmulte da datumoj estas transdonitaj tra ĉi tiu kanalo, 200..300 bajtoj sekundo, do ĝenerale, ĝia ĉeesto ne estas ŝarĝa. La havennumero al kiu RTCP-pakoj estas senditaj devas esti nepara kaj unu pli granda ol la havennumero de kiu la RTP-rivereto venas. En nia ekzemplo, ni ne uzos ĉi tiun kanalon, ĉar la kapabloj de la ricevilo kaj kanalo evidente superas niajn, ĝis nun modestajn, bezonojn.

En nia programo, la cirkvito de transdono de datumoj, male al la antaŭa ekzemplo, estos dividita en du partojn: elsendan vojon kaj ricevan vojon. Por ĉiu parto ni faros nian propran horloĝan fonton, kiel montrite en la titolbildo.

Unudirekta komunikado inter ili estos efektivigita per la RTP-protokolo. En ĉi tiu ekzemplo, ni ne bezonas eksteran reton, ĉar kaj la dissendilo kaj la ricevilo troviĝos sur la sama komputilo - la pakaĵoj vojaĝos ene de ĝi.

Por establi RTP-fluon, la amaskomunikilara streamer uzas du filtrilojn: MS_RTP_SEND kaj MS_RTP_RECV. La unua elsendas la duan kaj ricevas la RTP-rivereton. Por ke ĉi tiuj filtriloj funkciu, ili devas pasi montrilon al RTP-sesiobjekto, kiu povas aŭ konverti fluon de datumblokoj en fluon de RTP-pakaĵoj aŭ fari la malon. Ĉar la interna datumformato de la amaskomunikilara streamer ne kongruas kun la datumformato de la RTP-pako, antaŭ ol transdoni la datumojn al MS_RTP_SEND, vi devas uzi kodigan filtrilon, kiu konvertas 16-bitajn sonsignalojn en ok-bitajn kodigitajn laŭ la u-law (mu-law). Sur la ricevanta flanko, la malĉifrilo-filtrilo plenumas la kontraŭan funkcion.

Malsupre estas la teksto de la programo, kiu efektivigas la skemon montritan en la figuro (la # simboloj antaŭ la inkluzivaj direktivoj estis forigitaj, ne forgesu inkluzivi ilin):

/* Файл mstest6.c Имитатор пульта управления и приемника. */
#include <mediastreamer2/msfilter.h>
#include <mediastreamer2/msticker.h>
#include <mediastreamer2/dtmfgen.h>
#include <mediastreamer2/mssndcard.h>
#include <mediastreamer2/msvolume.h>
#include <mediastreamer2/mstonedetector.h>
#include <mediastreamer2/msrtp.h>
#include <ortp/rtpsession.h>
#include <ortp/payloadtype.h>
/* Подключаем заголовочный файл с функциями управления событиями
* медиастримера.*/
include <mediastreamer2/mseventqueue.h>
#define PCMU 0
/* Функция обратного вызова, она будет вызвана фильтром, как только он
обнаружит совпадение характеристик входного сигнала с заданными. */
static void tone_detected_cb(void *data, MSFilter *f, unsigned int event_id,
MSToneDetectorEvent *ev)
{
printf("Принята команда: %sn", ev->tone_name);
}
/*----------------------------------------------------------------------------*/
/* Функция регистрации типов полезных нагрузок. */
void register_payloads(void)
{
/*Регистрируем типы нагрузок в таблице профилей. Позднее, по индексу
взятому из заголовка RTP-пакета из этой таблицы будут извлекаться
параметры нагрузки, необходимые для декодирования данных пакета. */
rtp_profile_set_payload (&av_profile, PCMU, &payload_type_pcm8000);
}
/*----------------------------------------------------------------------------*/
/* Эта функция создана из функции create_duplex_rtpsession() в audiostream.c
медиастримера2. */
static RtpSession *
create_rtpsession (int loc_rtp_port, int loc_rtcp_port,
bool_t ipv6, RtpSessionMode mode)
{
RtpSession *rtpr;
rtpr = rtp_session_new ((int) mode);
rtp_session_set_scheduling_mode (rtpr, 0);
rtp_session_set_blocking_mode (rtpr, 0);
rtp_session_enable_adaptive_jitter_compensation (rtpr, TRUE);
rtp_session_set_symmetric_rtp (rtpr, TRUE);
rtp_session_set_local_addr (rtpr, ipv6 ? "::" : "0.0.0.0", loc_rtp_port,
loc_rtcp_port);
rtp_session_signal_connect (rtpr, "timestamp_jump",
(RtpCallback) rtp_session_resync, 0);
rtp_session_signal_connect (rtpr, "ssrc_changed",
(RtpCallback) rtp_session_resync, 0);
rtp_session_set_ssrc_changed_threshold (rtpr, 0);
rtp_session_set_send_payload_type(rtpr, PCMU);
/* По умолчанию выключаем RTCP-сессию, так как наш пульт не будет использовать её. */
rtp_session_enable_rtcp (rtpr, FALSE);
return rtpr;
}
/*----------------------------------------------------------------------------*/
int main()
{
ms_init();
/* Создаем экземпляры фильтров. */
MSFilter *voidsource = ms_filter_new(MS_VOID_SOURCE_ID);
MSFilter *dtmfgen = ms_filter_new(MS_DTMF_GEN_ID);
MSFilter *volume = ms_filter_new(MS_VOLUME_ID);
MSSndCard *card_playback =
ms_snd_card_manager_get_default_card(ms_snd_card_manager_get());
MSFilter *snd_card_write = ms_snd_card_create_writer(card_playback);
MSFilter *detector = ms_filter_new(MS_TONE_DETECTOR_ID);
/* Очищаем массив находящийся внутри детектора тонов, он описывает
* особые приметы разыскиваемых сигналов.*/
ms_filter_call_method(detector, MS_TONE_DETECTOR_CLEAR_SCANS, 0);
/* Подключаем к фильтру функцию обратного вызова. */
ms_filter_set_notify_callback(detector,
(MSFilterNotifyFunc)tone_detected_cb, NULL);
/* Создаем массив, каждый элемент которого описывает характеристику
* одного из тонов, который требуется обнаруживать: Текстовое имя
* данного элемента, частота в герцах, длительность в миллисекундах,
* минимальный уровень относительно 0,775В. */
MSToneDetectorDef scan[6]=
{
{"V+",440, 100, 0.1}, /* Команда "Увеличить громкость". */
{"V-",540, 100, 0.1}, /* Команда "Уменьшить громкость". */
{"C+",640, 100, 0.1}, /* Команда "Увеличить номер канала". */
{"C-",740, 100, 0.1}, /* Команда "Уменьшить номер канала". */
{"ON",840, 100, 0.1}, /* Команда "Включить телевизор". */
{"OFF", 940, 100, 0.1}/* Команда "Выключить телевизор". */
};
/* Передаем "приметы" сигналов детектор тонов. */
int i;
for (i = 0; i < 6; i++)
{
ms_filter_call_method(detector, MS_TONE_DETECTOR_ADD_SCAN,
&scan[i]);
}
/* Создаем фильтры кодера и декодера */
MSFilter *encoder = ms_filter_create_encoder("PCMU");
MSFilter *decoder=ms_filter_create_decoder("PCMU");
/* Регистрируем типы нагрузки. */
register_payloads();
/* Создаем RTP-сессию передатчика. */
RtpSession *tx_rtp_session = create_rtpsession (8010, 8011, FALSE, RTP_SESSION_SENDONLY);
rtp_session_set_remote_addr_and_port(tx_rtp_session,"127.0.0.1", 7010, 7011);
rtp_session_set_send_payload_type(tx_rtp_session, PCMU);
MSFilter *rtpsend = ms_filter_new(MS_RTP_SEND_ID);
ms_filter_call_method(rtpsend, MS_RTP_SEND_SET_SESSION, tx_rtp_session);
/* Создаем RTP-сессию приемника. */
MSFilter *rtprecv = ms_filter_new(MS_RTP_RECV_ID);
RtpSession *rx_rtp_session = create_rtpsession (7010, 7011, FALSE, RTP_SESSION_RECVONLY);
ms_filter_call_method(rtprecv, MS_RTP_RECV_SET_SESSION, rx_rtp_session);
/* Создаем источники тактов - тикеры. */
MSTicker *ticker_tx = ms_ticker_new();
MSTicker *ticker_rx = ms_ticker_new();
/* Соединяем фильтры передатчика. */
ms_filter_link(voidsource, 0, dtmfgen, 0);
ms_filter_link(dtmfgen, 0, volume, 0);
ms_filter_link(volume, 0, encoder, 0);
ms_filter_link(encoder, 0, rtpsend, 0);
/* Соединяем фильтры приёмника. */
ms_filter_link(rtprecv, 0, decoder, 0);
ms_filter_link(decoder, 0, detector, 0);
ms_filter_link(detector, 0, snd_card_write, 0);
/* Подключаем источник тактов. */
ms_ticker_attach(ticker_tx, voidsource);
ms_ticker_attach(ticker_rx, rtprecv);
/* Настраиваем структуру, управляющую выходным сигналом генератора. */
MSDtmfGenCustomTone dtmf_cfg;
dtmf_cfg.tone_name[0] = 0;
dtmf_cfg.duration = 1000;
dtmf_cfg.frequencies[0] = 440;
/* Будем генерировать один тон, частоту второго тона установим в 0. */
dtmf_cfg.frequencies[1] = 0;
dtmf_cfg.amplitude = 1.0;
dtmf_cfg.interval = 0.;
dtmf_cfg.repeat_count = 0.;
/* Организуем цикл сканирования нажатых клавиш. Ввод нуля завершает
* цикл и работу программы. */
char key='9';
printf("Нажмите клавишу команды, затем ввод.n"
"Для завершения программы введите 0.n");
while(key != '0')
{
key = getchar();
if ((key >= 49) && (key <= 54))
{
printf("Отправлена команда: %cn", key);
/* Устанавливаем частоту генератора в соответствии с
* кодом нажатой клавиши. */
dtmf_cfg.frequencies[0] = 440 + 100*(key-49);
/* Включаем звуковой генератор c обновленной частотой. */
ms_filter_call_method(dtmfgen, MS_DTMF_GEN_PLAY_CUSTOM,
(void*)&dtmf_cfg);
}
/* Укладываем тред в спячку на 20мс, чтобы другие треды
* приложения получили время на работу. */
ms_usleep(20000);
}
}

Ni kompilas kaj kuras. La programo funkcios kiel en la antaŭa ekzemplo, sed la datumoj estos transdonitaj per RTP-rivereto.

En la sekva artikolo ni dividos ĉi tiun programon en du sendependajn aplikojn - ricevilon kaj dissendilon kaj lanĉos ilin en malsamaj terminaloj. Samtempe, ni lernos kiel analizi RTP-pakojn per la programo TShark.

fonto: www.habr.com

Aldoni komenton