探索 Mediastreamer2 VoIP 引擎。 第 6 部分

文章素材取自我 禅频道.

通过RTP流传输音频信号

探索 Mediastreamer2 VoIP 引擎。 第 6 部分

在最后 文章 我们已经组装了一个由音调发生器和音调检测器组成的远程控制电路,它们在同一程序中运行。 在本文中,我们将学习如何使用 RTP 协议(​​RFC 3550 - RTP:实时应用程序的传输协议)用于通过以太网接收/传输音频信号。

RTP 协议(实时协议)翻译过来的意思是实时协议,它用来传输音频、视频、数据,一切需要实时传输的东西。 我们以音频信号为例。 该协议的灵活性使得您可以传输具有预定质量的音频信号。

使用UDP数据包进行传输,这意味着传输过程中丢包是可以接受的。 每个数据包包含一个特殊的 RTP 标头和传输信号的数据块。 标头包含随机选择的信号源标识符、有关正在传输的信号类型的信息以及唯一的数据包序列号,以便在解码时可以按正确的顺序排列数据包,而不管数据包传送的顺序如何网络。 标头还可以包含附加信息,即所谓的扩展,它允许标头适合在特定应用程序任务中使用。

数据块包含数据包的有效负载。 内容的内部组织取决于负载的类型,它可以是单声道信号、立体声信号、视频图像线等的样本。

负载类型由七位数字指示。 建议 RFC3551(用于音频和视频会议的 RTP 配置文件,具有最少的控制)建立了几种负载类型;相应的表提供了负载类型的描述以及指定它们的代码的含义。 有些代码并不严格与任何类型的负载相关;它们可用于指定任意负载。

数据块的大小受到在给定网络上无需分段即可传输的最大数据包大小(MTU 参数)的限制。 一般情况下,不超过 1500 字节。 因此,为了增加每秒传输的数据量,可以将数据包大小增加到一定程度,然后就需要增加发送数据包的频率。 在媒体流媒体中,这是一个可配置的设置。 默认情况下为 50 Hz,即每秒 50 个数据包。 我们将传输的 RTP 数据包序列称为 RTP 流。

要开始在源和接收器之间传输数据,发送器知道接收器的 IP 地址及其用于接收的端口号就足够了。 那些。 无需任何预备程序,源就开始传输数据,而接收方则准备立即接收和处理数据。 根据标准,用于发送或接收RTP流的端口号必须是偶数。

在无法提前知道接收者地址的情况下,可以在接收者留下其地址的地方使用服务器,并且发送者可以通过引用接收者的某个唯一名称来请求它。

在通信信道的质量或接收器的能力未知的情况下,可以组织反馈信道,接收器可以通过该反馈信道向发送器通知其能力、丢失的数据包数量等。 该通道使用RTCP协议。 RFC 3605 中定义了在此通道中传输的数据包的格式。通过此通道传输的数据相对较少,每秒 200..300 字节,因此一般来说,它的存在并不造成负担。 RTCP 数据包发送到的端口号必须是奇数,并且比 RTP 流来自的端口号大 XNUMX。 在我们的示例中,我们不会使用此通道,因为接收器和通道的功能显然超出了我们迄今为止的适度需求。

在我们的程序中,数据传输电路与前面的示例不同,将分为两部分:发送路径和接收路径。 对于每个部分,我们将制作自己的时钟源,如标题图片所示。

它们之间将使用RTP协议进行单向通信。 在此示例中,我们不需要外部网络,因为发送器和接收器都位于同一台计算机上 - 数据包将在其中传输。

为了建立 RTP 流,媒体流处理器使用两个过滤器:MS_RTP_SEND 和 MS_RTP_RECV。 第一个发送第二个并接收 RTP 流。 为了使这些过滤器工作,它们需要传递一个指向 RTP 会话对象的指针,该对象可以将数据块流转换为 RTP 数据包流,或者执行相反的操作。 由于媒体流器的内部数据格式与RTP数据包的数据格式不匹配,因此在将数据传输到MS_RTP_SEND之前,需要使用编码器过滤器,将16位音频信号样本根据以下格式转换为XNUMX位编码: u-law(mu-law)。 在接收侧,解码器滤波器执行相反的功能。

下面是实现如图所示方案的程序文本(include指令之前的#符号已被删除,不要忘记包含它们):

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

我们编译并运行。 该程序将像前面的示例一样工作,但数据将通过 RTP 流传输。

在下一篇文章中,我们将把这个程序分成两个独立的应用程序——接收器和发射器,并在不同的终端上启动它们。 同时,我们将学习如何使用TShark程序分析RTP数据包。

来源: habr.com

添加评论