Le contenu de cet article est tiré de ma .
Transmission de signal audio via un flux RTP

Dans le passé, Nous avons assemblé un schéma de contrôle à distance à partir d'un générateur et d'un détecteur de signaux tonals, qui fonctionnent au sein d'un même programme. Dans cet article, nous allons apprendre à utiliser le protocole RTP (RFC 3550 — RTP : Un protocole de transport pour des applications en temps réel) pour la réception/transmission de signal audio sur un réseau Ethernet.
Le protocole RTP (Real Time Protocol) désigne un protocole de temps réel, 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 permet de transmettre un signal audio avec une qualité prédéfinie.
La transmission se fait à l'aide de paquets UDP, ce qui signifie que la perte de paquets est tout à fait acceptée 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 choisi aléatoirement, des informations sur le type de signal transmis, un numéro de séquence unique pour que les paquets puissent être ordonnés correctement lors du décodage, peu importe 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, permettant d'adapter l'en-tête à 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, cela peut être des échantillons de signal monophonique, de signal stéréo, une ligne d'image vidéo, etc.
Le type de charge est désigné par un nombre de sept bits. La recommandation RFC3551 (RTP Profile for Audio and Video Conferences with Minimal Control) établit plusieurs types de charges, et un tableau pertinent décrit les types de charges et les valeurs de codes qui les désignent. Certains codes n'ont pas d'attachement rigide à un type de charge particulier ; ils peuvent être utilisés pour désigner une charge quelconque.
La taille d'un bloc de données est limitée par la taille maximale du paquet qui peut être transmis dans ce réseau sans segmentation (paramètre MTU). En général, cela ne dépasse pas 1500 octets. Ainsi, pour augmenter le nombre de données transmises par seconde, il est possible d'augmenter la taille du paquet jusqu'à un certain point, puis il faudra augmenter la fréquence d'envoi des paquets. Dans un média streamer, c'est un paramètre configurable. Par défaut, il est de 50 Hz, c'est-à-dire 50 paquets par seconde. Nous appellerons la séquence de paquets RTP transmis le flux RTP.
Pour commencer la transmission de 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 recevoir. C'est-à-dire qu'aucune procédure préalable n'est nécessaire, la source commence à transmettre des données, et le récepteur est prêt à les recevoir et à les traiter immédiatement. Selon la norme, le numéro de port utilisé pour transmettre ou recevoir le flux RTP doit être pair.
Dans les situations où l'adresse du récepteur ne peut pas être connue à l'avance, des serveurs sont utilisés sur lesquels les récepteurs laissent leur adresse, et l'émetteur peut la demander en se référant à un certain nom unique du récepteur.
Dans les cas où la qualité de la liaison ou les capacités du récepteur sont inconnues, un canal de retour est organisé, par lequel le récepteur peut informer l'émetteur de ses capacités, du nombre de paquets qu'il ne reçoit pas, etc. Dans ce canal, le protocole RTCP est utilisé. Le format des paquets transmis dans ce canal est défini dans la RFC 3605. Peu de données sont transmises par ce canal, soit 200 à 300 octets par seconde, donc en général, sa présence n'est pas obtrusive. Le numéro de port sur lequel les paquets RTCP sont envoyés doit être impair et supérieur d'une unité à celui du port d'où provient le flux RTP. Dans notre exemple, nous ne allons pas utiliser ce canal, car les capacités du récepteur et du canal dépassent notre besoins, encore modestes.
Dans notre programme, le schéma de transmission des données, contrairement au schéma de l'exemple précédent, sera divisé en deux parties : le canal d'émission et le canal de réception. Pour chaque partie, nous créerons notre propre source de synchronisation, comme montré sur l'image de couverture.
La communication unidirectionnelle entre eux se fera via le protocole RTP. Dans cet exemple, nous n'aurons pas besoin d'un réseau externe, car l'émetteur et le récepteur seront sur le même ordinateur — les paquets circuleront à l'intérieur de celui-ci.
Pour établir un flux RTP dans le médiastreamer, deux filtres sont utilisés : MS_RTP_SEND et MS_RTP_RECV. Le premier effectue la transmission, tandis que le second reçoit le flux RTP. Pour que ces filtres fonctionnent, ils doivent recevoir un pointeur vers un objet de session RTP, capable d'effectuer à la fois la conversion d'un flux de blocs de données en un flux de paquets RTP et l'opération inverse. Étant donné que le format de données interne du médiastreamer ne correspond pas au format des paquets RTP, un filtre convertisseur (encoder) doit être utilisé avant de transmettre des données à MS_RTP_SEND, afin de convertir les échantillons audio de 16 bits en échantillons de 8 bits, encodés selon la loi u (loi μ). Du côté de la réception, la fonction inverse est effectuée par le filtre decoder.
Voici le texte du programme implémentant le schéma montré dans l'image (les symboles # devant les directives include ont été retirés, n'oubliez pas de les ajouter) :
/* Файл 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 le flux RTP.
Dans l'article suivant, nous allons diviser ce programme en deux applications indépendantes — le récepteur et l'émetteur, et nous les exécuterons dans des terminaux différents. Parallèlement, nous apprendrons à analyser les paquets RTP avec le programme TShark.
Source : habr.com
