Explorando el motor VoIP Mediastreamer2. Parte 6

El material del artículo fue tomado de mi canal de Zen.

Transmisión de la señal de audio a través de un flujo RTP

Explorando el motor VoIP Mediastreamer2. Parte 6

En el anterior el artículo Hemos reunido un esquema de control remoto desde un generador y un detector de señales tonales, que funcionan dentro de un mismo programa. En este artículo, aprenderemos a utilizar el protocolo RTP (RFC 3550 — RTP: Un protocolo de transporte para aplicaciones en tiempo real) para enviar/recibir señales de audio a través de una red Ethernet.

El protocolo RTP (Protocolo de Tiempo Real) se traduce como protocolo en tiempo real, y se utiliza para la transmisión de audio, video, datos, todo aquello que requiere transmisión en tiempo real. Tomemos como ejemplo una señal de audio. La flexibilidad del protocolo es tal que permite transmitir una señal de audio con una calidad predefinida.

La transmisión se lleva a cabo utilizando paquetes UDP, lo que significa que durante la transmisión se permite la pérdida de paquetes. Cada paquete incluye un encabezado RTP especial y un bloque de datos de la señal transmitida. En el encabezado se encuentra un identificador de fuente de señal seleccionado aleatoriamente, información sobre el tipo de señal transmitida, un número de secuencia único del paquete, para que los paquetes pudieran ser ordenados correctamente al ser decodificados, independientemente del orden en que la red los entregó. El encabezado también puede contener información adicional, conocido como extensión, que permite adaptar el encabezado a aplicaciones específicas.

El bloque de datos contiene la carga útil del paquete. La organización interna del contenido depende del tipo de carga, que puede ser muestras de señal mono, señal estéreo, línea de video, etc.

El tipo de carga se designa con un número de siete bits. La recomendación RFC3551 (Perfil RTP para Conferencias de Audio y Video con Control Mínimo) establece varios tipos de carga en una tabla correspondiente que describe los tipos de carga y los valores de los códigos que los designan. Algunos códigos no están estrictamente asociados con un tipo de carga en específico; pueden usarse para indicar cargas arbitrarias.

El tamaño del bloque de datos está limitado por el tamaño máximo del paquete que se puede transmitir en esta red sin segmentación (parámetro MTU). En general, esto no supera los 1500 bytes. Por lo tanto, para aumentar la cantidad de datos transmitidos por segundo, se puede aumentar el tamaño del paquete hasta cierto punto, y luego será necesario aumentar la frecuencia de envío de paquetes. En el medio de transmisión, este es un parámetro configurable. Por defecto, se establece en 50 Hz, es decir, 50 paquetes por segundo. La secuencia de paquetes RTP transmitidos se llamará flujo RTP.

Para comenzar la transmisión de datos entre el origen y el receptor, es suficiente que el emisor conozca la dirección IP del receptor y el número de puerto que utiliza para recibir. Es decir, sin ningún procedimiento previo, el origen comienza a transmitir datos, mientras que el receptor, por su parte, está listo para recibirlos y procesarlos de inmediato. De acuerdo con el estándar, el número de puerto utilizado para transmitir o recibir el flujo RTP debe ser par.

En situaciones en las que no se puede conocer de antemano la dirección del receptor, se utilizan servidores en los que los receptores dejan su dirección, y el emisor puede solicitarla haciendo referencia a un nombre único del receptor.

En casos donde la calidad del canal de comunicación o las capacidades del receptor son desconocidas, se organiza un canal de retroalimentación, por el cual el receptor puede informar al emisor sobre sus capacidades, la cantidad de paquetes que no recibió, etc. En este canal se utiliza el protocolo RTCP. El formato de los paquetes transmitidos en este canal está definido en el RFC 3605. A través de este canal se transmiten relativamente pocos datos, entre 200 y 300 bytes por segundo, por lo que, en general, su existencia no es gravosa. El número de puerto al que se envían los paquetes RTCP debe ser impar y uno mayor que el número de puerto desde el que llega el flujo RTP. En nuestro ejemplo, no utilizaremos este canal, ya que las capacidades del receptor y del canal superan considerablemente nuestras necesidades, aún modestas.

En nuestro programa, el esquema de transmisión de datos, a diferencia del esquema del ejemplo anterior, se dividirá en dos partes: el camino de transmisión y el camino de recepción. Para cada parte, haremos nuestra propia fuente de reloj, como se muestra en la imagen principal.

La comunicación unidireccional entre ellos se realizará a través del protocolo RTP. En este ejemplo no necesitaremos una red externa, ya que tanto el transmisor como el receptor estarán en la misma computadora; los paquetes se moverán internamente.

Para establecer el flujo RTP en el mediastreamer, se utilizan dos filtros: MS_RTP_SEND y MS_RTP_RECV. El primero realiza la transmisión y el segundo recibe el flujo RTP. Para que estos filtros funcionen, necesitan recibir un puntero al objeto de la sesión RTP, que puede tanto convertir el flujo de bloques de datos en un flujo de paquetes RTP como realizar la acción inversa. Dado que el formato de datos interno del mediastreamer no coincide con el formato de los paquetes RTP, antes de enviar datos a MS_RTP_SEND, es necesario usar un filtro conversor (encoder) que transforme las muestras de audio de 16 bits en muestras de 8 bits, codificadas según la ley u (ley mu). En el lado receptor, la función inversa la realiza un filtro decoder.

A continuación se presenta el código del programa que implementa el esquema mostrado en la figura (los símbolos # antes de las directivas include han sido eliminados, no olviden agregarlos):

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

Compilamos y ejecutamos. El programa funcionará como en el ejemplo anterior, pero esta vez los datos se transmitirán a través del flujo RTP.

En el siguiente artículo, dividiremos este programa en dos aplicaciones independientes: el receptor y el transmisor, y los ejecutaremos en diferentes terminales. Al mismo tiempo, aprenderemos a analizar los paquetes RTP utilizando el programa TShark.

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster