Mediastreamer2 VoIP エンジンを探索します。 パート6

記事の素材は私のものから引用しました 禅チャンネル.

RTPストリーム経由で音声信号を送信する

Mediastreamer2 VoIP エンジンを探索します。 パート6

過去に статье 同一プログラム内で動作する音源部と音源部からのリモートコントロール回路を組み立てました。 この記事では、RTP プロトコル (RFC 3550 - RTP: リアルタイム アプリケーション用のトランスポート プロトコル) イーサネット ネットワーク上でオーディオ信号を送受信します。

RTP プロトコル (リアルタイムプロトコル) 翻訳されたものはリアルタイム プロトコルを意味し、オーディオ、ビデオ、データ、リアルタイムでの送信が必要なものすべてを送信するために使用されます。 オーディオ信号を例に考えてみましょう。 このプロトコルの柔軟性により、所定の品質でオーディオ信号を送信できます。

送信は UDP パケットを使用して実行されるため、送信中のパケット損失は十分に許容されます。 各パケットには、特別な RTP ヘッダーと送信信号のデータ ブロックが含まれています。 ヘッダーには、ランダムに選択された信号ソース識別子、送信されている信号のタイプに関する情報、および一意のパケット シーケンス番号が含まれているため、パケットが配信された順序に関係なく、デコード時に正しい順序でパケットを配置できます。通信網。 ヘッダーには追加情報 (いわゆる拡張子) を含めることもでき、これによりヘッダーを特定のアプリケーション タスクでの使用に適合させることができます。

データ ブロックにはパケットのペイロードが含まれます。 コンテンツの内部構成は負荷のタイプによって異なり、モノラル信号、ステレオ信号、ビデオ画像ラインなどのサンプルになる可能性があります。

負荷の種類は 3551 ビットの数値で示されます。 推奨事項 RFCXNUMX (最小限の制御による音声およびビデオ会議用の RTP プロファイル) はいくつかのタイプの荷重を確立します。対応する表には、荷重のタイプとそれらを指定するコードの意味が記載されています。 一部のコードは、いかなるタイプの負荷にも厳密に関連付けられておらず、任意の負荷を指定するために使用できます。

データ ブロックのサイズは、セグメント化なしで特定のネットワーク上で送信できる最大パケット サイズ (MTU パラメーター) によって制限されます。 一般に、これは 1500 バイト以下です。 したがって、50 秒あたりに送信されるデータ量を増やすには、パケット サイズをある程度まで増やすことができ、その後、パケットの送信頻度を増やす必要があります。 メディア ストリーマーでは、これは構成可能な設定です。 デフォルトでは 50 Hz、つまり XNUMX Hz です。 XNUMX秒あたりXNUMXパケット。 送信される RTP パケットのシーケンスを RTP ストリームと呼びます。

ソースとレシーバーの間でデータの送信を開始するには、トランスミッターがレシーバーの IP アドレスと受信に使用するポート番号を知っていれば十分です。 それらの。 事前手順を何も行わずに、送信元がデータの送信を開始すると、受信側はすぐにデータを受信して​​処理できるようになります。 標準によれば、RTP ストリームの送信または受信に使用されるポート番号は偶数である必要があります。

受信者のアドレスを事前に知ることが不可能な状況では、受信者がアドレスを残すサーバーが使用され、送信者は受信者の何らかの一意の名前を参照することでアドレスを要求できます。

通信チャネルの品質や受信機の機能が不明な場合は、受信機が送信機にその機能や受信できなかったパケットの数などを通知できるフィードバック チャネルが編成されます。 このチャネルは RTCP プロトコルを使用します。 このチャネルで送信されるパケットの形式は RFC 3605 で定義されています。このチャネルで送信されるデータは比較的少量で、200 秒あたり 300 ~ XNUMX バイトであるため、一般に、その存在は負担になりません。 RTCP パケットの送信先のポート番号は奇数であり、RTP ストリームの送信元のポート番号より XNUMX 大きい必要があります。 この例では、受信機とチャネルの機能が、これまでのところ控えめなニーズを明らかに超えているため、このチャネルは使用しません。

私たちのプログラムでは、前の例とは異なり、データ送信回路は送信パスと受信パスの XNUMX つの部分に分割されます。 タイトル画像に示すように、パーツごとに独自のクロック ソースを作成します。

それらの間の片方向通信は、RTP プロトコルを使用して実行されます。 この例では、送信機と受信機の両方が同じコンピュータ上に配置され、パケットはその内部を通過するため、外部ネットワークは必要ありません。

RTP ストリームを確立するために、メディア ストリーマーは MS_RTP_SEND と MS_RTP_RECV の 16 つのフィルターを使用します。 最初のストリームは XNUMX 番目のストリームを送信し、RTP ストリームを受信します。 これらのフィルタが機能するには、RTP セッション オブジェクトへのポインタを渡す必要があります。RTP セッション オブジェクトは、データ ブロックのストリームを RTP パケットのストリームに変換するか、その逆を行うことができます。 メディア ストリーマの内部データ形式は RTP パケットのデータ形式と一致しないため、データを MS_RTP_SEND に転送する前に、XNUMX ビットのオーディオ信号サンプルを XNUMX ビットのエンコードに変換するエンコーダ フィルタを使用する必要があります。ユーロー(ムーロー)。 受信側では、デコーダ フィルタが逆の機能を実行します。

以下は、図に示されているスキームを実装するプログラムのテキストです (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 ストリーム経由で送信されます。

次の記事では、このプログラムを XNUMX つの独立したアプリケーション (受信機と送信機) に分割し、異なる端末で起動します。 同時に、TShark プログラムを使用して RTP パケットを分析する方法を学びます。

出所: habr.com

コメントを追加します