Explorant el motor de VoIP Mediastreamer2. Part 6

El material de l'article està extret del meu canal zen.

Transmissió d'un senyal d'àudio mitjançant flux RTP

Explorant el motor de VoIP Mediastreamer2. Part 6

En l'últim article Hem muntat un circuit de control remot a partir d'un generador de to i un detector de to que funcionen dins del mateix programa. En aquest article aprendrem a utilitzar el protocol RTP (RFC 3550 - RTP: un protocol de transport per a aplicacions en temps real) per rebre/transmetre un senyal d'àudio a través d'una xarxa Ethernet.

Protocol RTP (Protocol en temps real) traduït vol dir protocol en temps real, s'utilitza per transmetre àudio, vídeo, dades, tot allò que requereix transmissió en temps real. Prenguem un senyal d'àudio com a exemple. La flexibilitat del protocol és tal que permet transmetre un senyal d'àudio amb una qualitat predeterminada.

La transmissió es realitza mitjançant paquets UDP, la qual cosa significa que la pèrdua de paquets és força acceptable durant la transmissió. Cada paquet conté una capçalera RTP especial i un bloc de dades del senyal transmès. La capçalera conté un identificador de la font de senyal seleccionat aleatòriament, informació sobre el tipus de senyal que s'està transmetent i un número de seqüència de paquet únic perquè els paquets es puguin organitzar en l'ordre correcte quan es descodifiquen, independentment de l'ordre en què van ser lliurats pel xarxa. La capçalera també pot contenir informació addicional, l'anomenada extensió, que permet adaptar la capçalera per utilitzar-la en una tasca específica d'aplicació.

El bloc de dades conté la càrrega útil del paquet. L'organització interna del contingut depèn del tipus de càrrega, poden ser mostres d'un senyal mono, un senyal estèreo, una línia d'imatge de vídeo, etc.

El tipus de càrrega s'indica amb un nombre de set bits. Recomanació RFC3551 (Perfil RTP per a conferències d'àudio i vídeo amb un control mínim) estableix diversos tipus de càrrega; la taula corresponent proporciona una descripció dels tipus de càrrega i el significat dels codis pels quals estan designats. Alguns codis no estan estrictament vinculats a cap tipus de càrrega; es poden utilitzar per designar una càrrega arbitrària.

La mida d'un bloc de dades està limitada a dalt per la mida màxima del paquet que es pot transmetre en una xarxa determinada sense segmentació (paràmetre MTU). En general, això no supera els 1500 bytes. Així, per augmentar la quantitat de dades transmeses per segon, podeu augmentar la mida del paquet fins a un cert punt i, aleshores, haureu d'augmentar la freqüència d'enviament de paquets. En un streamer multimèdia, aquesta és una configuració configurable. Per defecte és de 50 Hz, és a dir. 50 paquets per segon. Anomenarem flux RTP a la seqüència de paquets RTP transmesos.

Per començar a transmetre dades entre la font i el receptor, n'hi ha prou que l'emissor conegui l'adreça IP del receptor i el número de port que utilitza per rebre'l. Aquells. sense cap procediment previ, la font comença a transmetre dades i el receptor, al seu torn, està preparat per rebre-les i processar-les immediatament. Segons l'estàndard, el número de port utilitzat per transmetre o rebre un flux RTP ha de ser parell.

En situacions en què és impossible conèixer l'adreça del receptor amb antelació, s'utilitzen servidors on els receptors deixen la seva adreça, i el transmissor pot sol·licitar-ho fent referència a algun nom únic del receptor.

En els casos en què es desconeix la qualitat del canal de comunicació o les capacitats del receptor, s'organitza un canal de retroalimentació a través del qual el receptor pot informar al transmissor sobre les seves capacitats, el nombre de paquets que ha perdut, etc. Aquest canal utilitza el protocol RTCP. El format dels paquets transmesos en aquest canal està definit a la RFC 3605. Per aquest canal es transmeten relativament poques dades, 200..300 bytes per segon, de manera que, en general, la seva presència no és onerosa. El número de port al qual s'envien els paquets RTCP ha de ser senar i un més gran que el número de port del qual prové el flux RTP. En el nostre exemple, no utilitzarem aquest canal, ja que les capacitats del receptor i del canal òbviament superen les nostres, fins ara modestes, necessitats.

Al nostre programa, el circuit de transmissió de dades, a diferència de l'exemple anterior, es dividirà en dues parts: una ruta de transmissió i una ruta de recepció. Per a cada part farem la nostra pròpia font de rellotge, tal com es mostra a la imatge del títol.

La comunicació unidireccional entre ells es realitzarà mitjançant el protocol RTP. En aquest exemple, no necessitem una xarxa externa, ja que tant l'emissor com el receptor estaran ubicats al mateix ordinador: els paquets viatjaran a l'interior.

Per establir un flux RTP, el reproductor multimèdia utilitza dos filtres: MS_RTP_SEND i MS_RTP_RECV. El primer transmet el segon i rep el flux RTP. Perquè aquests filtres funcionin, han de passar un punter a un objecte de sessió RTP, que pot convertir un flux de blocs de dades en un flux de paquets RTP o fer el contrari. Com que el format de dades intern del transmissor multimèdia no coincideix amb el format de dades del paquet RTP, abans de transferir les dades a MS_RTP_SEND, cal que utilitzeu un filtre codificador que converteixi les mostres de senyal d'àudio de 16 bits en una codificació de vuit bits segons el u-law (mu-law). Al costat receptor, el filtre descodificador realitza la funció contrària.

A continuació es mostra el text del programa que implementa l'esquema que es mostra a la figura (s'han eliminat els símbols # abans de les directives d'inclusió, no oblideu incloure-los):

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

Compilem i executem. El programa funcionarà com a l'exemple anterior, però les dades es transmetran mitjançant un flux RTP.

En el següent article dividirem aquest programa en dues aplicacions independents: un receptor i un transmissor i els llançarem en diferents terminals. Al mateix temps, aprendrem a analitzar paquets RTP mitjançant el programa TShark.

Font: www.habr.com

Afegeix comentari