Erforschung des VoIP-Engines Mediastreamer2. Teil 6

Der Artikelinhalt stammt von meinem Zen-Kanal.

Übertragung des Audiosignals über RTP-Stream

Erforschung des VoIP-Engines Mediastreamer2. Teil 6

Letzte Woche Artikel Wir haben ein Steuermuster aus einem Generator und einem Detektor für Tonsignale gesammelt, die innerhalb eines Programms arbeiten. In diesem Artikel lernen wir, das RTP-Protokoll (RFC 3550 — RTP: Ein Transportprotokoll für Echtzeitanwendungen) zur Empfangs-/Übertragung des Audiosignals über ein Ethernet-Netzwerk zu verwenden.

Das RTP-Protokoll (Real Time Protocol) bedeutet auf Deutsch Protokoll für Echtzeit, es wird zur Übertragung von Audio, Video, Daten und allem verwendet, was Echtzeitübertragung erfordert. Als Beispiel nehmen wir das Audiosignal. Die Flexibilität des Protokolls ermöglicht es, das Audiosignal mit заранее festgelegter Qualität zu übertragen.

Die Übertragung erfolgt über UDP-Pakete, was bedeutet, dass Paketverluste während der Übertragung durchaus zulässig sind. In jedes Paket wird ein spezieller RTP-Header sowie ein Datenblock des übertragenden Signals eingefügt. Der Header enthält eine zufällig ausgewählte Identifikationsnummer der Signalquelle, Informationen über den Typ des übertragenden Signals, eine eindeutige Seriennummer des Pakets, damit die Pakete bei der Dekodierung in der richtigen Reihenfolge angeordnet werden können, unabhängig davon, in welcher Reihenfolge sie von der Netzwerkinfrastruktur zugestellt wurden. Der Header kann auch zusätzliche Informationen enthalten, das sogenannte Extension, das es erlaubt, den Header an eine spezifische Anwendungsaufgabe anzupassen.

Der Datenblock enthält die Nutzlast des Pakets. Die interne Organisation des Inhalts hängt von der Art der Nutzlast ab, dies können Abtastwerte eines Monosignals, Audiosignale in Stereo, eine Videozeile usw. sein.

Der Typ der Nutzlast wird durch eine siebenstellige Zahl bezeichnet. Die RFC3551-Empfehlung (RTP-Profil für Audio- und Video-Konferenzen mit minimaler Kontrolle) legt mehrere Typen von Nutzlasten fest; in der entsprechenden Tabelle sind die Beschreibungen der Nutzlasttypen und die Werte der Codes aufgeführt, mit denen sie bezeichnet werden. Einige Codes sind nicht strikt an einen bestimmten Typ von Nutzlast gebunden - sie können zur Kennzeichnung beliebiger Nutzlasten verwendet werden.

Die Größe des Datenblocks ist durch die maximale Paketgröße begrenzt, die in diesem Netzwerk ohne Segmentierung übertragen werden kann (MTU-Parameter). In der Regel liegt sie bei nicht mehr als 1500 Byte. Um die Menge der pro Sekunde übertragenen Daten zu erhöhen, kann die Paketgröße bis zu einem bestimmten Punkt erhöht werden, danach muss die Frequenz der Paketübertragungen erhöht werden. Bei einem Mediastreamer ist dies ein einstellbarer Parameter. Standardmäßig beträgt er 50 Hz, also 50 Pakete pro Sekunde. Die übermittelten RTP-Pakete nennen wir RTP-Stream.

Um mit der Datenübertragung zwischen Quelle und Empfänger zu beginnen, genügt es, dass der Sender die IP-Adresse des Empfängers und die Portnummer kennt, die dieser für den Empfang nutzt. Das heißt, ohne jegliche vorhergehenden Verfahren beginnt die Quelle mit der Datenübertragung, und der Empfänger ist bereit, diese sofort anzunehmen und zu verarbeiten. Nach dem Standard sollte die Portnummer, die für die Übertragung oder den Empfang des RTP-Streams verwendet wird, gerade sein.

In Situationen, in denen die Adresse des Empfängers im Voraus nicht bekannt ist, werden Server verwendet, auf denen die Empfänger ihre Adresse hinterlassen, und der Sender kann diese anfordern, indem er sich auf einen bestimmten einzigartigen Namen des Empfängers bezieht.

In Fällen, in denen die Qualität der Kommunikationsleitung oder die Möglichkeiten des Empfängers unbekannt sind, wird ein Feedback-Kanal eingerichtet, über den der Empfänger den Sender über seine Kapazitäten, die Anzahl der Pakete, die ihm fehlen, usw. informieren kann. In diesem Kanal wird das RTCP-Protokoll verwendet. Das Format der in diesem Kanal übermittelten Pakete ist in RFC 3605 definiert. Über diesen Kanal werden relativ wenige Daten übertragen, etwa 200-300 Byte pro Sekunde, weshalb seine Existenz insgesamt nicht belastend ist. Die Portnummer, an die die RTCP-Pakete gesendet werden, muss ungerade und um eins größer sein als die Portnummer, von der der RTP-Stream empfangen wird. In unserem Beispiel werden wir diesen Kanal nicht nutzen, da die Möglichkeiten des Empfängers und des Kanals deutlich über unseren derzeit bescheidenen Bedürfnissen liegen.

In unserem Programm wird das Datentransfer-Schema, im Gegensatz zum Schema des vorherigen Beispiels, in zwei Teile unterteilt: den sendenden Segment und den empfangenden Segment. Für jeden Teil werden wir unsere eigene Taktschwingung erstellen, wie im Titelbild gezeigt.

Die einseitige Kommunikation zwischen ihnen erfolgt über das RTP-Protokoll. In diesem Beispiel benötigen wir kein externes Netzwerk, da sowohl der Sender als auch der Empfänger auf demselben Computer platziert sind – die Pakete werden intern über diesen Computer gesendet.

Für die Einrichtung des RTP-Streams in der Media-Streamer-Anwendung werden zwei Filter verwendet: MS_RTP_SEND und MS_RTP_RECV. Der erste überträgt den zweiten Empfänger des RTP-Streams. Damit diese Filter arbeiten, müssen sie einen Zeiger auf das RTP-Sitzungsobjekt erhalten, das sowohl die Umwandlung des Datenblockstroms in einen RTP-Paketstrom als auch die umgekehrte Funktion durchführen kann. Da das interne Datenformat des Media-Streamers nicht mit dem Datenformat des RTP-Pakets übereinstimmt, muss vor der Übertragung der Daten in MS_RTP_SEND ein Konverterfilter (Encoder) verwendet werden, der 16-Bit-Audiosignalausgaben in 8-Bit-Werte, die nach dem u-Gesetz (mu-Law) kodiert sind, umwandelt. Auf der Empfangsseite wird die umgekehrte Funktion vom Decoder-Filter durchgeführt.

Im Folgenden finden Sie den Programmtext, der das auf dem Bild gezeigte Schema implementiert (die Zeichen # vor den include-Direktiven wurden entfernt, vergessen Sie nicht, sie wieder hinzuzufügen):

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

Wir kompilieren und führen das Programm aus. Es wird wie im vorherigen Beispiel funktionieren, jedoch werden die Daten über den RTP-Stream übertragen.

Im nächsten Artikel werden wir dieses Programm in zwei unabhängige Anwendungen – Empfänger und Sender – aufteilen und sie in verschiedenen Terminals starten. Gleichzeitig lernen wir, RTP-Pakete mit dem Programm TShark zu analysieren.

Quelle: habr.com

60GB SSD 8Gb DDR4