کاوش در موتور VoIP Mediastreamer2. قسمت 6

مطالب مقاله از من گرفته شده است کانال ذن.

انتقال سیگنال صوتی از طریق جریان RTP

کاوش در موتور VoIP Mediastreamer2. قسمت 6

در آخر مقاله ما یک مدار کنترل از راه دور را از یک مولد صدا و یک آشکارساز تن که در یک برنامه کار می کنند، مونتاژ کرده ایم. در این مقاله نحوه استفاده از پروتکل RTP (RFC 3550 - RTP: یک پروتکل حمل و نقل برای برنامه های بلادرنگ) برای دریافت/انتقال سیگنال صوتی از طریق شبکه اترنت.

پروتکل RTP (پروتکل زمان واقعی) ترجمه شده به معنای پروتکل بلادرنگ است، برای انتقال صدا، تصویر، داده، هر آنچه که نیاز به انتقال در زمان واقعی دارد استفاده می شود. بیایید یک سیگنال صوتی را به عنوان مثال در نظر بگیریم. انعطاف پذیری پروتکل به گونه ای است که به شما امکان می دهد سیگنال صوتی را با کیفیتی از پیش تعیین شده ارسال کنید.

انتقال با استفاده از بسته های UDP انجام می شود، به این معنی که از دست دادن بسته در طول انتقال کاملاً قابل قبول است. هر بسته حاوی یک هدر RTP ویژه و یک بلوک داده از سیگنال ارسالی است. هدر حاوی یک شناسه منبع سیگنال به طور تصادفی انتخاب شده، اطلاعاتی در مورد نوع سیگنال ارسالی، و یک شماره توالی بسته منحصر به فرد است تا بسته ها بدون توجه به ترتیبی که توسط آن ارسال شده اند، هنگام رمزگشایی به ترتیب درستی مرتب شوند. شبکه. هدر همچنین می تواند حاوی اطلاعات اضافی باشد، به اصطلاح پسوند، که اجازه می دهد تا هدر برای استفاده در یک کار برنامه خاص سازگار شود.

بلوک داده حاوی بار بار بسته است. سازماندهی داخلی محتوا به نوع بار بستگی دارد، می تواند نمونه هایی از یک سیگنال مونو، یک سیگنال استریو، یک خط تصویر ویدئویی و غیره باشد.

نوع بار با یک عدد هفت بیتی نشان داده می شود. توصیه RFC3551 (نمایه RTP برای کنفرانس های صوتی و تصویری با حداقل کنترل) چندین نوع بار را ایجاد می کند؛ جدول مربوطه شرحی از انواع بار و معنای کدهایی را که توسط آنها تعیین شده اند ارائه می دهد. برخی از کدها به شدت به هیچ نوع باری مرتبط نیستند، می توان از آنها برای تعیین یک بار دلخواه استفاده کرد.

اندازه یک بلوک داده در بالا با حداکثر اندازه بسته ای که می تواند در یک شبکه معین بدون بخش بندی (پارامتر MTU) منتقل شود محدود می شود. به طور کلی، این بیش از 1500 بایت نیست. بنابراین، برای افزایش حجم داده های ارسال شده در ثانیه، می توانید اندازه بسته را تا یک نقطه خاص افزایش دهید و سپس باید فرکانس ارسال بسته ها را افزایش دهید. در یک پخش کننده رسانه، این یک تنظیم قابل تنظیم است. به طور پیش فرض 50 هرتز است، یعنی. 50 بسته در ثانیه دنباله بسته های RTP ارسال شده را یک جریان RTP می نامیم.

برای شروع انتقال داده بین منبع و گیرنده کافی است فرستنده آدرس IP گیرنده و شماره پورتی را که برای دریافت استفاده می کند بداند. آن ها بدون هیچ روش اولیه، منبع شروع به انتقال داده ها می کند و گیرنده نیز به نوبه خود آماده دریافت و پردازش فوری آن است. طبق استاندارد، شماره پورت مورد استفاده برای ارسال یا دریافت جریان RTP باید زوج باشد.

در مواقعی که اطلاع از آدرس گیرنده از قبل غیرممکن باشد، از سرورهایی استفاده می شود که گیرنده ها آدرس خود را ترک می کنند و فرستنده می تواند با مراجعه به نام منحصر به فرد گیرنده، آن را درخواست کند.

در مواردی که کیفیت کانال ارتباطی یا قابلیت های گیرنده نامشخص باشد، یک کانال بازخورد سازماندهی می شود که از طریق آن گیرنده می تواند فرستنده را در مورد قابلیت های خود، تعداد بسته هایی که از دست داده و غیره مطلع کند. این کانال از پروتکل RTCP استفاده می کند. فرمت بسته های ارسال شده در این کانال در RFC 3605 تعریف شده است. داده های نسبتا کمی از طریق این کانال منتقل می شود، 200..300 بایت در ثانیه، بنابراین به طور کلی، حضور آن سنگین نیست. شماره پورتی که بسته های RTCP به آن ارسال می شود باید فرد و یک بزرگتر از شماره پورتی باشد که جریان RTP از آن می آید. در مثال ما، ما از این کانال استفاده نخواهیم کرد، زیرا توانایی های گیرنده و کانال آشکارا از نیازهای ما، تا کنون بسیار کم، فراتر رفته است.

در برنامه ما مدار انتقال داده بر خلاف مثال قبلی به دو قسمت تقسیم می شود: مسیر انتقال و مسیر دریافت. همانطور که در تصویر عنوان نشان داده شده است، برای هر قسمت، منبع ساعت خود را می سازیم.

ارتباط یک طرفه بین آنها با استفاده از پروتکل RTP انجام خواهد شد. در این مثال، ما نیازی به یک شبکه خارجی نداریم، زیرا فرستنده و گیرنده هر دو در یک رایانه قرار دارند - بسته ها در داخل آن حرکت می کنند.

برای ایجاد یک جریان RTP، پخش کننده رسانه از دو فیلتر استفاده می کند: MS_RTP_SEND و MS_RTP_RECV. اولی دومی را ارسال می کند و جریان RTP را دریافت می کند. برای اینکه این فیلترها کار کنند، باید یک اشاره گر به یک شی جلسه RTP ارسال کنند، که می تواند جریانی از بلوک های داده را به جریانی از بسته های RTP تبدیل کند یا برعکس آن را انجام دهد. از آنجایی که فرمت داده داخلی پخش کننده رسانه با فرمت داده بسته RTP مطابقت ندارد، قبل از انتقال داده ها به MS_RTP_SEND، باید از یک فیلتر رمزگذار استفاده کنید که نمونه های سیگنال صوتی 16 بیتی را به هشت بیت رمزگذاری شده با توجه به u-law (مو-لاو). در سمت دریافت، فیلتر رمزگشا عملکرد مخالف را انجام می دهد.

در زیر متن برنامه ای است که طرح نشان داده شده در شکل را اجرا می کند (# نمادها قبل از حذف دستورالعمل های شامل، فراموش نکنید که آنها را اضافه کنید):

/* Файл 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 منتقل می شوند.

در مقاله بعدی ما این برنامه را به دو برنامه مستقل - یک گیرنده و یک فرستنده تقسیم می کنیم و آنها را در ترمینال های مختلف راه اندازی می کنیم. در همان زمان، نحوه تجزیه و تحلیل بسته های RTP را با استفاده از برنامه TShark یاد خواهیم گرفت.

منبع: www.habr.com

اضافه کردن نظر