Materiał artykułu pochodzi z mojego .
Transmisja sygnału dźwiękowego przez strumień RTP

W poprzedniej Zebraliśmy schemat zdalnego sterowania z generatora i detektora sygnałów tonowych, które działają w ramach jednego programu. W tym artykule nauczymy się korzystać z protokołu RTP (RFC 3550 - RTP: Protokół transportowy do aplikacji czasu rzeczywistego) do odbioru/transmisji sygnału dźwiękowego przez sieć Ethernet.
Protokół RTP (Protokół Czasu Rzeczywistego) jest tłumaczony jako protokół czasu rzeczywistego, jest używany do transmisji dźwięku, wideo, danych, wszystkiego, co wymaga przesyłania w czasie rzeczywistym. Jako przykład weźmy sygnał dźwiękowy. Elastyczność protokołu umożliwia przesyłanie sygnału dźwiękowego z wcześniej określoną jakością.
Transmisja odbywa się za pomocą pakietów UDP, co oznacza, że przy przesyłaniu dopuszczalna jest utrata pakietów. Każdy pakiet zawiera specjalny nagłówek RTP oraz blok danych przesyłanego sygnału. W nagłówku znajduje się losowo wybrany identyfikator źródła sygnału, informacje o typie przesyłanego sygnału, unikalny numer porządkowy pakietu, aby pakiety podczas dekodowania mogły być uporządkowane poprawnie, niezależnie od tego, w jakiej kolejności zostały dostarczone przez sieć. Nagłówek może również zawierać dodatkowe informacje, tzw. rozszerzenie, które umożliwia dostosowanie nagłówka do konkretnego zastosowania.
Blok danych zawiera ładunek pakietu. Wewnętrzna organizacja zawartości zależy od typu ładunku, mogą to być próbki sygnału monofonicznego, sygnał stereofoniczny, linia obrazu wideo itp.
Typ ładunku jest oznaczany siedmiobitową liczbą. Zalecenie RFC3551 (Profil RTP dla audio i wideo konferencji z minimalną kontrolą) ustala kilka typów ładunku, w odpowiedniej tabeli znajduje się opis typów ładunku oraz wartości kodów, którymi są oznaczane. Część kodów nie ma ścisłego związku z żadnym typem ładunku - mogą być używane do oznaczania dowolnego ładunku.
Rozmiar bloku danych jest ograniczony maksymalnym rozmiarem pakietu, który może być przesyłany w danej sieci bez segmentacji (parametr MTU). Z reguły nie przekracza on 1500 bajtów. W związku z tym, aby zwiększyć ilość danych przesyłanych na sekundę, można do pewnego momentu zwiększać rozmiar pakietu, a następnie konieczne będzie zwiększenie częstotliwości wysyłania pakietów. W przypadku streamera medialnego jest to parametr konfigurowalny. Domyślnie wynosi on 50 Hz, czyli 50 pakietów na sekundę. Sekwencję przesyłanych pakietów RTP będziemy nazywać strumieniem RTP.
Aby rozpocząć przesyłanie danych między źródłem a odbiornikiem, wystarczy, aby nadajnik znał adres IP odbiornika i numer portu, który ten używa do odbioru. To znaczy, że bez żadnych wcześniejszych procedur źródło zaczyna przesyłać dane, a odbiornik jest gotowy, aby je natychmiast przyjąć i przetworzyć. Zgodnie ze standardem, numer portu używanego do przesyłania lub odbierania strumienia RTP powinien być parzysty.
W sytuacjach, gdy nie można z góry znać adresu odbiornika, wykorzystuje się serwery, na których odbiorcy zostawiają swój adres, a nadajnik może go zażądać, odnosząc się do pewnej unikalnej nazwy odbiornika.
W przypadkach, gdy jakość łącza komunikacyjnego lub możliwości odbiornika są nieznane, organizowany jest kanał zwrotny, za pomocą którego odbiornik może informować nadajnik o swoich możliwościach, ilości pakietów, których nie odebrał, itp. W takim kanale używany jest protokół RTCP. Format pakietów przesyłanych w tym kanale określony jest w RFC 3605. Przesyłane przez ten kanał dane są stosunkowo niewielkie i wynoszą 200-300 bajtów na sekundę, dlatego jego obecność nie jest obciążająca. Numer portu, na który wysyłane są pakiety RTCP, powinien być nieparzysty i o jeden większy od numeru portu, z którego przychodzi strumień RTP. W naszym przykładzie nie będziemy korzystać z tego kanału, ponieważ możliwości odbiornika i łącza zdecydowanie przekraczają nasze, jak na razie skromne, potrzeby.
W naszym programie schemat przesyłania danych, w przeciwieństwie do schematu w poprzednim przykładzie, będzie podzielony na dwie części: na tor nadawczy i tor odbiorczy. Dla każdej części stworzymy własne źródło taktowania, jak pokazano na głównym obrazku.
Jednokierunkowa komunikacja między nimi będzie realizowana za pomocą protokołu RTP. W tym przykładzie nie będziemy potrzebować zewnętrznej sieci, ponieważ zarówno nadajnik, jak i odbiornik będą znajdować się na tym samym komputerze — pakiety będą przemieszczane wewnątrz niego.
Do ustanowienia strumienia RTP w mediastreamerze używa się dwóch filtrów: MS_RTP_SEND i MS_RTP_RECV. Pierwszy z nich odpowiada za przesyłanie, a drugi za odbieranie strumienia RTP. Aby te filtry zaczęły działać, należy przekazać wskaźnik do obiektu sesji RTP, który może wykonywać zarówno konwersję strumienia bloków danych na strumień pakietów RTP, jak i odwrotne działanie. Ponieważ wewnętrzny format danych mediastreamera nie zgadza się z formatem danych pakietu RTP, przed przesłaniem danych do MS_RTP_SEND należy użyć filtra konwertera (encoder), który przekształca 16-bitowe próbki sygnału dźwiękowego na osiem bitów zakodowanych zgodnie z prawem u (mu-law). Funkcję odwrotną po stronie odbiorczej wykonuje filtr decoder.
Poniżej znajduje się tekst programu, który implementuje schemat przedstawiony na rysunku (symbole # przed dyrektywami include usunięto, nie zapomnij ich dodać):
/* Файл 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, uruchamiamy. Program będzie działał tak samo jak w poprzednim przykładzie, ale dane będą przesyłane przez strumień RTP.
W następnym artykule podzielimy ten program na dwie niezależne aplikacje — odbiornik i nadajnik, i uruchomimy je w różnych terminalach. Równocześnie nauczymy się analizować pakiety RTP za pomocą programu TShark.
Źródło: habr.com
