Exploration du moteur VoIP Mediastreamer2. Partie 6

Le matériel de l'article est tiré de mon canal zen.

Transmission d'un signal audio via un flux RTP

Exploration du moteur VoIP Mediastreamer2. Partie 6

À la fin article Nous avons assemblé un circuit de télécommande à partir d'un générateur de tonalité et d'un détecteur de tonalité qui fonctionnent au sein du même programme. Dans cet article nous allons apprendre à utiliser le protocole RTP (RFC 3550 - RTP : un protocole de transport pour les applications en temps réel) pour recevoir/transmettre un signal audio sur un réseau Ethernet.

Protocole RTP (Protocole en temps réel) traduit signifie protocole en temps réel, il est utilisé pour transmettre de l'audio, de la vidéo, des données, tout ce qui nécessite une transmission en temps réel. Prenons comme exemple un signal audio. La flexibilité du protocole est telle qu'il permet de transmettre un signal audio avec une qualité prédéterminée.

La transmission s'effectue à l'aide de paquets UDP, ce qui signifie que la perte de paquets est tout à fait acceptable lors de la transmission. Chaque paquet contient un en-tête RTP spécial et un bloc de données du signal transmis. L'en-tête contient un identifiant de source de signal sélectionné de manière aléatoire, des informations sur le type de signal transmis et un numéro de séquence de paquet unique afin que les paquets puissent être classés dans le bon ordre lors du décodage, quel que soit l'ordre dans lequel ils ont été livrés par le réseau. L'en-tête peut également contenir des informations supplémentaires, appelées extensions, qui permettent d'adapter l'en-tête pour une utilisation dans une tâche d'application spécifique.

Le bloc de données contient la charge utile du paquet. L'organisation interne du contenu dépend du type de charge, il peut s'agir d'échantillons d'un signal mono, d'un signal stéréo, d'une ligne d'image vidéo, etc.

Le type de charge est indiqué par un nombre à sept bits. Recommandation RFC3551 (Profil RTP pour les conférences audio et vidéo avec un contrôle minimal) établit plusieurs types de chargements; le tableau correspondant fournit une description des types de chargements et la signification des codes par lesquels ils sont désignés. Certains codes ne sont strictement liés à aucun type de charge ; ils peuvent être utilisés pour désigner une charge arbitraire.

La taille d'un bloc de données est limitée ci-dessus par la taille maximale des paquets pouvant être transmis sur un réseau donné sans segmentation (paramètre MTU). En général, cela ne dépasse pas 1500 50 octets. Ainsi, afin d'augmenter la quantité de données transmises par seconde, vous pouvez augmenter la taille des paquets jusqu'à un certain point, puis vous devrez augmenter la fréquence d'envoi des paquets. Dans un streamer multimédia, il s'agit d'un paramètre configurable. Par défaut c'est 50 Hz, c'est à dire XNUMX paquets par seconde. Nous appellerons la séquence de paquets RTP transmis un flux RTP.

Pour commencer à transmettre des données entre la source et le récepteur, il suffit que l'émetteur connaisse l'adresse IP du récepteur et le numéro de port qu'il utilise pour la réception. Ceux. sans aucune procédure préalable, la source commence à transmettre des données et le récepteur, à son tour, est prêt à les recevoir et à les traiter immédiatement. Selon la norme, le numéro de port utilisé pour transmettre ou recevoir un flux RTP doit être pair.

Dans les situations où il est impossible de connaître l'adresse du destinataire à l'avance, des serveurs sont utilisés où les récepteurs laissent leur adresse, et l'émetteur peut la demander en se référant à un nom unique du destinataire.

Dans les cas où la qualité du canal de communication ou les capacités du récepteur sont inconnues, un canal de retour est organisé à travers lequel le récepteur peut informer l'émetteur de ses capacités, du nombre de paquets qu'il a manqués, etc. Ce canal utilise le protocole RTCP. Le format des paquets transmis sur ce canal est défini dans la RFC 3605. Relativement peu de données sont transmises sur ce canal, 200 à 300 octets par seconde, donc en général, leur présence n'est pas gênante. Le numéro de port auquel les paquets RTCP sont envoyés doit être impair et supérieur de un au numéro de port d'où provient le flux RTP. Dans notre exemple, nous n'utiliserons pas ce canal, car les capacités du récepteur et du canal dépassent évidemment nos besoins, jusqu'ici modestes.

Dans notre programme, le circuit de transmission de données, contrairement à l'exemple précédent, sera divisé en deux parties : un chemin de transmission et un chemin de réception. Pour chaque partie, nous créerons notre propre source d'horloge, comme indiqué dans l'image du titre.

La communication unidirectionnelle entre eux s'effectuera à l'aide du protocole RTP. Dans cet exemple, nous n'avons pas besoin d'un réseau externe, puisque l'émetteur et le récepteur seront situés sur le même ordinateur - les paquets y circuleront.

Pour établir un flux RTP, le streamer multimédia utilise deux filtres : MS_RTP_SEND et MS_RTP_RECV. Le premier transmet le second et reçoit le flux RTP. Pour que ces filtres fonctionnent, ils doivent transmettre un pointeur vers un objet de session RTP, qui peut soit convertir un flux de blocs de données en un flux de paquets RTP, soit faire l'inverse. Étant donné que le format de données interne du streamer multimédia ne correspond pas au format de données du paquet RTP, avant de transférer les données vers MS_RTP_SEND, vous devez utiliser un filtre encodeur qui convertit les échantillons de signal audio de 16 bits en codes de huit bits selon le u-loi (mu-loi). Côté réception, le filtre décodeur remplit la fonction inverse.

Vous trouverez ci-dessous le texte du programme qui implémente le schéma illustré sur la figure (les symboles # avant les directives d'inclusion ont été supprimés, n'oubliez pas de les inclure) :

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

Nous compilons et exécutons. Le programme fonctionnera comme dans l'exemple précédent, mais les données seront transmises via un flux RTP.

Dans le prochain article, nous diviserons ce programme en deux applications indépendantes - un récepteur et un émetteur et les lancerons dans différents terminaux. Parallèlement, nous apprendrons à analyser les paquets RTP à l'aide du programme TShark.

Source: habr.com

Ajouter un commentaire