Εξερευνώντας τη μηχανή VoIP Mediastreamer2. Μέρος 6

Το υλικό του άρθρου είναι παρμένο από το δικό μου κανάλι ζεν.

Μετάδοση σήματος ήχου μέσω ροής RTP

Εξερευνώντας τη μηχανή VoIP Mediastreamer2. Μέρος 6

Στο παρελθόν άρθρο Έχουμε συναρμολογήσει ένα κύκλωμα τηλεχειρισμού από μια γεννήτρια τόνου και έναν ανιχνευτή τόνου που λειτουργούν στο ίδιο πρόγραμμα. Σε αυτό το άρθρο θα μάθουμε πώς να χρησιμοποιούμε το πρωτόκολλο RTP (RFC 3550 - RTP: Πρωτόκολλο μεταφοράς για εφαρμογές σε πραγματικό χρόνο) για λήψη/μετάδοση σήματος ήχου μέσω δικτύου Ethernet.

Πρωτόκολλο RTP (Πρωτόκολλο σε πραγματικό χρόνο) μεταφρασμένο σημαίνει πρωτόκολλο σε πραγματικό χρόνο, χρησιμοποιείται για τη μετάδοση ήχου, βίντεο, δεδομένων, οτιδήποτε απαιτεί μετάδοση σε πραγματικό χρόνο. Ας πάρουμε ένα ηχητικό σήμα ως παράδειγμα. Η ευελιξία του πρωτοκόλλου είναι τέτοια που σας επιτρέπει να μεταδώσετε ένα ηχητικό σήμα με προκαθορισμένη ποιότητα.

Η μετάδοση πραγματοποιείται χρησιμοποιώντας πακέτα UDP, πράγμα που σημαίνει ότι η απώλεια πακέτων είναι αρκετά αποδεκτή κατά τη μετάδοση. Κάθε πακέτο περιέχει μια ειδική κεφαλίδα RTP και ένα μπλοκ δεδομένων του μεταδιδόμενου σήματος. Η κεφαλίδα περιέχει ένα τυχαία επιλεγμένο αναγνωριστικό πηγής σήματος, πληροφορίες σχετικά με τον τύπο του σήματος που μεταδίδεται και έναν μοναδικό αριθμό ακολουθίας πακέτων, έτσι ώστε τα πακέτα να μπορούν να ταξινομηθούν με τη σωστή σειρά κατά την αποκωδικοποίηση, ανεξάρτητα από τη σειρά με την οποία παραδόθηκαν από το δίκτυο. Η κεφαλίδα μπορεί επίσης να περιέχει πρόσθετες πληροφορίες, τη λεγόμενη επέκταση, η οποία επιτρέπει την προσαρμογή της κεφαλίδας για χρήση σε μια συγκεκριμένη εργασία εφαρμογής.

Το μπλοκ δεδομένων περιέχει το ωφέλιμο φορτίο του πακέτου. Η εσωτερική οργάνωση του περιεχομένου εξαρτάται από τον τύπο του φορτίου, μπορεί να είναι δείγματα μονοφωνικού σήματος, στερεοφωνικό σήμα, γραμμή εικόνας βίντεο κ.λπ.

Ο τύπος φορτίου υποδεικνύεται με έναν αριθμό επτά bit. Σύσταση RFC3551 (Προφίλ RTP για διασκέψεις ήχου και βίντεο με ελάχιστο έλεγχο) καθορίζει διάφορους τύπους φορτίων· ο αντίστοιχος πίνακας παρέχει μια περιγραφή των τύπων φορτίων και τη σημασία των κωδικών με τους οποίους προσδιορίζονται. Ορισμένοι κωδικοί δεν συνδέονται αυστηρά με κανένα είδος φορτίου· μπορούν να χρησιμοποιηθούν για να ορίσουν ένα αυθαίρετο φορτίο.

Το μέγεθος ενός μπλοκ δεδομένων περιορίζεται παραπάνω από το μέγιστο μέγεθος πακέτου που μπορεί να μεταδοθεί σε ένα δεδομένο δίκτυο χωρίς τμηματοποίηση (παράμετρος MTU). Σε γενικές γραμμές, αυτό δεν είναι περισσότερο από 1500 byte. Έτσι, για να αυξήσετε την ποσότητα των δεδομένων που μεταδίδονται ανά δευτερόλεπτο, μπορείτε να αυξήσετε το μέγεθος του πακέτου μέχρι ένα ορισμένο σημείο και στη συνέχεια θα χρειαστεί να αυξήσετε τη συχνότητα αποστολής πακέτων. Σε μια ροή πολυμέσων, αυτή είναι μια ρύθμιση με δυνατότητα διαμόρφωσης. Από προεπιλογή είναι 50 Hz, δηλ. 50 πακέτα ανά δευτερόλεπτο. Θα ονομάσουμε την ακολουθία των μεταδιδόμενων πακέτων RTP RTP stream.

Για να ξεκινήσει η μετάδοση δεδομένων μεταξύ της πηγής και του δέκτη, αρκεί ο πομπός να γνωρίζει τη διεύθυνση IP του δέκτη και τον αριθμό θύρας που χρησιμοποιεί για τη λήψη. Εκείνοι. χωρίς προκαταρκτικές διαδικασίες, η πηγή αρχίζει να μεταδίδει δεδομένα και ο δέκτης, με τη σειρά του, είναι έτοιμος να τα λάβει και να τα επεξεργαστεί αμέσως. Σύμφωνα με το πρότυπο, ο αριθμός θύρας που χρησιμοποιείται για τη μετάδοση ή λήψη μιας ροής RTP πρέπει να είναι ζυγός.

Σε περιπτώσεις όπου είναι αδύνατο να γνωρίζετε τη διεύθυνση του δέκτη εκ των προτέρων, χρησιμοποιούνται διακομιστές όπου οι δέκτες αφήνουν τη διεύθυνσή τους και ο πομπός μπορεί να το ζητήσει αναφερόμενος σε κάποιο μοναδικό όνομα του δέκτη.

Σε περιπτώσεις που η ποιότητα του καναλιού επικοινωνίας ή οι δυνατότητες του δέκτη είναι άγνωστη, οργανώνεται ένα κανάλι ανάδρασης μέσω του οποίου ο δέκτης μπορεί να ενημερώσει τον πομπό για τις δυνατότητές του, τον αριθμό των πακέτων που έχασε κ.λπ. Αυτό το κανάλι χρησιμοποιεί το πρωτόκολλο RTCP. Η μορφή των πακέτων που μεταδίδονται σε αυτό το κανάλι ορίζεται στο RFC 3605. Σχετικά λίγα δεδομένα μεταδίδονται σε αυτό το κανάλι, 200..300 byte ανά δευτερόλεπτο, οπότε γενικά, η παρουσία του δεν είναι επαχθής. Ο αριθμός θύρας στην οποία αποστέλλονται τα πακέτα RTCP πρέπει να είναι περιττός και κατά ένα μεγαλύτερο από τον αριθμό θύρας από την οποία προέρχεται η ροή RTP. Στο παράδειγμά μας, δεν θα χρησιμοποιήσουμε αυτό το κανάλι, αφού οι δυνατότητες του δέκτη και του καναλιού ξεπερνούν προφανώς τις, μέχρι στιγμής μέτριες, ανάγκες μας.

Στο πρόγραμμά μας, το κύκλωμα μετάδοσης δεδομένων, σε αντίθεση με το προηγούμενο παράδειγμα, θα χωριστεί σε δύο μέρη: μια διαδρομή μετάδοσης και μια διαδρομή λήψης. Για κάθε μέρος θα φτιάξουμε τη δική μας πηγή ρολογιού, όπως φαίνεται στην εικόνα του τίτλου.

Η μονόδρομη επικοινωνία μεταξύ τους θα πραγματοποιείται χρησιμοποιώντας το πρωτόκολλο RTP. Σε αυτό το παράδειγμα, δεν χρειαζόμαστε εξωτερικό δίκτυο, καθώς τόσο ο πομπός όσο και ο δέκτης θα βρίσκονται στον ίδιο υπολογιστή - τα πακέτα θα ταξιδεύουν μέσα σε αυτόν.

Για τη δημιουργία μιας ροής RTP, η ροή πολυμέσων χρησιμοποιεί δύο φίλτρα: MS_RTP_SEND και MS_RTP_RECV. Ο πρώτος εκπέμπει το δεύτερο και λαμβάνει τη ροή RTP. Για να λειτουργήσουν αυτά τα φίλτρα, πρέπει να περάσουν έναν δείκτη σε ένα αντικείμενο συνεδρίας RTP, το οποίο μπορεί είτε να μετατρέψει μια ροή μπλοκ δεδομένων σε μια ροή πακέτων RTP είτε να κάνει το αντίθετο. Δεδομένου ότι η εσωτερική μορφή δεδομένων της ροής πολυμέσων δεν ταιριάζει με τη μορφή δεδομένων του πακέτου RTP, πριν μεταφέρετε τα δεδομένα στο MS_RTP_SEND, πρέπει να χρησιμοποιήσετε ένα φίλτρο κωδικοποιητή που μετατρέπει δείγματα σήματος ήχου 16 bit σε οκτώ bit κωδικοποιημένα σύμφωνα με 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

Προσθέστε ένα σχόλιο