Poznawanie silnika Mediastreamer2 VoIP. Część 6

Materiał artykułu pochodzi z mojego kanał zen.

Przesyłanie sygnału audio poprzez strumień RTP

Poznawanie silnika Mediastreamer2 VoIP. Część 6

W przeszłości Artykuł Złożyliśmy obwód zdalnego sterowania składający się z generatora tonów i detektora tonów, które działają w ramach tego samego programu. W tym artykule dowiemy się, jak korzystać z protokołu RTP (RFC 3550 - RTP: protokół transportowy dla aplikacji czasu rzeczywistego) do odbierania/przesyłania sygnału audio przez sieć Ethernet.

Protokół RTP (Protokół czasu rzeczywistego) w tłumaczeniu oznacza protokół czasu rzeczywistego, służy do przesyłania audio, wideo, danych, wszystkiego, co wymaga transmisji w czasie rzeczywistym. Weźmy jako przykład sygnał audio. Elastyczność protokołu polega na tym, że umożliwia przesyłanie sygnału audio o określonej jakości.

Transmisja odbywa się przy użyciu pakietów UDP, co oznacza, że ​​utrata pakietów podczas transmisji jest w miarę akceptowalna. Każdy pakiet zawiera specjalny nagłówek RTP i blok danych przesyłanego sygnału. Nagłówek zawiera losowo wybrany identyfikator źródła sygnału, informację o rodzaju przesyłanego sygnału oraz unikalny numer kolejny pakietu, dzięki czemu pakiety podczas dekodowania mogą być ułożone we właściwej kolejności, niezależnie od kolejności, w jakiej zostały dostarczone przez nagłówek. sieć. Nagłówek może zawierać także dodatkowe informacje, tzw. rozszerzenie, które pozwala na dostosowanie nagłówka do wykorzystania w konkretnym zadaniu aplikacji.

Blok danych zawiera ładunek pakietu. Wewnętrzna organizacja treści zależy od rodzaju obciążenia, mogą to być próbki sygnału mono, sygnału stereo, linia obrazu wideo itp.

Typ obciążenia jest wskazywany przez siedmiobitową liczbę. Zalecenie RFC3551 (Profil RTP do konferencji audio i wideo przy minimalnej kontroli) ustanawia kilka rodzajów obciążeń; odpowiednia tabela zawiera opis rodzajów obciążeń i znaczenie kodów, za pomocą których są one oznaczone. Niektóre kody nie są ściśle powiązane z żadnym rodzajem obciążenia; można je wykorzystać do oznaczenia dowolnego obciążenia.

Rozmiar bloku danych jest ograniczony powyżej maksymalnym rozmiarem pakietu, jaki można przesłać w danej sieci bez segmentacji (parametr MTU). Ogólnie rzecz biorąc, nie jest to więcej niż 1500 bajtów. Zatem, aby zwiększyć ilość danych przesyłanych na sekundę, można zwiększyć rozmiar pakietu do pewnego momentu, a wtedy konieczne będzie zwiększenie częstotliwości wysyłania pakietów. W przypadku streamera multimediów jest to ustawienie konfigurowalne. Domyślnie jest to 50 Hz, tj. 50 pakietów na sekundę. Sekwencję przesyłanych pakietów RTP nazwiemy strumieniem RTP.

Aby rozpocząć transmisję danych pomiędzy źródłem a odbiornikiem wystarczy, że nadajnik zna adres IP odbiornika oraz numer portu, którego używa do odbioru. Te. bez żadnych wstępnych procedur źródło zaczyna przesyłać dane, a odbiorca z kolei jest gotowy do ich natychmiastowego odbioru i przetworzenia. Zgodnie ze standardem numer portu używany do przesyłania lub odbierania strumienia RTP musi być parzysty.

W sytuacjach, gdy nie jest możliwe wcześniejsze poznanie adresu odbiorcy, stosuje się serwery, na których odbiorcy zostawiają swój adres, a nadawca może o to poprosić, powołując się na jakąś unikalną nazwę odbiorcy.

W przypadkach, gdy nie jest znana jakość kanału komunikacyjnego lub możliwości odbiornika, organizuje się kanał informacji zwrotnej, za pośrednictwem którego odbiorca może poinformować nadajnik o swoich możliwościach, liczbie przeoczonych pakietów itp. Kanał ten wykorzystuje protokół RTCP. Format pakietów przesyłanych tym kanałem jest zdefiniowany w RFC 3605. Tym kanałem przesyłanych jest stosunkowo niewiele danych, 200..300 bajtów na sekundę, więc generalnie jego obecność nie jest uciążliwa. Numer portu, do którego wysyłane są pakiety RTCP, musi być nieparzysty i o jeden większy od numeru portu, z którego pochodzi strumień RTP. W naszym przykładzie nie będziemy korzystać z tego kanału, gdyż możliwości odbiornika i kanału w oczywisty sposób przekraczają nasze, skromne na razie, potrzeby.

W naszym programie obwód transmisji danych, w przeciwieństwie do poprzedniego przykładu, zostanie podzielony na dwie części: tor nadawczy i tor odbiorczy. Dla każdej części wykonamy własne źródło zegara, jak pokazano na obrazku tytułowym.

Jednokierunkowa komunikacja pomiędzy nimi będzie realizowana z wykorzystaniem protokołu RTP. W tym przykładzie nie potrzebujemy sieci zewnętrznej, ponieważ zarówno nadajnik, jak i odbiornik będą znajdować się na tym samym komputerze - pakiety będą w nim podróżować.

Aby ustanowić strumień RTP, streamer multimediów wykorzystuje dwa filtry: MS_RTP_SEND i MS_RTP_RECV. Pierwszy transmituje drugi i odbiera strumień RTP. Aby te filtry zadziałały, muszą przekazać wskaźnik do obiektu sesji RTP, który może albo przekonwertować strumień bloków danych na strumień pakietów RTP, albo zrobić odwrotnie. Ponieważ wewnętrzny format danych streamera multimediów nie jest zgodny z formatem danych pakietu RTP, przed przesłaniem danych do MS_RTP_SEND należy zastosować filtr kodera, który konwertuje 16-bitowe próbki sygnału audio na XNUMX-bitowe zakodowane zgodnie z u-law (mu-law). Po stronie odbiorczej filtr dekodera pełni odwrotną funkcję.

Poniżej znajduje się tekst programu realizującego schemat pokazany na rysunku (symbole # przed dyrektywami include zostały usunięte, nie zapomnij o ich dołączeniu):

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

Kompilujemy i uruchamiamy. Program będzie działał jak w poprzednim przykładzie, ale dane będą przesyłane strumieniem RTP.

W następnym artykule podzielimy ten program na dwie niezależne aplikacje - odbiornik i nadajnik i uruchomimy je w różnych terminalach. Jednocześnie nauczymy się analizować pakiety RTP za pomocą programu TShark.

Źródło: www.habr.com

Dodaj komentarz