„Mediastreamer2 VoIP“ variklio tyrinėjimas. 6 dalis

Straipsnio medžiaga paimta iš mano zen kanalas.

Garso signalo perdavimas per RTP srautą

„Mediastreamer2 VoIP“ variklio tyrinėjimas. 6 dalis

Paskutiniame straipsnis Mes surinkome nuotolinio valdymo grandinę iš tonų generatoriaus ir tonų detektoriaus, kurie veikia pagal tą pačią programą. Šiame straipsnyje sužinosime, kaip naudoti RTP protokolą (RFC 3550 - RTP: Transporto protokolas, skirtas realaus laiko programoms) garso signalui priimti/perduoti eterneto tinklu.

RTP protokolas (Realaus laiko protokolas) išverstas reiškia realaus laiko protokolą, juo perduodamas garsas, vaizdas, duomenys, viskas, kas reikalauja perdavimo realiu laiku. Kaip pavyzdį paimkime garso signalą. Protokolo lankstumas yra toks, kad jis leidžia perduoti garso signalą iš anksto nustatyta kokybe.

Perdavimas atliekamas naudojant UDP paketus, o tai reiškia, kad paketų praradimas perdavimo metu yra gana priimtinas. Kiekviename pakete yra speciali RTP antraštė ir perduodamo signalo duomenų blokas. Antraštėje yra atsitiktinai parinktas signalo šaltinio identifikatorius, informacija apie perduodamo signalo tipą ir unikalus paketų eilės numeris, kad dekoduojant paketus būtų galima išdėstyti teisinga tvarka, neatsižvelgiant į tai, kokia tvarka jie buvo pristatyti tinklą. Antraštėje taip pat gali būti papildomos informacijos, vadinamojo plėtinio, kuris leidžia antraštę pritaikyti naudoti konkrečioje programos užduotyje.

Duomenų bloke yra naudingoji paketo apkrova. Vidinis turinio organizavimas priklauso nuo apkrovos tipo, tai gali būti mono signalo, stereo signalo, vaizdo vaizdo linijos pavyzdžiai ir kt.

Apkrovos tipas nurodomas septynių bitų skaičiumi. Rekomendacija RFC3551 (RTP profilis garso ir vaizdo konferencijoms su minimaliu valdymu) nustato kelis krovinių tipus; atitinkamoje lentelėje pateikiamas krovinių tipų aprašymas ir kodų, kuriais jie žymimi, reikšmė. Kai kurie kodai nėra griežtai susieti su bet kokio tipo apkrova; jie gali būti naudojami savavališkai apkrovai apibūdinti.

Duomenų bloko dydį riboja didžiausias paketo dydis, kurį galima perduoti tam tikrame tinkle be segmentavimo (MTU parametras). Apskritai tai yra ne daugiau kaip 1500 baitų. Taigi, norėdami padidinti per sekundę perduodamų duomenų kiekį, galite padidinti paketo dydį iki tam tikro taško, o tada reikės padidinti paketų siuntimo dažnumą. Medijos transliuotoje tai yra konfigūruojamas nustatymas. Pagal numatytuosius nustatymus jis yra 50 Hz, t.y. 50 paketų per sekundę. Perduotų RTP paketų seką vadinsime RTP srautu.

Norint pradėti perduoti duomenis tarp šaltinio ir imtuvo, pakanka, kad siųstuvas žinotų imtuvo IP adresą ir prievado numerį, kurį naudoja priėmimui. Tie. be jokių išankstinių procedūrų šaltinis pradeda perduoti duomenis, o imtuvas, savo ruožtu, yra pasirengęs nedelsiant juos priimti ir apdoroti. Pagal standartą prievado numeris, naudojamas RTP srautui perduoti arba priimti, turi būti lyginis.

Tais atvejais, kai iš anksto sužinoti imtuvo adreso neįmanoma, naudojami serveriai, kuriuose imtuvai palieka savo adresą, o siųstuvas gali jo paprašyti, nurodydamas kokį nors unikalų imtuvo pavadinimą.

Tais atvejais, kai nežinoma ryšio kanalo kokybė ar imtuvo galimybės, organizuojamas grįžtamojo ryšio kanalas, kuriuo imtuvas gali informuoti siųstuvą apie savo galimybes, praleistų paketų skaičių ir pan. Šis kanalas naudoja RTCP protokolą. Šiuo kanalu perduodamų paketų formatas yra apibrėžtas RFC 3605. Šiuo kanalu perduodama palyginti mažai duomenų, 200..300 baitų per sekundę, todėl apskritai jo buvimas neapsunkina. Prievado numeris, į kurį siunčiami RTCP paketai, turi būti nelyginis ir vienu didesnis už prievado numerį, iš kurio gaunamas RTP srautas. Mūsų pavyzdyje mes nenaudosime šio kanalo, nes imtuvo ir kanalo galimybės akivaizdžiai viršija mūsų, kol kas kuklius, poreikius.

Mūsų programoje duomenų perdavimo grandinė, skirtingai nei ankstesniame pavyzdyje, bus padalinta į dvi dalis: perdavimo kelią ir priėmimo kelią. Kiekvienai daliai sukursime savo laikrodžio šaltinį, kaip parodyta pavadinimo paveikslėlyje.

Vienpusis ryšys tarp jų bus vykdomas naudojant RTP protokolą. Šiame pavyzdyje mums nereikia išorinio tinklo, nes ir siųstuvas, ir imtuvas bus tame pačiame kompiuteryje – paketai keliaus jo viduje.

Norėdami sukurti RTP srautą, medijos transliuotojas naudoja du filtrus: MS_RTP_SEND ir MS_RTP_RECV. Pirmasis perduoda antrąjį ir priima RTP srautą. Kad šie filtrai veiktų, jie turi perduoti žymeklį RTP seanso objektui, kuris gali konvertuoti duomenų blokų srautą į RTP paketų srautą arba daryti priešingai. Kadangi medijos transliuotojo vidinis duomenų formatas nesutampa su RTP paketo duomenų formatu, prieš perduodant duomenis į MS_RTP_SEND, reikia naudoti kodavimo filtrą, kuris konvertuoja 16 bitų garso signalo pavyzdžius į aštuonių bitų koduotus pagal u-teisė (mu-teisė). Priėmimo pusėje dekoderio filtras atlieka priešingą funkciją.

Žemiau pateikiamas programos, kuri įgyvendina paveikslėlyje pavaizduotą schemą, tekstas (simboliai # prieš įtraukimo direktyvas buvo pašalinti, nepamirškite jų įtraukti):

/* Файл 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);
}
}

Surenkame ir vykdome. Programa veiks kaip ir ankstesniame pavyzdyje, tačiau duomenys bus perduodami per RTP srautą.

Kitame straipsnyje mes padalinsime šią programą į dvi nepriklausomas programas - imtuvą ir siųstuvą ir paleisime juos skirtinguose terminaluose. Tuo pačiu išmoksime analizuoti RTP paketus naudojant TShark programą.

Šaltinis: www.habr.com

Добавить комментарий