استكشاف محرك Mediastreamer2 VoIP. الجزء 6

مادة المقال مأخوذة من بلدي قناة زين.

نقل إشارة صوتية عبر دفق RTP

استكشاف محرك Mediastreamer2 VoIP. الجزء 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 بت إلى ثمانية بتات مشفرة وفقًا لـ ش القانون (مو القانون). وعلى الجانب المستقبل، يؤدي مرشح وحدة فك التشفير الوظيفة المعاكسة.

فيما يلي نص البرنامج الذي ينفذ المخطط الموضح في الشكل (تمت إزالة # الرموز قبل توجيهات التضمين، لا تنس تضمينها):

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

إضافة تعليق