Mediastreamer2 VoIP motorunu keşfetme. 6. Bölüm

Makalenin materyali benim zenci kanalı.

RTP akışı yoluyla bir ses sinyalinin iletilmesi

Mediastreamer2 VoIP motorunu keşfetme. 6. Bölüm

Geçmişte Makale Aynı program içerisinde çalışan ton üreteci ve ton dedektöründen uzaktan kumanda devresi kurduk. Bu yazımızda RTP protokolünün (RFC 3550) nasıl kullanılacağını öğreneceğiz. RTP: Gerçek Zamanlı Uygulamalar için Bir Taşıma Protokolü) bir Ethernet ağı üzerinden ses sinyali almak/iletmek için.

RTP protokolü (Gerçek Zamanlı Protokol) çevrilmiş, gerçek zamanlı protokol anlamına gelir; ses, video, veri ve iletim gerektiren her şeyin gerçek zamanlı olarak iletilmesi için kullanılır. Örnek olarak bir ses sinyalini ele alalım. Protokolün esnekliği, önceden belirlenmiş kalitede bir ses sinyali iletmenize izin verecek şekildedir.

İletim, UDP paketleri kullanılarak gerçekleştirilir; bu, iletim sırasında paket kaybının oldukça kabul edilebilir olduğu anlamına gelir. Her paket özel bir RTP başlığı ve iletilen sinyalin bir veri bloğunu içerir. Başlık, rastgele seçilen bir sinyal kaynağı tanımlayıcısını, iletilen sinyalin türü hakkında bilgiyi ve benzersiz bir paket sıra numarasını içerir; böylece paketler, kod çözme sırasında, paketlerin gönderilme sırasına bakılmaksızın doğru sırada düzenlenebilmektedir. ağ. Başlık ayrıca, başlığın belirli bir uygulama görevinde kullanılmak üzere uyarlanmasına olanak tanıyan uzantı adı verilen ek bilgileri de içerebilir.

Veri bloğu paketin yükünü içerir. İçeriğin dahili organizasyonu yükün türüne bağlıdır; mono sinyal, stereo sinyal, video görüntü hattı vb. örnekleri olabilir.

Yük türü yedi bitlik bir sayıyla gösterilir. Öneri RFC3551 (Minimum Kontrolle Sesli ve Görüntülü Konferanslar için RTP Profili) çeşitli yük türlerini belirler; ilgili tablo, yük türlerinin bir tanımını ve bunların belirlendiği kodların anlamlarını sağlar. Bazı kodlar herhangi bir yük türüne sıkı sıkıya bağlı değildir; isteğe bağlı bir yükü belirlemek için kullanılabilirler.

Bir veri bloğunun boyutu yukarıda, belirli bir ağ üzerinde segmentasyon olmadan iletilebilecek maksimum paket boyutu (MTU parametresi) ile sınırlıdır. Genel olarak bu 1500 bayttan fazla değildir. Dolayısıyla saniyede iletilen veri miktarını artırmak için paket boyutunu belli bir noktaya kadar artırabilirsiniz, sonrasında paket gönderme sıklığını artırmanız gerekecektir. Bir medya aktarıcıda bu yapılandırılabilir bir ayardır. Varsayılan olarak 50 Hz'dir, yani. Saniyede 50 paket. İletilen RTP paketlerinin sırasına RTP akışı adını vereceğiz.

Kaynak ile alıcı arasında veri aktarımına başlamak için vericinin, alıcının IP adresini ve alım için kullandığı port numarasını bilmesi yeterlidir. Onlar. Herhangi bir ön prosedür olmaksızın kaynak, verileri iletmeye başlar ve alıcı da onu hemen alıp işlemeye hazırdır. Standarda göre RTP akışını iletmek veya almak için kullanılan port numarasının çift olması gerekir.

Alıcının adresini önceden bilmenin mümkün olmadığı durumlarda, alıcıların adreslerini bıraktıkları sunucular kullanılır ve verici, alıcıya özgü bazı adlara başvurarak bunu talep edebilir.

İletişim kanalının kalitesinin veya alıcının yeteneklerinin bilinmediği durumlarda, alıcının vericiyi yetenekleri, kaçırdığı paket sayısı vb. hakkında bilgilendirebileceği bir geri bildirim kanalı düzenlenir. Bu kanal RTCP protokolünü kullanır. Bu kanalda iletilen paketlerin formatı RFC 3605'te tanımlanmıştır. Bu kanal üzerinden saniyede 200..300 bayt gibi nispeten az veri iletilir, dolayısıyla varlığı genel olarak külfetli değildir. RTCP paketlerinin gönderildiği port numarası tek olmalı ve RTP akışının geldiği port numarasından bir büyük olmalıdır. Örneğimizde, alıcının ve kanalın yetenekleri şu ana kadar mütevazı ihtiyaçlarımızı açıkça aştığı için bu kanalı kullanmayacağız.

Programımızda veri iletim devresi önceki örnekten farklı olarak iki kısma ayrılacaktır: gönderme yolu ve alma yolu. Her bölüm için başlık resminde gösterildiği gibi kendi saat kaynağımızı yapacağız.

Aralarında tek yönlü iletişim RTP protokolü kullanılarak gerçekleştirilecek. Bu örnekte, hem verici hem de alıcı aynı bilgisayarda bulunacağından harici bir ağa ihtiyacımız yok; paketler onun içinde hareket edecek.

Bir RTP akışı oluşturmak için medya aktarıcı iki filtre kullanır: MS_RTP_SEND ve MS_RTP_RECV. Birincisi ikinciyi iletir ve RTP akışını alır. Bu filtrelerin çalışabilmesi için, bir RTP oturum nesnesine bir işaretçi iletmeleri gerekir; bu işaretçi, veri bloklarının akışını bir RTP paketleri akışına dönüştürebilir veya tam tersini yapabilir. Medya aktarıcının dahili veri formatı, RTP paketinin veri formatıyla eşleşmediğinden, verileri MS_RTP_SEND'e aktarmadan önce, 16 bit ses sinyali örneklerini aşağıdakilere göre kodlanmış sekiz bit'e dönüştüren bir kodlayıcı filtre kullanmanız gerekir. u-yasası (mu-yasası). Alıcı tarafta kod çözücü filtresi tam tersi işlevi yerine getirir.

Şekilde gösterilen şemayı uygulayan programın metni aşağıdadır (include direktiflerinin önündeki # sembolleri kaldırılmıştır, eklemeyi unutmayın):

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

Derleyip çalıştırıyoruz. Program önceki örnekte olduğu gibi çalışacak ancak veriler bir RTP akışı aracılığıyla iletilecektir.

Bir sonraki makalede bu programı bir alıcı ve bir verici olmak üzere iki bağımsız uygulamaya ayıracağız ve bunları farklı terminallerde başlatacağız. Aynı zamanda TShark programını kullanarak RTP paketlerini nasıl analiz edeceğimizi öğreneceğiz.

Kaynak: habr.com

Yorum ekle