חקר מנוע ה-VoIP Mediastreamer2. חלק 6

החומר של המאמר לקוח ממני ערוץ זן.

העברת אות שמע באמצעות זרם RTP

חקר מנוע ה-VoIP Mediastreamer2. חלק 6

בעבר статье הרכבנו מעגל שלט רחוק מחולל צלילים וגלאי צלילים הפועלים בתוך אותה תוכנית. במאמר זה נלמד כיצד להשתמש בפרוטוקול RTP (RFC 3550 - RTP: פרוטוקול תחבורה ליישומי זמן אמת) עבור קבלה/שידור אות שמע ברשת Ethernet.

פרוטוקול 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 (מו-חוק). בצד המקבל, מסנן המפענח מבצע את הפונקציה ההפוכה.

להלן הטקסט של התוכנית המיישמת את הסכימה המוצגת באיור (סימני # לפני הנחיות ה-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.

במאמר הבא נחלק את התוכנית הזו לשתי אפליקציות עצמאיות – מקלט ומשדר ונפעיל אותם במסופים שונים. במקביל, נלמד כיצד לנתח מנות RTP באמצעות תוכנת TShark.

מקור: www.habr.com

הוספת תגובה