Explorando el motor VoIP de Mediastreamer2. Parte 6

El material del artículo está tomado de mi canal zen.

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

Explorando el motor VoIP de Mediastreamer2. Parte 6

En el pasado статье Hemos montado un circuito de control remoto a partir de un generador de tonos y un detector de tonos que funcionan dentro del 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 recibir/transmitir una señal de audio a través de una red Ethernet.

protocolo RTP (Protocolo de tiempo real) traducido significa protocolo en tiempo real, se utiliza para transmitir audio, video, datos, todo lo que requiera 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 predeterminada.

La transmisión se realiza mediante paquetes UDP, lo que significa que la pérdida de paquetes es bastante aceptable durante la transmisión. Cada paquete contiene un encabezado RTP especial y un bloque de datos de la señal transmitida. El encabezado contiene un identificador de fuente de señal seleccionado aleatoriamente, información sobre el tipo de señal que se transmite y un número de secuencia de paquete único para que los paquetes se puedan organizar en el orden correcto al decodificar, independientemente del orden en que fueron entregados por el red. El encabezado también puede contener información adicional, la llamada extensión, que permite adaptar el encabezado para su uso en una tarea de aplicación específica.

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

El tipo de carga se indica mediante un número de siete bits. Recomendación RFC3551 (Perfil RTP para conferencias de audio y vídeo con control mínimo) establece varios tipos de carga; en la tabla correspondiente se proporciona una descripción de los tipos de carga y el significado de los códigos con los que se designan. Algunos códigos no están estrictamente vinculados a ningún tipo de carga; pueden usarse para designar una carga arbitraria.

El tamaño de un bloque de datos está limitado anteriormente por el tamaño máximo de paquete que se puede transmitir en una red determinada sin segmentación (parámetro MTU). En general, no supera los 1500 bytes. Por lo tanto, para aumentar la cantidad de datos transmitidos por segundo, puede aumentar el tamaño del paquete hasta cierto punto y luego deberá aumentar la frecuencia de envío de paquetes. En un transmisor de medios, esta es una configuración configurable. Por defecto es 50 Hz, es decir 50 paquetes por segundo. Llamaremos flujo RTP a la secuencia de paquetes RTP transmitidos.

Para comenzar a transmitir datos entre la fuente y el receptor, basta con que el transmisor conozca la dirección IP del receptor y el número de puerto que utiliza para recibir. Aquellos. sin ningún procedimiento previo, la fuente comienza a transmitir datos y el receptor, a su vez, está listo para recibirlos y procesarlos inmediatamente. Según el estándar, el número de puerto utilizado para transmitir o recibir un flujo RTP debe ser par.

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

En los casos en que se desconoce la calidad del canal de comunicación o las capacidades del receptor, se organiza un canal de retroalimentación a través del cual el receptor puede informar al transmisor sobre sus capacidades, la cantidad de paquetes perdidos, etc. Este canal utiliza el protocolo RTCP. El formato de los paquetes transmitidos en este canal está definido en RFC 3605. Se transmiten relativamente pocos datos a través de este canal, 200...300 bytes por segundo, por lo que, en general, su presencia no es una carga. 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 del que proviene el flujo RTP. En nuestro ejemplo, no utilizaremos este canal, ya que las capacidades del receptor y del canal obviamente exceden nuestras, hasta ahora modestas, necesidades.

En nuestro programa, el circuito de transmisión de datos, a diferencia del ejemplo anterior, se dividirá en dos partes: una ruta de transmisión y una ruta de recepción. Para cada parte crearemos nuestra propia fuente de reloj, como se muestra en la imagen del título.

La comunicación unidireccional entre ellos se realizará mediante el protocolo RTP. En este ejemplo, no necesitamos una red externa, ya que tanto el transmisor como el receptor estarán ubicados en la misma computadora, los paquetes viajarán dentro de ella.

Para establecer una transmisión RTP, el transmisor de medios utiliza dos filtros: MS_RTP_SEND y MS_RTP_RECV. El primero transmite el segundo y recibe el flujo RTP. Para que estos filtros funcionen, deben pasar un puntero a un objeto de sesión RTP, que puede convertir un flujo de bloques de datos en un flujo de paquetes RTP o hacer lo contrario. Dado que el formato de datos interno del transmisor de medios no coincide con el formato de datos del paquete RTP, antes de transferir los datos a MS_RTP_SEND, debe usar un filtro codificador que convierta muestras de señales de audio de 16 bits en codificadas de ocho bits de acuerdo con el ley u (ley mu). En el lado receptor, el filtro decodificador realiza la función opuesta.

A continuación se muestra el texto del programa que implementa el esquema que se muestra en la figura (los símbolos # antes de las directivas de inclusión han sido eliminados, no olvide incluirlos):

/* Файл 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 los datos se transmitirán mediante un flujo RTP.

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

Fuente: habr.com

Añadir un comentario