Wir erforschen den VoIP-Engine Mediastreamer2. Teil 6

Der Inhalt dieses Artikels stammt von meinem Zen-Kanal.

Übertragung von Audiosignalen über einen RTP-Stream

Wir erforschen den VoIP-Engine Mediastreamer2. Teil 6

Im letzten Artikel Wir haben ein Steuerungsschema aus einem Generator und einem Detektor für Tonsignale erstellt, die innerhalb eines Programms arbeiten. In diesem Artikel lernen wir, wie man das RTP-Protokoll (RFC 3550 — RTP: Ein Transportprotokoll für Echtzeitanwendungen) zur Übertragung von Audiosignalen über ein Ethernet-Netzwerk verwendet.

Das RTP-Protokoll (Real Time Protocol) dient der Übertragung von Ton, Video, Daten – alles, was eine Übertragung in Echtzeit erfordert. Ein Beispiel dafür ist ein Audiosignal. Die Flexibilität des Protokolls ermöglicht die Übertragung von Audiosignalen mit vordefinierter Qualität.

Die Übertragung erfolgt über UDP-Pakete, was bedeutet, dass Paketverluste während der Übertragung durchaus möglich sind. Jedes Paket enthält einen speziellen RTP-Header und einen Datenblock des übertragene Signals. Im Header befindet sich eine zufällig ausgewählte Quelle-ID, Informationen über den Typ des übertragenen Signals sowie die eindeutige Sequenznummer des Pakets, damit die Pakete beim Decodieren in der richtigen Reihenfolge angeordnet werden können, unabhängig davon, in welcher Reihenfolge sie vom Netzwerk geliefert wurden. Der Header kann auch zusätzliche Informationen, das sogenannte Erweiterungsfeld, enthalten, das es ermöglicht, den Header auf spezifische Anwendungsfälle anzupassen.

Der Datenblock enthält die Nutzlast des Pakets. Die interne Organisation des Inhalts hängt vom Typ der Nutzlast ab; dies können Monosignale, Stereo-Signale, Videobilder usw. sein.

Der Typ der Nutzlast wird durch eine siebenstellige Zahl angegeben. Die Empfehlung RFC3551 (RTP Profile for Audio and Video Conferences with Minimal Control) legt verschiedene Lasttypen fest, die in der entsprechenden Tabelle beschrieben sind. Teile der Codes haben keine feste Zuordnung zu einem bestimmten Lasttyp und können zur Kennzeichnung beliebiger Lasten verwendet werden.

Die Blockgröße ist durch die maximale Paketgröße begrenzt, die in diesem Netzwerk ohne Segmentierung übertragen werden kann (MTU-Parameter). Im Allgemeinen beträgt dies nicht mehr als 1500 Byte. Um die Menge der pro Sekunde übertragenen Daten zu erhöhen, kann zunächst die Paketgröße bis zu einem bestimmten Punkt vergrößert werden, bevor dann die Frequenz der Paketübertragungen erhöht werden muss. Bei einem Mediastrimmer ist dies ein einstellbarer Parameter. Der Standardwert beträgt 50 Hz, das heißt, 50 Pakete pro Sekunde. Die Sequenz der übertragenen RTP-Pakete nennen wir RTP-Stream.

Um mit der Übertragung von Daten zwischen Sender und Empfänger zu beginnen, reicht es, wenn der Sender die IP-Adresse des Empfängers und die Portnummer, die dieser für den Empfang verwendet, kennt. Das heißt, ohne jegliche Vorbereitungen beginnt der Sender mit der Datenübertragung, während der Empfänger bereit ist, diese sofort zu empfangen und zu verarbeiten. Laut Standard sollte die für die Übertragung oder den Empfang eines RTP-Streams verwendete Portnummer 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 auf einen einzigartigen Namen des Empfängers verweist.

In Fällen, in denen die Qualität des Übertragungskanals oder die Möglichkeiten des Empfängers unbekannt sind, wird ein Rückkanal eingerichtet, über den der Empfänger den Sender über seine Möglichkeiten, die Anzahl der Pakete, die ihm fehlen, usw. informieren kann. In diesem Kanal wird das RTCP-Protokoll verwendet. Das Format der in diesem Kanal übertragenen Pakete wird in RFC 3605 definiert. Über diesen Kanal werden vergleichsweise wenige Daten übertragen, etwa 200–300 Bytes pro Sekunde, weshalb seine Nutzung insgesamt unproblematisch ist. Die Portnummer, an die die RTCP-Pakete gesendet werden, muss ungerade und um eins höher sein als die Portnummer, von der der RTP-Stream kommt. In unserem Beispiel werden wir diesen Kanal nicht verwenden, da die Möglichkeiten des Empfängers und des Kanals offensichtlich unsere, bisher bescheidenen, Bedürfnisse übersteigen.

In unserem Programm wird das Datenübertragungsschema, im Gegensatz zum Schema des vorherigen Beispiels, in zwei Teile unterteilt: in den Sender- und den Empfängerweg. Für jeden Teil werden wir unsere eigene Taktquelle schaffen, wie auf dem Titelbild dargestellt.

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 sind — die Pakete werden intern ausgetauscht.

Um den RTP-Stream im Media-Streamer einzurichten, werden zwei Filter verwendet: MS_RTP_SEND und MS_RTP_RECV. Der erste filtert das Sender-Signal, der zweite empfängt den RTP-Stream. Damit diese Filter funktionieren, müssen sie einen Verweis auf das RTP-Sitzungsobjekt erhalten, welches sowohl die Umwandlung von Datenblockströmen in RTP-Pakete als auch die umgekehrte Funktion ausführen kann. Da das interne Datenformat des Media-Streamers nicht mit dem Format des RTP-Pakets übereinstimmt, muss vor der Übertragung von Daten an MS_RTP_SEND ein Konverter-Filter verwendet werden, der 16-Bit-Audiosamples in 8-Bit- u-law kodierte Samples umwandelt. Auf der Empfangsseite übernimmt der Decoder-Filter diese Umwandlungsfunktion.

Im Folgenden finden Sie den Quellcode, der das im Bild dargestellte Schema implementiert (die #-Symbole vor den Include-Direktiven wurden entfernt, denken Sie daran, 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 starten. Das Programm 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 aufteilen – Empfänger und Sender – und sie in verschiedenen Terminals starten. Gleichzeitig lernen wir, RTP-Pakete mit der Software TShark zu analysieren.

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster