探索 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資料包。

來源: www.habr.com

添加評論