Mediastreamer2 VoIP programmas izpēte. 6. daļa

Raksta materiāls ņemts no mana zen kanāls.

Audio signāla pārsūtīšana, izmantojot RTP straumi

Mediastreamer2 VoIP programmas izpēte. 6. daļa

Pagātnē raksts Mēs esam salikuši tālvadības ķēdi no toņu ģeneratora un toņu detektora, kas darbojas vienas programmas ietvaros. Šajā rakstā mēs uzzināsim, kā izmantot RTP protokolu (RFC 3550 - RTP: transporta protokols reāllaika lietojumprogrammām) audio signāla saņemšanai/pārraidīšanai Ethernet tīklā.

RTP protokols (Reālā laika protokols) tulkots nozīmē reāllaika protokolu, to izmanto, lai pārraidītu audio, video, datus, visu, kam nepieciešama pārraide reālajā laikā. Kā piemēru ņemsim audio signālu. Protokola elastība ir tāda, ka tas ļauj pārraidīt audio signālu ar iepriekš noteiktu kvalitāti.

Pārraide tiek veikta, izmantojot UDP paketes, kas nozīmē, ka pakešu zudums pārraides laikā ir diezgan pieņemams. Katra pakete satur īpašu RTP galveni un pārraidītā signāla datu bloku. Galvenē ir nejauši izvēlēts signāla avota identifikators, informācija par pārraidāmā signāla veidu un unikāls pakešu kārtas numurs, lai dekodējot paketes varētu sakārtot pareizā secībā neatkarīgi no secības, kādā tās tika piegādātas tīkls. Galvenē var būt arī papildu informācija, tā sauktais paplašinājums, kas ļauj galveni pielāgot izmantošanai konkrētā lietojumprogrammas uzdevumā.

Datu bloks satur paketes lietderīgo slodzi. Satura iekšējā organizācija ir atkarīga no slodzes veida, tie var būt mono signāla paraugi, stereo signāls, video attēla līnija utt.

Slodzes veidu norāda ar septiņu bitu skaitli. Ieteikums RFC3551 (RTP profils audio un video konferencēm ar minimālu kontroli) nosaka vairākus kravas veidus; attiecīgajā tabulā ir sniegts kravas veidu apraksts un to kodu nozīme, ar kuriem tie apzīmēti. Daži kodi nav stingri saistīti ar jebkāda veida slodzi; tos var izmantot, lai apzīmētu patvaļīgu slodzi.

Datu bloka lielumu iepriekš ierobežo maksimālais pakešu lielums, ko var pārsūtīt noteiktā tīklā bez segmentācijas (MTU parametrs). Kopumā tas ir ne vairāk kā 1500 baiti. Tādējādi, lai palielinātu pārraidīto datu apjomu sekundē, jūs varat palielināt pakešu izmēru līdz noteiktam punktam, un pēc tam jums būs jāpalielina pakešu sūtīšanas biežums. Multivides straumētājā šis ir konfigurējams iestatījums. Pēc noklusējuma tas ir 50 Hz, t.i. 50 paketes sekundē. Pārsūtīto RTP pakešu secību sauksim par RTP straumi.

Lai sāktu datu pārraidi starp avotu un uztvērēju, pietiek ar to, ka raidītājs zina uztvērēja IP adresi un porta numuru, ko tas izmanto saņemšanai. Tie. bez jebkādām iepriekšējām procedūrām avots sāk pārraidīt datus, un uztvērējs, savukārt, ir gatavs tos nekavējoties saņemt un apstrādāt. Saskaņā ar standartu porta numuram, ko izmanto RTP straumes pārsūtīšanai vai saņemšanai, jābūt vienmērīgam.

Situācijās, kad nav iespējams iepriekš zināt uztvērēja adresi, tiek izmantoti serveri, kuros uztvērēji atstāj savu adresi, un raidītājs to var pieprasīt, atsaucoties uz kādu unikālu uztvērēja nosaukumu.

Gadījumos, kad nav zināma sakaru kanāla kvalitāte vai uztvērēja iespējas, tiek organizēts atgriezeniskās saites kanāls, caur kuru uztvērējs var informēt raidītāju par savām iespējām, nokavēto pakešu skaitu utt. Šis kanāls izmanto RTCP protokolu. Šajā kanālā pārraidīto pakešu formāts ir definēts RFC 3605. Pa šo kanālu tiek pārraidīts salīdzinoši maz datu, 200..300 baiti sekundē, tāpēc kopumā tā klātbūtne nav apgrūtinoša. Porta numuram, uz kuru tiek sūtītas RTCP paketes, ir jābūt nepāra un par vienu lielākam par porta numuru, no kura nāk RTP straume. Mūsu piemērā mēs neizmantosim šo kanālu, jo uztvērēja un kanāla iespējas acīmredzami pārsniedz mūsu līdz šim pieticīgās vajadzības.

Mūsu programmā datu pārraides ķēde, atšķirībā no iepriekšējā piemēra, tiks sadalīta divās daļās: pārraides ceļš un uztveršanas ceļš. Katrai daļai mēs izveidosim savu pulksteņa avotu, kā parādīts titulbildē.

Vienvirziena saziņa starp tām tiks veikta, izmantojot RTP protokolu. Šajā piemērā mums nav nepieciešams ārējais tīkls, jo gan raidītājs, gan uztvērējs atradīsies vienā datorā - paketes ceļos tajā.

Lai izveidotu RTP straumi, multivides straumētājs izmanto divus filtrus: MS_RTP_SEND un MS_RTP_RECV. Pirmais pārraida otro un saņem RTP straumi. Lai šie filtri darbotos, tiem ir jānosūta rādītājs RTP sesijas objektam, kas var pārveidot datu bloku straumi RTP pakešu straumē vai rīkoties pretēji. Tā kā multivides straumētāja iekšējais datu formāts neatbilst RTP paketes datu formātam, pirms datu pārsūtīšanas uz MS_RTP_SEND ir jāizmanto kodētāja filtrs, kas pārveido 16 bitu audio signālu paraugus astoņu bitu kodētos saskaņā ar u-likums (mu-likums). Uztvērēja pusē dekodera filtrs veic pretēju funkciju.

Zemāk ir programmas teksts, kas ievieš attēlā redzamo shēmu (simboli # pirms iekļaušanas direktīvām ir noņemti, neaizmirstiet tos iekļaut):

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

Mēs apkopojam un palaižam. Programma darbosies tāpat kā iepriekšējā piemērā, bet dati tiks pārsūtīti, izmantojot RTP straumi.

Nākamajā rakstā mēs sadalīsim šo programmu divās neatkarīgās lietojumprogrammās - uztvērējā un raidītājā un palaidīsim tos dažādos terminālos. Tajā pašā laikā mēs uzzināsim, kā analizēt RTP paketes, izmantojot programmu TShark.

Avots: www.habr.com

Pievieno komentāru