Explorando o motor de VoIP Mediastreamer2. Parte 6

O material do artigo está tirado do meu canle zen.

Transmitir un sinal de audio mediante fluxo RTP

Explorando o motor de VoIP Mediastreamer2. Parte 6

No último Artigo Montamos un circuíto de control remoto a partir dun xerador de tons e un detector de tons que funcionan dentro do mesmo programa. Neste artigo aprenderemos a usar o protocolo RTP (RFC 3550 - RTP: un protocolo de transporte para aplicacións en tempo real) para recibir/transmitir un sinal de audio a través dunha rede Ethernet.

Protocolo RTP (Protocolo en tempo real) traducido significa protocolo en tempo real, úsase para transmitir audio, vídeo, datos, todo o que requira transmisión en tempo real. Poñamos un sinal de audio como exemplo. A flexibilidade do protocolo é tal que permite transmitir un sinal de audio cunha calidade predeterminada.

A transmisión realízase mediante paquetes UDP, o que significa que a perda de paquetes é bastante aceptable durante a transmisión. Cada paquete contén unha cabeceira RTP especial e un bloque de datos do sinal transmitido. A cabeceira contén un identificador de orixe de sinal seleccionado aleatoriamente, información sobre o tipo de sinal que se está a transmitir e un número de secuencia de paquetes único para que os paquetes se poidan organizar na orde correcta cando se descodifiquen, independentemente da orde en que foron entregados polo rede. A cabeceira tamén pode conter información adicional, a chamada extensión, que permite adaptar a cabeceira para o seu uso nunha tarefa específica da aplicación.

O bloque de datos contén a carga útil do paquete. A organización interna do contido depende do tipo de carga, podendo ser mostras dun sinal mono, un sinal estéreo, unha liña de imaxe de vídeo, etc.

O tipo de carga indícase cun número de sete bits. Recomendación RFC3551 (Perfil RTP para conferencias de audio e vídeo cun control mínimo) establece varios tipos de carga; a táboa correspondente ofrece unha descrición dos tipos de carga e o significado dos códigos polos que se designan. Algúns códigos non están estritamente ligados a ningún tipo de carga; poden usarse para designar unha carga arbitraria.

O tamaño dun bloque de datos está limitado anteriormente polo tamaño máximo de paquete que se pode transmitir nunha determinada rede sen segmentación (parámetro MTU). En xeral, isto non supera os 1500 bytes. Así, para aumentar a cantidade de datos transmitidos por segundo, pode aumentar o tamaño do paquete ata certo punto e, a continuación, terá que aumentar a frecuencia de envío de paquetes. Nunha transmisión multimedia, esta é unha configuración configurable. Por defecto é de 50 Hz, é dicir. 50 paquetes por segundo. Chamaremos fluxo RTP á secuencia de paquetes RTP transmitidos.

Para comezar a transmitir datos entre a fonte e o receptor, é suficiente que o transmisor coñeza o enderezo IP do receptor e o número de porto que utiliza para recibir. Eses. sen ningún procedemento previo, a fonte comeza a transmitir datos e o receptor, á súa vez, está preparado para recibilos e procesalos inmediatamente. Segundo o estándar, o número de porto utilizado para transmitir ou recibir un fluxo RTP debe ser par.

Nas situacións nas que é imposible coñecer previamente o enderezo do receptor, utilízanse servidores onde os receptores deixan o seu enderezo, e o transmisor pode solicitalo facendo referencia a algún nome único do receptor.

Nos casos nos que se descoñece a calidade da canle de comunicación ou as capacidades do receptor, organízase unha canle de retroalimentación a través da cal o receptor pode informar ao transmisor sobre as súas capacidades, o número de paquetes que perdeu, etc. Esta canle usa o protocolo RTCP. O formato dos paquetes transmitidos nesta canle está definido na RFC 3605. Por esta canle transmítense relativamente poucos datos, 200..300 bytes por segundo, polo que, en xeral, a súa presenza non é gravosa. O número de porto ao que se envían os paquetes RTCP debe ser impar e un maior que o número de porto do que procede o fluxo RTP. No noso exemplo, non usaremos esta canle, xa que as capacidades do receptor e da canle obviamente superan as nosas, ata agora modestas, necesidades.

No noso programa, o circuíto de transmisión de datos, a diferenza do exemplo anterior, dividirase en dúas partes: un camiño de transmisión e un camiño de recepción. Para cada parte faremos a nosa propia fonte de reloxo, como se mostra na imaxe do título.

A comunicación unidireccional entre eles realizarase mediante o protocolo RTP. Neste exemplo, non necesitamos unha rede externa, xa que tanto o transmisor como o receptor estarán situados no mesmo ordenador - os paquetes viaxarán no seu interior.

Para establecer un fluxo RTP, o transmisor multimedia usa dous filtros: MS_RTP_SEND e MS_RTP_RECV. O primeiro transmite o segundo e recibe o fluxo RTP. Para que estes filtros funcionen, necesitan pasar un punteiro a un obxecto de sesión RTP, que pode converter un fluxo de bloques de datos nun fluxo de paquetes RTP ou facer o contrario. Dado que o formato de datos interno do streamer multimedia non coincide co formato de datos do paquete RTP, antes de transferir os datos a MS_RTP_SEND, cómpre utilizar un filtro codificador que converta mostras de sinal de audio de 16 bits en oito bits codificados segundo o u-law (mu-law). No lado receptor, o filtro decodificador realiza a función oposta.

Abaixo está o texto do programa que implementa o esquema que se mostra na figura (elimináronse os símbolos # antes das directivas de inclusión, non esquezas incluí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);
}
}

Compilamos e executamos. O programa funcionará como no exemplo anterior, pero os datos transmitiranse mediante un fluxo RTP.

No seguinte artigo dividiremos este programa en dúas aplicacións independentes: un receptor e un transmisor e lanzámolos en diferentes terminais. Ao mesmo tempo, aprenderemos a analizar paquetes RTP usando o programa TShark.

Fonte: www.habr.com

Engadir un comentario